初始化
This commit is contained in:
commit
89c2e6c5c4
15
README.md
Normal file
15
README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Spring Boot 3 项目模版
|
||||
|
||||
## 本模版项目使用方法
|
||||
|
||||
1. 安装 [cookiecutter](https://cookiecutter.readthedocs.io/en/stable/) 命令
|
||||
2. cookiecutter 本模版的文件夹
|
||||
3. 根据命令行提示,生成项目
|
||||
|
||||
## 项目模版
|
||||
|
||||
1. Spring Boot 3 + mybatis plus + mapstruct + mysql / postgis + redis 包含 RABC 权限模型
|
||||
|
||||
```
|
||||
cookiecutter https://www.llvy.ltd/llvy.ltd/sample-project.git --checkout main
|
||||
```
|
15
cookiecutter.json
Normal file
15
cookiecutter.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"project_hans": "项目名称",
|
||||
"project_name": "Sample Project",
|
||||
"project_slug": "{{ cookiecutter.project_name | lower | replace(' ', '_') | replace('-', '_') }}",
|
||||
"mvn_group_id": "com.sample",
|
||||
"mvn_artifact_id": "{{ cookiecutter.project_name | lower | replace(' ', '_') | replace('-', '_') }}",
|
||||
"__mvn_package": "{{ cookiecutter.mvn_group_id }}.{{ cookiecutter.mvn_artifact_id }}",
|
||||
"__package_path": "{{ cookiecutter.__mvn_package | replace('.', '/') }}",
|
||||
"platform": ["mysql", "postgis"],
|
||||
"author": "zweiandlen",
|
||||
"email": "zweiandlen@outlook.com",
|
||||
"_copy_without_render": [
|
||||
"*.vue"
|
||||
]
|
||||
}
|
50
{{cookiecutter.project_slug}}/.gitignore
vendored
Normal file
50
{{cookiecutter.project_slug}}/.gitignore
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
HELP.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### Vue.js ###
|
||||
.DS_Store
|
||||
node_modules/
|
||||
dist/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
package-lock.json
|
||||
tests/**/coverage/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/
|
||||
.mvn/
|
||||
/src/main/resources/static/
|
||||
/files/
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"title": "disable",
|
||||
"type": "BOOLEAN",
|
||||
"selectValue": ""
|
||||
},
|
||||
{
|
||||
"title": "support",
|
||||
"type": "SELECT",
|
||||
"selectValue": "add,edit,query,del,ui"
|
||||
}
|
||||
]
|
@ -0,0 +1,4 @@
|
||||
##自动导入包(仅导入实体属性需要的包,通常用于实体类)
|
||||
#foreach($import in $importList)
|
||||
import $!import;
|
||||
#end
|
@ -0,0 +1,39 @@
|
||||
##(Velocity宏定义)
|
||||
|
||||
##定义设置表名后缀的宏定义,调用方式:#setTableSuffix("Test")
|
||||
#macro(setTableSuffix $suffix)
|
||||
#set($tableName = $!tool.append($tableInfo.name, $suffix))
|
||||
#end
|
||||
|
||||
##定义设置包名后缀的宏定义,调用方式:#setPackageSuffix("Test")
|
||||
#macro(setPackageSuffix $suffix)
|
||||
#if($suffix!="")package #end#if($tableInfo.savePackageName!="")$!{tableInfo.savePackageName}.#{end}$!suffix;
|
||||
#end
|
||||
|
||||
##定义直接保存路径与文件名简化的宏定义,调用方式:#save("/entity", ".java")
|
||||
#macro(save $path $fileName)
|
||||
$!callback.setSavePath($tool.append($tableInfo.savePath, $path))
|
||||
$!callback.setFileName($tool.append($tableInfo.name, $fileName))
|
||||
#end
|
||||
|
||||
##定义表注释的宏定义,调用方式:#tableComment("注释信息")
|
||||
#macro(tableComment $desc)
|
||||
/**
|
||||
* $!{tableInfo.comment}($!{tableInfo.name})$desc
|
||||
*
|
||||
* @author $!author
|
||||
* @since $!time.currTime()
|
||||
*/
|
||||
#end
|
||||
|
||||
##定义GET,SET方法的宏定义,调用方式:#getSetMethod($column)
|
||||
#macro(getSetMethod $column)
|
||||
|
||||
public $!{tool.getClsNameByFullName($column.type)} get$!{tool.firstUpperCase($column.name)}() {
|
||||
return $!{column.name};
|
||||
}
|
||||
|
||||
public void set$!{tool.firstUpperCase($column.name)}($!{tool.getClsNameByFullName($column.type)} $!{column.name}) {
|
||||
this.$!{column.name} = $!{column.name};
|
||||
}
|
||||
#end
|
@ -0,0 +1,41 @@
|
||||
##初始化区域
|
||||
|
||||
##去掉表的t_前缀
|
||||
$!tableInfo.setName($tool.getClassName($tableInfo.obj.name.replaceFirst("t_","")))
|
||||
|
||||
##参考阿里巴巴开发手册,POJO 类中布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误
|
||||
## when use actual columns name, should set this.
|
||||
###foreach($column in $tableInfo.fullColumn)
|
||||
## $!column.setName($column.obj.name)
|
||||
###end
|
||||
|
||||
#foreach($column in $tableInfo.fullColumn)
|
||||
#if($column.name.startsWith("is") && $column.type.equals("java.lang.Boolean"))
|
||||
$!column.setName($tool.firstLowerCase($column.name.substring(2)))
|
||||
#end
|
||||
#end
|
||||
|
||||
##实现动态排除列
|
||||
#set($temp = $tool.newHashSet("testCreateTime", "otherColumn"))
|
||||
#foreach($item in $temp)
|
||||
#set($newList = $tool.newArrayList())
|
||||
#foreach($column in $tableInfo.fullColumn)
|
||||
#if($column.name!=$item)
|
||||
##带有反回值的方法调用时使用$tool.call来消除返回值
|
||||
$tool.call($newList.add($column))
|
||||
#end
|
||||
#end
|
||||
##重新保存
|
||||
$tableInfo.setFullColumn($newList)
|
||||
#end
|
||||
|
||||
##对importList进行篡改
|
||||
#set($temp = $tool.newHashSet())
|
||||
#foreach($column in $tableInfo.fullColumn)
|
||||
#if(!$column.type.startsWith("java.lang."))
|
||||
##带有反回值的方法调用时使用$tool.call来消除返回值
|
||||
$tool.call($temp.add($column.type))
|
||||
#end
|
||||
#end
|
||||
##覆盖
|
||||
#set($importList = $temp)
|
@ -0,0 +1,27 @@
|
||||
##following code can be generated use MybatisCodeHelperPro plugin mybatis generator mingrate to template generate.
|
||||
##copy group for different project.
|
||||
#set($javamodelSrcFolder="${projectPath}/src/main/java")
|
||||
#set($modelPackageName="{{ cookiecutter.__mvn_package }}.rest.entity")
|
||||
#set($mapperSrcFolder="${projectPath}/src/main/java")
|
||||
#set($mapperPackageName="{{ cookiecutter.__mvn_package }}.rest.dao")
|
||||
#set($mapperXmlFolder="${projectPath}/src/main/resources")
|
||||
#set($mapperXmlPackage="mapper")
|
||||
#set($serviceSrcFolder="${projectPath}/src/main/java")
|
||||
#set($servicePackageName="{{ cookiecutter.__mvn_package }}.rest.service")
|
||||
#set($serviceImplSrcFolder="${projectPath}/src/main/java")
|
||||
#set($serviceImplPackageName="{{ cookiecutter.__mvn_package }}.rest.service.impl")
|
||||
#set($controllerSrcFolder="${projectPath}/src/main/java")
|
||||
#set($controllerPackageName="{{ cookiecutter.__mvn_package }}.rest.controller")
|
||||
#set($useLombok=true)
|
||||
#set($useSwagger=false)
|
||||
#set($useOpenApi=false)
|
||||
#set($addSchemaName=false)
|
||||
#set($mapperSuffix="Mapper")
|
||||
#set($daoSuffix="Dao")
|
||||
#set($useActualColumName=false)
|
||||
|
||||
#if($useActualColumName)
|
||||
#foreach($column in $tableInfo.fullColumn)
|
||||
$!column.setName($column.obj.name)
|
||||
#end
|
||||
#end
|
@ -0,0 +1,31 @@
|
||||
##针对Mybatis 进行支持,主要用于生成xml文件
|
||||
#foreach($column in $tableInfo.fullColumn)
|
||||
##储存列类型
|
||||
$tool.call($column.ext.put("sqlType", $tool.getField($column.obj.dataType, "typeName")))
|
||||
#if($tool.newHashSet("java.lang.String").contains($column.type))
|
||||
#set($jdbcType="VARCHAR")
|
||||
#elseif($tool.newHashSet("java.lang.Boolean", "boolean").contains($column.type))
|
||||
#set($jdbcType="BOOLEAN")
|
||||
#elseif($tool.newHashSet("java.lang.Byte", "byte").contains($column.type))
|
||||
#set($jdbcType="BYTE")
|
||||
#elseif($tool.newHashSet("java.lang.Integer", "int", "java.lang.Short", "short").contains($column.type))
|
||||
#set($jdbcType="INTEGER")
|
||||
#elseif($tool.newHashSet("java.lang.Long", "long").contains($column.type))
|
||||
#set($jdbcType="BIGINT")
|
||||
#elseif($tool.newHashSet("java.lang.Float", "float", "java.lang.Double", "double").contains($column.type))
|
||||
#set($jdbcType="NUMERIC")
|
||||
#elseif($tool.newHashSet(
|
||||
"java.util.Date", "java.sql.Timestamp", "java.time.Instant", "java.time.LocalDateTime",
|
||||
"java.time.OffsetDateTime", "java.time.ZonedDateTime").contains($column.type))
|
||||
#set($jdbcType="TIMESTAMP")
|
||||
#elseif($tool.newHashSet("java.sql.Date", "java.time.LocalDate", "java.time.LocalTime").contains($column.type))
|
||||
#set($jdbcType="TIMESTAMP")
|
||||
#else
|
||||
##其他类型
|
||||
#set($jdbcType="VARCHAR")
|
||||
#end
|
||||
$tool.call($column.ext.put("jdbcType", $jdbcType))
|
||||
#end
|
||||
|
||||
##定义宏,查询所有列
|
||||
#macro(allSqlColumn)#foreach($column in $tableInfo.fullColumn)$column.obj.name#if($velocityHasNext), #end#end#end
|
@ -0,0 +1,182 @@
|
||||
##导入宏定义
|
||||
$!{define.vm}
|
||||
$!{mybatisCodehelper.vm}
|
||||
|
||||
##设置表后缀(宏定义)
|
||||
#set($controllerName = $tool.append($tableInfo.name, "Controller"))
|
||||
##设置回调
|
||||
#set($controllerSavePath = $tool.append(${controllerSrcFolder},"/",${controllerPackageName.replace(".","/")}))
|
||||
|
||||
$!callback.setSavePath($controllerSavePath)
|
||||
$!callback.setFileName($tool.append($controllerName, ".java"))
|
||||
|
||||
##定义服务名
|
||||
#set($serviceName = $!tool.append($!tool.firstLowerCase($!tableInfo.name), "Service"))
|
||||
|
||||
##定义实体对象名
|
||||
#set($entityName = $!tool.firstLowerCase($!tableInfo.name))
|
||||
|
||||
package ${controllerPackageName};
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.ApiController;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.R;
|
||||
import $!{modelPackageName}.$!{tableInfo.name};
|
||||
import ${servicePackageName}.$!{tableInfo.name}Service;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
##表注释(宏定义)
|
||||
#tableComment("表控制层.")
|
||||
###if(${tableInfo.comment})
|
||||
##@Tag(name = "${tableInfo.comment}")
|
||||
###end
|
||||
@Tag(name = "标准接口", description = "由表结构自动生成")
|
||||
@RestController
|
||||
@RequestMapping("db/$!tool.firstLowerCase($!tableInfo.name)")
|
||||
public class $!{tableInfo.name}Controller extends ApiController {
|
||||
/** 服务对象. */
|
||||
private final $!{tableInfo.name}Service $!{serviceName};
|
||||
|
||||
public $!{tableInfo.name}Controller($!{tableInfo.name}Service $!{serviceName}) {
|
||||
this.$!{serviceName} = $!{serviceName};
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询所有数据.
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param $!entityName 查询实体
|
||||
* @return 分页数据
|
||||
*/
|
||||
@Operation(
|
||||
summary = "${tableInfo.comment} - 分页查询所有数据",
|
||||
parameters = {
|
||||
@Parameter(name = "page", description = "分页对象"),
|
||||
@Parameter(name = "$!entityName", description = "查询实体"),
|
||||
},
|
||||
responses = @ApiResponse(description = "分页数据"),
|
||||
description = "size=-1时查询所有数据,orders配合asc排序")
|
||||
@GetMapping
|
||||
@SaCheckPermission("db:rest:get")
|
||||
public R selectAll(@Nullable PageDTO<$!{tableInfo.name}> page, @Nullable $!{tableInfo.name} $!entityName) {
|
||||
return success(this.$!{serviceName}.page(page, new QueryWrapper<>($!entityName)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据.
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 单条数据
|
||||
*/
|
||||
@Operation(
|
||||
summary = "${tableInfo.comment} - 通过主键查询单条数据",
|
||||
parameters = {
|
||||
@Parameter(name = "id", description = "主键", schema = @Schema(type = "string")),
|
||||
},
|
||||
responses = @ApiResponse(description = "单条数据"))
|
||||
@GetMapping("{id}")
|
||||
@SaCheckPermission("db:rest:get")
|
||||
public R selectOne(@PathVariable Serializable id) {
|
||||
return success(this.$!{serviceName}.getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增数据.
|
||||
*
|
||||
* @param $!entityName 实体对象
|
||||
* @return 新增结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "${tableInfo.comment} - 新增数据",
|
||||
parameters = {
|
||||
@Parameter(name = "$!entityName", description = "实体对象"),
|
||||
},
|
||||
responses = @ApiResponse(description = "新增结果"))
|
||||
@PostMapping
|
||||
@SaCheckPermission("db:rest:post")
|
||||
public R insert(@RequestBody $!tableInfo.name $!entityName) {
|
||||
return success(this.$!{serviceName}.save($!entityName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增数据.
|
||||
*
|
||||
* @param $!{entityName}s 实体对象
|
||||
* @return 新增结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "${tableInfo.comment} - 批量新增数据",
|
||||
parameters = {
|
||||
@Parameter(name = "$!{entityName}s", description = "实体对象"),
|
||||
},
|
||||
responses = @ApiResponse(description = "新增结果"))
|
||||
@PostMapping("s")
|
||||
@SaCheckPermission("db:rest:post")
|
||||
public R inserts(@RequestBody List<$!tableInfo.name> $!{entityName}s) {
|
||||
return success(this.$!{serviceName}.saveBatch($!{entityName}s));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改数据.
|
||||
*
|
||||
* @param $!entityName 实体对象
|
||||
* @return 修改结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "${tableInfo.comment} - 修改数据",
|
||||
parameters = {@Parameter(name = "$!entityName", description = "实体对象")},
|
||||
responses = @ApiResponse(description = "修改结果"),
|
||||
description = "不存在的对象会新增")
|
||||
@PutMapping
|
||||
@SaCheckPermission("db:rest:put")
|
||||
public R update(@RequestBody $!tableInfo.name $!entityName) {
|
||||
return success(this.$!{serviceName}.saveOrUpdate($!entityName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量修改数据.
|
||||
*
|
||||
* @param $!{entityName}s 实体对象
|
||||
* @return 修改结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "${tableInfo.comment} - 批量修改数据",
|
||||
parameters = {@Parameter(name = "$!{entityName}s", description = "实体对象")},
|
||||
responses = @ApiResponse(description = "修改结果"),
|
||||
description = "不存在的对象会新增")
|
||||
@PutMapping("s")
|
||||
@SaCheckPermission("db:rest:put")
|
||||
public R updates(@RequestBody List<$!tableInfo.name> $!{entityName}s) {
|
||||
return success(this.$!{serviceName}.saveOrUpdateBatch($!{entityName}s));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据.
|
||||
*
|
||||
* @param idList 主键结合
|
||||
* @return 删除结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "${tableInfo.comment} - 删除数据",
|
||||
parameters = {
|
||||
@Parameter(name = "idList", description = "主键结合", schema = @Schema(type = "string"))
|
||||
},
|
||||
responses = @ApiResponse(description = "删除结果"),
|
||||
description = "主键用逗号拼接")
|
||||
@DeleteMapping
|
||||
@SaCheckPermission("db:rest:del")
|
||||
public R delete(@RequestParam("idList") List<Long> idList) {
|
||||
return success(this.$!{serviceName}.removeByIds(idList));
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
##导入宏定义
|
||||
$!{define.vm}
|
||||
$!{mybatisCodehelper.vm}
|
||||
|
||||
##设置表后缀(宏定义)
|
||||
#set($daoName = $tool.append($tableInfo.name, ${daoSuffix}))
|
||||
##设置回调
|
||||
#set($daoSavePath = $tool.append(${mapperSrcFolder},"/",${mapperPackageName.replace(".","/")}))
|
||||
|
||||
$!callback.setSavePath($daoSavePath)
|
||||
$!callback.setFileName($tool.append($daoName, ".java"))
|
||||
|
||||
package ${mapperPackageName};
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import $!{modelPackageName}.$!{tableInfo.name};
|
||||
|
||||
##表注释(宏定义)
|
||||
#tableComment("表数据库访问层.")
|
||||
public interface $!{tableInfo.name}Dao extends BaseMapper<$!tableInfo.name> {
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
##导入宏定义
|
||||
$!{define.vm}
|
||||
$!{mybatisCodehelper.vm}
|
||||
|
||||
#set($entitySavePath = $tool.append(${javamodelSrcFolder},"/",${modelPackageName.replace(".","/")}))
|
||||
|
||||
$!callback.setSavePath($entitySavePath)
|
||||
$!callback.setFileName($tool.append($tableInfo.name, ".java"))
|
||||
|
||||
##自动导入包(全局变量)
|
||||
package ${modelPackageName};
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.Version;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import ltd.llvy.postgis.handler.GeometryDeserializer;
|
||||
import ltd.llvy.postgis.handler.GeometrySerializer;
|
||||
|
||||
import java.io.Serializable;
|
||||
$!autoImport
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
##表注释(宏定义)
|
||||
#tableComment("表实体类.")
|
||||
#if(${tableInfo.comment})
|
||||
@Schema(description = "${tableInfo.comment}")
|
||||
#end
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class $!{tableInfo.name} extends Model<$!{tableInfo.name}> {
|
||||
#foreach($column in $tableInfo.fullColumn)
|
||||
#if(${column.comment})
|
||||
//${column.comment}
|
||||
#end
|
||||
#if($!{column.name} == "id" || $!{column.name.indexOf("Id")} != -1)
|
||||
@Schema(description = "${column.comment}", type = "string")
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
#else
|
||||
@Schema(description = "${column.comment}")
|
||||
#end
|
||||
#if("password" == $!{column.name})
|
||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
#end
|
||||
#if("version" == $!{column.name})
|
||||
@Version
|
||||
#end
|
||||
#if("deleteTime" == $!{column.name})
|
||||
@TableLogic
|
||||
#end
|
||||
#if("createTime" == $!{column.name})
|
||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||
{% if cookiecutter.platform == "postgis" -%}
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.OTHER)
|
||||
{% else %}
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.DATETIMEOFFSET)
|
||||
{% endif %}
|
||||
#end
|
||||
#if("updateTime" == $!{column.name})
|
||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||
{% if cookiecutter.platform == "postgis" -%}
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.OTHER)
|
||||
{% else %}
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.DATETIMEOFFSET)
|
||||
{% endif %}
|
||||
#end
|
||||
#if("createBy" == $!{column.name})
|
||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
#end
|
||||
#if("updateBy" == $!{column.name})
|
||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
#end
|
||||
#if("geom" == $!{column.name})
|
||||
@JsonSerialize(using = GeometrySerializer.class)
|
||||
@JsonDeserialize(using = GeometryDeserializer.class)
|
||||
#end
|
||||
private $!{tool.getClsNameByFullName($column.type)} $!{column.name};
|
||||
#end
|
||||
#foreach($column in $tableInfo.pkColumn)
|
||||
/**
|
||||
* 获取主键值.
|
||||
*
|
||||
* @return 主键值
|
||||
*/
|
||||
@Override
|
||||
public Serializable pkVal () {
|
||||
return this.$!column.name;
|
||||
}
|
||||
#break
|
||||
#end
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
##引入mybatis支持
|
||||
$!{mybatisCodehelper.vm}
|
||||
$!{mybatisSupport.vm}
|
||||
##设置保存名称与保存位置
|
||||
#set($XmlSavePath = $tool.append(${mapperXmlFolder},"/",${mapperXmlPackage.replace(".","/")}))
|
||||
$!callback.setSavePath($XmlSavePath)
|
||||
$!callback.setFileName($tool.append($!{tableInfo.name}, $!{mapperSuffix},".xml"))
|
||||
#set($daoName = $tool.append($tableInfo.name, ${daoSuffix}))
|
||||
##拿到主键
|
||||
#if(!$tableInfo.pkColumn.isEmpty())
|
||||
#set($pk = $tableInfo.pkColumn.get(0))
|
||||
#end
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="{{ cookiecutter.__mvn_package }}.rest.dao.$!{tableInfo.name}Dao">
|
||||
<resultMap type="{{ cookiecutter.__mvn_package }}.rest.entity.$!{tableInfo.name}" id="$!{tableInfo.name}Map">
|
||||
#foreach($column in $tableInfo.fullColumn)
|
||||
<result property="$!column.name" column="$!column.obj.name" jdbcType="$!column.ext.jdbcType"/>
|
||||
#end
|
||||
</resultMap>
|
||||
</mapper>
|
@ -0,0 +1,24 @@
|
||||
##导入宏定义
|
||||
$!{define.vm}
|
||||
|
||||
##定义初始变量
|
||||
$!{mybatisCodehelper.vm}
|
||||
#set($serviceName = $tool.append($tableInfo.name, "Service"))
|
||||
##设置回调
|
||||
#set($serviceSavePath = $tool.append(${serviceSrcFolder},"/",${servicePackageName.replace(".","/")}))
|
||||
|
||||
$!callback.setSavePath($serviceSavePath)
|
||||
$!callback.setFileName($tool.append($serviceName, ".java"))
|
||||
|
||||
|
||||
|
||||
package $!{servicePackageName};
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import $!{modelPackageName}.$!{tableInfo.name};
|
||||
|
||||
##表注释(宏定义)
|
||||
#tableComment("表服务接口.")
|
||||
public interface $!{serviceName} extends IService
|
||||
|
||||
<$!tableInfo.name> {
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
##导入宏定义
|
||||
$!{define.vm}
|
||||
|
||||
$!{mybatisCodehelper.vm}
|
||||
#set($ServiceImplName = $tool.append($tableInfo.name, "ServiceImpl"))
|
||||
##设置回调
|
||||
##$!callback.setFileName($tool.append($ServiceImplName, ".java"))
|
||||
##$!callback.setSavePath($tool.append($tableInfo.savePath, "/service/impl"))
|
||||
#set($serviceImplSavePath = $tool.append(${serviceImplSrcFolder},"/",${serviceImplPackageName.replace(".","/")}))
|
||||
|
||||
$!callback.setSavePath($serviceImplSavePath)
|
||||
$!callback.setFileName($tool.append($ServiceImplName, ".java"))
|
||||
|
||||
#set($daoName = $tool.append($tableInfo.name, ${daoSuffix}))
|
||||
|
||||
package $!{serviceImplPackageName};
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import ${mapperPackageName}.${daoName};
|
||||
import $!{modelPackageName}.$!{tableInfo.name};
|
||||
import ${servicePackageName}.$!{tableInfo.name}Service;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
##表注释(宏定义)
|
||||
#tableComment("表服务实现类.")
|
||||
@Service("$!tool.firstLowerCase($tableInfo.name)Service")
|
||||
public class $!{ServiceImplName} extends ServiceImpl<$!{daoName}, $!{tableInfo.name}> implements
|
||||
|
||||
$!{tableInfo.name}Service {
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
[
|
||||
{
|
||||
"matchType": "REGEX",
|
||||
"columnType": "varchar(\\(\\d+\\))?",
|
||||
"javaType": "java.lang.String"
|
||||
},
|
||||
{
|
||||
"matchType": "REGEX",
|
||||
"columnType": "char(\\(\\d+\\))?",
|
||||
"javaType": "java.lang.String"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "tinyint(1)",
|
||||
"javaType": "java.lang.Boolean"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "bit(1)",
|
||||
"javaType": "java.lang.Boolean"
|
||||
},
|
||||
{
|
||||
"matchType": "REGEX",
|
||||
"columnType": "(tiny|medium|long)*text",
|
||||
"javaType": "java.lang.String"
|
||||
},
|
||||
{
|
||||
"matchType": "REGEX",
|
||||
"columnType": "decimal(\\(\\d+,\\d+\\))?",
|
||||
"javaType": "java.math.BigDecimal"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "integer",
|
||||
"javaType": "java.lang.Integer"
|
||||
},
|
||||
{
|
||||
"matchType": "REGEX",
|
||||
"columnType": "(tiny|small|medium)*int(\\(\\d+\\))?",
|
||||
"javaType": "java.lang.Integer"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "int4",
|
||||
"javaType": "java.lang.Integer"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "int8",
|
||||
"javaType": "java.lang.Long"
|
||||
},
|
||||
{
|
||||
"matchType": "REGEX",
|
||||
"columnType": "bigint(\\(\\d+\\))?",
|
||||
"javaType": "java.lang.Long"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "double",
|
||||
"javaType": "java.lang.Double"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "date",
|
||||
"javaType": "java.time.LocalDate"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "datetime",
|
||||
"javaType": "java.time.OffsetDateTime"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "timestamp with time zone",
|
||||
"javaType": "java.time.OffsetDateTime"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "timestamp",
|
||||
"javaType": "java.lang.Long"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "time",
|
||||
"javaType": "java.time.LocalTime"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "boolean",
|
||||
"javaType": "java.lang.Boolean"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "geometry",
|
||||
"javaType": "org.postgis.Geometry"
|
||||
}
|
||||
]
|
@ -0,0 +1,72 @@
|
||||
[
|
||||
{
|
||||
"matchType": "REGEX",
|
||||
"columnType": "varchar(\\(\\d+\\))?",
|
||||
"javaType": "java.lang.String"
|
||||
},
|
||||
{
|
||||
"matchType": "REGEX",
|
||||
"columnType": "char(\\(\\d+\\))?",
|
||||
"javaType": "java.lang.String"
|
||||
},
|
||||
{
|
||||
"matchType": "REGEX",
|
||||
"columnType": "(tiny|medium|long)*text",
|
||||
"javaType": "java.lang.String"
|
||||
},
|
||||
{
|
||||
"matchType": "REGEX",
|
||||
"columnType": "decimal(\\(\\d+,\\d+\\))?",
|
||||
"javaType": "java.lang.Double"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "integer",
|
||||
"javaType": "java.lang.Integer"
|
||||
},
|
||||
{
|
||||
"matchType": "REGEX",
|
||||
"columnType": "(tiny|small|medium)*int(\\(\\d+\\))?",
|
||||
"javaType": "java.lang.Integer"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "int4",
|
||||
"javaType": "java.lang.Integer"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "int8",
|
||||
"javaType": "java.lang.Long"
|
||||
},
|
||||
{
|
||||
"matchType": "REGEX",
|
||||
"columnType": "bigint(\\(\\d+\\))?",
|
||||
"javaType": "java.lang.Long"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "date",
|
||||
"javaType": "java.time.LocalDate"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "datetime",
|
||||
"javaType": "java.time.LocalDate"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "timestamp",
|
||||
"javaType": "java.time.LocalDate"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "time",
|
||||
"javaType": "java.time.LocalTime"
|
||||
},
|
||||
{
|
||||
"matchType": "ORDINARY",
|
||||
"columnType": "boolean",
|
||||
"javaType": "java.lang.Boolean"
|
||||
}
|
||||
]
|
11
{{cookiecutter.project_slug}}/EasyCode/group.json
Normal file
11
{{cookiecutter.project_slug}}/EasyCode/group.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"groupName": "MybatisPlus",
|
||||
"templateName": "MybatisPlus",
|
||||
"globalConfigName": "MybatisCodeHelperPro",
|
||||
"columnConfigName": "Default.json",
|
||||
"typeMapperName": "Default.json",
|
||||
"tableNameRegex": ".*",
|
||||
"schemaNameRegex": ".*"
|
||||
}
|
||||
]
|
12
{{cookiecutter.project_slug}}/EasyCode/velocity_implicit.vm
Normal file
12
{{cookiecutter.project_slug}}/EasyCode/velocity_implicit.vm
Normal file
@ -0,0 +1,12 @@
|
||||
#* @implicitly included *#
|
||||
#* @vtlvariable name="author" type="java.lang.String" *#
|
||||
#* @vtlvariable name="encode" type="java.lang.String" *#
|
||||
#* @vtlvariable name="modulePath" type="java.lang.String" *#
|
||||
#* @vtlvariable name="projectPath" type="java.lang.String" *#
|
||||
#* @vtlvariable name="importList" type="java.util.List<java.lang.String>" *#
|
||||
#* @vtlvariable name="callback" type="com.bruce.plugin.entity.Callback" *#
|
||||
#* @vtlvariable name="tool" type="com.bruce.plugin.tool.GlobalTool" *#
|
||||
#* @vtlvariable name="time" type="com.bruce.plugin.tool.TimeUtils" *#
|
||||
#* @vtlvariable name="tableInfo" type="com.bruce.plugin.entity.TableInfo" *#
|
||||
#* @vtlvariable name="tableInfoList" type="java.util.List<com.bruce.plugin.entity.TableInfo>" *#
|
||||
#* @vtlvariable name="generateService" type="com.bruce.plugin.tool.ExtraCodeGenerateUtils" *#
|
9
{{cookiecutter.project_slug}}/ManagerUI/.editorconfig
Normal file
9
{{cookiecutter.project_slug}}/ManagerUI/.editorconfig
Normal file
@ -0,0 +1,9 @@
|
||||
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
|
||||
charset = utf-8
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
end_of_line = lf
|
||||
max_line_length = 100
|
2
{{cookiecutter.project_slug}}/ManagerUI/.env.development
Normal file
2
{{cookiecutter.project_slug}}/ManagerUI/.env.development
Normal file
@ -0,0 +1,2 @@
|
||||
VITE_APP_TITLE=测试环境
|
||||
VITE_BASE_URL=api
|
2
{{cookiecutter.project_slug}}/ManagerUI/.env.production
Normal file
2
{{cookiecutter.project_slug}}/ManagerUI/.env.production
Normal file
@ -0,0 +1,2 @@
|
||||
VITE_APP_TITLE={{ cookiecutter.project_hans }}
|
||||
VITE_BASE_URL=/
|
1
{{cookiecutter.project_slug}}/ManagerUI/.gitattributes
vendored
Normal file
1
{{cookiecutter.project_slug}}/ManagerUI/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
30
{{cookiecutter.project_slug}}/ManagerUI/.gitignore
vendored
Normal file
30
{{cookiecutter.project_slug}}/ManagerUI/.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
6
{{cookiecutter.project_slug}}/ManagerUI/.prettierrc.json
Normal file
6
{{cookiecutter.project_slug}}/ManagerUI/.prettierrc.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100
|
||||
}
|
36
{{cookiecutter.project_slug}}/ManagerUI/README.md
Normal file
36
{{cookiecutter.project_slug}}/ManagerUI/README.md
Normal file
@ -0,0 +1,36 @@
|
||||
# manager
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and
|
||||
disable Vetur).
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lint with [ESLint](https://eslint.org/)
|
||||
|
||||
```sh
|
||||
npm run lint
|
||||
```
|
28
{{cookiecutter.project_slug}}/ManagerUI/eslint.config.js
Normal file
28
{{cookiecutter.project_slug}}/ManagerUI/eslint.config.js
Normal file
@ -0,0 +1,28 @@
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
import globals from 'globals'
|
||||
import js from '@eslint/js'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import pluginOxlint from 'eslint-plugin-oxlint'
|
||||
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
name: 'app/files-to-lint',
|
||||
files: ['**/*.{js,mjs,jsx,vue}'],
|
||||
},
|
||||
|
||||
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
|
||||
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
js.configs.recommended,
|
||||
...pluginVue.configs['flat/essential'],
|
||||
...pluginOxlint.configs['flat/recommended'],
|
||||
skipFormatting,
|
||||
])
|
13
{{cookiecutter.project_slug}}/ManagerUI/index.html
Normal file
13
{{cookiecutter.project_slug}}/ManagerUI/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-Hans">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link href="/favicon.ico" rel="icon">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title><%= VITE_APP_TITLE %></title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="/src/main.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
13
{{cookiecutter.project_slug}}/ManagerUI/jsconfig.json
Normal file
13
{{cookiecutter.project_slug}}/ManagerUI/jsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
51
{{cookiecutter.project_slug}}/ManagerUI/package.json
Normal file
51
{{cookiecutter.project_slug}}/ManagerUI/package.json
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "manager-ui",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore",
|
||||
"lint:eslint": "eslint . --fix",
|
||||
"lint": "run-s lint:*",
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
"ant-design-vue": "^4.2.6",
|
||||
"axios": "^1.8.4",
|
||||
"dayjs": "^1.11.13",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"pinia": "^3.0.2",
|
||||
"pinia-plugin-persistedstate": "^4.2.0",
|
||||
"qs": "^6.14.0",
|
||||
"tree-lodash": "^0.4.0",
|
||||
"vue": "^3.5.13",
|
||||
"vue-request": "^2.0.4",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.25.1",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.2",
|
||||
"@vue/eslint-config-prettier": "^10.2.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint-plugin-oxlint": "^0.16.7",
|
||||
"eslint-plugin-vue": "~10.0.0",
|
||||
"globals": "^16.0.0",
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"oxlint": "^0.16.7",
|
||||
"prettier": "3.5.3",
|
||||
"sass-embedded": "^1.87.0",
|
||||
"unplugin-vue-components": "^28.5.0",
|
||||
"vite": "^6.3.2",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-html": "^3.2.2",
|
||||
"vite-plugin-vue-devtools": "^7.7.5"
|
||||
}
|
||||
}
|
BIN
{{cookiecutter.project_slug}}/ManagerUI/public/favicon.ico
Normal file
BIN
{{cookiecutter.project_slug}}/ManagerUI/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
13
{{cookiecutter.project_slug}}/ManagerUI/src/App.vue
Normal file
13
{{cookiecutter.project_slug}}/ManagerUI/src/App.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<script setup>
|
||||
import { onBeforeMount } from 'vue'
|
||||
import { useSystemStore } from '@/stores/system.js'
|
||||
import zhCN from 'ant-design-vue/es/locale/zh_CN'
|
||||
|
||||
onBeforeMount(useSystemStore().init)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-config-provider :locale="zhCN" >
|
||||
<RouterView />
|
||||
</a-config-provider>
|
||||
</template>
|
@ -0,0 +1,8 @@
|
||||
:root {
|
||||
}
|
||||
|
||||
.dark {
|
||||
}
|
||||
|
||||
.light {
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
@import 'base.css';
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 全局滚动条样式(内侧滚动条) */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent; /* 滚动条轨道透明 */
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.text-single {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 71 KiB |
@ -0,0 +1,181 @@
|
||||
<script setup>
|
||||
import { reactive } from 'vue'
|
||||
import {
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
NotificationOutlined,
|
||||
UserOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
import LayoutMenu from '@/components/Layout/LayoutMenu.vue'
|
||||
import { useSystemStore } from '@/stores/system.js'
|
||||
import axios from '@/http/api.js'
|
||||
import router from '@/router/index.js'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { useRequest } from 'vue-request'
|
||||
import qs from 'qs'
|
||||
|
||||
const systemStore = useSystemStore()
|
||||
|
||||
const state = reactive({
|
||||
width: 256,
|
||||
sider_width: 80,
|
||||
sider: true,
|
||||
drawer: false,
|
||||
broken: false,
|
||||
})
|
||||
|
||||
const breakpoint = (broken) => {
|
||||
state.broken = broken
|
||||
collapsed()
|
||||
}
|
||||
|
||||
const collapsed = () => {
|
||||
if (state.broken) {
|
||||
state.sider_width = 0
|
||||
state.sider = true
|
||||
state.drawer = !state.drawer
|
||||
} else {
|
||||
state.sider_width = 80
|
||||
state.drawer = false
|
||||
state.sider = !state.sider
|
||||
}
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
axios.post('/manager/logout').then(([err, res]) => {
|
||||
if (err || res.code !== 0) {
|
||||
message.error(err.msg || '注销失败')
|
||||
} else {
|
||||
message.success(res.msg || '注销成功')
|
||||
useSystemStore().$reset()
|
||||
}
|
||||
})
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
const toMessage = () => router.push({ path: '/message' })
|
||||
|
||||
// 轮询消息
|
||||
const fetchMessage = async (params) => {
|
||||
let [err, res] = await axios.get('/message?' + qs.stringify(params, { allowDots: true }))
|
||||
if (err || res.code !== 0) {
|
||||
return message.warning('查询最新消息失败')
|
||||
}
|
||||
return res?.data?.records || []
|
||||
}
|
||||
|
||||
const { data: messages } = useRequest(fetchMessage, {
|
||||
pollingInterval: import.meta.env.DEV ? -1 : 1000,
|
||||
defaultParams: [{ size: -1, status: 0 }],
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ALayout>
|
||||
<ADrawer
|
||||
v-model:open="state.drawer"
|
||||
:bodyStyle="{ padding: '0px', backgroundColor: '#001529' }"
|
||||
:closable="false"
|
||||
:width="state.width"
|
||||
placement="left"
|
||||
>
|
||||
<LayoutMenu />
|
||||
</ADrawer>
|
||||
<ALayoutSider
|
||||
v-model:collapsed="state.sider"
|
||||
:collapsed-width="state.sider_width"
|
||||
:defaultCollapsed="true"
|
||||
:trigger="null"
|
||||
:width="state.width"
|
||||
breakpoint="lg"
|
||||
class="sider"
|
||||
@breakpoint="breakpoint"
|
||||
>
|
||||
<LayoutMenu />
|
||||
</ALayoutSider>
|
||||
<ALayout>
|
||||
<ALayoutHeader class="header">
|
||||
<div class="btn-sider" @click="collapsed">
|
||||
<MenuUnfoldOutlined v-if="state.sider || state.drawer" />
|
||||
<MenuFoldOutlined v-else />
|
||||
</div>
|
||||
<ABadge :count="messages?.length || 0" class="btn-notify" @click="toMessage">
|
||||
<NotificationOutlined />
|
||||
</ABadge>
|
||||
<ADropdown>
|
||||
<div class="btn-avatar">
|
||||
<AAvatar shape="square">
|
||||
<template #icon>
|
||||
<UserOutlined />
|
||||
</template>
|
||||
</AAvatar>
|
||||
<span class="name">
|
||||
<span v-if="systemStore.tenant">{{ systemStore.tenant }} / </span>
|
||||
<span>{{ systemStore.realName || systemStore.name }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<template #overlay>
|
||||
<AMenu>
|
||||
<AMenuItem>系统设置</AMenuItem>
|
||||
<AMenuItem @click="logout">注销登录</AMenuItem>
|
||||
</AMenu>
|
||||
</template>
|
||||
</ADropdown>
|
||||
</ALayoutHeader>
|
||||
<ALayout>
|
||||
<ALayoutContent class="content">
|
||||
<RouterView />
|
||||
</ALayoutContent>
|
||||
</ALayout>
|
||||
</ALayout>
|
||||
</ALayout>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sider {
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: white;
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
|
||||
.btn-sider {
|
||||
height: 64px;
|
||||
font-size: 20px;
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-notify {
|
||||
height: 40px;
|
||||
font-size: 20px;
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
|
||||
line-height: 40px;
|
||||
align-self: center;
|
||||
margin-left: auto;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.btn-avatar {
|
||||
cursor: pointer;
|
||||
|
||||
.name {
|
||||
margin-left: 8px;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
height: calc(100vh - 64px - 20px);
|
||||
margin: 10px;
|
||||
padding: 24px;
|
||||
background-color: white;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,45 @@
|
||||
<script setup>
|
||||
import { AntDesignOutlined } from '@ant-design/icons-vue'
|
||||
import router from '@/router/index.js'
|
||||
import { useSystemStore } from '@/stores/system.js'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const title = computed(() => import.meta.env.VITE_APP_TITLE)
|
||||
|
||||
const systemStore = useSystemStore()
|
||||
|
||||
const selectMenu = ({ item, key }) => {
|
||||
if (item.link) {
|
||||
// 显示网页
|
||||
router.push({
|
||||
name: 'WebView',
|
||||
query: { link: item.link },
|
||||
})
|
||||
} else {
|
||||
router.push(key)
|
||||
}
|
||||
}
|
||||
|
||||
const toHome = () => router.push({ path: '/' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="title" @click="toHome">
|
||||
<AntDesignOutlined />
|
||||
<span style="margin-left: 12px">{{ title }}</span>
|
||||
</div>
|
||||
<AMenu :items="systemStore.fmtMenus()" mode="inline" theme="dark" @select="selectMenu" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
color: white;
|
||||
height: 64px;
|
||||
line-height: 64px;
|
||||
padding-left: 26px;
|
||||
font-size: 1.8rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,20 @@
|
||||
import { useSystemStore } from '@/stores/system.js'
|
||||
|
||||
export default function (el, binding) {
|
||||
const systemStore = useSystemStore()
|
||||
const { value } = binding
|
||||
|
||||
if (systemStore.isSa) return true
|
||||
|
||||
if (value && value instanceof Array && value.length > 0) {
|
||||
let has = systemStore.auths.some((item) => {
|
||||
return value.includes(item)
|
||||
})
|
||||
|
||||
if (!has) {
|
||||
el.parentNode && el.parentNode.removeChild(el)
|
||||
}
|
||||
} else {
|
||||
throw new Error('需要指定权限')
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
import hasAuth from '@/directive/hasAuth.js'
|
||||
import isa from '@/directive/isa.js'
|
||||
|
||||
export default {
|
||||
install(app) {
|
||||
app.directive('hasAuth', hasAuth)
|
||||
app.directive('isa', isa)
|
||||
},
|
||||
}
|
11
{{cookiecutter.project_slug}}/ManagerUI/src/directive/isa.js
Normal file
11
{{cookiecutter.project_slug}}/ManagerUI/src/directive/isa.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { useSystemStore } from '@/stores/system.js'
|
||||
|
||||
export default function (el) {
|
||||
const systemStore = useSystemStore()
|
||||
|
||||
if (systemStore.isSa) {
|
||||
return true
|
||||
} else {
|
||||
el.parentNode && el.parentNode.removeChild(el)
|
||||
}
|
||||
}
|
20
{{cookiecutter.project_slug}}/ManagerUI/src/http/api.js
Normal file
20
{{cookiecutter.project_slug}}/ManagerUI/src/http/api.js
Normal file
@ -0,0 +1,20 @@
|
||||
import axios from 'axios'
|
||||
import router from '@/router/index.js'
|
||||
|
||||
const http = axios.create({ baseURL: import.meta.env.VITE_BASE_URL })
|
||||
http.interceptors.request.use(
|
||||
(config) => config,
|
||||
(error) => Promise.reject(error),
|
||||
)
|
||||
http.interceptors.response.use(
|
||||
(response) => {
|
||||
return [null, response.data]
|
||||
},
|
||||
(error) => {
|
||||
let response = error.response
|
||||
if (response.status === 401) return router.push('/login')
|
||||
return [response.data, null]
|
||||
},
|
||||
)
|
||||
|
||||
export default http
|
26
{{cookiecutter.project_slug}}/ManagerUI/src/main.js
Normal file
26
{{cookiecutter.project_slug}}/ManagerUI/src/main.js
Normal file
@ -0,0 +1,26 @@
|
||||
import './assets/css/main.css'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import dayjs from 'dayjs'
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
import directive from '@/directive/index.js'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
dayjs.extend(localizedFormat)
|
||||
dayjs.locale('zh-cn')
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const pinia = createPinia()
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
app.use(pinia)
|
||||
app.use(router)
|
||||
app.use(directive)
|
||||
|
||||
app.mount('#app')
|
93
{{cookiecutter.project_slug}}/ManagerUI/src/router/index.js
Normal file
93
{{cookiecutter.project_slug}}/ManagerUI/src/router/index.js
Normal file
@ -0,0 +1,93 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import LayoutIndex from '@/components/Layout/LayoutIndex.vue'
|
||||
import { useSystemStore } from '@/stores/system.js'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/login',
|
||||
component: () => import('@/views/LoginView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: LayoutIndex,
|
||||
redirect: '/home',
|
||||
children: [
|
||||
{
|
||||
path: '/home',
|
||||
meta: { needLogin: true },
|
||||
component: () => import('@/views/HomeView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system',
|
||||
children: [
|
||||
{
|
||||
path: '/system/account',
|
||||
meta: { needLogin: true, needMenu: true },
|
||||
component: () => import('@/views/AccountView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/role',
|
||||
meta: { needLogin: true, needMenu: true },
|
||||
component: () => import('@/views/RoleView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/authority',
|
||||
meta: { needLogin: true, needMenu: true },
|
||||
component: () => import('@/views/AuthorityView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/tenant',
|
||||
meta: { needLogin: true, needMenu: true },
|
||||
component: () => import('@/views/TenantView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/logger',
|
||||
meta: { needLogin: true, needMenu: true },
|
||||
component: () => import('@/views/LoggerView.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/message',
|
||||
name: 'Message',
|
||||
component: () => import('@/views/MessageView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/webview',
|
||||
name: 'WebView',
|
||||
props: (route) => ({ link: route.query.link || '#' }),
|
||||
component: () => import('@/views/WebView.vue'),
|
||||
},
|
||||
{
|
||||
path: '403',
|
||||
component: () => import('@/views/exception/403View.vue'),
|
||||
},
|
||||
{
|
||||
path: '500',
|
||||
component: () => import('@/views/exception/500View.vue'),
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
component: () => import('@/views/exception/404View.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
const systemStore = useSystemStore()
|
||||
// 判断是否需要登录
|
||||
if (to.meta.needLogin && !systemStore.isLogin) {
|
||||
return next('/login')
|
||||
}
|
||||
// 判断是否拥有菜单权限
|
||||
if (to.meta.needMenu && !systemStore.menus.some((item) => to.path.startsWith(item.path))) {
|
||||
return next('/403')
|
||||
}
|
||||
return next()
|
||||
})
|
||||
|
||||
export default router
|
73
{{cookiecutter.project_slug}}/ManagerUI/src/stores/system.js
Normal file
73
{{cookiecutter.project_slug}}/ManagerUI/src/stores/system.js
Normal file
@ -0,0 +1,73 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { jwtDecode } from 'jwt-decode'
|
||||
import Cookies from 'js-cookie'
|
||||
import axios from '@/http/api.js'
|
||||
import { fromArray, map } from 'tree-lodash'
|
||||
import { toRaw } from 'vue'
|
||||
|
||||
const TokenKey = 'Sa-Token'
|
||||
|
||||
export const useSystemStore = defineStore('system', {
|
||||
state: () => {
|
||||
return {
|
||||
token: '', // 令牌
|
||||
name: '', // 用户名
|
||||
realName: '', // 显示姓名
|
||||
tenant: '', // 租户
|
||||
auths: [], // 角色和权限
|
||||
menus: [], // 菜单
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
isSa(state) {
|
||||
return !state.tenant
|
||||
},
|
||||
isLogin(state) {
|
||||
return !!state.token
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// 解析令牌
|
||||
parseToken() {
|
||||
let token = Cookies.get(TokenKey)
|
||||
if (token) {
|
||||
this.token = token
|
||||
var payload = jwtDecode(token)
|
||||
this.name = payload['name']
|
||||
this.realName = payload['real_name']
|
||||
this.tenant = payload['tenant']
|
||||
} else {
|
||||
useSystemStore().$reset()
|
||||
}
|
||||
},
|
||||
// 获得角色和权限
|
||||
ownAuths() {
|
||||
let token = Cookies.get(TokenKey)
|
||||
if (token) {
|
||||
axios.get('/manager/own/auths').then(([_, res]) => (this.auths = res.data || []))
|
||||
}
|
||||
},
|
||||
// 获得菜单
|
||||
ownMenus() {
|
||||
let token = Cookies.get(TokenKey)
|
||||
if (token) {
|
||||
axios.get('/manager/own/menus').then(([_, res]) => (this.menus = res.data || []))
|
||||
}
|
||||
},
|
||||
// 格式化菜单
|
||||
fmtMenus() {
|
||||
return map(fromArray(toRaw(this.menus), { parentKey: 'parentId' }), (item) => ({
|
||||
key: item.path || item.value,
|
||||
label: item.name,
|
||||
link: item.link || '',
|
||||
}))
|
||||
},
|
||||
// 初始化
|
||||
init() {
|
||||
this.parseToken()
|
||||
this.ownAuths()
|
||||
this.ownMenus()
|
||||
},
|
||||
},
|
||||
persist: true,
|
||||
})
|
11
{{cookiecutter.project_slug}}/ManagerUI/src/utils/crypto.js
Normal file
11
{{cookiecutter.project_slug}}/ManagerUI/src/utils/crypto.js
Normal file
@ -0,0 +1,11 @@
|
||||
import JSEncrypt from 'jsencrypt'
|
||||
|
||||
const publicKey = `
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDORIceR8iDNIdH366iHZ9LCrkq VF84SRgO0JsZO79vc/1hcsECcs7kQCtFD9kj5Bz4P4iMJQ+hZeaPBKmrfHl91DDr hjuACgA3Pk0Pr5TBdN3eemA0Ri50NyjhoGpJvE8dZe1sbn4lfQwtOsx+kmP+Ixb3 oa6wdPQb3gfnQJqxDQIDAQAB
|
||||
`
|
||||
|
||||
export function encrypt(text) {
|
||||
const encryptor = new JSEncrypt()
|
||||
encryptor.setPublicKey(publicKey) // 设置公钥
|
||||
return encryptor.encrypt(text) // 对数据进行加密
|
||||
}
|
@ -0,0 +1,578 @@
|
||||
<script setup>
|
||||
import qs from 'qs'
|
||||
import dayjs from 'dayjs'
|
||||
import axios from '@/http/api.js'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { DownOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { usePagination, useRequest } from 'vue-request'
|
||||
|
||||
const searchRef = ref()
|
||||
const editRef = ref()
|
||||
const pwdRef = ref()
|
||||
|
||||
const state = reactive({
|
||||
search: {},
|
||||
edit: {},
|
||||
modal: {
|
||||
title: '',
|
||||
show: false,
|
||||
},
|
||||
pwd: {
|
||||
title: '设置密码',
|
||||
show: false,
|
||||
},
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '账户',
|
||||
dataIndex: 'name',
|
||||
sorter: true,
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'realName',
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'mobile',
|
||||
maxWidth: 150,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
maxWidth: 200,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 80,
|
||||
customRender: ({ text: status }) => {
|
||||
return status === 0 ? '可用' : '禁用'
|
||||
},
|
||||
filters: [
|
||||
{ text: '可用', value: 0 },
|
||||
{ text: '禁用', value: 1 },
|
||||
],
|
||||
filterMultiple: false,
|
||||
},
|
||||
{
|
||||
title: '激活',
|
||||
dataIndex: 'isActive',
|
||||
width: 80,
|
||||
customRender: ({ text: isActive }) => {
|
||||
return isActive === 0 ? '正常' : '冻结'
|
||||
},
|
||||
filters: [
|
||||
{ text: '正常', value: 0 },
|
||||
{ text: '冻结', value: 1 },
|
||||
],
|
||||
filterMultiple: false,
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'description',
|
||||
maxWidth: 200,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
maxWidth: 150,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
return text ? dayjs(text).format('LLL') : ''
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime',
|
||||
maxWidth: 150,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
return text ? dayjs(text).format('LLL') : ''
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
align: 'center',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
},
|
||||
]
|
||||
|
||||
const rules = {
|
||||
name: [
|
||||
{ required: true, message: '账户必填', trigger: 'blur' },
|
||||
{ min: 2, max: 50, message: '最少2个字符,最多50个字符', trigger: 'blur' },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '密码必填', trigger: 'blur' },
|
||||
{ min: 6, max: 100, message: '最少6个字符,最多100个字符', trigger: 'blur' },
|
||||
],
|
||||
repeat: [
|
||||
{ required: true, message: '重复密码必填', trigger: 'blur' },
|
||||
{
|
||||
trigger: 'blur',
|
||||
validator: async (_rule, value) => {
|
||||
if (value !== state.edit.password) {
|
||||
return Promise.reject('两次输入密码不同')
|
||||
} else {
|
||||
return Promise.resolve()
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
mobile: [
|
||||
{
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
message: '手机格式不正确',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
email: [{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }],
|
||||
tenant: [
|
||||
{
|
||||
trigger: 'blur',
|
||||
validator: async (_rule, value) => {
|
||||
if (value || value === '') {
|
||||
return Promise.resolve()
|
||||
} else {
|
||||
return Promise.reject('租户必选')
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const onSearchRest = () => {
|
||||
searchRef.value.resetFields()
|
||||
onTableChange()
|
||||
}
|
||||
|
||||
const onEditChange = (record) => {
|
||||
if (record) {
|
||||
Object.assign(state.edit, record)
|
||||
state.modal.title = '修改'
|
||||
} else {
|
||||
state.edit = {}
|
||||
state.modal.title = '新增'
|
||||
}
|
||||
state.modal.show = true
|
||||
}
|
||||
|
||||
const onPwdChange = (record) => {
|
||||
if (record) {
|
||||
Object.assign(state.edit, record)
|
||||
state.pwd.show = true
|
||||
}
|
||||
}
|
||||
|
||||
const fetchTenantOpt = async (params) => {
|
||||
let [err, res] = await axios.get('/db/tTenant', { params })
|
||||
if (err || res.code !== 0) {
|
||||
message.error(err.msg || '获取数据失败')
|
||||
return []
|
||||
} else {
|
||||
let opts = res.data.records.map((item) => ({ label: item.name, value: item.name }))
|
||||
opts.unshift({ label: '公共数据', value: '' })
|
||||
return opts
|
||||
}
|
||||
}
|
||||
|
||||
const fetchRoleOpt = async (params) => {
|
||||
let [err, res] = await axios.get('/db/tRole', { params })
|
||||
if (err || res.code !== 0) {
|
||||
message.error(err.msg || '获取数据失败')
|
||||
return []
|
||||
} else {
|
||||
return res.data.records.map((item) => ({ label: item.name, value: item.id }))
|
||||
}
|
||||
}
|
||||
|
||||
const fetchData = (params) => {
|
||||
return axios
|
||||
.get('/db/tAccount?' + qs.stringify(params, { allowDots: true }))
|
||||
.then(([, res]) => res.data)
|
||||
}
|
||||
|
||||
const submitData = async () => {
|
||||
editRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
if (state.edit.id) {
|
||||
axios.put('/db/tAccount', state.edit).then(([err, res]) => {
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '修改数据失败')
|
||||
} else {
|
||||
state.modal.show = false
|
||||
onTableChange()
|
||||
return message.success('修改数据成功')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
axios.post('/db/tAccount', state.edit).then(([err, res]) => {
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '保存数据失败')
|
||||
} else {
|
||||
state.modal.show = false
|
||||
onTableChange()
|
||||
return message.success('保存数据成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
message.warning('表单校验错误')
|
||||
})
|
||||
}
|
||||
|
||||
const deleteData = (record) => {
|
||||
let params = { idList: record.id }
|
||||
axios.delete('/db/tAccount', { params }).then(([err, res]) => {
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '删除数据失败')
|
||||
} else {
|
||||
onTableChange()
|
||||
return message.success('删除数据成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const submitPwd = async () => {
|
||||
pwdRef.value.validate().then(() => {
|
||||
if (state.edit.id) {
|
||||
let params = { id: state.edit.id, password: state.edit.password }
|
||||
axios.put('/manager/password', params).then(([err, res]) => {
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '修改密码失败')
|
||||
} else {
|
||||
state.pwd.show = false
|
||||
return message.success('修改密码成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const { data: tenantOpts, run: runTenantOpt } = useRequest(fetchTenantOpt, {
|
||||
debounceInterval: 300,
|
||||
defaultParams: [{ size: -1 }],
|
||||
})
|
||||
|
||||
const { data: roleOpts, run: runRoleOpt } = useRequest(fetchRoleOpt, {
|
||||
debounceInterval: 300,
|
||||
defaultParams: [{ size: -1 }],
|
||||
})
|
||||
|
||||
const { data, run, loading, current, pageSize, total } = usePagination(fetchData, {
|
||||
pagination: {
|
||||
currentKey: 'current',
|
||||
pageSizeKey: 'size',
|
||||
listKey: 'records',
|
||||
totalKey: 'total',
|
||||
},
|
||||
defaultParams: [
|
||||
{
|
||||
current: 1,
|
||||
size: 10,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const onTableChange = (pagination, filters, sorter) => {
|
||||
// 排序条件
|
||||
let s = {}
|
||||
if (sorter) {
|
||||
s.orders = [{ column: sorter?.field, asc: sorter?.order === 'ascend' }]
|
||||
}
|
||||
// 筛选条件
|
||||
let f = {}
|
||||
if (filters) {
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value) {
|
||||
f[key] = value.join(',')
|
||||
} else {
|
||||
f[key] = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
run({
|
||||
current: pagination?.current || current,
|
||||
size: pagination?.pageSize || pageSize,
|
||||
...state.search,
|
||||
...s,
|
||||
...f,
|
||||
})
|
||||
}
|
||||
|
||||
const excelExport = async () => {
|
||||
let params = { ...state.search }
|
||||
let [err, res] = await axios.get('/db/tAccount/export', { params, responseType: 'blob' })
|
||||
if (err) {
|
||||
return message.error(err.msg || '获取数据失败')
|
||||
}
|
||||
// 文件导出
|
||||
var blob = res
|
||||
var filename = '凭据.xls'
|
||||
if (typeof window.navigator.msSaveBlob !== 'undefined') {
|
||||
window.navigator.msSaveBlob(blob, filename)
|
||||
} else {
|
||||
var blobURL =
|
||||
window.URL && window.URL.createObjectURL
|
||||
? window.URL.createObjectURL(blob)
|
||||
: window.webkitURL.createObjectURL(blob)
|
||||
var tempLink = document.createElement('a')
|
||||
tempLink.style.display = 'none'
|
||||
tempLink.href = blobURL
|
||||
tempLink.setAttribute('download', filename)
|
||||
if (typeof tempLink.download === 'undefined') {
|
||||
tempLink.setAttribute('target', '_blank')
|
||||
}
|
||||
document.body.appendChild(tempLink)
|
||||
tempLink.click()
|
||||
setTimeout(function () {
|
||||
document.body.removeChild(tempLink)
|
||||
window.URL.revokeObjectURL(blobURL)
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
|
||||
const excelImport = ({ file }) => {
|
||||
switch (file.status) {
|
||||
case 'error':
|
||||
message.error(file?.response?.msg || '上传失败')
|
||||
break
|
||||
case 'done':
|
||||
message.success('导入成功')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const pagination = computed(() => ({
|
||||
current: current.value,
|
||||
pageSize: pageSize.value,
|
||||
total: total.value,
|
||||
}))
|
||||
|
||||
const roleOpt = computed({
|
||||
get() {
|
||||
if (state.edit?.roleIds) {
|
||||
return state.edit.roleIds.split(',')
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
},
|
||||
set(val) {
|
||||
state.edit.roleIds = val.join(',')
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="account">
|
||||
<a-form ref="searchRef" :labelCol="{ flex: '80px' }" :model="state.search" layout="inline">
|
||||
<a-form-item label="账户" name="name">
|
||||
<a-input v-model:value="state.search.name" placeholder="请输入账户" />
|
||||
</a-form-item>
|
||||
<a-form-item label="名称" name="realName">
|
||||
<a-input v-model:value="state.search.realName" placeholder="请输入显示名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号" name="mobile">
|
||||
<a-input v-model:value="state.search.mobile" placeholder="请输入手机号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="邮箱" name="email">
|
||||
<a-input v-model:value="state.search.email" placeholder="请输入邮箱" />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button html-type="submit" type="primary" @click="onTableChange()">查询</a-button>
|
||||
<a-button html-type="reset" style="margin-left: 10px" @click="onSearchRest">重置</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button v-hasAuth="['system:account:post']" type="primary" @click="onEditChange()"
|
||||
>新增
|
||||
</a-button>
|
||||
<a-dropdown>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<div v-hasAuth="['import:excel']">
|
||||
<a-menu-item key="import">
|
||||
<a-upload
|
||||
:showUploadList="false"
|
||||
accept=".xlsx, .xls, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
|
||||
action="/api/db/tAccount/import"
|
||||
@change="excelImport"
|
||||
>
|
||||
导入 Excel
|
||||
</a-upload>
|
||||
</a-menu-item>
|
||||
</div>
|
||||
<div v-hasAuth="['export:excel']">
|
||||
<a-menu-item key="export" @click="excelExport">导出 Excel</a-menu-item>
|
||||
</div>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button style="margin-left: 10px">
|
||||
更多
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="data?.records"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:scroll="{ x: '100%' }"
|
||||
@change="onTableChange"
|
||||
>
|
||||
<template v-slot:bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<a-button v-hasAuth="['system:account:put']" type="link" @click="onEditChange(record)"
|
||||
>修改
|
||||
</a-button>
|
||||
<a-button v-hasAuth="['system:account:put']" type="link" @click="onPwdChange(record)"
|
||||
>密码修改
|
||||
</a-button>
|
||||
<a-popconfirm title="确定要删除这条记录吗?" @confirm="deleteData(record)">
|
||||
<a-button v-hasAuth="['system:account:del']" danger type="link">删除</a-button>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 弹出框 -->
|
||||
<a-modal v-model:open="state.modal.show" :title="state.modal.title" @ok="submitData">
|
||||
<a-form ref="editRef" :labelCol="{ flex: '80px' }" :model="state.edit" :rules="rules">
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="账户" name="name">
|
||||
<a-input v-model:value="state.edit.name" placeholder="请输入账户" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="名称" name="realName">
|
||||
<a-input v-model:value="state.edit.realName" placeholder="请输入显示名称" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<template v-if="!state.edit.id">
|
||||
<a-form-item label="密码" name="password">
|
||||
<a-input-password v-model:value="state.edit.password" placeholder="请输入密码" />
|
||||
</a-form-item>
|
||||
<a-form-item label="重复密码" name="repeat">
|
||||
<a-input-password v-model:value="state.edit.repeat" placeholder="请输入确认密码" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="手机号" name="mobile">
|
||||
<a-input v-model:value="state.edit.mobile" placeholder="请输入手机号" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :push="2" :span="12">
|
||||
<a-form-item label="是否启用" name="status">
|
||||
<a-switch v-model:checked="state.edit.status" :checkedValue="0" :unCheckedValue="1" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="邮箱" name="email">
|
||||
<a-input v-model:value="state.edit.email" placeholder="请输入邮箱" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :push="2" :span="12">
|
||||
<a-form-item label="是否激活" name="isActive">
|
||||
<a-switch
|
||||
v-model:checked="state.edit.isActive"
|
||||
:checkedValue="0"
|
||||
:unCheckedValue="1"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="关联角色" name="roleIds">
|
||||
<a-select
|
||||
v-model:value="roleOpt"
|
||||
:filterOption="false"
|
||||
:options="roleOpts"
|
||||
mode="multiple"
|
||||
placeholder="请多选用户角色"
|
||||
@search="(value) => runRoleOpt({ size: -1, name: value || null })"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="关联员工" name="staffId">
|
||||
<a-input v-model:value="state.edit.staffId" placeholder="请选择员工" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="关联会员" name="userId">
|
||||
<a-input v-model:value="state.edit.userId" placeholder="请选择会员" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item v-isa label="租户" name="tenant">
|
||||
<a-select
|
||||
v-model:value="state.edit.tenant"
|
||||
:filterOption="false"
|
||||
:options="tenantOpts"
|
||||
placeholder="请选择隶属租户"
|
||||
show-search
|
||||
@search="(value) => runTenantOpt({ size: -1, name: value || null })"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="描述" name="description">
|
||||
<a-textarea v-model:value="state.edit.description" placeholder="请输入描述" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<a-modal v-model:open="state.pwd.show" :title="state.pwd.title" @ok="submitPwd">
|
||||
<a-form ref="pwdRef" :labelCol="{ flex: '80px' }" :model="state.edit" :rules="rules">
|
||||
<a-form-item label="密码" name="password">
|
||||
<a-input-password v-model:value="state.edit.password" placeholder="请输入密码" />
|
||||
</a-form-item>
|
||||
<a-form-item label="重复密码" name="repeat">
|
||||
<a-input-password v-model:value="state.edit.repeat" placeholder="请输入确认密码" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.account {
|
||||
.ant-form {
|
||||
.ant-form-item:nth-last-child(2) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.ant-form-item:last-child {
|
||||
margin-left: auto;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-btn-link {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 10px;
|
||||
|
||||
.ant-row {
|
||||
flex-flow: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,548 @@
|
||||
<script setup>
|
||||
import _ from 'lodash'
|
||||
import qs from 'qs'
|
||||
import dayjs from 'dayjs'
|
||||
import axios from '@/http/api.js'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { DownOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { usePagination, useRequest } from 'vue-request'
|
||||
|
||||
const searchRef = ref()
|
||||
const editRef = ref()
|
||||
|
||||
const state = reactive({
|
||||
search: {},
|
||||
edit: {},
|
||||
modal: {
|
||||
title: '',
|
||||
show: false,
|
||||
},
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '权限名称',
|
||||
dataIndex: 'name',
|
||||
sorter: true,
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '英文值',
|
||||
dataIndex: 'value',
|
||||
sorter: true,
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '路径',
|
||||
dataIndex: 'path',
|
||||
sorter: true,
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '外部链接',
|
||||
dataIndex: 'link',
|
||||
maxWidth: 200,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
sorter: true,
|
||||
width: 100,
|
||||
customRender: ({ text: type }) => {
|
||||
switch (type) {
|
||||
case 0:
|
||||
return '接口'
|
||||
case 1:
|
||||
return '菜单'
|
||||
case 2:
|
||||
return '按钮'
|
||||
default:
|
||||
return '其他'
|
||||
}
|
||||
},
|
||||
filters: [
|
||||
{ text: '接口', value: 0 },
|
||||
{ text: '菜单', value: 1 },
|
||||
{ text: '按钮', value: 2 },
|
||||
],
|
||||
filterMultiple: false,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 100,
|
||||
customRender: ({ text: status }) => {
|
||||
return status === 0 ? '正常' : '冻结'
|
||||
},
|
||||
filters: [
|
||||
{ text: '正常', value: 0 },
|
||||
{ text: '冻结', value: 1 },
|
||||
],
|
||||
filterMultiple: false,
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'description',
|
||||
maxWidth: 200,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
maxWidth: 150,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
return text ? dayjs(text).format('LLL') : ''
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime',
|
||||
maxWidth: 150,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
return text ? dayjs(text).format('LLL') : ''
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
align: 'center',
|
||||
width: 120,
|
||||
fixed: 'right',
|
||||
},
|
||||
]
|
||||
|
||||
const rules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '权限名称必填',
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
min: 2,
|
||||
max: 100,
|
||||
message: '最少2个字符,最多100个字符',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
value: [
|
||||
{
|
||||
required: true,
|
||||
message: '英文值必填',
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
min: 4,
|
||||
max: 100,
|
||||
message: '最少4个字符,最多100个字符',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
parentId: [
|
||||
{
|
||||
required: true,
|
||||
message: '父节点必选',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
type: [
|
||||
{
|
||||
required: true,
|
||||
message: '类型必选',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
tenant: [
|
||||
{
|
||||
trigger: 'blur',
|
||||
validator: async (_rule, value) => {
|
||||
if (value || value === '') {
|
||||
return Promise.resolve()
|
||||
} else {
|
||||
return Promise.reject('租户必选')
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const onSearchRest = () => {
|
||||
searchRef.value.resetFields()
|
||||
onTableChange()
|
||||
}
|
||||
|
||||
const onEditChange = (record) => {
|
||||
if (record) {
|
||||
Object.assign(state.edit, record)
|
||||
state.modal.title = '修改'
|
||||
} else {
|
||||
state.edit = {}
|
||||
state.modal.title = '新增'
|
||||
}
|
||||
state.modal.show = true
|
||||
}
|
||||
|
||||
const fetchTenantOpt = async (params) => {
|
||||
let [err, res] = await axios.get('/db/tTenant?' + qs.stringify(params, { skipNulls: true }))
|
||||
if (err || res.code !== 0) {
|
||||
message.error(err.msg || '获取数据失败')
|
||||
return []
|
||||
} else {
|
||||
let opts = res.data.records.map((item) => ({ label: item.name, value: item.name }))
|
||||
opts.unshift({ label: '公共数据', value: '' })
|
||||
return opts
|
||||
}
|
||||
}
|
||||
|
||||
const fetchAuthorityOpt = async (params) => {
|
||||
let [err, res] = await axios.get('/db/tAuthority?' + qs.stringify(params, { skipNulls: true }))
|
||||
if (err || res.code !== 0) {
|
||||
message.error(err.msg || '获取数据失败')
|
||||
return []
|
||||
} else {
|
||||
let opts = res.data.records.map((item) => ({
|
||||
label: item.name + ' (' + (item.description || '无描述') + ')',
|
||||
value: item.id,
|
||||
}))
|
||||
opts.unshift({ label: '根节点', value: '0' })
|
||||
return opts
|
||||
}
|
||||
}
|
||||
|
||||
const fetchData = (params) => {
|
||||
return axios
|
||||
.get('/db/tAuthority?' + qs.stringify(params, { allowDots: true }))
|
||||
.then(([, res]) => res.data)
|
||||
}
|
||||
|
||||
const submitData = async () => {
|
||||
editRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
if (state.edit.id) {
|
||||
if (state.edit.id === state.edit.parentId) {
|
||||
return message.warning('父节点不能是自身')
|
||||
}
|
||||
axios.put('/db/tAuthority', state.edit).then(([err, res]) => {
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '修改数据失败')
|
||||
} else {
|
||||
state.modal.show = false
|
||||
onTableChange()
|
||||
return message.success('修改数据成功')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
axios.post('/db/tAuthority', state.edit).then(([err, res]) => {
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '保存数据失败')
|
||||
} else {
|
||||
state.modal.show = false
|
||||
onTableChange()
|
||||
return message.success('保存数据成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
message.warning('表单校验错误')
|
||||
})
|
||||
}
|
||||
|
||||
const deleteData = (record) => {
|
||||
let params = { idList: record.id }
|
||||
axios.delete('/db/tAuthority', { params }).then(([err, res]) => {
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '删除数据失败')
|
||||
} else {
|
||||
onTableChange()
|
||||
return message.success('删除数据成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const { data: tenantOpts, run: runTenantOpt } = useRequest(fetchTenantOpt, {
|
||||
debounceInterval: 300,
|
||||
defaultParams: [{ size: -1 }],
|
||||
})
|
||||
|
||||
const { data: authorityOpts, run: runAuthorityOpt } = useRequest(fetchAuthorityOpt, {
|
||||
debounceInterval: 300,
|
||||
defaultParams: [{ size: -1 }],
|
||||
})
|
||||
|
||||
const { data, run, loading, current, pageSize, total } = usePagination(fetchData, {
|
||||
pagination: {
|
||||
currentKey: 'current',
|
||||
pageSizeKey: 'size',
|
||||
listKey: 'records',
|
||||
totalKey: 'total',
|
||||
},
|
||||
defaultParams: [
|
||||
{
|
||||
current: 1,
|
||||
size: 10,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const onTableChange = (pagination, filters, sorter) => {
|
||||
// 排序条件
|
||||
let s = {}
|
||||
if (sorter) {
|
||||
s.orders = [{ column: sorter?.field, asc: sorter?.order === 'ascend' }]
|
||||
}
|
||||
// 筛选条件
|
||||
let f = {}
|
||||
if (filters) {
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value) {
|
||||
f[key] = value.join(',')
|
||||
} else {
|
||||
f[key] = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
run({
|
||||
current: pagination?.current || current,
|
||||
size: pagination?.pageSize || pageSize,
|
||||
...state.search,
|
||||
...s,
|
||||
...f,
|
||||
})
|
||||
}
|
||||
|
||||
const onChangePath = (path) => {
|
||||
if (path) {
|
||||
state.edit.value = _(path).trim('/').replaceAll('/', ':')
|
||||
} else {
|
||||
state.edit.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
const excelExport = async () => {
|
||||
let params = { ...state.search }
|
||||
let [err, res] = await axios.get('/db/tAuthority/export', { params, responseType: 'blob' })
|
||||
if (err) {
|
||||
return message.error(err.msg || '获取数据失败')
|
||||
}
|
||||
// 文件导出
|
||||
var blob = res
|
||||
var filename = '权限.xls'
|
||||
if (typeof window.navigator.msSaveBlob !== 'undefined') {
|
||||
window.navigator.msSaveBlob(blob, filename)
|
||||
} else {
|
||||
var blobURL =
|
||||
window.URL && window.URL.createObjectURL
|
||||
? window.URL.createObjectURL(blob)
|
||||
: window.webkitURL.createObjectURL(blob)
|
||||
var tempLink = document.createElement('a')
|
||||
tempLink.style.display = 'none'
|
||||
tempLink.href = blobURL
|
||||
tempLink.setAttribute('download', filename)
|
||||
if (typeof tempLink.download === 'undefined') {
|
||||
tempLink.setAttribute('target', '_blank')
|
||||
}
|
||||
document.body.appendChild(tempLink)
|
||||
tempLink.click()
|
||||
setTimeout(function () {
|
||||
document.body.removeChild(tempLink)
|
||||
window.URL.revokeObjectURL(blobURL)
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
|
||||
const excelImport = ({ file }) => {
|
||||
switch (file.status) {
|
||||
case 'error':
|
||||
message.error(file?.response?.msg || '上传失败')
|
||||
break
|
||||
case 'done':
|
||||
message.success('导入成功')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const pagination = computed(() => ({
|
||||
current: current.value,
|
||||
pageSize: pageSize.value,
|
||||
total: total.value,
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="authority">
|
||||
<a-form ref="searchRef" :labelCol="{ flex: '80px' }" :model="state.search" layout="inline">
|
||||
<a-form-item label="权限名称" name="name">
|
||||
<a-input v-model:value="state.search.name" placeholder="请输入名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="权限类型" name="type">
|
||||
<a-select v-model:value="state.search.type" allowClear placeholder="请选择类型">
|
||||
<a-select-option value="0">接口</a-select-option>
|
||||
<a-select-option value="1">菜单</a-select-option>
|
||||
<a-select-option value="2">按钮</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button html-type="submit" type="primary" @click="onTableChange()">查询</a-button>
|
||||
<a-button html-type="reset" style="margin-left: 10px" @click="onSearchRest">重置</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button v-hasAuth="['system:authority:post']" type="primary" @click="onEditChange()"
|
||||
>新增
|
||||
</a-button>
|
||||
<a-dropdown>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<div v-hasAuth="['import:excel']">
|
||||
<a-menu-item key="import">
|
||||
<a-upload
|
||||
:showUploadList="false"
|
||||
accept=".xlsx, .xls, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
|
||||
action="/api/db/tAuthority/import"
|
||||
@change="excelImport"
|
||||
>
|
||||
导入 Excel
|
||||
</a-upload>
|
||||
</a-menu-item>
|
||||
</div>
|
||||
<div v-hasAuth="['export:excel']">
|
||||
<a-menu-item key="export" @click="excelExport">导出 Excel</a-menu-item>
|
||||
</div>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button style="margin-left: 10px">
|
||||
更多
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="data?.records"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:scroll="{ x: '100%' }"
|
||||
@change="onTableChange"
|
||||
>
|
||||
<template v-slot:bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<a-button v-hasAuth="['system:authority:put']" type="link" @click="onEditChange(record)"
|
||||
>修改
|
||||
</a-button>
|
||||
<a-popconfirm title="确定要删除这条记录吗?" @confirm="deleteData(record)">
|
||||
<a-button v-hasAuth="['system:authority:del']" danger type="link">删除</a-button>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 弹出框 -->
|
||||
<a-modal v-model:open="state.modal.show" :title="state.modal.title" @ok="submitData">
|
||||
<a-form ref="editRef" :labelCol="{ flex: '80px' }" :model="state.edit" :rules="rules">
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="名称" name="name">
|
||||
<a-input v-model:value="state.edit.name" placeholder="请输入权限名称" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="英文值" name="value">
|
||||
<a-input v-model:value="state.edit.value" placeholder="请输入英文简写" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="父节点" name="parentId">
|
||||
<a-select
|
||||
v-model:value="state.edit.parentId"
|
||||
:filterOption="false"
|
||||
:options="authorityOpts"
|
||||
placeholder="请选择父节点"
|
||||
show-search
|
||||
@search="(value) => runAuthorityOpt({ size: -1, name: value || null })"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="类型" name="type">
|
||||
<a-select v-model:value="state.edit.type" placeholder="请选择类型">
|
||||
<a-select-option :value="0">接口</a-select-option>
|
||||
<a-select-option :value="1">菜单</a-select-option>
|
||||
<a-select-option :value="2">按钮</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :push="2" :span="12">
|
||||
<a-form-item label="是否启用" name="status">
|
||||
<a-switch
|
||||
v-model:checked="state.edit.status"
|
||||
:checkedValue="0"
|
||||
:unCheckedValue="1"
|
||||
placeholder="请输入状态"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<template v-if="state.edit.type === 1">
|
||||
<a-form-item label="路径" name="path">
|
||||
<a-input
|
||||
v-model:value="state.edit.path"
|
||||
placeholder="请输入以 / 开头的路径,将修改英文值"
|
||||
@change="onChangePath(state.edit.path)"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="外部链接" name="link">
|
||||
<a-input v-model:value="state.edit.link" placeholder="请输入外部链接" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
<a-form-item v-isa label="租户" name="tenant">
|
||||
<a-select
|
||||
v-model:value="state.edit.tenant"
|
||||
:filterOption="false"
|
||||
:options="tenantOpts"
|
||||
placeholder="请选择隶属租户"
|
||||
show-search
|
||||
@search="(value) => runTenantOpt({ size: -1, name: value || null })"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="描述" name="description">
|
||||
<a-textarea v-model:value="state.edit.description" placeholder="请输入描述" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.authority {
|
||||
.ant-form {
|
||||
.ant-form-item:nth-last-child(2) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.ant-form-item:last-child {
|
||||
margin-left: auto;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-btn-link {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 10px;
|
||||
|
||||
.ant-row {
|
||||
flex-flow: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,9 @@
|
||||
<script setup></script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>首页</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
268
{{cookiecutter.project_slug}}/ManagerUI/src/views/LoggerView.vue
Normal file
268
{{cookiecutter.project_slug}}/ManagerUI/src/views/LoggerView.vue
Normal file
@ -0,0 +1,268 @@
|
||||
<script setup>
|
||||
import qs from 'qs'
|
||||
import dayjs from 'dayjs'
|
||||
import axios from '@/http/api.js'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { usePagination } from 'vue-request'
|
||||
|
||||
const searchRef = ref()
|
||||
|
||||
const state = reactive({
|
||||
search: {},
|
||||
edit: {},
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
sorter: true,
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
switch (text) {
|
||||
case 0:
|
||||
return '数据变更自动日志'
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
sorter: true,
|
||||
width: 80,
|
||||
customRender: ({ text }) => {
|
||||
switch (text) {
|
||||
case 'insert':
|
||||
return '新增'
|
||||
case 'update':
|
||||
return '修改'
|
||||
case 'delete':
|
||||
return '删除'
|
||||
default:
|
||||
return '其他'
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '表名称',
|
||||
dataIndex: 'tableName',
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '日志',
|
||||
dataIndex: 'recordStaus',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
customRender: ({ text }) => (text ? '是' : '否'),
|
||||
},
|
||||
{
|
||||
title: '变动内容',
|
||||
dataIndex: 'changed',
|
||||
maxWidth: 250,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'description',
|
||||
maxWidth: 200,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
maxWidth: 150,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
return text ? dayjs(text).format('LLL') : ''
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime',
|
||||
maxWidth: 150,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
return text ? dayjs(text).format('LLL') : ''
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
align: 'center',
|
||||
width: 80,
|
||||
fixed: 'right',
|
||||
},
|
||||
]
|
||||
|
||||
const onSearchRest = () => {
|
||||
searchRef.value.resetFields()
|
||||
onTableChange()
|
||||
}
|
||||
|
||||
const fetchData = (params) => {
|
||||
return axios
|
||||
.get('/db/tLogger?' + qs.stringify(params, { allowDots: true }))
|
||||
.then(([, res]) => res.data)
|
||||
}
|
||||
|
||||
const deleteData = (record) => {
|
||||
let params = { idList: record.id }
|
||||
axios.delete('/db/tLogger', { params }).then(([err, res]) => {
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '删除数据失败')
|
||||
} else {
|
||||
onTableChange()
|
||||
return message.success('删除数据成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const { data, run, loading, current, pageSize, total } = usePagination(fetchData, {
|
||||
pagination: {
|
||||
currentKey: 'current',
|
||||
pageSizeKey: 'size',
|
||||
listKey: 'records',
|
||||
totalKey: 'total',
|
||||
},
|
||||
defaultParams: [
|
||||
{
|
||||
current: 1,
|
||||
size: 10,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const onTableChange = (pagination, filters, sorter) => {
|
||||
// 排序条件
|
||||
let s = {}
|
||||
if (sorter) {
|
||||
s.orders = [{ column: sorter?.field, asc: sorter?.order === 'ascend' }]
|
||||
}
|
||||
// 筛选条件
|
||||
let f = {}
|
||||
if (filters) {
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value) {
|
||||
f[key] = value.join(',')
|
||||
} else {
|
||||
f[key] = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
run({
|
||||
current: pagination?.current || current,
|
||||
size: pagination?.pageSize || pageSize,
|
||||
...state.search,
|
||||
...s,
|
||||
...f,
|
||||
})
|
||||
}
|
||||
|
||||
const excelExport = async () => {
|
||||
let params = { ...state.search }
|
||||
let [err, res] = await axios.get('/db/tLogger/export', { params, responseType: 'blob' })
|
||||
if (err) {
|
||||
return message.error(err.msg || '获取数据失败')
|
||||
}
|
||||
// 文件导出
|
||||
var blob = res
|
||||
var filename = '日志.xls'
|
||||
if (typeof window.navigator.msSaveBlob !== 'undefined') {
|
||||
window.navigator.msSaveBlob(blob, filename)
|
||||
} else {
|
||||
var blobURL =
|
||||
window.URL && window.URL.createObjectURL
|
||||
? window.URL.createObjectURL(blob)
|
||||
: window.webkitURL.createObjectURL(blob)
|
||||
var tempLink = document.createElement('a')
|
||||
tempLink.style.display = 'none'
|
||||
tempLink.href = blobURL
|
||||
tempLink.setAttribute('download', filename)
|
||||
if (typeof tempLink.download === 'undefined') {
|
||||
tempLink.setAttribute('target', '_blank')
|
||||
}
|
||||
document.body.appendChild(tempLink)
|
||||
tempLink.click()
|
||||
setTimeout(function () {
|
||||
document.body.removeChild(tempLink)
|
||||
window.URL.revokeObjectURL(blobURL)
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
|
||||
const pagination = computed(() => ({
|
||||
current: current.value,
|
||||
pageSize: pageSize.value,
|
||||
total: total.value,
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tenant">
|
||||
<a-form ref="searchRef" :labelCol="{ flex: '80px' }" :model="state.search" layout="inline">
|
||||
<a-form-item label="表名称" name="tableName">
|
||||
<a-input v-model:value="state.search.tableName" placeholder="请输入表名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="操作类型" name="operation">
|
||||
<a-select v-model:value="state.search.operation" allowClear placeholder="请选择类型">
|
||||
<a-select-option value="insert">增加</a-select-option>
|
||||
<a-select-option value="update">修改</a-select-option>
|
||||
<a-select-option value="delete">删除</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button html-type="submit" type="primary" @click="onTableChange()">查询</a-button>
|
||||
<a-button html-type="reset" style="margin-left: 10px" @click="onSearchRest">重置</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button v-hasAuth="['export:excel']" type="primary" @click="excelExport()">导出</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="data?.records"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:scroll="{ x: '100%' }"
|
||||
@change="onTableChange"
|
||||
>
|
||||
<template v-slot:bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<a-popconfirm title="确定要删除这条记录吗?" @confirm="deleteData(record)">
|
||||
<a-button v-hasAuth="['system:logger:del']" danger type="link">删除</a-button>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tenant {
|
||||
.ant-form {
|
||||
.ant-form-item:nth-last-child(2) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.ant-form-item:last-child {
|
||||
margin-left: auto;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-btn-link {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 10px;
|
||||
|
||||
.ant-row {
|
||||
flex-flow: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
232
{{cookiecutter.project_slug}}/ManagerUI/src/views/LoginView.vue
Normal file
232
{{cookiecutter.project_slug}}/ManagerUI/src/views/LoginView.vue
Normal file
@ -0,0 +1,232 @@
|
||||
<script setup>
|
||||
import api from '@/http/api.js'
|
||||
import { computed, onBeforeMount, ref } from 'vue'
|
||||
import { LockOutlined, SafetyOutlined, UserOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { encrypt } from '@/utils/crypto.js'
|
||||
import { useSystemStore } from '@/stores/system.js'
|
||||
import router from '@/router/index.js'
|
||||
|
||||
const title = computed(() => import.meta.env.VITE_APP_TITLE)
|
||||
|
||||
const formRef = ref()
|
||||
|
||||
const systemStore = useSystemStore()
|
||||
|
||||
const userForm = ref({
|
||||
name: '',
|
||||
password: '',
|
||||
captcha: '',
|
||||
captchaId: '',
|
||||
captchaImg: '',
|
||||
})
|
||||
|
||||
const login = async () => {
|
||||
await formRef.value.validate()
|
||||
let [err, res] = await api.post('/manager/login', {
|
||||
name: encrypt(userForm.value.name),
|
||||
password: encrypt(userForm.value.password),
|
||||
captcha: userForm.value.captcha,
|
||||
captchaId: userForm.value.captchaId,
|
||||
})
|
||||
if (err || res.code !== 0) {
|
||||
formRef.value.resetFields()
|
||||
getCaptcha()
|
||||
return message.error(err.msg || '登录失败')
|
||||
} else {
|
||||
systemStore.init()
|
||||
router.push('/')
|
||||
return message.success(res.data || '登录成功')
|
||||
}
|
||||
}
|
||||
|
||||
const getCaptcha = async () => {
|
||||
let [err, res] = await api.get('/pub/captcha/img')
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '获取验证码失败')
|
||||
}
|
||||
userForm.value.captchaImg = res.data.captcha
|
||||
userForm.value.captchaId = res.data.objectId
|
||||
}
|
||||
|
||||
onBeforeMount(getCaptcha)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login">
|
||||
<div class="login-form">
|
||||
<h3 class="title">{{ title }}</h3>
|
||||
<a-form ref="formRef" :model="userForm" :wrapper-col="{ span: 18 }" name="basic">
|
||||
<a-form-item
|
||||
:rules="[{ required: true, message: '用户名不能为空', trigger: 'blur' }]"
|
||||
name="name"
|
||||
>
|
||||
<a-input v-model:value="userForm.name" placeholder="请输入用户名">
|
||||
<template #prefix>
|
||||
<UserOutlined />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:rules="[{ required: true, message: '密码不能为空', trigger: 'blur' }]"
|
||||
name="password"
|
||||
>
|
||||
<a-input-password v-model:value="userForm.password" placeholder="请输入密码">
|
||||
<template #prefix>
|
||||
<LockOutlined />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:rules="[{ required: true, message: '验证码不能为空', trigger: 'blur' }]"
|
||||
name="captcha"
|
||||
>
|
||||
<a-input v-model:value="userForm.captcha" placeholder="请输入验证码">
|
||||
<template #prefix>
|
||||
<SafetyOutlined />
|
||||
</template>
|
||||
</a-input>
|
||||
<a-image
|
||||
:preview="false"
|
||||
:src="userForm.captchaImg"
|
||||
class="captcha"
|
||||
@click="getCaptcha"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="login">登陆</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url(@/assets/images/login.webp);
|
||||
background-size: cover;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
@media screen and (min-width: 1025px) {
|
||||
align-items: flex-start;
|
||||
padding-left: 15%;
|
||||
|
||||
:deep(.login-form) {
|
||||
width: 450px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 769px) and (max-width: 1024px) {
|
||||
align-items: center;
|
||||
|
||||
:deep(.login-form) {
|
||||
width: 450px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
align-items: center;
|
||||
|
||||
:deep(.login-form) {
|
||||
width: calc(100% - 40px);
|
||||
}
|
||||
}
|
||||
|
||||
.login-form {
|
||||
background-color: rgb(104 121 165 / 30%);
|
||||
border-radius: 20px;
|
||||
border: 1px solid #fff;
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
color: #b3dbfc;
|
||||
}
|
||||
|
||||
:deep(.ant-form) {
|
||||
.ant-form-item {
|
||||
margin-bottom: 30px;
|
||||
|
||||
.ant-form-item-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-form-item-control-input-content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ant-input-affix-wrapper {
|
||||
position: relative;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 20px;
|
||||
padding: 0;
|
||||
height: 40px;
|
||||
|
||||
.ant-input-prefix {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 50%;
|
||||
z-index: 10;
|
||||
transform: translateY(-50%);
|
||||
margin-inline-end: 0;
|
||||
font-size: 18px;
|
||||
color: #b3dbfc;
|
||||
}
|
||||
|
||||
.ant-input-suffix {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 18px;
|
||||
|
||||
.anticon {
|
||||
color: #b3dbfc;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-input {
|
||||
padding-left: 40px;
|
||||
border-radius: 20px;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: #b3dbfc;
|
||||
|
||||
&::placeholder {
|
||||
color: #b3dbfc;
|
||||
}
|
||||
|
||||
&:-webkit-autofill {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-form-item-explain-error {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.captcha {
|
||||
width: auto;
|
||||
height: 100%;
|
||||
margin-left: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ant-btn {
|
||||
border-radius: 20px;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background-image: linear-gradient(to right, #2b72ff, #0055fb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,371 @@
|
||||
<script setup>
|
||||
import qs from 'qs'
|
||||
import dayjs from 'dayjs'
|
||||
import axios from '@/http/api.js'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { usePagination, useRequest } from 'vue-request'
|
||||
import { useSystemStore } from '@/stores/system.js'
|
||||
|
||||
const searchRef = ref()
|
||||
const editRef = ref()
|
||||
|
||||
const systemStore = useSystemStore()
|
||||
|
||||
const state = reactive({
|
||||
search: {},
|
||||
edit: {},
|
||||
modal: {
|
||||
title: '',
|
||||
show: false,
|
||||
},
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '发送人',
|
||||
dataIndex: 'sender',
|
||||
sorter: true,
|
||||
maxWidth: 120,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '接收人',
|
||||
dataIndex: 'recipient',
|
||||
maxWidth: 180,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '过期时间',
|
||||
dataIndex: 'expiry',
|
||||
maxWidth: 150,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
return text ? dayjs(text).format('LLL') : ''
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 80,
|
||||
customRender: ({ text: status }) => {
|
||||
return status === 0 ? '未读' : '已读'
|
||||
},
|
||||
filters: [
|
||||
{ text: '未读', value: 0 },
|
||||
{ text: '已读', value: 1 },
|
||||
],
|
||||
filterMultiple: false,
|
||||
},
|
||||
{
|
||||
title: '内容',
|
||||
dataIndex: 'description',
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
maxWidth: 150,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
return text ? dayjs(text).format('LLL') : ''
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime',
|
||||
maxWidth: 150,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
return text ? dayjs(text).format('LLL') : ''
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
align: 'center',
|
||||
width: 160,
|
||||
fixed: 'right',
|
||||
},
|
||||
]
|
||||
|
||||
const rules = {
|
||||
recipient: [
|
||||
{
|
||||
required: true,
|
||||
message: '接收人必填',
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
min: 2,
|
||||
max: 100,
|
||||
message: '最少2个字符,最多100个字符',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
description: [
|
||||
{
|
||||
required: true,
|
||||
message: '内容必填',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const onSearchRest = () => {
|
||||
searchRef.value.resetFields()
|
||||
onTableChange()
|
||||
}
|
||||
|
||||
const onEditChange = (record) => {
|
||||
if (record) {
|
||||
Object.assign(state.edit, record)
|
||||
state.modal.title = '修改'
|
||||
} else {
|
||||
state.edit = {}
|
||||
state.modal.title = '新增'
|
||||
}
|
||||
state.modal.show = true
|
||||
}
|
||||
|
||||
const onReadChange = async (record) => {
|
||||
let [err, res] = await axios.put('/message/read/' + record.id)
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '已读失败')
|
||||
} else {
|
||||
onTableChange()
|
||||
}
|
||||
}
|
||||
|
||||
const fetchRecipientOpt = async (params) => {
|
||||
let res = await Promise.all([
|
||||
axios.get('/db/tTenant', { params }),
|
||||
axios.get('/db/tAccount', { params }),
|
||||
])
|
||||
if (res[0][0] || res[0][1].code !== 0 || res[1][0] || res[1][1].code !== 0) {
|
||||
return message.error('加载数据失败')
|
||||
}
|
||||
res[0][1].data.records.forEach((record) => {
|
||||
record.isExpiry = true
|
||||
})
|
||||
return [...res[0][1].data.records, ...res[1][1].data.records]
|
||||
}
|
||||
|
||||
const fetchData = (params) => {
|
||||
return axios
|
||||
.get('/message?' + qs.stringify(params, { allowDots: true }))
|
||||
.then(([, res]) => res.data)
|
||||
}
|
||||
|
||||
const submitData = async () => {
|
||||
state.edit.sender = systemStore.realName || systemStore.name
|
||||
editRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
if (state.edit.id) {
|
||||
axios.put('/message', state.edit).then(([err, res]) => {
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '修改数据失败')
|
||||
} else {
|
||||
state.modal.show = false
|
||||
onTableChange()
|
||||
return message.success('修改数据成功')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
axios.post('/message', state.edit).then(([err, res]) => {
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '保存数据失败')
|
||||
} else {
|
||||
state.modal.show = false
|
||||
onTableChange()
|
||||
return message.success('保存数据成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
message.warning('表单校验错误')
|
||||
})
|
||||
}
|
||||
|
||||
const deleteData = (record) => {
|
||||
let params = { idList: record.id }
|
||||
axios.delete('/message', { params }).then(([err, res]) => {
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '删除数据失败')
|
||||
} else {
|
||||
onTableChange()
|
||||
return message.success('删除数据成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const { data: recipient, run: runRecipient } = useRequest(fetchRecipientOpt, {
|
||||
manual: true,
|
||||
debounceInterval: 300,
|
||||
defaultParams: [{ size: -1 }],
|
||||
})
|
||||
|
||||
const { data, run, loading, current, pageSize, total } = usePagination(fetchData, {
|
||||
pagination: {
|
||||
currentKey: 'current',
|
||||
pageSizeKey: 'size',
|
||||
listKey: 'records',
|
||||
totalKey: 'total',
|
||||
},
|
||||
defaultParams: [
|
||||
{
|
||||
current: 1,
|
||||
size: 10,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const onTableChange = (pagination, filters, sorter) => {
|
||||
// 排序条件
|
||||
let s = {}
|
||||
if (sorter) {
|
||||
s.orders = [{ column: sorter?.field, asc: sorter?.order === 'ascend' }]
|
||||
}
|
||||
// 筛选条件
|
||||
let f = {}
|
||||
if (filters) {
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value) {
|
||||
f[key] = value.join(',')
|
||||
} else {
|
||||
f[key] = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
run({
|
||||
current: pagination?.current || current,
|
||||
size: pagination?.pageSize || pageSize,
|
||||
...state.search,
|
||||
...s,
|
||||
...f,
|
||||
})
|
||||
}
|
||||
|
||||
const pagination = computed(() => ({
|
||||
current: current.value,
|
||||
pageSize: pageSize.value,
|
||||
total: total.value,
|
||||
}))
|
||||
|
||||
const recipientOpts = computed(() => {
|
||||
if (recipient.value) {
|
||||
let items = recipient.value.map((item) => ({
|
||||
value: item.realName || item.name,
|
||||
label: item.realName || item.name,
|
||||
isExpiry: item.isExpiry || false,
|
||||
}))
|
||||
items.unshift({ value: '所有人', label: '所有人', isExpiry: true })
|
||||
return items
|
||||
} else {
|
||||
return [{ value: '所有人', label: '所有人', isExpiry: true }]
|
||||
}
|
||||
})
|
||||
|
||||
const onSearchRecipient = (value) => {
|
||||
if (value) runRecipient({ size: -1, mention: value })
|
||||
}
|
||||
|
||||
const onSelectRecipient = (option) => {
|
||||
if (option.isExpiry) state.edit.isExpiry = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tenant">
|
||||
<a-form ref="searchRef" :labelCol="{ flex: '40px' }" :model="state.search" layout="inline">
|
||||
<a-form-item label="内容" name="description">
|
||||
<a-input v-model:value="state.search.description" placeholder="请输入内容" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-select v-model:value="state.search.status" placeholder="请选择类型">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="0">未读</a-select-option>
|
||||
<a-select-option value="1">已读</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button html-type="submit" type="primary" @click="onTableChange()">查询</a-button>
|
||||
<a-button html-type="reset" style="margin-left: 10px" @click="onSearchRest">重置</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button v-hasAuth="['message:post']" type="primary" @click="onEditChange()"
|
||||
>新增
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="data?.records"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:scroll="{ x: '100%' }"
|
||||
@change="onTableChange"
|
||||
>
|
||||
<template v-slot:bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<a-button v-hasAuth="['message:put']" type="link" @click="onEditChange(record)"
|
||||
>修改
|
||||
</a-button>
|
||||
<a-button type="link" @click="onReadChange(record)">已读</a-button>
|
||||
<a-popconfirm title="确定要删除这条记录吗?" @confirm="deleteData(record)">
|
||||
<a-button v-hasAuth="['message:del']" danger type="link">删除</a-button>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 弹出框 -->
|
||||
<a-modal v-model:open="state.modal.show" :title="state.modal.title" @ok="submitData">
|
||||
<a-form ref="editRef" :labelCol="{ flex: '80px' }" :model="state.edit" :rules="rules">
|
||||
<a-form-item label="接收人" name="recipient">
|
||||
<a-mentions
|
||||
v-model:value="state.edit.recipient"
|
||||
:filterOption="false"
|
||||
:options="recipientOpts"
|
||||
placeholder="@接收人,可选所有人、租户、账户"
|
||||
@search="onSearchRecipient"
|
||||
@select="onSelectRecipient"
|
||||
>
|
||||
</a-mentions>
|
||||
</a-form-item>
|
||||
<a-form-item label="内容" name="description">
|
||||
<a-textarea v-model:value="state.edit.description" placeholder="请输入描述" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tenant {
|
||||
.ant-form {
|
||||
.ant-form-item:nth-last-child(2) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.ant-form-item:last-child {
|
||||
margin-left: auto;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-btn-link {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 10px;
|
||||
|
||||
.ant-row {
|
||||
flex-flow: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
527
{{cookiecutter.project_slug}}/ManagerUI/src/views/RoleView.vue
Normal file
527
{{cookiecutter.project_slug}}/ManagerUI/src/views/RoleView.vue
Normal file
@ -0,0 +1,527 @@
|
||||
<script setup>
|
||||
import qs from 'qs'
|
||||
import dayjs from 'dayjs'
|
||||
import axios from '@/http/api.js'
|
||||
import _ from 'lodash'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { DownOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { usePagination, useRequest } from 'vue-request'
|
||||
import { fromArray } from 'tree-lodash'
|
||||
|
||||
const searchRef = ref()
|
||||
const editRef = ref()
|
||||
|
||||
const state = reactive({
|
||||
search: {},
|
||||
edit: {},
|
||||
compare: {
|
||||
id: 0,
|
||||
tenant: '',
|
||||
list: [],
|
||||
before: [],
|
||||
beforeHalf: [],
|
||||
later: [],
|
||||
laterHalf: [],
|
||||
},
|
||||
checked: [],
|
||||
halfChecked: [],
|
||||
modal: {
|
||||
title: '',
|
||||
show: false,
|
||||
},
|
||||
auth: {
|
||||
title: '设置权限',
|
||||
show: false,
|
||||
},
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '角色名称',
|
||||
dataIndex: 'name',
|
||||
sorter: true,
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '英文值',
|
||||
dataIndex: 'value',
|
||||
sorter: true,
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 80,
|
||||
customRender: ({ text: status }) => {
|
||||
return status === 0 ? '正常' : '冻结'
|
||||
},
|
||||
filters: [
|
||||
{ text: '正常', value: 0 },
|
||||
{ text: '冻结', value: 1 },
|
||||
],
|
||||
filterMultiple: false,
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'description',
|
||||
maxWidth: 200,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
maxWidth: 150,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
return text ? dayjs(text).format('LLL') : ''
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime',
|
||||
maxWidth: 150,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
return text ? dayjs(text).format('LLL') : ''
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
align: 'center',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
},
|
||||
]
|
||||
|
||||
const rules = {
|
||||
name: [
|
||||
{ required: true, message: '角色名称必填', trigger: 'blur' },
|
||||
{ min: 2, max: 100, message: '最少2个字符,最多100个字符', trigger: 'blur' },
|
||||
],
|
||||
value: [
|
||||
{ required: true, message: '英文值必填', trigger: 'blur' },
|
||||
{ min: 4, max: 100, message: '最少4个字符,最多100个字符', trigger: 'blur' },
|
||||
],
|
||||
tenant: [
|
||||
{
|
||||
trigger: 'blur',
|
||||
validator: async (_rule, value) => {
|
||||
if (value || value === '') {
|
||||
return Promise.resolve()
|
||||
} else {
|
||||
return Promise.reject('租户必选')
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const onSearchRest = () => {
|
||||
searchRef.value.resetFields()
|
||||
onTableChange()
|
||||
}
|
||||
|
||||
const onEditChange = (record) => {
|
||||
if (record) {
|
||||
Object.assign(state.edit, record)
|
||||
state.modal.title = '修改'
|
||||
} else {
|
||||
state.edit = {}
|
||||
state.modal.title = '新增'
|
||||
}
|
||||
state.modal.show = true
|
||||
}
|
||||
|
||||
const onAuthChange = (record) => {
|
||||
state.compare.id = record.id
|
||||
state.compare.tenant = record.tenant
|
||||
fetchRoleAuthorityOpt({
|
||||
size: -1,
|
||||
roleId: state.compare.id,
|
||||
})
|
||||
state.auth.show = true
|
||||
}
|
||||
|
||||
const onAuthCheck = (checkedKeys, { halfCheckedKeys }) => {
|
||||
state.halfChecked = halfCheckedKeys
|
||||
//
|
||||
state.compare.later = checkedKeys
|
||||
state.compare.laterHalf = halfCheckedKeys
|
||||
}
|
||||
|
||||
const fetchTenantOpt = async (params) => {
|
||||
let [err, res] = await axios.get('/db/tTenant', { params })
|
||||
if (err || res.code !== 0) {
|
||||
message.error(err.msg || '获取数据失败')
|
||||
return []
|
||||
} else {
|
||||
let opts = res.data.records.map((item) => ({ label: item.name, value: item.name }))
|
||||
opts.unshift({ label: '公共数据', value: '' })
|
||||
return opts
|
||||
}
|
||||
}
|
||||
|
||||
const fetchAuthorityOpt = async (params) => {
|
||||
let [err, res] = await axios.get('/db/tAuthority?' + qs.stringify(params, { skipNulls: true }))
|
||||
if (err || res.code !== 0) {
|
||||
message.error(err.msg || '获取数据失败')
|
||||
return []
|
||||
} else {
|
||||
return res.data.records || []
|
||||
}
|
||||
}
|
||||
|
||||
const fetchRoleAuthorityOpt = async (params) => {
|
||||
let [err, res] = await axios.get(
|
||||
'/db/tRoleAuthority?' + qs.stringify(params, { skipNulls: true }),
|
||||
)
|
||||
if (err || res.code !== 0) {
|
||||
message.error(err.msg || '获取数据失败')
|
||||
} else {
|
||||
const data = res.data.records || []
|
||||
state.compare.list = data
|
||||
//
|
||||
state.checked = data.filter((item) => !item.half).map((item) => item.authorityId)
|
||||
state.halfChecked = data.filter((item) => item.half).map((item) => item.authorityId)
|
||||
state.compare.before = data.filter((item) => !item.half).map((item) => item.authorityId)
|
||||
state.compare.beforeHalf = data.filter((item) => item.half).map((item) => item.authorityId)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchData = (params) => {
|
||||
return axios
|
||||
.get('/db/tRole?' + qs.stringify(params, { allowDots: true }))
|
||||
.then(([, res]) => res.data)
|
||||
}
|
||||
|
||||
const submitData = async () => {
|
||||
editRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
if (state.edit.id) {
|
||||
axios.put('/db/tRole', state.edit).then(([err, res]) => {
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '修改数据失败')
|
||||
} else {
|
||||
state.modal.show = false
|
||||
onTableChange()
|
||||
return message.success('修改数据成功')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
axios.post('/db/tRole', state.edit).then(([err, res]) => {
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '保存数据失败')
|
||||
} else {
|
||||
state.modal.show = false
|
||||
onTableChange()
|
||||
return message.success('保存数据成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
message.warning('表单校验错误')
|
||||
})
|
||||
}
|
||||
|
||||
const deleteData = (record) => {
|
||||
let params = { idList: record.id }
|
||||
axios.delete('/db/tRole', { params }).then(([err, res]) => {
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '删除数据失败')
|
||||
} else {
|
||||
onTableChange()
|
||||
return message.success('删除数据成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const submitCheck = () => {
|
||||
// 比较前后选择
|
||||
let deletes = _.difference(state.compare.before, state.compare.later)
|
||||
let deletesHalf = _.difference(state.compare.beforeHalf, state.compare.laterHalf)
|
||||
let creates = _.difference(state.compare.later, state.compare.before)
|
||||
let createsHalf = _.difference(state.compare.laterHalf, state.compare.beforeHalf)
|
||||
console.log(deletes, deletesHalf, creates, createsHalf)
|
||||
// 删除
|
||||
let deleteIds = state.compare.list
|
||||
.filter(
|
||||
(item) => _.includes(deletes, item.authorityId) || _.includes(deletesHalf, item.authorityId),
|
||||
)
|
||||
.map((item) => item.id)
|
||||
if (deleteIds && deleteIds.length > 0) {
|
||||
let params = { idList: deleteIds.join(',') }
|
||||
axios.delete('/db/tRoleAuthority', { params })
|
||||
}
|
||||
// 新增
|
||||
if (createsHalf && createsHalf.length > 0) {
|
||||
axios.post(
|
||||
'/db/tRoleAuthority/s',
|
||||
createsHalf.map((item) => ({
|
||||
tenant: state.compare.tenant,
|
||||
roleId: state.compare.id,
|
||||
authorityId: item,
|
||||
half: true,
|
||||
})),
|
||||
)
|
||||
}
|
||||
if (creates && creates.length > 0) {
|
||||
axios.post(
|
||||
'/db/tRoleAuthority/s',
|
||||
creates.map((item) => ({
|
||||
tenant: state.compare.tenant,
|
||||
roleId: state.compare.id,
|
||||
authorityId: item,
|
||||
half: false,
|
||||
})),
|
||||
)
|
||||
}
|
||||
state.auth.show = false
|
||||
}
|
||||
|
||||
const { data: tenantOpts, run: runTenantOpt } = useRequest(fetchTenantOpt, {
|
||||
debounceInterval: 300,
|
||||
defaultParams: [{ size: -1 }],
|
||||
})
|
||||
|
||||
const { data: authorityOpts } = useRequest(fetchAuthorityOpt, {
|
||||
debounceInterval: 300,
|
||||
defaultParams: [{ size: -1 }],
|
||||
})
|
||||
|
||||
const { data, run, loading, current, pageSize, total } = usePagination(fetchData, {
|
||||
pagination: {
|
||||
currentKey: 'current',
|
||||
pageSizeKey: 'size',
|
||||
listKey: 'records',
|
||||
totalKey: 'total',
|
||||
},
|
||||
defaultParams: [
|
||||
{
|
||||
current: 1,
|
||||
size: 10,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const onTableChange = (pagination, filters, sorter) => {
|
||||
// 排序条件
|
||||
let s = {}
|
||||
if (sorter) {
|
||||
s.orders = [{ column: sorter?.field, asc: sorter?.order === 'ascend' }]
|
||||
}
|
||||
// 筛选条件
|
||||
let f = {}
|
||||
if (filters) {
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value) {
|
||||
f[key] = value.join(',')
|
||||
} else {
|
||||
f[key] = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
run({
|
||||
current: pagination?.current || current,
|
||||
size: pagination?.pageSize || pageSize,
|
||||
...state.search,
|
||||
...s,
|
||||
...f,
|
||||
})
|
||||
}
|
||||
|
||||
const excelExport = async () => {
|
||||
let params = { ...state.search }
|
||||
let [err, res] = await axios.get('/db/tRole/export', { params, responseType: 'blob' })
|
||||
if (err) {
|
||||
return message.error(err.msg || '获取数据失败')
|
||||
}
|
||||
// 文件导出
|
||||
var blob = res
|
||||
var filename = '角色.xls'
|
||||
if (typeof window.navigator.msSaveBlob !== 'undefined') {
|
||||
window.navigator.msSaveBlob(blob, filename)
|
||||
} else {
|
||||
var blobURL =
|
||||
window.URL && window.URL.createObjectURL
|
||||
? window.URL.createObjectURL(blob)
|
||||
: window.webkitURL.createObjectURL(blob)
|
||||
var tempLink = document.createElement('a')
|
||||
tempLink.style.display = 'none'
|
||||
tempLink.href = blobURL
|
||||
tempLink.setAttribute('download', filename)
|
||||
if (typeof tempLink.download === 'undefined') {
|
||||
tempLink.setAttribute('target', '_blank')
|
||||
}
|
||||
document.body.appendChild(tempLink)
|
||||
tempLink.click()
|
||||
setTimeout(function () {
|
||||
document.body.removeChild(tempLink)
|
||||
window.URL.revokeObjectURL(blobURL)
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
|
||||
const excelImport = ({ file }) => {
|
||||
switch (file.status) {
|
||||
case 'error':
|
||||
message.error(file?.response?.msg || '上传失败')
|
||||
break
|
||||
case 'done':
|
||||
message.success('导入成功')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const pagination = computed(() => ({
|
||||
current: current.value,
|
||||
pageSize: pageSize.value,
|
||||
total: total.value,
|
||||
}))
|
||||
|
||||
const treeAuth = computed(() => fromArray(authorityOpts.value, { parentKey: 'parentId' }))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="role">
|
||||
<a-form ref="searchRef" :labelCol="{ flex: '80px' }" :model="state.search" layout="inline">
|
||||
<a-form-item label="角色名称" name="name">
|
||||
<a-input v-model:value="state.search.name" placeholder="请输入名称" />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button html-type="submit" type="primary" @click="onTableChange()">查询</a-button>
|
||||
<a-button html-type="reset" style="margin-left: 10px" @click="onSearchRest">重置</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button v-hasAuth="['system:role:post']" type="primary" @click="onEditChange()"
|
||||
>新增
|
||||
</a-button>
|
||||
<a-dropdown>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<div v-hasAuth="['import:excel']">
|
||||
<a-menu-item key="import">
|
||||
<a-upload
|
||||
:showUploadList="false"
|
||||
accept=".xlsx, .xls, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
|
||||
action="/api/db/tRole/import"
|
||||
@change="excelImport"
|
||||
>
|
||||
导入 Excel
|
||||
</a-upload>
|
||||
</a-menu-item>
|
||||
</div>
|
||||
<div v-hasAuth="['export:excel']">
|
||||
<a-menu-item key="export" @click="excelExport">导出 Excel</a-menu-item>
|
||||
</div>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button style="margin-left: 10px">
|
||||
更多
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="data?.records"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:scroll="{ x: '100%' }"
|
||||
@change="onTableChange"
|
||||
>
|
||||
<template v-slot:bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<a-button v-hasAuth="['system:role:put']" type="link" @click="onEditChange(record)"
|
||||
>修改
|
||||
</a-button>
|
||||
<a-button v-hasAuth="['system:role:put']" type="link" @click="onAuthChange(record)"
|
||||
>分配权限
|
||||
</a-button>
|
||||
<a-popconfirm title="确定要删除这条记录吗?" @confirm="deleteData(record)">
|
||||
<a-button v-hasAuth="['system:role:del']" danger type="link">删除</a-button>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 弹出框 -->
|
||||
<a-modal v-model:open="state.modal.show" :title="state.modal.title" @ok="submitData">
|
||||
<a-form ref="editRef" :labelCol="{ flex: '80px' }" :model="state.edit" :rules="rules">
|
||||
<a-form-item label="名称" name="name">
|
||||
<a-input v-model:value="state.edit.name" placeholder="请输入角色名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="英文值" name="value">
|
||||
<a-input v-model:value="state.edit.value" placeholder="请输入英文值" />
|
||||
</a-form-item>
|
||||
<a-form-item label="是否启用" name="status">
|
||||
<a-switch
|
||||
v-model:checked="state.edit.status"
|
||||
:checkedValue="0"
|
||||
:unCheckedValue="1"
|
||||
placeholder="请输入状态"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-isa label="租户" name="tenant">
|
||||
<a-select
|
||||
v-model:value="state.edit.tenant"
|
||||
:filterOption="false"
|
||||
:options="tenantOpts"
|
||||
placeholder="请选择隶属租户"
|
||||
show-search
|
||||
@search="(value) => runTenantOpt({ size: -1, name: value || null })"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="描述" name="description">
|
||||
<a-textarea v-model:value="state.edit.description" placeholder="请输入描述" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<a-modal v-model:open="state.auth.show" :title="state.auth.title" @ok="submitCheck">
|
||||
<a-tree
|
||||
v-model:checked-keys="state.checked"
|
||||
:field-names="{ key: 'id' }"
|
||||
:tree-data="treeAuth"
|
||||
checkable
|
||||
@check="onAuthCheck"
|
||||
>
|
||||
<template #title="{ name, description }">
|
||||
{{ name + '' }}
|
||||
<span style="color: darkgray">{{ description }}</span>
|
||||
</template>
|
||||
</a-tree>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.role {
|
||||
.ant-form {
|
||||
.ant-form-item:nth-last-child(2) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.ant-form-item:last-child {
|
||||
margin-left: auto;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-btn-link {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 10px;
|
||||
|
||||
.ant-row {
|
||||
flex-flow: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
432
{{cookiecutter.project_slug}}/ManagerUI/src/views/TenantView.vue
Normal file
432
{{cookiecutter.project_slug}}/ManagerUI/src/views/TenantView.vue
Normal file
@ -0,0 +1,432 @@
|
||||
<script setup>
|
||||
import qs from 'qs'
|
||||
import dayjs from 'dayjs'
|
||||
import axios from '@/http/api.js'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { DownOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { usePagination, useRequest } from 'vue-request'
|
||||
|
||||
const searchRef = ref()
|
||||
const editRef = ref()
|
||||
|
||||
const state = reactive({
|
||||
search: {},
|
||||
edit: {},
|
||||
modal: {
|
||||
title: '',
|
||||
show: false,
|
||||
},
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '租户名称',
|
||||
dataIndex: 'name',
|
||||
sorter: true,
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '共享给...',
|
||||
dataIndex: 'shared',
|
||||
width: 180,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '租户类型',
|
||||
dataIndex: 'type',
|
||||
sorter: true,
|
||||
width: 120,
|
||||
customRender: ({ text: type }) => {
|
||||
return type === 0 ? '企业' : '个人'
|
||||
},
|
||||
filters: [
|
||||
{ text: '企业', value: 0 },
|
||||
{ text: '个人', value: 1 },
|
||||
],
|
||||
filterMultiple: false,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 80,
|
||||
customRender: ({ text: status }) => {
|
||||
return status === 0 ? '正常' : '冻结'
|
||||
},
|
||||
filters: [
|
||||
{ text: '正常', value: 0 },
|
||||
{ text: '冻结', value: 1 },
|
||||
],
|
||||
filterMultiple: false,
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'description',
|
||||
maxWidth: 200,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
maxWidth: 150,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
return text ? dayjs(text).format('LLL') : ''
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime',
|
||||
maxWidth: 150,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
return text ? dayjs(text).format('LLL') : ''
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
align: 'center',
|
||||
width: 120,
|
||||
fixed: 'right',
|
||||
},
|
||||
]
|
||||
|
||||
const rules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '租户名称必填',
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
min: 2,
|
||||
max: 100,
|
||||
message: '最少2个字符,最多100个字符',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const onSearchRest = () => {
|
||||
searchRef.value.resetFields()
|
||||
onTableChange()
|
||||
}
|
||||
|
||||
const onEditChange = (record) => {
|
||||
if (record) {
|
||||
Object.assign(state.edit, record)
|
||||
state.modal.title = '修改'
|
||||
} else {
|
||||
state.edit = {}
|
||||
state.modal.title = '新增'
|
||||
}
|
||||
state.modal.show = true
|
||||
}
|
||||
|
||||
const fetchTenantOpt = async (params) => {
|
||||
let [err, res] = await axios.get('/db/tTenant', { params })
|
||||
if (err || res.code !== 0) {
|
||||
message.error(err.msg || '获取数据失败')
|
||||
return []
|
||||
} else {
|
||||
return res.data.records.map((item) => ({ label: item.name, value: item.name }))
|
||||
}
|
||||
}
|
||||
|
||||
const fetchData = (params) => {
|
||||
return axios
|
||||
.get('/db/tTenant?' + qs.stringify(params, { allowDots: true }))
|
||||
.then(([, res]) => res.data)
|
||||
}
|
||||
|
||||
const submitData = async () => {
|
||||
editRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
if (state.edit.id) {
|
||||
axios.put('/db/tTenant', state.edit).then(([err, res]) => {
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '修改数据失败')
|
||||
} else {
|
||||
state.modal.show = false
|
||||
onTableChange()
|
||||
return message.success('修改数据成功')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
axios.post('/db/tTenant', state.edit).then(([err, res]) => {
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '保存数据失败')
|
||||
} else {
|
||||
state.modal.show = false
|
||||
onTableChange()
|
||||
return message.success('保存数据成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
message.warning('表单校验错误')
|
||||
})
|
||||
}
|
||||
|
||||
const deleteData = (record) => {
|
||||
let params = { idList: record.id }
|
||||
axios.delete('/db/tTenant', { params }).then(([err, res]) => {
|
||||
if (err || res.code !== 0) {
|
||||
return message.error(err.msg || '删除数据失败')
|
||||
} else {
|
||||
onTableChange()
|
||||
return message.success('删除数据成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const { data: tenantOpts, run: runTenantOpt } = useRequest(fetchTenantOpt, {
|
||||
debounceInterval: 300,
|
||||
defaultParams: [{ size: -1 }],
|
||||
})
|
||||
|
||||
const { data, run, loading, current, pageSize, total } = usePagination(fetchData, {
|
||||
pagination: {
|
||||
currentKey: 'current',
|
||||
pageSizeKey: 'size',
|
||||
listKey: 'records',
|
||||
totalKey: 'total',
|
||||
},
|
||||
defaultParams: [
|
||||
{
|
||||
current: 1,
|
||||
size: 10,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const onTableChange = (pagination, filters, sorter) => {
|
||||
// 排序条件
|
||||
let s = {}
|
||||
if (sorter) {
|
||||
s.orders = [{ column: sorter?.field, asc: sorter?.order === 'ascend' }]
|
||||
}
|
||||
// 筛选条件
|
||||
let f = {}
|
||||
if (filters) {
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value) {
|
||||
f[key] = value.join(',')
|
||||
} else {
|
||||
f[key] = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
run({
|
||||
current: pagination?.current || current,
|
||||
size: pagination?.pageSize || pageSize,
|
||||
...state.search,
|
||||
...s,
|
||||
...f,
|
||||
})
|
||||
}
|
||||
|
||||
const excelExport = async () => {
|
||||
let params = { ...state.search }
|
||||
let [err, res] = await axios.get('/db/tTenant/export', { params, responseType: 'blob' })
|
||||
if (err) {
|
||||
return message.error(err.msg || '获取数据失败')
|
||||
}
|
||||
// 文件导出
|
||||
var blob = res
|
||||
var filename = '租户.xls'
|
||||
if (typeof window.navigator.msSaveBlob !== 'undefined') {
|
||||
window.navigator.msSaveBlob(blob, filename)
|
||||
} else {
|
||||
var blobURL =
|
||||
window.URL && window.URL.createObjectURL
|
||||
? window.URL.createObjectURL(blob)
|
||||
: window.webkitURL.createObjectURL(blob)
|
||||
var tempLink = document.createElement('a')
|
||||
tempLink.style.display = 'none'
|
||||
tempLink.href = blobURL
|
||||
tempLink.setAttribute('download', filename)
|
||||
if (typeof tempLink.download === 'undefined') {
|
||||
tempLink.setAttribute('target', '_blank')
|
||||
}
|
||||
document.body.appendChild(tempLink)
|
||||
tempLink.click()
|
||||
setTimeout(function () {
|
||||
document.body.removeChild(tempLink)
|
||||
window.URL.revokeObjectURL(blobURL)
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
|
||||
const excelImport = ({ file }) => {
|
||||
switch (file.status) {
|
||||
case 'error':
|
||||
message.error(file?.response?.msg || '上传失败')
|
||||
break
|
||||
case 'done':
|
||||
message.success('导入成功')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const pagination = computed(() => ({
|
||||
current: current.value,
|
||||
pageSize: pageSize.value,
|
||||
total: total.value,
|
||||
}))
|
||||
|
||||
const tenantOpt = computed({
|
||||
get() {
|
||||
if (state.edit?.shared) {
|
||||
return state.edit.shared.split(',')
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
},
|
||||
set(val) {
|
||||
state.edit.shared = val.join(',')
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tenant">
|
||||
<a-form ref="searchRef" :labelCol="{ flex: '80px' }" :model="state.search" layout="inline">
|
||||
<a-form-item label="租户名称" name="name">
|
||||
<a-input v-model:value="state.search.name" placeholder="请输入名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="租户类型" name="type">
|
||||
<a-select v-model:value="state.search.type" placeholder="请选择类型">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="0">企业</a-select-option>
|
||||
<a-select-option value="1">个人</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button html-type="submit" type="primary" @click="onTableChange()">查询</a-button>
|
||||
<a-button html-type="reset" style="margin-left: 10px" @click="onSearchRest">重置</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button v-hasAuth="['system:tenant:post']" type="primary" @click="onEditChange()"
|
||||
>新增
|
||||
</a-button>
|
||||
<a-dropdown>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<div v-hasAuth="['import:excel']">
|
||||
<a-menu-item key="import">
|
||||
<a-upload
|
||||
:showUploadList="false"
|
||||
accept=".xlsx, .xls, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
|
||||
action="/api/db/tTenant/import"
|
||||
@change="excelImport"
|
||||
>
|
||||
导入 Excel
|
||||
</a-upload>
|
||||
</a-menu-item>
|
||||
</div>
|
||||
<div v-hasAuth="['export:excel']">
|
||||
<a-menu-item key="export" @click="excelExport">导出 Excel</a-menu-item>
|
||||
</div>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button style="margin-left: 10px">
|
||||
更多
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="data?.records"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:scroll="{ x: '100%' }"
|
||||
@change="onTableChange"
|
||||
>
|
||||
<template v-slot:bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<a-button v-hasAuth="['system:tenant:put']" type="link" @click="onEditChange(record)"
|
||||
>修改
|
||||
</a-button>
|
||||
<a-popconfirm title="确定要删除这条记录吗?" @confirm="deleteData(record)">
|
||||
<a-button v-hasAuth="['system:tenant:del']" danger type="link">删除</a-button>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 弹出框 -->
|
||||
<a-modal v-model:open="state.modal.show" :title="state.modal.title" @ok="submitData">
|
||||
<a-form ref="editRef" :labelCol="{ flex: '80px' }" :model="state.edit" :rules="rules">
|
||||
<a-form-item label="名称" name="name">
|
||||
<a-input v-model:value="state.edit.name" placeholder="请输入名称" />
|
||||
</a-form-item>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="类型" name="type">
|
||||
<a-select v-model:value="state.edit.type" placeholder="请选择类型">
|
||||
<a-select-option :value="0">企业</a-select-option>
|
||||
<a-select-option :value="1">个人</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :push="2" :span="10">
|
||||
<a-form-item label="是否启用" name="status">
|
||||
<a-switch
|
||||
v-model:checked="state.edit.status"
|
||||
:checkedValue="0"
|
||||
:unCheckedValue="1"
|
||||
placeholder="请输入状态"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="共享数据给" name="status">
|
||||
<a-select
|
||||
v-model:value="tenantOpt"
|
||||
:filterOption="false"
|
||||
:options="tenantOpts"
|
||||
mode="multiple"
|
||||
placeholder="请多选共享数据的其他租户"
|
||||
@search="(value) => runTenantOpt({ size: -1, name: value || null })"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="描述" name="description">
|
||||
<a-textarea v-model:value="state.edit.description" placeholder="请输入描述" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tenant {
|
||||
.ant-form {
|
||||
.ant-form-item:nth-last-child(2) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.ant-form-item:last-child {
|
||||
margin-left: auto;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-btn-link {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 10px;
|
||||
|
||||
.ant-row {
|
||||
flex-flow: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,31 @@
|
||||
<script setup>
|
||||
import { LoadingOutlined } from '@ant-design/icons-vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps({
|
||||
link: String,
|
||||
})
|
||||
|
||||
const loading = ref(true)
|
||||
|
||||
const iframeLoaded = () => {
|
||||
loading.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-result v-if="loading" subTitle="应用加载中,请耐心等待。" title="加载中...">
|
||||
<template #icon>
|
||||
<LoadingOutlined />
|
||||
</template>
|
||||
</a-result>
|
||||
<iframe :onload="iframeLoaded" :src="link" allow="microphone" />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
import router from '@/router/index.js'
|
||||
|
||||
const toHome = () => router.push({ path: '/' })
|
||||
|
||||
const toBack = () => router.back()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-result status="403" sub-title="抱歉,您无权访问此页面。" title="403">
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="toHome">回到首页</a-button>
|
||||
<a-button type="default" @click="toBack">上一页</a-button>
|
||||
</template>
|
||||
</a-result>
|
||||
</template>
|
@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
import router from '@/router/index.js'
|
||||
|
||||
const toHome = () => router.push({ path: '/' })
|
||||
|
||||
const toBack = () => router.back()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-result status="404" sub-title="抱歉,您访问的页面不存在。" title="404">
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="toHome">回到首页</a-button>
|
||||
<a-button type="default" @click="toBack">上一页</a-button>
|
||||
</template>
|
||||
</a-result>
|
||||
</template>
|
@ -0,0 +1,18 @@
|
||||
<script setup>
|
||||
import router from '@/router/index.js'
|
||||
|
||||
const toHome = () => router.push({ path: '/' })
|
||||
|
||||
const toBack = () => router.back()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-result status="500" sub-title="对不起,服务器出错。" title="500">
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="toHome">回到首页</a-button>
|
||||
<a-button type="default" @click="toBack">上一页</a-button>
|
||||
</template>
|
||||
</a-result>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
89
{{cookiecutter.project_slug}}/ManagerUI/vite.config.js
Normal file
89
{{cookiecutter.project_slug}}/ManagerUI/vite.config.js
Normal file
@ -0,0 +1,89 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
|
||||
import { createHtmlPlugin } from 'vite-plugin-html'
|
||||
import viteCompression from 'vite-plugin-compression'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig(({ mode }) => {
|
||||
// eslint-disable-next-line no-undef
|
||||
const env = loadEnv(mode, process.cwd())
|
||||
|
||||
return {
|
||||
base: './',
|
||||
build: {
|
||||
emptyOutDir: true,
|
||||
manifest: true,
|
||||
outDir: '../src/main/resources/static/manager-ui',
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks(id) {
|
||||
if (id.includes('node_modules')) {
|
||||
if (id.includes('ant-design-vue')) {
|
||||
return 'ant-design-vue'
|
||||
} else if (id.includes('dayjs')) {
|
||||
return 'dayjs'
|
||||
} else if (id.includes('lodash')) {
|
||||
return 'lodash'
|
||||
} else if (id.includes('tree-lodash')) {
|
||||
return 'tree-lodash'
|
||||
} else if (id.includes('vue-request')) {
|
||||
return 'vue-request'
|
||||
} else if (id.includes('js-cookie')) {
|
||||
return 'js-cookie'
|
||||
} else if (id.includes('jsencrypt')) {
|
||||
return 'jsencrypt'
|
||||
} else if (id.includes('jwt-decode')) {
|
||||
return 'jwt-decode'
|
||||
} else {
|
||||
return 'vendor'
|
||||
}
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
vueJsx(),
|
||||
viteCompression({ threshold: 1024 * 300 }),
|
||||
vueDevTools(),
|
||||
Components({
|
||||
resolvers: [
|
||||
AntDesignVueResolver({
|
||||
importStyle: false, // css in js
|
||||
}),
|
||||
],
|
||||
}),
|
||||
createHtmlPlugin({
|
||||
inject: {
|
||||
data: {
|
||||
VITE_APP_TITLE: env.VITE_APP_TITLE,
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
113
{{cookiecutter.project_slug}}/README.md
Normal file
113
{{cookiecutter.project_slug}}/README.md
Normal file
@ -0,0 +1,113 @@
|
||||
# {{ cookiecutter.project_hans }}
|
||||
|
||||
## 后端快速开始
|
||||
|
||||
1. 创建数据库
|
||||
|
||||
```sql
|
||||
# mysql
|
||||
CREATE DATABASE `{{ cookiecutter.mvn_artifact_id }}` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
```
|
||||
|
||||
```sql
|
||||
# postgis 启用 gis 扩展
|
||||
CREATE DATABASE {{ cookiecutter.mvn_artifact_id }} WITH ENCODING = 'utf8';
|
||||
CREATE EXTENSION postgis;
|
||||
```
|
||||
|
||||
2. 项目运行
|
||||
|
||||
```shell
|
||||
mvn spring-boot:run
|
||||
```
|
||||
3. 项目发布,修改 yml 配置文件选择发布配置
|
||||
|
||||
```shell
|
||||
mvn package
|
||||
```
|
||||
|
||||
4. 项目运行
|
||||
|
||||
```shell
|
||||
java -jar {{ cookiecutter.mvn_artifact_id }}-0.0.1-SNAPSHOT.jar
|
||||
```
|
||||
|
||||
## 管理后台快速开始
|
||||
|
||||
1. 安装项目依赖
|
||||
|
||||
```shell
|
||||
npm i
|
||||
```
|
||||
|
||||
2. 运行
|
||||
|
||||
```shell
|
||||
npm run dev
|
||||
```
|
||||
|
||||
3. 发布后,会打包至后端的资源文件夹 manager-ui 下
|
||||
|
||||
```shell
|
||||
npm run build:prod
|
||||
```
|
||||
|
||||
## 访问地址
|
||||
|
||||
1. 接口文档路径 [swagger-ui](http://localhost:8080/swagger-ui.html)
|
||||
2. 后台管理路径 [manager-ui](http://localhost:8080/manager-ui.html)
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 修改项目常量 `{{ cookiecutter.__mvn_package }}.common.Constants`
|
||||
2. 前后端 RSA 密钥替换 `{{ cookiecutter.__mvn_package }}.utils.CryptoUtil`
|
||||
|
||||
## 代码生成
|
||||
|
||||
1. Idea 安装插件 [EasyCode-MybatisCodeHelper](https://plugins.jetbrains.com/plugin/13847-easycode-mybatiscodehelper)
|
||||
2. Idea 安装插件 [CheckStyle-IDEA](https://plugins.jetbrains.com/plugin/1065-checkstyle-idea)
|
||||
3. Idea 开发工具连接数据库 {{ cookiecutter.mvn_artifact_id }}
|
||||
4. 数据库表名右键即可生成代码
|
||||
|
||||
## 文件夹说明
|
||||
|
||||
```
|
||||
── {{cookiecutter.project_slug}} {{ cookiecutter.project_hans }} 项目
|
||||
├── CheckStyle 代码样式检查的配置
|
||||
├── ManagerUI Vue.js 的后台项目
|
||||
├── files 接口上传文件
|
||||
├── EasyCode 代码生成工具模版
|
||||
└── src
|
||||
├── main
|
||||
│ ├── java
|
||||
│ │ └── {{cookiecutter.__package_path}}
|
||||
│ │ ├── common 常量
|
||||
│ │ ├── config 配置
|
||||
│ │ ├── enums 枚举
|
||||
│ │ ├── mapper 对象转换
|
||||
│ │ ├── module 业务模块
|
||||
│ │ │ ├── dao 自定义 SQL 语句
|
||||
│ │ │ ├── manager 后台管理接口
|
||||
│ │ │ │ └── request 自定义请求体
|
||||
│ │ │ └── schedule 定时任务
|
||||
│ │ ├── rest 代码生成 Restful 接口
|
||||
│ │ │ ├── api
|
||||
│ │ │ ├── controller
|
||||
│ │ │ ├── dao
|
||||
│ │ │ ├── entity
|
||||
│ │ │ ├── enums
|
||||
│ │ │ ├── exceptions
|
||||
│ │ │ └── service
|
||||
│ │ │ └── impl
|
||||
│ │ ├── service 自定义服务
|
||||
│ │ │ └── impl
|
||||
│ │ ├── utils 常用工具
|
||||
│ │ └── web Web 配置
|
||||
│ └── resources
|
||||
| ├── application.yml 配置文件
|
||||
| ├── schema.sql 数据库结构,项目启动时运行
|
||||
│ ├── mapper 代码生成
|
||||
│ └── static 静态资源
|
||||
| └── manager-ui 后端管理网页
|
||||
└── test 单元测试
|
||||
```
|
234
{{cookiecutter.project_slug}}/pom.xml
Normal file
234
{{cookiecutter.project_slug}}/pom.xml
Normal file
@ -0,0 +1,234 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>{{ cookiecutter.mvn_group_id }}</groupId>
|
||||
<artifactId>{{ cookiecutter.mvn_artifact_id }}</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>{{ cookiecutter.mvn_group_id }}</name>
|
||||
<description>{{ cookiecutter.project_name }}</description>
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
|
||||
<org.projectlombok.version>1.18.30</org.projectlombok.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/cn.dev33/sa-token-spring-boot3-starter -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot3-starter</artifactId>
|
||||
<version>1.39.0</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/cn.dev33/sa-token-jwt -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-jwt</artifactId>
|
||||
<version>1.39.0</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/cn.dev33/sa-token-redis-jackson -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-jackson</artifactId>
|
||||
<version>1.39.0</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
<version>2.12.0</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.6.0</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-spring-boot3-starter -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<version>3.5.8</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.dromara.hutool/hutool-all -->
|
||||
<dependency>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>6.0.0-M13</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>8.0.1.Final</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.alibaba/transmittable-thread-local -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>transmittable-thread-local</artifactId>
|
||||
<version>2.14.5</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.mapstruct/mapstruct -->
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/io.github.biezhi/TinyPinyin -->
|
||||
<dependency>
|
||||
<groupId>io.github.biezhi</groupId>
|
||||
<artifactId>TinyPinyin</artifactId>
|
||||
<version>2.0.3.RELEASE</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<version>5.2.5</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine -->
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<version>3.1.8</version>
|
||||
</dependency>
|
||||
{% if cookiecutter.platform == "postgis" -%}
|
||||
<!-- 私有包 mybatis 的 GIS 对象处理 -->
|
||||
<dependency>
|
||||
<groupId>ltd.llvy</groupId>
|
||||
<artifactId>handler</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
{% elif cookiecutter.platform == "mysql" -%}
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
{% endif %}
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${org.projectlombok.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>central</id>
|
||||
<url>https://maven.aliyun.com/repository/public</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
{% if cookiecutter.platform == "postgis" -%}
|
||||
<repository>
|
||||
<id>gitea</id>
|
||||
<url>https://www.llvy.ltd/api/packages/llvy.ltd/maven</url>
|
||||
</repository>
|
||||
{% endif %}
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>central</id>
|
||||
<url>https://maven.aliyun.com/repository/public</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
{% if cookiecutter.platform == "postgis" -%}
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>gitea</id>
|
||||
<url>https://www.llvy.ltd/api/packages/llvy.ltd/maven</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>gitea</id>
|
||||
<url>https://www.llvy.ltd/api/packages/llvy.ltd/maven</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
{% endif %}
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${org.projectlombok.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok-mapstruct-binding</artifactId>
|
||||
<version>0.2.0</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<compilerArgs>
|
||||
<arg>
|
||||
-Amapstruct.defaultComponentModel=spring
|
||||
</arg>
|
||||
<arg>
|
||||
-Amapstruct.defaultInjectionStrategy=constructor
|
||||
</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,29 @@
|
||||
package {{ cookiecutter.__mvn_package }};
|
||||
|
||||
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
||||
import io.swagger.v3.oas.annotations.info.Contact;
|
||||
import io.swagger.v3.oas.annotations.info.Info;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
/** {{ cookiecutter.project_hans }}后端. */
|
||||
@OpenAPIDefinition(
|
||||
info =
|
||||
@Info(
|
||||
title = "{{ cookiecutter.project_hans }}后端",
|
||||
version = "0.0.1",
|
||||
description = "{{ cookiecutter.project_hans }}的接口文档",
|
||||
contact = @Contact(name = "{{ cookiecutter.author }}", email = "{{ cookiecutter.email }}")))
|
||||
@MapperScan({"{{ cookiecutter.__mvn_package }}.rest.dao", "{{ cookiecutter.__mvn_package }}.module.dao"})
|
||||
@EnableAsync(proxyTargetClass = true)
|
||||
@EnableScheduling
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package {{ cookiecutter.__mvn_package }}.common;
|
||||
|
||||
/**
|
||||
* (Constants) 常量.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/7/3 上午10:52
|
||||
*/
|
||||
public class Constants {
|
||||
// 令牌名称
|
||||
public static final String TOKEN = "Sa-Token";
|
||||
// 多租户字段
|
||||
public static final String TENANT = "tenant";
|
||||
// 登录名称
|
||||
public static final String NAME = "name";
|
||||
// 显示名称
|
||||
public static final String REAL_NAME = "real_name";
|
||||
// Jwt 密钥
|
||||
public static final String JWT_KEY = "pHdSZVNUG71OY2IF";
|
||||
// 临时签名有效时间,5分钟
|
||||
public static final Integer SIGN_EXPIRE = 5 * 60;
|
||||
// 超级管理员名称
|
||||
public static final String SUPER_MANAGER = "sa";
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package {{ cookiecutter.__mvn_package }}.config;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cache.caffeine.CaffeineCache;
|
||||
import org.springframework.cache.support.SimpleCacheManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* (CaffeineConfig) 本地缓存.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/6/13 下午3:02
|
||||
*/
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class CacheConfig {
|
||||
|
||||
// 验证码有效时常,默认10分钟
|
||||
public static final int CAPTCHA_OVER_TIME = 600;
|
||||
// 数据库缓存时间,默认1小时
|
||||
public static final int DATA_OVER_TIME = 3600;
|
||||
|
||||
/**
|
||||
* 缓存管理器,包含不同的缓存配置.
|
||||
*
|
||||
* @return 缓存管理
|
||||
*/
|
||||
@Bean
|
||||
public CacheManager cacheManager() {
|
||||
SimpleCacheManager cacheManager = new SimpleCacheManager();
|
||||
List<CaffeineCache> caches =
|
||||
List.of(
|
||||
new CaffeineCache(
|
||||
CacheEnum.CAPTCHA.name(),
|
||||
Caffeine.newBuilder()
|
||||
.expireAfterWrite(CacheEnum.CAPTCHA.ttl, TimeUnit.SECONDS)
|
||||
.maximumSize(CacheEnum.CAPTCHA.max)
|
||||
.build()),
|
||||
new CaffeineCache(
|
||||
CacheEnum.DATA.name(),
|
||||
Caffeine.newBuilder()
|
||||
.expireAfterWrite(CacheEnum.DATA.ttl, TimeUnit.SECONDS)
|
||||
.maximumSize(CacheEnum.DATA.max)
|
||||
.build()));
|
||||
|
||||
cacheManager.setCaches(caches);
|
||||
return cacheManager;
|
||||
}
|
||||
|
||||
/** 缓存配置枚举. */
|
||||
@AllArgsConstructor
|
||||
public enum CacheEnum {
|
||||
CAPTCHA(CAPTCHA_OVER_TIME, 100),
|
||||
DATA(DATA_OVER_TIME, 1000);
|
||||
|
||||
private final long ttl;
|
||||
private final int max;
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package {{ cookiecutter.__mvn_package }}.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import {{ cookiecutter.__mvn_package }}.common.Constants;
|
||||
import {{ cookiecutter.__mvn_package }}.utils.ThreadLocalUtil;
|
||||
import {{ cookiecutter.__mvn_package }}.web.DataChangeInnerInterceptor;
|
||||
import {{ cookiecutter.__mvn_package }}.web.OrgLineInnerInterceptor;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Properties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* (MybatisConfig) Mybatis配置.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/3/28 10:13
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MybatisConfig implements MetaObjectHandler {
|
||||
|
||||
private OrgLineInnerInterceptor orgLineInnerInterceptor;
|
||||
|
||||
public MybatisConfig(OrgLineInnerInterceptor orgLineInnerInterceptor) {
|
||||
this.orgLineInnerInterceptor = orgLineInnerInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
this.fillStrategy(metaObject, "createTime", OffsetDateTime.now());
|
||||
this.fillStrategy(
|
||||
metaObject,
|
||||
"createBy",
|
||||
ThreadLocalUtil.getByKey(
|
||||
Constants.REAL_NAME, ThreadLocalUtil.getByKey(Constants.NAME, "匿名")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
metaObject.setValue("updateTime", OffsetDateTime.now());
|
||||
metaObject.setValue(
|
||||
"updateBy",
|
||||
ThreadLocalUtil.getByKey(
|
||||
Constants.REAL_NAME, ThreadLocalUtil.getByKey(Constants.NAME, "匿名")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mybatis 插件配置.
|
||||
*
|
||||
* @return 拦截器
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
// 多租户插件
|
||||
interceptor.addInnerInterceptor(orgLineInnerInterceptor);
|
||||
// 分页插件
|
||||
{% if cookiecutter.platform == "mysql" -%}
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||||
{% elif cookiecutter.platform == "postgis" -%}
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
|
||||
{% endif %}
|
||||
// 乐观锁插件
|
||||
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor(true));
|
||||
// 防止全表更新、删除插件
|
||||
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
|
||||
// 数据变动记录插件,自定义
|
||||
DataChangeInnerInterceptor dataChangeInnerInterceptor = new DataChangeInnerInterceptor();
|
||||
Properties dataChangeProperties = new Properties();
|
||||
dataChangeProperties.setProperty(
|
||||
"ignoredTableColumns", "logger.*;*.version,create_time,update_time");
|
||||
dataChangeInnerInterceptor.setProperties(dataChangeProperties);
|
||||
interceptor.addInnerInterceptor(dataChangeInnerInterceptor);
|
||||
return interceptor;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package {{ cookiecutter.__mvn_package }}.config;
|
||||
|
||||
import cn.dev33.satoken.jwt.StpLogicJwtForMixin;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import {{ cookiecutter.__mvn_package }}.common.Constants;
|
||||
import {{ cookiecutter.__mvn_package }}.web.SuperManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* (SaTokenConfigure) SaToken 配置.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/7/3 上午11:15
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfig {
|
||||
|
||||
/**
|
||||
* 组件配置.
|
||||
*
|
||||
* @param config 配置
|
||||
*/
|
||||
@Autowired
|
||||
public void configSaToken(cn.dev33.satoken.config.SaTokenConfig config) {
|
||||
// Jwt 令牌
|
||||
config.setTokenName(Constants.TOKEN);
|
||||
config.setJwtSecretKey(Constants.JWT_KEY);
|
||||
// 允许同一账号同一登录
|
||||
config.setIsConcurrent(true);
|
||||
// 同时登录时用一块令牌
|
||||
config.setIsShare(true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public StpLogic getStpLogicJwt() {
|
||||
// Jwt 混入业务字段
|
||||
return new StpLogicJwtForMixin();
|
||||
}
|
||||
|
||||
@Bean(initMethod = "randomPassword")
|
||||
public SuperManager getSuperUser() {
|
||||
return new SuperManager();
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
package {{ cookiecutter.__mvn_package }}.config;
|
||||
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
|
||||
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
|
||||
import {{ cookiecutter.__mvn_package }}.common.Constants;
|
||||
import {{ cookiecutter.__mvn_package }}.utils.ThreadLocalUtil;
|
||||
import {{ cookiecutter.__mvn_package }}.web.SuperManager;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import org.dromara.hutool.core.io.file.FileUtil;
|
||||
import org.dromara.hutool.core.map.MapBuilder;
|
||||
import org.dromara.hutool.core.map.MapUtil;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.extra.management.ManagementUtil;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.AsyncHandlerInterceptor;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* (WebConfig) Mvc 配置.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/3/26 09:12
|
||||
*/
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Resource SuperManager superManager;
|
||||
|
||||
@Value("${file.path:}")
|
||||
private String path;
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
// 创建文件夹
|
||||
if (StrUtil.isBlank(this.path)) {
|
||||
// 默认在jar包所在位置创建 files 文件夹
|
||||
String currentDir = ManagementUtil.getUserInfo().getCurrentDir();
|
||||
File files = FileUtil.file(currentDir, "files");
|
||||
this.path = StrUtil.replace(files.getAbsolutePath(), StrUtil.BACKSLASH, StrUtil.SLASH);
|
||||
this.path = StrUtil.appendIfMissing(this.path, StrUtil.SLASH);
|
||||
FileUtil.mkdir(files);
|
||||
} else {
|
||||
// 在配置文件位置创建 files 文件夹
|
||||
this.path = StrUtil.replace(this.path, StrUtil.BACKSLASH, StrUtil.SLASH);
|
||||
this.path = StrUtil.appendIfMissing(this.path, StrUtil.SLASH);
|
||||
FileUtil.mkdir(this.path);
|
||||
}
|
||||
// 作为资源路径
|
||||
registry.addResourceHandler("/files/**").addResourceLocations("file:" + this.path);
|
||||
WebMvcConfigurer.super.addResourceHandlers(registry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
|
||||
registry.addInterceptor(
|
||||
new AsyncHandlerInterceptor() {
|
||||
|
||||
// 请求开始时的拦截策略
|
||||
@Override
|
||||
public boolean preHandle(
|
||||
HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||
throws Exception {
|
||||
// 请求时设置共享值
|
||||
if (StpUtil.isLogin()) {
|
||||
MapBuilder<String, Object> builder =
|
||||
MapUtil.<String, Object>builder()
|
||||
.put(
|
||||
Constants.NAME,
|
||||
StrUtil.defaultIfBlank(
|
||||
String.valueOf(StpUtil.getExtra(Constants.NAME)), StrUtil.EMPTY))
|
||||
.put(
|
||||
Constants.REAL_NAME,
|
||||
StrUtil.defaultIfBlank(
|
||||
String.valueOf(StpUtil.getExtra(Constants.REAL_NAME)), StrUtil.EMPTY))
|
||||
.put(
|
||||
Constants.TENANT,
|
||||
StrUtil.defaultIfBlank(
|
||||
String.valueOf(StpUtil.getExtra(Constants.TENANT)), StrUtil.EMPTY));
|
||||
ThreadLocalUtil.setMap(builder.build());
|
||||
}
|
||||
// 超级管理员时忽略租户
|
||||
if (superManager.isReal()) {
|
||||
InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
|
||||
}
|
||||
return AsyncHandlerInterceptor.super.preHandle(request, response, handler);
|
||||
}
|
||||
|
||||
// 请求结束时启用所有的拦截策略,清空上下文
|
||||
@Override
|
||||
public void afterCompletion(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Object handler,
|
||||
Exception ex)
|
||||
throws Exception {
|
||||
// 清除上下文
|
||||
ThreadLocalUtil.clear();
|
||||
// 启用mybatisplus的拦截策略
|
||||
InterceptorIgnoreHelper.clearIgnoreStrategy();
|
||||
AsyncHandlerInterceptor.super.afterCompletion(request, response, handler, ex);
|
||||
}
|
||||
});
|
||||
|
||||
// 路由拦截
|
||||
registry
|
||||
.addInterceptor(
|
||||
new SaInterceptor(
|
||||
handle -> {
|
||||
// 校验登录
|
||||
SaRouter.match("/**")
|
||||
.notMatch(
|
||||
"/error/**",
|
||||
"/files/**",
|
||||
"/index.html",
|
||||
"/pub/**",
|
||||
"/v3/api-docs/**",
|
||||
"/swagger-ui/**",
|
||||
"/swagger-ui.html",
|
||||
"/manager-ui/**",
|
||||
"/manager-ui.html",
|
||||
"/manager/login",
|
||||
"/manager/register")
|
||||
.check(r -> StpUtil.checkLogin());
|
||||
// 校验权限
|
||||
SaRouter.match("/db/**", r -> StpUtil.checkPermission("db:rest"));
|
||||
}))
|
||||
.addPathPatterns("/**");
|
||||
WebMvcConfigurer.super.addInterceptors(registry);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void addCorsMappings(CorsRegistry registry) {
|
||||
// WebMvcConfigurer.super.addCorsMappings(registry);
|
||||
// registry
|
||||
// .addMapping("/**")
|
||||
// .allowedOriginPatterns("*")
|
||||
// .allowCredentials(true)
|
||||
// .allowedHeaders("*")
|
||||
// .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
// .maxAge(3600);
|
||||
// }
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package {{ cookiecutter.__mvn_package }}.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.Arrays;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* (EAuthorityType) 权限类型.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/4/10 上午11:40
|
||||
*/
|
||||
@Schema(
|
||||
enumAsRef = true,
|
||||
description =
|
||||
"""
|
||||
权限表,分类(type)
|
||||
|
||||
0 - 后台接口
|
||||
1 - 前台菜单
|
||||
2 - 前台按钮
|
||||
""")
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum AuthorityType {
|
||||
INTERFACE(0, "接口"),
|
||||
MENU(1, "菜单"),
|
||||
BUTTON(2, "按钮");
|
||||
|
||||
@EnumValue private final int field;
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* 创建枚举.
|
||||
*
|
||||
* @param field 值
|
||||
* @return 枚举
|
||||
*/
|
||||
@JsonCreator
|
||||
public static AuthorityType valueOf(int field) {
|
||||
return Arrays.stream(AuthorityType.values())
|
||||
.filter(i -> i.field == field)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public int getField() {
|
||||
return field;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package {{ cookiecutter.__mvn_package }}.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.Arrays;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* (ELoggerType) 日志类型.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/4/28 下午4:02
|
||||
*/
|
||||
@Schema(
|
||||
enumAsRef = true,
|
||||
description =
|
||||
"""
|
||||
日志表,分类(type)
|
||||
|
||||
0 - 数据变更
|
||||
""")
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum LoggerType {
|
||||
DB_AUTO_CHANGE(0, "数据变更");
|
||||
|
||||
@EnumValue private final int field;
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* 创建枚举.
|
||||
*
|
||||
* @param field 值
|
||||
* @return 枚举
|
||||
*/
|
||||
@JsonCreator
|
||||
public static LoggerType valueOf(int field) {
|
||||
return Arrays.stream(LoggerType.values())
|
||||
.filter(e -> e.field == field)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public int getField() {
|
||||
return field;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package {{ cookiecutter.__mvn_package }}.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.Arrays;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* (EStatus) 启用标识.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/4/10 上午11:31
|
||||
*/
|
||||
@Schema(
|
||||
enumAsRef = true,
|
||||
description =
|
||||
"""
|
||||
状态(status)
|
||||
|
||||
0 - 启用
|
||||
1 - 禁用
|
||||
""")
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum Status {
|
||||
ENABLE(0, "启用"),
|
||||
DISABLE(1, "禁用");
|
||||
|
||||
@EnumValue private final int field;
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* 创建枚举.
|
||||
*
|
||||
* @param field 值
|
||||
* @return 枚举
|
||||
*/
|
||||
@JsonCreator
|
||||
public static Status valueOf(int field) {
|
||||
return Arrays.stream(Status.values())
|
||||
.filter(i -> i.getField() == field)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public int getField() {
|
||||
return field;
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package {{ cookiecutter.__mvn_package }}.mapper;
|
||||
|
||||
import {{ cookiecutter.__mvn_package }}.mapper.entity.AccountExcel;
|
||||
import {{ cookiecutter.__mvn_package }}.module.manager.request.AccountPassword;
|
||||
import {{ cookiecutter.__mvn_package }}.module.manager.request.AccountRegister;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.entity.Account;
|
||||
import java.util.List;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Named;
|
||||
|
||||
/**
|
||||
* 凭据表(AccountMapper)表数据库访问层.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/7/5 上午9:48
|
||||
*/
|
||||
@Mapper(uses = {ConvertWorker.class})
|
||||
public interface AccountMapper {
|
||||
Account toAccount(AccountRegister account);
|
||||
|
||||
Account toAccount(AccountPassword account);
|
||||
|
||||
@Mapping(target = "isActive", source = "isActive", qualifiedByName = "toActiveStr")
|
||||
@Mapping(target = "status", source = "status", qualifiedByName = "toStatusStr")
|
||||
AccountExcel toExcel(Account account);
|
||||
|
||||
List<AccountExcel> toExcel(List<Account> accounts);
|
||||
|
||||
@Mapping(
|
||||
target = "password",
|
||||
source = "password",
|
||||
defaultValue = "$2a$10$t/cCJoxEWFbVRJy3MAfQhe4/7EeB2sCxE0BcIL6Pz/A28D/Xw0A9K")
|
||||
@Mapping(target = "isActive", source = "isActive", qualifiedByName = "toActiveId")
|
||||
@Mapping(target = "status", source = "status", qualifiedByName = "toStatusId")
|
||||
@Mapping(target = "version", ignore = true)
|
||||
@Mapping(target = "deleteTime", ignore = true)
|
||||
@Mapping(target = "createTime", ignore = true)
|
||||
@Mapping(target = "updateTime", ignore = true)
|
||||
@Mapping(target = "createBy", ignore = true)
|
||||
@Mapping(target = "updateBy", ignore = true)
|
||||
@Mapping(target = "tenant", source = "tenant", defaultValue = "")
|
||||
Account toEntity(AccountExcel excel);
|
||||
|
||||
List<Account> toEntity(List<AccountExcel> excels);
|
||||
|
||||
/** 空字符串转化为null. */
|
||||
default Long pkEmptyStr(String pk) {
|
||||
return StrUtil.isEmptyIfStr(pk) ? null : Long.parseLong(pk);
|
||||
}
|
||||
|
||||
@Named("toActiveId")
|
||||
default Integer toActiveId(String active) {
|
||||
return switch (active) {
|
||||
case "激活" -> 0;
|
||||
case "未激活" -> 1;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
@Named("toActiveStr")
|
||||
default String toActiveStr(Integer active) {
|
||||
return switch (active) {
|
||||
case 0 -> "激活";
|
||||
case 1 -> "未激活";
|
||||
default -> "其他";
|
||||
};
|
||||
}
|
||||
|
||||
@Named("toStatusId")
|
||||
default Integer toStatusId(String status) {
|
||||
return switch (status) {
|
||||
case "可用" -> 0;
|
||||
case "禁用" -> 1;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
@Named("toStatusStr")
|
||||
default String toStatusStr(Integer status) {
|
||||
return switch (status) {
|
||||
case 0 -> "可用";
|
||||
case 1 -> "禁用";
|
||||
default -> "其他";
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package {{ cookiecutter.__mvn_package }}.mapper;
|
||||
|
||||
import {{ cookiecutter.__mvn_package }}.mapper.entity.AuthorityExcel;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.entity.Authority;
|
||||
import java.util.List;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Named;
|
||||
|
||||
/**
|
||||
* (AuthorityMapper)
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2025/4/22 09:12
|
||||
*/
|
||||
@Mapper
|
||||
public interface AuthorityMapper {
|
||||
|
||||
@Mapping(target = "type", source = "type", qualifiedByName = "toTypeStr")
|
||||
@Mapping(target = "status", source = "status", qualifiedByName = "toStatusStr")
|
||||
AuthorityExcel toExcel(Authority authority);
|
||||
|
||||
List<AuthorityExcel> toExcel(List<Authority> authorities);
|
||||
|
||||
@Mapping(target = "type", source = "type", qualifiedByName = "toTypeId")
|
||||
@Mapping(target = "status", source = "status", qualifiedByName = "toStatusId")
|
||||
@Mapping(target = "version", ignore = true)
|
||||
@Mapping(target = "deleteTime", ignore = true)
|
||||
@Mapping(target = "createTime", ignore = true)
|
||||
@Mapping(target = "updateTime", ignore = true)
|
||||
@Mapping(target = "createBy", ignore = true)
|
||||
@Mapping(target = "updateBy", ignore = true)
|
||||
@Mapping(target = "tenant", source = "tenant", defaultValue = "")
|
||||
Authority toEntiry(AuthorityExcel authority);
|
||||
|
||||
List<Authority> toEntity(List<AuthorityExcel> authorities);
|
||||
|
||||
/** 空字符串转化为null */
|
||||
default Long mapEmptyStr(String pk) {
|
||||
return StrUtil.isEmptyIfStr(pk) ? null : Long.parseLong(pk);
|
||||
}
|
||||
|
||||
@Named("toTypeId")
|
||||
default Integer toTypeId(String type) {
|
||||
return switch (type) {
|
||||
case "接口" -> 0;
|
||||
case "菜单" -> 1;
|
||||
case "按钮" -> 2;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
@Named("toTypeStr")
|
||||
default String toTypeStr(Integer type) {
|
||||
return switch (type) {
|
||||
case 0 -> "接口";
|
||||
case 1 -> "菜单";
|
||||
case 2 -> "按钮";
|
||||
default -> "其他";
|
||||
};
|
||||
}
|
||||
|
||||
@Named("toStatusId")
|
||||
default Integer toStatusId(String status) {
|
||||
return switch (status) {
|
||||
case "可用" -> 0;
|
||||
case "禁用" -> 1;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
@Named("toStatusStr")
|
||||
default String toStatusStr(Integer status) {
|
||||
return switch (status) {
|
||||
case 0 -> "可用";
|
||||
case 1 -> "禁用";
|
||||
default -> "其他";
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package {{ cookiecutter.__mvn_package }}.mapper;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* (ConvertWorker) 类型转换.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/7/5 上午9:50
|
||||
*/
|
||||
@Component
|
||||
public class ConvertWorker {}
|
@ -0,0 +1,63 @@
|
||||
package {{ cookiecutter.__mvn_package }}.mapper;
|
||||
|
||||
import {{ cookiecutter.__mvn_package }}.mapper.entity.LoggerExcel;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.entity.Logger;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
import org.dromara.hutool.core.date.DatePattern;
|
||||
import org.dromara.hutool.core.date.DateUtil;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Named;
|
||||
|
||||
/**
|
||||
* (LoggerMapper)
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2025/4/15 14:30
|
||||
*/
|
||||
@Mapper
|
||||
public interface LoggerMapper {
|
||||
|
||||
@Mapping(target = "type", source = "type", qualifiedByName = "toTypeStr")
|
||||
LoggerExcel toExcel(Logger logger);
|
||||
|
||||
List<LoggerExcel> toExcel(List<Logger> loggers);
|
||||
|
||||
@Mapping(target = "type", source = "type", qualifiedByName = "toTypeId")
|
||||
@Mapping(target = "deleteTime", ignore = true)
|
||||
@Mapping(target = "createTime", ignore = true)
|
||||
@Mapping(target = "updateTime", ignore = true)
|
||||
@Mapping(target = "createBy", ignore = true)
|
||||
@Mapping(target = "updateBy", ignore = true)
|
||||
Logger toEntity(LoggerExcel loggerExcel);
|
||||
|
||||
List<Logger> toEntity(List<LoggerExcel> loggerExcels);
|
||||
|
||||
/** 空字符串转化为null */
|
||||
default Long mapEmptyStr(String pk) {
|
||||
return StrUtil.isEmptyIfStr(pk) ? null : Long.parseLong(pk);
|
||||
}
|
||||
|
||||
/** 时间格式化 */
|
||||
default String mapTimer(OffsetDateTime time) {
|
||||
return DateUtil.format(time.toLocalDateTime(), DatePattern.CHINESE_DATE_TIME_PATTERN);
|
||||
}
|
||||
|
||||
@Named("toTypeId")
|
||||
default Integer toTypeId(String type) {
|
||||
return switch (type) {
|
||||
case "数据变更自动日志" -> 0;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
@Named("toTypeStr")
|
||||
default String toTypeStr(Integer type) {
|
||||
return switch (type) {
|
||||
case 0 -> "数据变更自动日志";
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package {{ cookiecutter.__mvn_package }}.mapper;
|
||||
|
||||
import {{ cookiecutter.__mvn_package }}.mapper.entity.RoleExcel;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.entity.Role;
|
||||
import java.util.List;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Named;
|
||||
|
||||
/**
|
||||
* (RoleMapper)
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2025/4/22 09:29
|
||||
*/
|
||||
@Mapper
|
||||
public interface RoleMapper {
|
||||
|
||||
@Mapping(target = "status", source = "status", qualifiedByName = "toStatusStr")
|
||||
RoleExcel toExcel(Role role);
|
||||
|
||||
List<RoleExcel> toExcel(List<Role> roles);
|
||||
|
||||
@Mapping(target = "status", source = "status", qualifiedByName = "toStatusId")
|
||||
@Mapping(target = "version", ignore = true)
|
||||
@Mapping(target = "deleteTime", ignore = true)
|
||||
@Mapping(target = "createTime", ignore = true)
|
||||
@Mapping(target = "updateTime", ignore = true)
|
||||
@Mapping(target = "createBy", ignore = true)
|
||||
@Mapping(target = "updateBy", ignore = true)
|
||||
@Mapping(target = "tenant", source = "tenant", defaultValue = "")
|
||||
Role toEntity(RoleExcel excel);
|
||||
|
||||
List<Role> toEntity(List<RoleExcel> excels);
|
||||
|
||||
/** 空字符串转化为null */
|
||||
default Long mapEmptyStr(String pk) {
|
||||
return StrUtil.isEmptyIfStr(pk) ? null : Long.parseLong(pk);
|
||||
}
|
||||
|
||||
@Named("toStatusId")
|
||||
default Integer toStatusId(String status) {
|
||||
return switch (status) {
|
||||
case "可用" -> 0;
|
||||
case "禁用" -> 1;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
@Named("toStatusStr")
|
||||
default String toStatusStr(Integer status) {
|
||||
return switch (status) {
|
||||
case 0 -> "可用";
|
||||
case 1 -> "禁用";
|
||||
default -> "其他";
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package {{ cookiecutter.__mvn_package }}.mapper;
|
||||
|
||||
import {{ cookiecutter.__mvn_package }}.mapper.entity.TenantExcel;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.entity.Tenant;
|
||||
import java.util.List;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Named;
|
||||
|
||||
/**
|
||||
* (TenantMapper)
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2025/3/31 10:59
|
||||
*/
|
||||
@Mapper
|
||||
public interface TenantMapper {
|
||||
|
||||
@Mapping(target = "type", source = "type", qualifiedByName = "toTypeStr")
|
||||
@Mapping(target = "status", source = "status", qualifiedByName = "toStatusStr")
|
||||
TenantExcel toExcel(Tenant tenant);
|
||||
|
||||
List<TenantExcel> toExcel(List<Tenant> tenants);
|
||||
|
||||
@Mapping(target = "type", source = "type", qualifiedByName = "toTypeId")
|
||||
@Mapping(target = "status", source = "status", qualifiedByName = "toStatusId")
|
||||
@Mapping(target = "version", ignore = true)
|
||||
@Mapping(target = "deleteTime", ignore = true)
|
||||
@Mapping(target = "createTime", ignore = true)
|
||||
@Mapping(target = "updateTime", ignore = true)
|
||||
@Mapping(target = "createBy", ignore = true)
|
||||
@Mapping(target = "updateBy", ignore = true)
|
||||
Tenant toEntity(TenantExcel excel);
|
||||
|
||||
List<Tenant> toEntity(List<TenantExcel> excels);
|
||||
|
||||
/** 空字符串转化为null */
|
||||
default Long mapEmptyStr(String pk) {
|
||||
return StrUtil.isEmptyIfStr(pk) ? null : Long.parseLong(pk);
|
||||
}
|
||||
|
||||
@Named("toTypeId")
|
||||
default Integer toTypeId(String type) {
|
||||
return switch (type) {
|
||||
case "企业" -> 0;
|
||||
case "个人" -> 1;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
@Named("toTypeStr")
|
||||
default String toTypeStr(Integer type) {
|
||||
return switch (type) {
|
||||
case 0 -> "企业";
|
||||
case 1 -> "个人";
|
||||
default -> "其他";
|
||||
};
|
||||
}
|
||||
|
||||
@Named("toStatusId")
|
||||
default Integer toStatusId(String status) {
|
||||
return switch (status) {
|
||||
case "可用" -> 0;
|
||||
case "禁用" -> 1;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
@Named("toStatusStr")
|
||||
default String toStatusStr(Integer status) {
|
||||
return switch (status) {
|
||||
case 0 -> "可用";
|
||||
case 1 -> "禁用";
|
||||
default -> "其他";
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package {{ cookiecutter.__mvn_package }}.mapper.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* (AccountExcel)
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2025/4/22 09:38
|
||||
*/
|
||||
@Data
|
||||
public class AccountExcel {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String realName;
|
||||
private String password;
|
||||
private String mobile;
|
||||
private String email;
|
||||
private String isActive;
|
||||
private String status;
|
||||
private String description;
|
||||
private String createBy;
|
||||
private String tenant;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package {{ cookiecutter.__mvn_package }}.mapper.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* (AuthorityExcel)
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2025/4/22 09:12
|
||||
*/
|
||||
@Data
|
||||
public class AuthorityExcel {
|
||||
|
||||
private String id;
|
||||
private String parentId;
|
||||
private String name;
|
||||
private String value;
|
||||
private String path;
|
||||
private String link;
|
||||
private String type;
|
||||
private String status;
|
||||
private String description;
|
||||
private String createBy;
|
||||
private String tenant;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package {{ cookiecutter.__mvn_package }}.mapper.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* (LoggerExcel)
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2025/4/15 14:28
|
||||
*/
|
||||
@Data
|
||||
public class LoggerExcel {
|
||||
|
||||
private String id;
|
||||
private String type;
|
||||
private String operation;
|
||||
private String tableName;
|
||||
private String recordStatus;
|
||||
private String changed;
|
||||
private String description;
|
||||
private String createTime;
|
||||
private String createBy;
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package {{ cookiecutter.__mvn_package }}.mapper.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* (RoleExcel)
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2025/4/22 09:29
|
||||
*/
|
||||
@Data
|
||||
public class RoleExcel {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String value;
|
||||
private String status;
|
||||
private String description;
|
||||
private String createBy;
|
||||
private String tenant;
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package {{ cookiecutter.__mvn_package }}.mapper.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* (TenantExcel)
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2025/3/31 10:56
|
||||
*/
|
||||
@Data
|
||||
public class TenantExcel {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String shared;
|
||||
private String type;
|
||||
private String status;
|
||||
private String description;
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package {{ cookiecutter.__mvn_package }}.module;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
/**
|
||||
* (IndexController).
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/7/3 下午2:42
|
||||
*/
|
||||
@Controller
|
||||
public class IndexController {
|
||||
|
||||
@GetMapping(value = {"/manager-ui", "/manager-ui.html"})
|
||||
public String manager() {
|
||||
return "redirect:/manager-ui/";
|
||||
}
|
||||
|
||||
@GetMapping("/manager-ui/")
|
||||
public String managerUi() {
|
||||
return "/manager-ui/index.html";
|
||||
}
|
||||
}
|
@ -0,0 +1,267 @@
|
||||
package {{ cookiecutter.__mvn_package }}.module;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.temp.SaTempUtil;
|
||||
import {{ cookiecutter.__mvn_package }}.common.Constants;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.R;
|
||||
import {{ cookiecutter.__mvn_package }}.service.CaptchaService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.hutool.core.array.ArrayUtil;
|
||||
import org.dromara.hutool.core.compress.ZipUtil;
|
||||
import org.dromara.hutool.core.data.id.IdUtil;
|
||||
import org.dromara.hutool.core.date.DatePattern;
|
||||
import org.dromara.hutool.core.date.DateTime;
|
||||
import org.dromara.hutool.core.date.format.FastDateFormat;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.io.file.FileNameUtil;
|
||||
import org.dromara.hutool.core.io.file.FileReader;
|
||||
import org.dromara.hutool.core.io.file.FileUtil;
|
||||
import org.dromara.hutool.core.lang.Validator;
|
||||
import org.dromara.hutool.core.map.MapUtil;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.core.text.split.SplitUtil;
|
||||
import org.dromara.hutool.core.util.CharsetUtil;
|
||||
import org.dromara.hutool.extra.management.ManagementUtil;
|
||||
import org.dromara.hutool.extra.pinyin.PinyinUtil;
|
||||
import org.dromara.hutool.http.meta.ContentType;
|
||||
import org.dromara.hutool.http.meta.HeaderName;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* (PubController) 公共接口.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/7/3 下午2:43
|
||||
*/
|
||||
@Slf4j
|
||||
@Tag(name = "公共接口", description = "常用工具,无需令牌,部分接口需临时签名[sign]")
|
||||
@RestController
|
||||
@RequestMapping("pub")
|
||||
public class PubController {
|
||||
|
||||
@Resource CaptchaService captchaService;
|
||||
|
||||
@Value("${file.path:}")
|
||||
private String path;
|
||||
|
||||
@Operation(summary = "服务器时间")
|
||||
@GetMapping("/now")
|
||||
public OffsetDateTime now() {
|
||||
return OffsetDateTime.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 图形验证码.
|
||||
*
|
||||
* @return 图形与ID
|
||||
*/
|
||||
@Operation(summary = "图形验证码")
|
||||
@GetMapping("/captcha/img")
|
||||
public R getCaptchaImg() {
|
||||
String objectId = IdUtil.objectId();
|
||||
String captcha = captchaService.getCodeById(objectId);
|
||||
|
||||
Map<String, String> result =
|
||||
MapUtil.builder(new HashMap<String, String>())
|
||||
.put("objectId", objectId)
|
||||
.put("captcha", captcha)
|
||||
.build();
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 邮件验证码.
|
||||
*
|
||||
* @return 邮件ID
|
||||
*/
|
||||
@Operation(summary = "邮件验证码")
|
||||
@GetMapping("/captcha/email")
|
||||
public R getCaptchaEmail(String email) {
|
||||
if (!Validator.isEmail(email)) {
|
||||
throw new RuntimeException("邮箱地址格式错误");
|
||||
}
|
||||
String objectId = IdUtil.objectId();
|
||||
objectId = captchaService.getCodeByEmail(objectId, email);
|
||||
|
||||
Map<String, String> result =
|
||||
MapUtil.builder(new HashMap<String, String>()).put("objectId", objectId).build();
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得拼音首字母.
|
||||
*
|
||||
* @param text 文字
|
||||
* @return 拼音首字母
|
||||
*/
|
||||
@Operation(summary = "获得拼音首字母")
|
||||
@GetMapping("/pinyin/first")
|
||||
public R getPinyinFirst(String text) {
|
||||
if (StrUtil.isNotBlank(text)) {
|
||||
return R.ok(PinyinUtil.getFirstLetter(text, StrUtil.EMPTY));
|
||||
} else {
|
||||
return R.failed("不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得完整拼音.
|
||||
*
|
||||
* @param text 文字
|
||||
* @return 拼音
|
||||
*/
|
||||
@Operation(summary = "获得完整拼音")
|
||||
@GetMapping("/pinyin/full")
|
||||
public R getPinyinFull(String text) {
|
||||
if (StrUtil.isNotBlank(text)) {
|
||||
return R.ok(PinyinUtil.getPinyin(text));
|
||||
} else {
|
||||
return R.failed("不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得临时签名,5分钟有效.
|
||||
*
|
||||
* @param desc 注释
|
||||
* @return 签名
|
||||
*/
|
||||
@Operation(summary = "获得临时签名,5分钟有效")
|
||||
@PostMapping("/sign")
|
||||
public R getTempToken(@RequestParam(defaultValue = "缺省", required = false) String desc) {
|
||||
return R.ok(
|
||||
SaTempUtil.createToken(
|
||||
StrUtil.format("临时签名 / {} / {}", IdUtil.objectId(), desc), Constants.SIGN_EXPIRE));
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件,已登录无需签名,未登录需要签名.
|
||||
*
|
||||
* @param files 多文件
|
||||
* @param sign 签名
|
||||
* @return 存储路径
|
||||
*/
|
||||
@Operation(summary = "上传文件,已登录无需签名,未登录需要签名")
|
||||
@PostMapping(value = "/file/upload", consumes = "multipart/form-data")
|
||||
public R upload(
|
||||
@RequestPart("file") MultipartFile[] files,
|
||||
@RequestParam(value = "sign", required = false) String sign) {
|
||||
// 解析签名
|
||||
if (StpUtil.isLogin()) {
|
||||
String loginId = StpUtil.getLoginIdAsString();
|
||||
log.info("登录凭据 {} 开始上传 {} 个文件", loginId, files.length);
|
||||
} else {
|
||||
String token = SaTempUtil.parseToken(sign, String.class);
|
||||
if (StrUtil.isNotBlank(token)) {
|
||||
log.info("临时签名 {} 开始上传 {} 个文件", token, files.length);
|
||||
} else {
|
||||
throw new RuntimeException("缺少签名参数");
|
||||
}
|
||||
}
|
||||
// 保存文件
|
||||
if (StrUtil.isBlank(this.path)) {
|
||||
// 默认在jar包所在位置创建 files 文件夹
|
||||
String currentDir = ManagementUtil.getUserInfo().getCurrentDir();
|
||||
File dir = FileUtil.file(currentDir, "files");
|
||||
this.path = dir.getAbsolutePath();
|
||||
}
|
||||
String[] filepath = new String[files.length];
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
MultipartFile file = files[i];
|
||||
String fullname = IdUtil.objectId();
|
||||
String extName = FileNameUtil.extName(file.getOriginalFilename());
|
||||
// 文件后缀黑名单
|
||||
String[] blackList = {"jsp", "php", "exe", "dll", "vxd", "html", "htm", "js", "asp", "aspx"};
|
||||
if (ArrayUtil.containsIgnoreCase(blackList, extName)) {
|
||||
throw new RuntimeException("该文件不允许上传");
|
||||
}
|
||||
if (StrUtil.isNotBlank(extName)) {
|
||||
fullname = StrUtil.join(StrUtil.DOT, fullname, extName);
|
||||
}
|
||||
String date =
|
||||
FastDateFormat.getInstance(DatePattern.PURE_DATE_PATTERN).format(DateTime.now());
|
||||
File dest = FileUtil.file(this.path, date, fullname);
|
||||
FileUtil.mkdir(dest);
|
||||
try {
|
||||
file.transferTo(dest);
|
||||
filepath[i] = FileUtil.subPath(this.path, dest);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("文件保存失败!");
|
||||
}
|
||||
}
|
||||
String join = ArrayUtil.join(filepath, StrUtil.COMMA);
|
||||
log.info("文件上传成功 {}", join);
|
||||
return R.ok(join);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打包下载 Zip 存档,已登录无需签名,未登录需要签名.
|
||||
*
|
||||
* @param urls 多存储路径
|
||||
* @param sign 签名
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
@Operation(summary = "打包下载 Zip 存档,已登录无需签名,未登录需要签名")
|
||||
@GetMapping("/file/zip")
|
||||
public void archive(
|
||||
HttpServletResponse response,
|
||||
String urls,
|
||||
@RequestParam(value = "sign", required = false) String sign)
|
||||
throws IOException {
|
||||
if (StrUtil.isBlank(urls)) {
|
||||
return;
|
||||
}
|
||||
// 解析签名
|
||||
if (StpUtil.isLogin()) {
|
||||
String loginId = StpUtil.getLoginIdAsString();
|
||||
log.info("登录凭据 {} 开始打包文件 {}", loginId, urls);
|
||||
} else {
|
||||
String token = SaTempUtil.parseToken(sign, String.class);
|
||||
if (StrUtil.isNotBlank(token)) {
|
||||
log.info("临时签名 {} 开始打包文件 {}", token, urls);
|
||||
} else {
|
||||
throw new RuntimeException("缺少签名参数");
|
||||
}
|
||||
}
|
||||
String path;
|
||||
if (StrUtil.isBlank(this.path)) {
|
||||
String currentDir = ManagementUtil.getUserInfo().getCurrentDir();
|
||||
File files = FileUtil.file(currentDir, "files");
|
||||
path = StrUtil.replace(files.getAbsolutePath(), StrUtil.BACKSLASH, StrUtil.SLASH);
|
||||
path = StrUtil.appendIfMissing(path, StrUtil.SLASH);
|
||||
} else {
|
||||
path = this.path;
|
||||
}
|
||||
String finalPath = path;
|
||||
String[] fileStr =
|
||||
SplitUtil.split(urls, StrUtil.COMMA).stream()
|
||||
.map(item -> (finalPath + item))
|
||||
.toArray(String[]::new);
|
||||
File[] files = new File[fileStr.length];
|
||||
for (int i = 0; i < fileStr.length; i++) {
|
||||
files[i] = FileUtil.file(fileStr[i]);
|
||||
}
|
||||
response.reset();
|
||||
response.setHeader(HeaderName.CONTENT_TYPE.getValue(), ContentType.OCTET_STREAM.getValue());
|
||||
response.setHeader(
|
||||
HeaderName.CONTENT_DISPOSITION.getValue(),
|
||||
"attachment; filename=" + URLEncoder.encode(IdUtil.objectId() + ".zip", CharsetUtil.UTF_8));
|
||||
ServletOutputStream out = response.getOutputStream();
|
||||
FileReader.of(ZipUtil.zip(FileUtil.createTempFile(), false, files)).writeToStream(out);
|
||||
IoUtil.closeQuietly(out);
|
||||
log.info("文件打包成功,共 {} 个文件", files.length);
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package {{ cookiecutter.__mvn_package }}.module.dao;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* (CustomDao) 自定义语句.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/7/3 下午2:26
|
||||
*/
|
||||
public interface CustomDao {
|
||||
|
||||
/**
|
||||
* 清空日志.
|
||||
*
|
||||
* @param time 指定时间
|
||||
* @return 删除条数
|
||||
*/
|
||||
int clearLogger(@Param("time") LocalDateTime time);
|
||||
|
||||
int expiryMessage();
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package {{ cookiecutter.__mvn_package }}.module.manager;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.R;
|
||||
import {{ cookiecutter.__mvn_package }}.service.OwnService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* (AuthController) 管理接口.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/7/5 上午11:32
|
||||
*/
|
||||
@Tag(name = "管理接口", description = "需要令牌")
|
||||
@RestController
|
||||
@RequestMapping("manager/own")
|
||||
public class AuthController {
|
||||
|
||||
@Resource OwnService ownService;
|
||||
|
||||
/**
|
||||
* 拥有的角色及权限.
|
||||
*
|
||||
* @return 结果数据
|
||||
*/
|
||||
@Operation(summary = "用户的角色及权限")
|
||||
@GetMapping("auths")
|
||||
public R ownAuths() {
|
||||
return R.ok(ownService.getOwnAuths(StpUtil.getLoginIdAsString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 拥有的角色.
|
||||
*
|
||||
* @return 结果数据
|
||||
*/
|
||||
@Operation(summary = "用户的角色")
|
||||
@GetMapping("roles")
|
||||
public R ownRoles() {
|
||||
return R.ok(ownService.getOwnRole(StpUtil.getLoginIdAsString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 拥有的权限.
|
||||
*
|
||||
* @return 结果数据
|
||||
*/
|
||||
@Operation(summary = "用户的权限")
|
||||
@GetMapping("authorities")
|
||||
public R ownAuthority() {
|
||||
return R.ok(ownService.getOwnAuthority(StpUtil.getLoginIdAsString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 拥有的菜单.
|
||||
*
|
||||
* @return 结果数据
|
||||
*/
|
||||
@Operation(summary = "用户的菜单")
|
||||
@GetMapping("menus")
|
||||
public R ownMenu() {
|
||||
return R.ok(ownService.getOwnMenus(StpUtil.getLoginIdAsString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 拥有的按钮.
|
||||
*
|
||||
* @return 结果数据
|
||||
*/
|
||||
@Operation(summary = "用户的按钮")
|
||||
@GetMapping("buttons")
|
||||
public R ownButton() {
|
||||
return R.ok(ownService.getOwnButtons(StpUtil.getLoginIdAsString()));
|
||||
}
|
||||
}
|
@ -0,0 +1,205 @@
|
||||
package {{ cookiecutter.__mvn_package }}.module.manager;
|
||||
|
||||
import cn.dev33.satoken.secure.BCrypt;
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
|
||||
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import {{ cookiecutter.__mvn_package }}.common.Constants;
|
||||
import {{ cookiecutter.__mvn_package }}.enums.Status;
|
||||
import {{ cookiecutter.__mvn_package }}.mapper.AccountMapper;
|
||||
import {{ cookiecutter.__mvn_package }}.module.manager.request.AccountLogin;
|
||||
import {{ cookiecutter.__mvn_package }}.module.manager.request.AccountPassword;
|
||||
import {{ cookiecutter.__mvn_package }}.module.manager.request.AccountRegister;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.R;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.entity.Account;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.entity.Tenant;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.service.AccountService;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.service.TenantService;
|
||||
import {{ cookiecutter.__mvn_package }}.service.CaptchaService;
|
||||
import {{ cookiecutter.__mvn_package }}.utils.CryptoUtil;
|
||||
import {{ cookiecutter.__mvn_package }}.web.SuperManager;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* (LoginController).
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/7/3 下午3:24
|
||||
*/
|
||||
@Tag(name = "管理接口", description = "需要令牌")
|
||||
@RestController
|
||||
@RequestMapping("manager")
|
||||
public class LoginController {
|
||||
|
||||
@Resource SuperManager superManager;
|
||||
@Resource AccountMapper accountMapper;
|
||||
@Resource CaptchaService captchaService;
|
||||
@Resource AccountService accountService;
|
||||
@Resource TenantService tenantService;
|
||||
|
||||
/**
|
||||
* 凭据登录.
|
||||
*
|
||||
* @param account 请求体
|
||||
* @return 响应结果
|
||||
*/
|
||||
@Operation(summary = "凭据登录", description = "公钥:" + CryptoUtil.PUB)
|
||||
@PostMapping("login")
|
||||
public R doLogin(@Valid @RequestBody AccountLogin account) {
|
||||
// 忽略租户
|
||||
InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
|
||||
if (StrUtil.isNotBlank(account.getCaptcha()) && StrUtil.isNotBlank(account.getCaptchaId())) {
|
||||
if (!captchaService.checkCodeById(account.getCaptchaId(), account.getCaptcha())) {
|
||||
throw new RuntimeException("验证码错误");
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("验证码与回执不能为空");
|
||||
}
|
||||
// 判断是否超级管理员
|
||||
if (StrUtil.isNotBlank(account.getName())) {
|
||||
String decrypt = CryptoUtil.decrypt(account.getName());
|
||||
if (StrUtil.equalsIgnoreCase(superManager.getUsername(), decrypt)) {
|
||||
// 校验密码
|
||||
if (!StrUtil.equals(
|
||||
CryptoUtil.decrypt(account.getPassword()), superManager.getPassword())) {
|
||||
throw new RuntimeException("密码错误");
|
||||
}
|
||||
// 写入登录参数
|
||||
SaLoginModel model = new SaLoginModel();
|
||||
model.setExtra(Constants.NAME, superManager.getUsername());
|
||||
model.setExtra(Constants.REAL_NAME, superManager.getRealName());
|
||||
model.setExtra(Constants.TENANT, superManager.getTenant());
|
||||
StpUtil.login(superManager.getUsername(), model);
|
||||
return R.ok("超级管理员登录成功");
|
||||
}
|
||||
}
|
||||
// 查找已有凭据
|
||||
Account find = null;
|
||||
if (StrUtil.isNotBlank(account.getName())) {
|
||||
String decrypt = CryptoUtil.decrypt(account.getName());
|
||||
find =
|
||||
accountService.getOne(Wrappers.lambdaQuery(Account.class).eq(Account::getName, decrypt));
|
||||
} else if (StrUtil.isNotBlank(account.getMobile())) {
|
||||
String decrypt = CryptoUtil.decrypt(account.getMobile());
|
||||
find =
|
||||
accountService.getOne(
|
||||
Wrappers.lambdaQuery(Account.class).eq(Account::getMobile, decrypt));
|
||||
account.setName(account.getMobile());
|
||||
} else if (StrUtil.isNotBlank(account.getEmail())) {
|
||||
String decrypt = CryptoUtil.decrypt(account.getEmail());
|
||||
find =
|
||||
accountService.getOne(Wrappers.lambdaQuery(Account.class).eq(Account::getEmail, decrypt));
|
||||
account.setName(StrUtil.subBefore(account.getName(), "@", false));
|
||||
} else {
|
||||
throw new RuntimeException("凭据不存在");
|
||||
}
|
||||
if (find == null) {
|
||||
throw new RuntimeException("凭据不存在");
|
||||
}
|
||||
// 校验凭据状态
|
||||
if (find.getStatus() != Status.ENABLE.getField()) {
|
||||
throw new RuntimeException("凭据已禁用");
|
||||
}
|
||||
// 校验密码
|
||||
if (!BCrypt.checkpw(CryptoUtil.decrypt(account.getPassword()), find.getPassword())) {
|
||||
throw new RuntimeException("密码错误");
|
||||
}
|
||||
// 写入登录参数
|
||||
SaLoginModel model = new SaLoginModel();
|
||||
model.setExtra(Constants.NAME, find.getName());
|
||||
model.setExtra(Constants.REAL_NAME, find.getRealName());
|
||||
model.setExtra(Constants.TENANT, find.getTenant());
|
||||
StpUtil.login(String.valueOf(find.getId()), model);
|
||||
return R.ok("登录成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 凭据注册.
|
||||
*
|
||||
* @param account 请求体
|
||||
* @return 响应结果
|
||||
*/
|
||||
@Transactional
|
||||
@Operation(summary = "凭据注册")
|
||||
@PostMapping("register")
|
||||
public R register(@Valid @RequestBody AccountRegister account) {
|
||||
// 忽略租户
|
||||
InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
|
||||
if (StrUtil.isNotBlank(account.getCaptcha()) && StrUtil.isNotBlank(account.getCaptchaId())) {
|
||||
if (!captchaService.checkCodeById(account.getCaptchaId(), account.getCaptcha())) {
|
||||
throw new RuntimeException("验证码错误");
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("验证码与回执不能为空");
|
||||
}
|
||||
// 查找已有凭据
|
||||
Account find = null;
|
||||
if (StrUtil.isNotBlank(account.getName())) {
|
||||
find =
|
||||
accountService.getOne(
|
||||
Wrappers.lambdaQuery(Account.class).eq(Account::getName, account.getName()));
|
||||
} else if (StrUtil.isNotBlank(account.getMobile())) {
|
||||
find =
|
||||
accountService.getOne(
|
||||
Wrappers.lambdaQuery(Account.class).eq(Account::getMobile, account.getMobile()));
|
||||
account.setName(account.getMobile());
|
||||
} else if (StrUtil.isNotBlank(account.getEmail())) {
|
||||
find =
|
||||
accountService.getOne(
|
||||
Wrappers.lambdaQuery(Account.class).eq(Account::getEmail, account.getEmail()));
|
||||
account.setName(StrUtil.subBefore(account.getEmail(), "@", false));
|
||||
}
|
||||
if (find != null) {
|
||||
throw new RuntimeException("该凭据已存在");
|
||||
}
|
||||
// 新建租户
|
||||
Tenant findTenant =
|
||||
tenantService.getOne(
|
||||
Wrappers.lambdaQuery(Tenant.class).eq(Tenant::getName, account.getTenant()));
|
||||
if (findTenant == null) {
|
||||
Tenant tenant = new Tenant();
|
||||
tenant.setName(account.getTenant());
|
||||
tenant.setDescription("创建凭据时自动生成");
|
||||
tenantService.save(tenant);
|
||||
}
|
||||
// 新建凭据
|
||||
Account mapperAccount = accountMapper.toAccount(account);
|
||||
mapperAccount.setPassword(BCrypt.hashpw(account.getPassword()));
|
||||
return R.ok(accountService.save(mapperAccount));
|
||||
}
|
||||
|
||||
/**
|
||||
* 密码修改.
|
||||
*
|
||||
* @param account 请求体
|
||||
* @return 响应结果
|
||||
*/
|
||||
@Operation(summary = "密码修改")
|
||||
@PutMapping("password")
|
||||
public R password(@Valid @RequestBody AccountPassword account) {
|
||||
// 查找已有凭据
|
||||
Account find = accountService.getById(account.getId());
|
||||
if (find == null) {
|
||||
throw new RuntimeException("凭据不存在");
|
||||
}
|
||||
// 修改密码
|
||||
Account mapperAccount = accountMapper.toAccount(account);
|
||||
mapperAccount.setPassword(BCrypt.hashpw(account.getPassword()));
|
||||
return R.ok(accountService.updateById(mapperAccount));
|
||||
}
|
||||
|
||||
@Operation(summary = "凭据注销")
|
||||
@PostMapping("logout")
|
||||
public R logout() {
|
||||
StpUtil.logout();
|
||||
return R.ok("注销成功");
|
||||
}
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
package {{ cookiecutter.__mvn_package }}.module.manager;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
|
||||
import {{ cookiecutter.__mvn_package }}.common.Constants;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.ApiController;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.R;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.entity.Message;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.service.MessageService;
|
||||
import {{ cookiecutter.__mvn_package }}.utils.ThreadLocalUtil;
|
||||
import {{ cookiecutter.__mvn_package }}.web.SuperManager;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.io.Serializable;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
import org.dromara.hutool.core.date.DateField;
|
||||
import org.dromara.hutool.core.date.DateTime;
|
||||
import org.dromara.hutool.core.date.DateUtil;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* (MessageController)
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2025/4/21 09:32
|
||||
*/
|
||||
@Tag(name = "消息接口", description = "需要令牌")
|
||||
@RestController
|
||||
@RequestMapping("message")
|
||||
public class MessageController extends ApiController {
|
||||
|
||||
/** 服务对象. */
|
||||
private final SuperManager superManager;
|
||||
|
||||
private final MessageService messageService;
|
||||
|
||||
public MessageController(SuperManager superManager, MessageService messageService) {
|
||||
this.superManager = superManager;
|
||||
this.messageService = messageService;
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "获取自己的消息",
|
||||
parameters = {
|
||||
@Parameter(name = "page", description = "分页对象"),
|
||||
@Parameter(name = "message", description = "查询实体"),
|
||||
},
|
||||
responses = @ApiResponse(description = "分页数据"),
|
||||
description = "size=-1时查询所有数据,orders配合asc排序")
|
||||
@GetMapping
|
||||
public R message(@Nullable PageDTO<Message> page, @Nullable Message message) {
|
||||
String name =
|
||||
ThreadLocalUtil.getByKey(Constants.NAME, ThreadLocalUtil.getByKey(Constants.NAME, ""));
|
||||
String realName = ThreadLocalUtil.getByKey(Constants.REAL_NAME, "");
|
||||
String tenant =
|
||||
ThreadLocalUtil.getByKey(Constants.TENANT, ThreadLocalUtil.getByKey(Constants.TENANT, ""));
|
||||
QueryWrapper<Message> queryWrapper = new QueryWrapper<>(message);
|
||||
if (!superManager.isReal()) {
|
||||
queryWrapper.or().eq("sender", name).or().eq("sender", realName);
|
||||
queryWrapper.or().like("recipient", "所有人");
|
||||
if (StrUtil.isNotBlank(name)) {
|
||||
queryWrapper.or().like("recipient", name);
|
||||
}
|
||||
if (StrUtil.isNotBlank(realName)) {
|
||||
queryWrapper.or().like("recipient", realName);
|
||||
}
|
||||
if (StrUtil.isNotBlank(tenant)) {
|
||||
queryWrapper.or().like("recipient", tenant);
|
||||
}
|
||||
}
|
||||
if (message != null && StrUtil.isNotEmpty(message.getDescription())) {
|
||||
queryWrapper.like("description", message.getDescription());
|
||||
message.setDescription(null);
|
||||
}
|
||||
queryWrapper.orderByAsc("status").orderByDesc("create_time");
|
||||
return success(messageService.page(page, queryWrapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增数据.
|
||||
*
|
||||
* @param message 实体对象
|
||||
* @return 新增结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "消息表 - 新增数据",
|
||||
parameters = {
|
||||
@Parameter(name = "message", description = "实体对象"),
|
||||
},
|
||||
responses = @ApiResponse(description = "新增结果"))
|
||||
@PostMapping
|
||||
public R insert(@RequestBody Message message) {
|
||||
if (message.getIsExpiry() != null && message.getIsExpiry()) {
|
||||
// 设置过期时间,10天后
|
||||
DateTime offset = DateUtil.offset(DateUtil.today(), DateField.DAY_OF_MONTH, 10);
|
||||
message.setExpiry(OffsetDateTime.of(DateUtil.toLocalDateTime(offset), ZoneOffset.ofHours(8)));
|
||||
}
|
||||
return success(this.messageService.save(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改数据.
|
||||
*
|
||||
* @param message 实体对象
|
||||
* @return 修改结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "消息表 - 修改数据",
|
||||
parameters = {@Parameter(name = "message", description = "实体对象")},
|
||||
responses = @ApiResponse(description = "修改结果"),
|
||||
description = "不存在的对象会新增")
|
||||
@PutMapping
|
||||
public R update(@RequestBody Message message) {
|
||||
if (message.getIsExpiry() != null && message.getIsExpiry()) {
|
||||
// 设置过期时间,10天后
|
||||
DateTime offset = DateUtil.offset(DateUtil.today(), DateField.DAY_OF_MONTH, 10);
|
||||
message.setExpiry(OffsetDateTime.of(DateUtil.toLocalDateTime(offset), ZoneOffset.ofHours(8)));
|
||||
}
|
||||
return success(this.messageService.saveOrUpdate(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记为已读.
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 修改结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "消息表 - 通过主键已读单条数据",
|
||||
parameters = {
|
||||
@Parameter(name = "id", description = "主键", schema = @Schema(type = "string")),
|
||||
},
|
||||
responses = @ApiResponse(description = "修改结果"))
|
||||
@PutMapping("read/{id}")
|
||||
public R read(@PathVariable("id") Serializable id) {
|
||||
Message message = this.messageService.getById(id);
|
||||
if (message.getIsExpiry()) {
|
||||
// 设置过期时间的消息不允许已读
|
||||
throw new RuntimeException("消息自动已读,不可手动设置");
|
||||
} else {
|
||||
message.setStatus(1);
|
||||
}
|
||||
return success(this.messageService.updateById(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据.
|
||||
*
|
||||
* @param idList 主键结合
|
||||
* @return 删除结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "消息表 - 删除数据",
|
||||
parameters = {
|
||||
@Parameter(name = "idList", description = "主键结合", schema = @Schema(type = "string"))
|
||||
},
|
||||
responses = @ApiResponse(description = "删除结果"),
|
||||
description = "主键用逗号拼接")
|
||||
@DeleteMapping
|
||||
public R delete(@RequestParam("idList") List<Long> idList) {
|
||||
return success(this.messageService.removeByIds(idList));
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package {{ cookiecutter.__mvn_package }}.module.manager.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* (AccountLogin) 登录请求体.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/4/1 09:40
|
||||
*/
|
||||
@Data
|
||||
public class AccountLogin {
|
||||
|
||||
// @Size(min = 3, max = 50, message = "登录名长度在3到50个字符之间")
|
||||
@Schema(description = "登录名,RSA加密")
|
||||
private String name;
|
||||
|
||||
// @Size(min = 8, max = 15, message = "手机号长度在8到15个字符之间")
|
||||
@Schema(description = "手机号,RSA加密")
|
||||
private String mobile;
|
||||
|
||||
// @Email(message = "邮箱格式错误")
|
||||
@Schema(description = "邮箱,RSA加密")
|
||||
private String email;
|
||||
|
||||
@NotBlank(message = "登录密码不能为空")
|
||||
// @Size(min = 6, max = 32, message = "登录密码长度在6到32个字符之间")
|
||||
@Schema(description = "登录密码,RSA加密")
|
||||
private String password;
|
||||
|
||||
@NotBlank(message = "验证码不能为空")
|
||||
@Schema(description = "图形验证码")
|
||||
private String captcha;
|
||||
|
||||
@NotBlank(message = "验证码回执不能为空")
|
||||
@Schema(description = "图形验证码回执")
|
||||
private String captchaId;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package {{ cookiecutter.__mvn_package }}.module.manager.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* (AccountPassword) 修改密码请求体.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/4/1 09:17
|
||||
*/
|
||||
@Data
|
||||
public class AccountPassword {
|
||||
|
||||
@NotNull(message = "ID不能为空")
|
||||
@Schema(description = "ID")
|
||||
private long id;
|
||||
|
||||
@NotBlank(message = "登录密码不能为空")
|
||||
@Size(min = 6, max = 32, message = "登录密码长度在6到32个字符之间")
|
||||
@Schema(description = "登录密码")
|
||||
private String password;
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package {{ cookiecutter.__mvn_package }}.module.manager.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* (RAccount) 注册请求体.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/3/28 17:12
|
||||
*/
|
||||
@Data
|
||||
public class AccountRegister {
|
||||
|
||||
@NotBlank(message = "登录名不能为空")
|
||||
@Size(min = 3, max = 50, message = "登录名长度在3到50个字符之间")
|
||||
@Schema(description = "登录名")
|
||||
private String name;
|
||||
|
||||
@Size(min = 8, max = 15, message = "手机号长度在8到15个字符之间")
|
||||
@Schema(description = "手机号")
|
||||
private String mobile;
|
||||
|
||||
@Email(message = "邮箱格式错误")
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
|
||||
@NotBlank(message = "登录密码不能为空")
|
||||
@Size(min = 6, max = 32, message = "登录密码长度在6到32个字符之间")
|
||||
@Schema(description = "登录密码")
|
||||
private String password;
|
||||
|
||||
@NotBlank(message = "公司名称不能为空")
|
||||
@Size(min = 4, max = 100, message = "公司名称长度在4到100个字符之间")
|
||||
@Schema(description = "公司名称")
|
||||
private String tenant;
|
||||
|
||||
@NotBlank(message = "验证码不能为空")
|
||||
@Schema(description = "图形验证码")
|
||||
private String captcha;
|
||||
|
||||
@NotBlank(message = "验证码回执不能为空")
|
||||
@Schema(description = "图形验证码回执")
|
||||
private String captchaId;
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package {{ cookiecutter.__mvn_package }}.module.schedule;
|
||||
|
||||
import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
|
||||
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
|
||||
import {{ cookiecutter.__mvn_package }}.module.dao.CustomDao;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.hutool.core.date.StopWatch;
|
||||
import org.dromara.hutool.core.date.TimeUtil;
|
||||
import org.dromara.hutool.core.lang.ConsoleTable;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* (ScheduledService) 定时任务.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024/7/3 下午2:27
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ScheduledService {
|
||||
|
||||
@Resource CustomDao customDao;
|
||||
|
||||
/** 每天早上两点清理一个月前的日志. */
|
||||
@Scheduled(cron = "0 0 2 * * ?")
|
||||
public void clearLogger() {
|
||||
// 定时
|
||||
StopWatch watchClearLogger = StopWatch.of("日志清理");
|
||||
watchClearLogger.start();
|
||||
// 执行
|
||||
InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
|
||||
LocalDateTime offset = TimeUtil.offset(TimeUtil.of(TimeUtil.today()), -1, ChronoUnit.MONTHS);
|
||||
int size = customDao.clearLogger(offset);
|
||||
InterceptorIgnoreHelper.clearIgnoreStrategy();
|
||||
watchClearLogger.stop();
|
||||
// 统计
|
||||
ConsoleTable.of()
|
||||
.setSBCMode(true)
|
||||
.addHeader("定时任务", "日志时间", "删除行数", "耗时(毫秒)")
|
||||
.addBody(
|
||||
"日志清理",
|
||||
offset.toString(),
|
||||
String.valueOf(size),
|
||||
String.valueOf(watchClearLogger.getTotalTimeMillis()))
|
||||
.print();
|
||||
}
|
||||
|
||||
/** 每天早上两点半执行一次过期消息. */
|
||||
@Scheduled(cron = "0 30 2 * * ?")
|
||||
public void expiryMessage() {
|
||||
// 定时
|
||||
StopWatch watchClearLogger = StopWatch.of("过期消息");
|
||||
watchClearLogger.start();
|
||||
// 执行
|
||||
InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
|
||||
int size = customDao.expiryMessage();
|
||||
InterceptorIgnoreHelper.clearIgnoreStrategy();
|
||||
watchClearLogger.stop();
|
||||
// 统计
|
||||
ConsoleTable.of()
|
||||
.setSBCMode(true)
|
||||
.addHeader("定时任务", "更新行数", "耗时(毫秒)")
|
||||
.addBody(
|
||||
"过期消息", String.valueOf(size), String.valueOf(watchClearLogger.getTotalTimeMillis()))
|
||||
.print();
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2011-2021, baomidou (jobob@qq.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package {{ cookiecutter.__mvn_package }}.rest.api;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* REST API 通用控制器.
|
||||
*
|
||||
* @author hubin
|
||||
* @since 2018-06-08
|
||||
*/
|
||||
public class ApiController {
|
||||
|
||||
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
/**
|
||||
* 请求成功.
|
||||
*
|
||||
* @param data 数据内容
|
||||
* @param <T> 对象泛型
|
||||
* @return ignore
|
||||
*/
|
||||
protected <T> R<T> success(T data) {
|
||||
return R.ok(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求失败.
|
||||
*
|
||||
* @param msg 提示内容
|
||||
* @return ignore
|
||||
*/
|
||||
protected <T> R<T> failed(String msg) {
|
||||
return R.failed(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求失败.
|
||||
*
|
||||
* @param errorCode 请求错误码
|
||||
* @return ignore
|
||||
*/
|
||||
protected <T> R<T> failed(ErrorCode errorCode) {
|
||||
return R.failed(errorCode);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2011-2021, baomidou (jobob@qq.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package {{ cookiecutter.__mvn_package }}.rest.api;
|
||||
|
||||
/**
|
||||
* REST API 错误码接口.
|
||||
*
|
||||
* @author hubin
|
||||
* @since 2018-06-05
|
||||
*/
|
||||
public interface ErrorCode {
|
||||
|
||||
/** 错误编码 -1、失败 0、成功. */
|
||||
long getCode();
|
||||
|
||||
/** 错误描述. */
|
||||
String getMsg();
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (c) 2011-2021, baomidou (jobob@qq.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package {{ cookiecutter.__mvn_package }}.rest.api;
|
||||
|
||||
import {{ cookiecutter.__mvn_package }}.rest.enums.ApiErrorCode;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.exceptions.ApiException;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* REST API 返回结果.
|
||||
*
|
||||
* @author hubin
|
||||
* @since 2018-06-05
|
||||
*/
|
||||
@Schema(description = "通用响应体")
|
||||
@Data
|
||||
@Slf4j
|
||||
@Accessors(chain = true)
|
||||
public class R<T> implements Serializable {
|
||||
|
||||
@Serial private static final long serialVersionUID = -1481239544950144592L;
|
||||
|
||||
/** 错误码. */
|
||||
private long code;
|
||||
|
||||
/** 结果集. */
|
||||
private T data;
|
||||
|
||||
/** 描述. */
|
||||
private String msg;
|
||||
|
||||
public R() {}
|
||||
|
||||
/**
|
||||
* 按枚举初始化.
|
||||
*
|
||||
* @param errorCode 枚举
|
||||
*/
|
||||
public R(ErrorCode errorCode) {
|
||||
errorCode = Optional.ofNullable(errorCode).orElse(ApiErrorCode.FAILED);
|
||||
this.code = errorCode.getCode();
|
||||
this.msg = errorCode.getMsg();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按内容构建响应结果.
|
||||
*
|
||||
* @param data 内容
|
||||
* @param <T> 类型
|
||||
* @return 响应结果
|
||||
*/
|
||||
public static <T> R<T> ok(T data) {
|
||||
ApiErrorCode aec = ApiErrorCode.SUCCESS;
|
||||
if (data instanceof Boolean && Boolean.FALSE.equals(data)) {
|
||||
aec = ApiErrorCode.FAILED;
|
||||
}
|
||||
return restResult(data, aec);
|
||||
}
|
||||
|
||||
public static <T> R<T> failed(String msg) {
|
||||
return restResult(null, ApiErrorCode.FAILED.getCode(), msg);
|
||||
}
|
||||
|
||||
public static <T> R<T> failed(ErrorCode errorCode) {
|
||||
return restResult(null, errorCode);
|
||||
}
|
||||
|
||||
public static <T> R<T> restResult(T data, ErrorCode errorCode) {
|
||||
return restResult(data, errorCode.getCode(), errorCode.getMsg());
|
||||
}
|
||||
|
||||
private static <T> R<T> restResult(T data, long code, String msg) {
|
||||
R<T> apiResult = new R<>();
|
||||
apiResult.setCode(code);
|
||||
apiResult.setData(data);
|
||||
apiResult.setMsg(msg);
|
||||
return apiResult;
|
||||
}
|
||||
|
||||
public boolean ok() {
|
||||
return ApiErrorCode.SUCCESS.getCode() == this.code;
|
||||
}
|
||||
|
||||
/** 服务间调用非业务正常,异常直接释放. */
|
||||
public T serviceData() {
|
||||
if (!this.ok()) {
|
||||
throw new ApiException(this.msg);
|
||||
}
|
||||
return this.data;
|
||||
}
|
||||
}
|
@ -0,0 +1,283 @@
|
||||
package {{ cookiecutter.__mvn_package }}.rest.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.secure.BCrypt;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
|
||||
import {{ cookiecutter.__mvn_package }}.mapper.AccountMapper;
|
||||
import {{ cookiecutter.__mvn_package }}.mapper.entity.AccountExcel;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.ApiController;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.R;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.entity.Account;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.service.AccountService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.poi.ss.util.CellRangeAddressList;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.poi.excel.ExcelReader;
|
||||
import org.dromara.hutool.poi.excel.ExcelUtil;
|
||||
import org.dromara.hutool.poi.excel.ExcelWriter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 凭据表(Account)表控制层.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024-07-03 17:49:36
|
||||
*/
|
||||
@Tag(name = "标准接口", description = "由表结构自动生成")
|
||||
@RestController
|
||||
@RequestMapping("db/tAccount")
|
||||
@RequiredArgsConstructor
|
||||
public class AccountController extends ApiController {
|
||||
/** 服务对象. */
|
||||
private final AccountService accountService;
|
||||
|
||||
private final AccountMapper accountMapper;
|
||||
|
||||
/**
|
||||
* 分页查询所有数据.
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param account 查询实体
|
||||
* @return 分页数据
|
||||
*/
|
||||
@Operation(
|
||||
summary = "凭据表 - 分页查询所有数据",
|
||||
parameters = {
|
||||
@Parameter(name = "page", description = "分页对象"),
|
||||
@Parameter(name = "account", description = "查询实体"),
|
||||
},
|
||||
responses = @ApiResponse(description = "分页数据"),
|
||||
description = "size=-1时查询所有数据,orders配合asc排序")
|
||||
@GetMapping
|
||||
@SaCheckPermission("db:rest:get")
|
||||
public R selectAll(
|
||||
@Nullable PageDTO<Account> page, @Nullable Account account, @Nullable String mention) {
|
||||
QueryWrapper<Account> queryWrapper = new QueryWrapper<>(account);
|
||||
if (account != null && StrUtil.isNotBlank(account.getName())) {
|
||||
queryWrapper.like("name", account.getName());
|
||||
account.setName(null);
|
||||
}
|
||||
if (account != null && StrUtil.isNotBlank(account.getRealName())) {
|
||||
queryWrapper.like("real_name", account.getRealName());
|
||||
account.setRealName(null);
|
||||
}
|
||||
if (StrUtil.isNotBlank(mention)) {
|
||||
queryWrapper.like("name", mention).or().like("real_name", mention);
|
||||
}
|
||||
return success(this.accountService.page(page, queryWrapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据.
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 单条数据
|
||||
*/
|
||||
@Operation(
|
||||
summary = "凭据表 - 通过主键查询单条数据",
|
||||
parameters = {
|
||||
@Parameter(name = "id", description = "主键", schema = @Schema(type = "string")),
|
||||
},
|
||||
responses = @ApiResponse(description = "单条数据"))
|
||||
@GetMapping("{id}")
|
||||
@SaCheckPermission("db:rest:get")
|
||||
public R selectOne(@PathVariable Serializable id) {
|
||||
return success(this.accountService.getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增数据.
|
||||
*
|
||||
* @param account 实体对象
|
||||
* @return 新增结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "凭据表 - 新增数据",
|
||||
parameters = {
|
||||
@Parameter(name = "account", description = "实体对象"),
|
||||
},
|
||||
responses = @ApiResponse(description = "新增结果"))
|
||||
@PostMapping
|
||||
@SaCheckPermission("db:rest:post")
|
||||
public R insert(@RequestBody Account account) {
|
||||
if (StrUtil.isNotBlank(account.getPassword())) {
|
||||
account.setPassword(BCrypt.hashpw(account.getPassword()));
|
||||
}
|
||||
return success(this.accountService.save(account));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增数据.
|
||||
*
|
||||
* @param accounts 实体对象
|
||||
* @return 新增结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "凭据表 - 批量新增数据",
|
||||
parameters = {
|
||||
@Parameter(name = "accounts", description = "实体对象"),
|
||||
},
|
||||
responses = @ApiResponse(description = "新增结果"))
|
||||
@PostMapping("s")
|
||||
@SaCheckPermission("db:rest:post")
|
||||
public R inserts(@RequestBody List<Account> accounts) {
|
||||
accounts.forEach(
|
||||
item -> {
|
||||
if (StrUtil.isNotBlank(item.getPassword())) {
|
||||
item.setPassword(BCrypt.hashpw(item.getPassword()));
|
||||
}
|
||||
});
|
||||
return success(this.accountService.saveBatch(accounts));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改数据.
|
||||
*
|
||||
* @param account 实体对象
|
||||
* @return 修改结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "凭据表 - 修改数据",
|
||||
parameters = {@Parameter(name = "account", description = "实体对象")},
|
||||
responses = @ApiResponse(description = "修改结果"),
|
||||
description = "不存在的对象会新增")
|
||||
@PutMapping
|
||||
@SaCheckPermission("db:rest:put")
|
||||
public R update(@RequestBody Account account) {
|
||||
if (StrUtil.isNotBlank(account.getPassword())) {
|
||||
account.setPassword(BCrypt.hashpw(account.getPassword()));
|
||||
}
|
||||
return success(this.accountService.saveOrUpdate(account));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量修改数据.
|
||||
*
|
||||
* @param accounts 实体对象
|
||||
* @return 修改结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "凭据表 - 批量修改数据",
|
||||
parameters = {@Parameter(name = "accounts", description = "实体对象")},
|
||||
responses = @ApiResponse(description = "修改结果"),
|
||||
description = "不存在的对象会新增")
|
||||
@PutMapping("s")
|
||||
@SaCheckPermission("db:rest:put")
|
||||
public R updates(@RequestBody List<Account> accounts) {
|
||||
accounts.forEach(
|
||||
item -> {
|
||||
if (StrUtil.isNotBlank(item.getPassword())) {
|
||||
item.setPassword(BCrypt.hashpw(item.getPassword()));
|
||||
}
|
||||
});
|
||||
return success(this.accountService.saveOrUpdateBatch(accounts));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据.
|
||||
*
|
||||
* @param idList 主键结合
|
||||
* @return 删除结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "凭据表 - 删除数据",
|
||||
parameters = {
|
||||
@Parameter(name = "idList", description = "主键结合", schema = @Schema(type = "string"))
|
||||
},
|
||||
responses = @ApiResponse(description = "删除结果"),
|
||||
description = "主键用逗号拼接")
|
||||
@DeleteMapping
|
||||
@SaCheckPermission("db:rest:del")
|
||||
public R delete(@RequestParam("idList") List<Long> idList) {
|
||||
return success(this.accountService.removeByIds(idList));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出符合条件的数据
|
||||
*
|
||||
* @param account 查询条件
|
||||
* @param response 响应内容
|
||||
*/
|
||||
@GetMapping("export")
|
||||
@SaCheckPermission("db:rest:export")
|
||||
public void excelExport(@Nullable Account account, HttpServletResponse response)
|
||||
throws IOException {
|
||||
List<Account> list = this.accountService.list(new QueryWrapper<>(account));
|
||||
List<AccountExcel> excels = this.accountMapper.toExcel(list);
|
||||
ExcelWriter writer = ExcelUtil.getWriter();
|
||||
// 表头
|
||||
writer.addHeaderAlias("id", "唯一标识");
|
||||
writer.addHeaderAlias("name", "账户");
|
||||
writer.addHeaderAlias("realName", "名称");
|
||||
writer.addHeaderAlias("mobile", "手机号");
|
||||
writer.addHeaderAlias("email", "邮箱");
|
||||
writer.addHeaderAlias("isActive", "激活");
|
||||
writer.addHeaderAlias("status", "状态");
|
||||
writer.addHeaderAlias("description", "描述");
|
||||
writer.addHeaderAlias("createBy", "创建人");
|
||||
writer.addHeaderAlias("tenant", "租户");
|
||||
// 可选项
|
||||
if (!excels.isEmpty()) {
|
||||
writer.addSelect(new CellRangeAddressList(1, excels.size(), 5, 5), "激活", "未激活");
|
||||
writer.addSelect(new CellRangeAddressList(1, excels.size(), 6, 6), "可用", "禁用");
|
||||
} else {
|
||||
writer.addSelect(new CellRangeAddressList(1, 1, 5, 5), "激活", "未激活");
|
||||
writer.addSelect(new CellRangeAddressList(1, 1, 6, 6), "可用", "禁用");
|
||||
}
|
||||
// 默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之
|
||||
writer.setOnlyAlias(true);
|
||||
// 写入数据
|
||||
writer.write(excels, true);
|
||||
writer.autoSizeColumnAll();
|
||||
// 写到响应流
|
||||
response.setContentType(writer.getContentType());
|
||||
response.setHeader(
|
||||
HttpHeaders.CONTENT_DISPOSITION, writer.getDisposition("凭据", StandardCharsets.UTF_8));
|
||||
ServletOutputStream out = response.getOutputStream();
|
||||
writer.flush(out);
|
||||
// 关闭流
|
||||
IoUtil.closeQuietly(writer, out);
|
||||
}
|
||||
|
||||
@PostMapping("import")
|
||||
@SaCheckPermission("db:rest:import")
|
||||
public R excelImport(@RequestParam("file") MultipartFile[] files) throws IOException {
|
||||
if (files == null || files.length < 1) throw new RuntimeException("文件上传失败");
|
||||
InputStream in = files[0].getInputStream();
|
||||
ExcelReader reader = ExcelUtil.getReader(in);
|
||||
// 表头
|
||||
reader.addHeaderAlias("唯一标识", "id");
|
||||
reader.addHeaderAlias("账户", "name");
|
||||
reader.addHeaderAlias("名称", "realName");
|
||||
reader.addHeaderAlias("手机号", "mobile");
|
||||
reader.addHeaderAlias("邮箱", "email");
|
||||
reader.addHeaderAlias("激活", "isActive");
|
||||
reader.addHeaderAlias("状态", "status");
|
||||
reader.addHeaderAlias("描述", "description");
|
||||
reader.addHeaderAlias("创建人", "createBy");
|
||||
reader.addHeaderAlias("租户", "tenant");
|
||||
|
||||
List<AccountExcel> excels = reader.readAll(AccountExcel.class);
|
||||
List<Account> tenants = accountMapper.toEntity(excels);
|
||||
// 关闭流
|
||||
IoUtil.closeQuietly(reader, in);
|
||||
return success(accountService.saveOrUpdateBatch(tenants));
|
||||
}
|
||||
}
|
@ -0,0 +1,257 @@
|
||||
package {{ cookiecutter.__mvn_package }}.rest.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
|
||||
import {{ cookiecutter.__mvn_package }}.mapper.AuthorityMapper;
|
||||
import {{ cookiecutter.__mvn_package }}.mapper.entity.AuthorityExcel;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.ApiController;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.R;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.entity.Authority;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.service.AuthorityService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.poi.ss.util.CellRangeAddressList;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.poi.excel.ExcelReader;
|
||||
import org.dromara.hutool.poi.excel.ExcelUtil;
|
||||
import org.dromara.hutool.poi.excel.ExcelWriter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 权限表(Authority)表控制层.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024-07-03 17:49:34
|
||||
*/
|
||||
@Tag(name = "标准接口", description = "由表结构自动生成")
|
||||
@RestController
|
||||
@RequestMapping("db/tAuthority")
|
||||
@RequiredArgsConstructor
|
||||
public class AuthorityController extends ApiController {
|
||||
/** 服务对象. */
|
||||
private final AuthorityService authorityService;
|
||||
|
||||
private final AuthorityMapper authorityMapper;
|
||||
|
||||
/**
|
||||
* 分页查询所有数据.
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param authority 查询实体
|
||||
* @return 分页数据
|
||||
*/
|
||||
@Operation(
|
||||
summary = "权限表 - 分页查询所有数据",
|
||||
parameters = {
|
||||
@Parameter(name = "page", description = "分页对象"),
|
||||
@Parameter(name = "authority", description = "查询实体"),
|
||||
},
|
||||
responses = @ApiResponse(description = "分页数据"),
|
||||
description = "size=-1时查询所有数据,orders配合asc排序")
|
||||
@GetMapping
|
||||
@SaCheckPermission("db:rest:get")
|
||||
public R selectAll(@Nullable PageDTO<Authority> page, @Nullable Authority authority) {
|
||||
QueryWrapper<Authority> queryWrapper = new QueryWrapper<>(authority);
|
||||
if (authority != null && StrUtil.isNotBlank(authority.getName())) {
|
||||
queryWrapper.like("name", authority.getName());
|
||||
authority.setName(null);
|
||||
}
|
||||
return success(this.authorityService.page(page, queryWrapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据.
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 单条数据
|
||||
*/
|
||||
@Operation(
|
||||
summary = "权限表 - 通过主键查询单条数据",
|
||||
parameters = {
|
||||
@Parameter(name = "id", description = "主键", schema = @Schema(type = "string")),
|
||||
},
|
||||
responses = @ApiResponse(description = "单条数据"))
|
||||
@GetMapping("{id}")
|
||||
@SaCheckPermission("db:rest:get")
|
||||
public R selectOne(@PathVariable Serializable id) {
|
||||
return success(this.authorityService.getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增数据.
|
||||
*
|
||||
* @param authority 实体对象
|
||||
* @return 新增结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "权限表 - 新增数据",
|
||||
parameters = {
|
||||
@Parameter(name = "authority", description = "实体对象"),
|
||||
},
|
||||
responses = @ApiResponse(description = "新增结果"))
|
||||
@PostMapping
|
||||
@SaCheckPermission("db:rest:post")
|
||||
public R insert(@RequestBody Authority authority) {
|
||||
return success(this.authorityService.save(authority));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增数据.
|
||||
*
|
||||
* @param authorities 实体对象
|
||||
* @return 新增结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "权限表 - 批量新增数据",
|
||||
parameters = {
|
||||
@Parameter(name = "authorities", description = "实体对象"),
|
||||
},
|
||||
responses = @ApiResponse(description = "新增结果"))
|
||||
@PostMapping("s")
|
||||
@SaCheckPermission("db:rest:post")
|
||||
public R inserts(@RequestBody List<Authority> authorities) {
|
||||
return success(this.authorityService.saveBatch(authorities));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改数据.
|
||||
*
|
||||
* @param authority 实体对象
|
||||
* @return 修改结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "权限表 - 修改数据",
|
||||
parameters = {@Parameter(name = "authority", description = "实体对象")},
|
||||
responses = @ApiResponse(description = "修改结果"),
|
||||
description = "不存在的对象会新增")
|
||||
@PutMapping
|
||||
@SaCheckPermission("db:rest:put")
|
||||
public R update(@RequestBody Authority authority) {
|
||||
return success(this.authorityService.saveOrUpdate(authority));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量修改数据.
|
||||
*
|
||||
* @param authorities 实体对象
|
||||
* @return 修改结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "权限表 - 批量修改数据",
|
||||
parameters = {@Parameter(name = "authorities", description = "实体对象")},
|
||||
responses = @ApiResponse(description = "修改结果"),
|
||||
description = "不存在的对象会新增")
|
||||
@PutMapping("s")
|
||||
@SaCheckPermission("db:rest:put")
|
||||
public R updates(@RequestBody List<Authority> authorities) {
|
||||
return success(this.authorityService.saveOrUpdateBatch(authorities));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据.
|
||||
*
|
||||
* @param idList 主键结合
|
||||
* @return 删除结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "权限表 - 删除数据",
|
||||
parameters = {
|
||||
@Parameter(name = "idList", description = "主键结合", schema = @Schema(type = "string"))
|
||||
},
|
||||
responses = @ApiResponse(description = "删除结果"),
|
||||
description = "主键用逗号拼接")
|
||||
@DeleteMapping
|
||||
@SaCheckPermission("db:rest:del")
|
||||
public R delete(@RequestParam("idList") List<Long> idList) {
|
||||
return success(this.authorityService.removeByIds(idList));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出符合条件的数据
|
||||
*
|
||||
* @param authority 查询条件
|
||||
* @param response 响应内容
|
||||
*/
|
||||
@GetMapping("export")
|
||||
@SaCheckPermission("db:rest:export")
|
||||
public void excelExport(@Nullable Authority authority, HttpServletResponse response)
|
||||
throws IOException {
|
||||
List<Authority> list = this.authorityService.list(new QueryWrapper<>(authority));
|
||||
List<AuthorityExcel> excels = this.authorityMapper.toExcel(list);
|
||||
ExcelWriter writer = ExcelUtil.getWriter();
|
||||
// 表头
|
||||
writer.addHeaderAlias("id", "唯一标识");
|
||||
writer.addHeaderAlias("parentId", "父标识");
|
||||
writer.addHeaderAlias("name", "名称");
|
||||
writer.addHeaderAlias("value", "英文值");
|
||||
writer.addHeaderAlias("path", "路径");
|
||||
writer.addHeaderAlias("link", "外部链接");
|
||||
writer.addHeaderAlias("type", "类型");
|
||||
writer.addHeaderAlias("status", "状态");
|
||||
writer.addHeaderAlias("description", "描述");
|
||||
writer.addHeaderAlias("createBy", "创建人");
|
||||
writer.addHeaderAlias("tenant", "租户");
|
||||
// 可选项
|
||||
if (!excels.isEmpty()) {
|
||||
writer.addSelect(new CellRangeAddressList(1, excels.size(), 6, 6), "接口", "菜单", "按钮");
|
||||
writer.addSelect(new CellRangeAddressList(1, excels.size(), 7, 7), "可用", "禁用");
|
||||
} else {
|
||||
writer.addSelect(new CellRangeAddressList(1, 1, 6, 6), "接口", "菜单", "按钮");
|
||||
writer.addSelect(new CellRangeAddressList(1, 1, 7, 7), "可用", "禁用");
|
||||
}
|
||||
// 默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之
|
||||
writer.setOnlyAlias(true);
|
||||
// 写入数据
|
||||
writer.write(excels, true);
|
||||
writer.autoSizeColumnAll();
|
||||
// 写到响应流
|
||||
response.setContentType(writer.getContentType());
|
||||
response.setHeader(
|
||||
HttpHeaders.CONTENT_DISPOSITION, writer.getDisposition("权限", StandardCharsets.UTF_8));
|
||||
ServletOutputStream out = response.getOutputStream();
|
||||
writer.flush(out);
|
||||
// 关闭流
|
||||
IoUtil.closeQuietly(writer, out);
|
||||
}
|
||||
|
||||
@PostMapping("import")
|
||||
@SaCheckPermission("db:rest:import")
|
||||
public R excelImport(@RequestParam("file") MultipartFile[] files) throws IOException {
|
||||
if (files == null || files.length < 1) throw new RuntimeException("文件上传失败");
|
||||
InputStream in = files[0].getInputStream();
|
||||
ExcelReader reader = ExcelUtil.getReader(in);
|
||||
// 表头
|
||||
reader.addHeaderAlias("唯一标识", "id");
|
||||
reader.addHeaderAlias("父标识", "parentId");
|
||||
reader.addHeaderAlias("名称", "name");
|
||||
reader.addHeaderAlias("英文值", "value");
|
||||
reader.addHeaderAlias("路径", "path");
|
||||
reader.addHeaderAlias("外部链接", "link");
|
||||
reader.addHeaderAlias("类型", "type");
|
||||
reader.addHeaderAlias("状态", "status");
|
||||
reader.addHeaderAlias("描述", "description");
|
||||
reader.addHeaderAlias("创建人", "createBy");
|
||||
reader.addHeaderAlias("租户", "tenant");
|
||||
List<AuthorityExcel> excels = reader.readAll(AuthorityExcel.class);
|
||||
List<Authority> tenants = authorityMapper.toEntity(excels);
|
||||
// 关闭流
|
||||
IoUtil.closeQuietly(reader, in);
|
||||
return success(authorityService.saveOrUpdateBatch(tenants));
|
||||
}
|
||||
}
|
@ -0,0 +1,219 @@
|
||||
package {{ cookiecutter.__mvn_package }}.rest.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
|
||||
import {{ cookiecutter.__mvn_package }}.mapper.LoggerMapper;
|
||||
import {{ cookiecutter.__mvn_package }}.mapper.entity.LoggerExcel;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.ApiController;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.R;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.entity.Logger;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.service.LoggerService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.poi.ss.util.CellRangeAddressList;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.poi.excel.ExcelUtil;
|
||||
import org.dromara.hutool.poi.excel.ExcelWriter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 日志表(Logger)表控制层.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024-07-03 17:49:33
|
||||
*/
|
||||
@Tag(name = "标准接口", description = "由表结构自动生成")
|
||||
@RestController
|
||||
@RequestMapping("db/tLogger")
|
||||
@RequiredArgsConstructor
|
||||
public class LoggerController extends ApiController {
|
||||
/** 服务对象. */
|
||||
private final LoggerService loggerService;
|
||||
|
||||
private final LoggerMapper loggerMapper;
|
||||
|
||||
/**
|
||||
* 分页查询所有数据.
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param logger 查询实体
|
||||
* @return 分页数据
|
||||
*/
|
||||
@Operation(
|
||||
summary = "日志表 - 分页查询所有数据",
|
||||
parameters = {
|
||||
@Parameter(name = "page", description = "分页对象"),
|
||||
@Parameter(name = "logger", description = "查询实体"),
|
||||
},
|
||||
responses = @ApiResponse(description = "分页数据"),
|
||||
description = "size=-1时查询所有数据,orders配合asc排序")
|
||||
@GetMapping
|
||||
@SaCheckPermission("db:rest:get")
|
||||
public R selectAll(@Nullable PageDTO<Logger> page, @Nullable Logger logger) {
|
||||
return success(this.loggerService.page(page, new QueryWrapper<>(logger)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据.
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 单条数据
|
||||
*/
|
||||
@Operation(
|
||||
summary = "日志表 - 通过主键查询单条数据",
|
||||
parameters = {
|
||||
@Parameter(name = "id", description = "主键", schema = @Schema(type = "string")),
|
||||
},
|
||||
responses = @ApiResponse(description = "单条数据"))
|
||||
@GetMapping("{id}")
|
||||
@SaCheckPermission("db:rest:get")
|
||||
public R selectOne(@PathVariable Serializable id) {
|
||||
return success(this.loggerService.getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增数据.
|
||||
*
|
||||
* @param logger 实体对象
|
||||
* @return 新增结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "日志表 - 新增数据",
|
||||
parameters = {
|
||||
@Parameter(name = "logger", description = "实体对象"),
|
||||
},
|
||||
responses = @ApiResponse(description = "新增结果"))
|
||||
@PostMapping
|
||||
@SaCheckPermission("db:rest:post")
|
||||
public R insert(@RequestBody Logger logger) {
|
||||
return success(this.loggerService.save(logger));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增数据.
|
||||
*
|
||||
* @param loggers 实体对象
|
||||
* @return 新增结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "日志表 - 批量新增数据",
|
||||
parameters = {
|
||||
@Parameter(name = "loggers", description = "实体对象"),
|
||||
},
|
||||
responses = @ApiResponse(description = "新增结果"))
|
||||
@PostMapping("s")
|
||||
@SaCheckPermission("db:rest:post")
|
||||
public R inserts(@RequestBody List<Logger> loggers) {
|
||||
return success(this.loggerService.saveBatch(loggers));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改数据.
|
||||
*
|
||||
* @param logger 实体对象
|
||||
* @return 修改结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "日志表 - 修改数据",
|
||||
parameters = {@Parameter(name = "logger", description = "实体对象")},
|
||||
responses = @ApiResponse(description = "修改结果"),
|
||||
description = "不存在的对象会新增")
|
||||
@PutMapping
|
||||
@SaCheckPermission("db:rest:put")
|
||||
public R update(@RequestBody Logger logger) {
|
||||
return success(this.loggerService.saveOrUpdate(logger));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量修改数据.
|
||||
*
|
||||
* @param loggers 实体对象
|
||||
* @return 修改结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "日志表 - 批量修改数据",
|
||||
parameters = {@Parameter(name = "loggers", description = "实体对象")},
|
||||
responses = @ApiResponse(description = "修改结果"),
|
||||
description = "不存在的对象会新增")
|
||||
@PutMapping("s")
|
||||
@SaCheckPermission("db:rest:put")
|
||||
public R updates(@RequestBody List<Logger> loggers) {
|
||||
return success(this.loggerService.saveOrUpdateBatch(loggers));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据.
|
||||
*
|
||||
* @param idList 主键结合
|
||||
* @return 删除结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "日志表 - 删除数据",
|
||||
parameters = {
|
||||
@Parameter(name = "idList", description = "主键结合", schema = @Schema(type = "string"))
|
||||
},
|
||||
responses = @ApiResponse(description = "删除结果"),
|
||||
description = "主键用逗号拼接")
|
||||
@DeleteMapping
|
||||
@SaCheckPermission("db:rest:del")
|
||||
public R delete(@RequestParam("idList") List<Long> idList) {
|
||||
return success(this.loggerService.removeByIds(idList));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出符合条件的数据
|
||||
*
|
||||
* @param logger 查询条件
|
||||
* @param response 响应内容
|
||||
*/
|
||||
@GetMapping("export")
|
||||
@SaCheckPermission("db:rest:export")
|
||||
public void excelExport(@Nullable Logger logger, HttpServletResponse response)
|
||||
throws IOException {
|
||||
List<Logger> list = this.loggerService.list(new QueryWrapper<>(logger));
|
||||
List<LoggerExcel> excels = this.loggerMapper.toExcel(list);
|
||||
ExcelWriter writer = ExcelUtil.getWriter();
|
||||
// 表头
|
||||
writer.addHeaderAlias("id", "唯一标识");
|
||||
writer.addHeaderAlias("type", "类型");
|
||||
writer.addHeaderAlias("operate", "操作");
|
||||
writer.addHeaderAlias("tableName", "表名称");
|
||||
writer.addHeaderAlias("recordStatus", "打印日志");
|
||||
writer.addHeaderAlias("changed", "变动内容");
|
||||
writer.addHeaderAlias("description", "描述");
|
||||
writer.addHeaderAlias("createTime", "创建时间");
|
||||
writer.addHeaderAlias("createBy", "创建人");
|
||||
// 可选项
|
||||
if (!excels.isEmpty()) {
|
||||
writer.addSelect(new CellRangeAddressList(1, excels.size(), 1, 1), "数据变更自动日志");
|
||||
} else {
|
||||
writer.addSelect(new CellRangeAddressList(1, 1, 1, 1), "数据变更自动日志");
|
||||
}
|
||||
// 默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之
|
||||
writer.setOnlyAlias(true);
|
||||
// 写入数据
|
||||
writer.write(excels, true);
|
||||
writer.autoSizeColumnAll();
|
||||
// 写到响应流
|
||||
response.setContentType(writer.getContentType());
|
||||
response.setHeader(
|
||||
HttpHeaders.CONTENT_DISPOSITION, writer.getDisposition("日志", StandardCharsets.UTF_8));
|
||||
ServletOutputStream out = response.getOutputStream();
|
||||
writer.flush(out);
|
||||
// 关闭流
|
||||
IoUtil.closeQuietly(writer, out);
|
||||
}
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
package {{ cookiecutter.__mvn_package }}.rest.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.ApiController;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.R;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.entity.RoleAuthority;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.service.RoleAuthorityService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 角色权限关联表(RoleAuthority)表控制层.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024-07-03 17:49:30
|
||||
*/
|
||||
@Tag(name = "标准接口", description = "由表结构自动生成")
|
||||
@RestController
|
||||
@RequestMapping("db/tRoleAuthority")
|
||||
public class RoleAuthorityController extends ApiController {
|
||||
/** 服务对象. */
|
||||
private final RoleAuthorityService roleAuthorityService;
|
||||
|
||||
public RoleAuthorityController(RoleAuthorityService roleAuthorityService) {
|
||||
this.roleAuthorityService = roleAuthorityService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询所有数据.
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param roleAuthority 查询实体
|
||||
* @return 分页数据
|
||||
*/
|
||||
@Operation(
|
||||
summary = "角色权限关联表 - 分页查询所有数据",
|
||||
parameters = {
|
||||
@Parameter(name = "page", description = "分页对象"),
|
||||
@Parameter(name = "roleAuthority", description = "查询实体"),
|
||||
},
|
||||
responses = @ApiResponse(description = "分页数据"),
|
||||
description = "size=-1时查询所有数据,orders配合asc排序")
|
||||
@GetMapping
|
||||
@SaCheckPermission("db:rest:get")
|
||||
public R selectAll(@Nullable PageDTO<RoleAuthority> page, @Nullable RoleAuthority roleAuthority) {
|
||||
return success(this.roleAuthorityService.page(page, new QueryWrapper<>(roleAuthority)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据.
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 单条数据
|
||||
*/
|
||||
@Operation(
|
||||
summary = "角色权限关联表 - 通过主键查询单条数据",
|
||||
parameters = {
|
||||
@Parameter(name = "id", description = "主键", schema = @Schema(type = "string")),
|
||||
},
|
||||
responses = @ApiResponse(description = "单条数据"))
|
||||
@GetMapping("{id}")
|
||||
@SaCheckPermission("db:rest:get")
|
||||
public R selectOne(@PathVariable Serializable id) {
|
||||
return success(this.roleAuthorityService.getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增数据.
|
||||
*
|
||||
* @param roleAuthority 实体对象
|
||||
* @return 新增结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "角色权限关联表 - 新增数据",
|
||||
parameters = {
|
||||
@Parameter(name = "roleAuthority", description = "实体对象"),
|
||||
},
|
||||
responses = @ApiResponse(description = "新增结果"))
|
||||
@PostMapping
|
||||
@SaCheckPermission("db:rest:post")
|
||||
public R insert(@RequestBody RoleAuthority roleAuthority) {
|
||||
return success(this.roleAuthorityService.save(roleAuthority));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增数据.
|
||||
*
|
||||
* @param roleAuthorities 实体对象
|
||||
* @return 新增结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "角色权限关联表 - 批量新增数据",
|
||||
parameters = {
|
||||
@Parameter(name = "roleAuthorities", description = "实体对象"),
|
||||
},
|
||||
responses = @ApiResponse(description = "新增结果"))
|
||||
@PostMapping("s")
|
||||
@SaCheckPermission("db:rest:post")
|
||||
public R inserts(@RequestBody List<RoleAuthority> roleAuthorities) {
|
||||
return success(this.roleAuthorityService.saveBatch(roleAuthorities));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改数据.
|
||||
*
|
||||
* @param roleAuthority 实体对象
|
||||
* @return 修改结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "角色权限关联表 - 修改数据",
|
||||
parameters = {@Parameter(name = "roleAuthority", description = "实体对象")},
|
||||
responses = @ApiResponse(description = "修改结果"),
|
||||
description = "不存在的对象会新增")
|
||||
@PutMapping
|
||||
@SaCheckPermission("db:rest:put")
|
||||
public R update(@RequestBody RoleAuthority roleAuthority) {
|
||||
return success(this.roleAuthorityService.saveOrUpdate(roleAuthority));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量修改数据.
|
||||
*
|
||||
* @param roleAuthorities 实体对象
|
||||
* @return 修改结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "角色权限关联表 - 批量修改数据",
|
||||
parameters = {@Parameter(name = "roleAuthorities", description = "实体对象")},
|
||||
responses = @ApiResponse(description = "修改结果"),
|
||||
description = "不存在的对象会新增")
|
||||
@PutMapping("s")
|
||||
@SaCheckPermission("db:rest:put")
|
||||
public R updates(@RequestBody List<RoleAuthority> roleAuthorities) {
|
||||
return success(this.roleAuthorityService.saveOrUpdateBatch(roleAuthorities));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据.
|
||||
*
|
||||
* @param idList 主键结合
|
||||
* @return 删除结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "角色权限关联表 - 删除数据",
|
||||
parameters = {
|
||||
@Parameter(name = "idList", description = "主键结合", schema = @Schema(type = "string"))
|
||||
},
|
||||
responses = @ApiResponse(description = "删除结果"),
|
||||
description = "主键用逗号拼接")
|
||||
@DeleteMapping
|
||||
@SaCheckPermission("db:rest:del")
|
||||
public R delete(@RequestParam("idList") List<Long> idList) {
|
||||
return success(this.roleAuthorityService.removeByIds(idList));
|
||||
}
|
||||
}
|
@ -0,0 +1,246 @@
|
||||
package {{ cookiecutter.__mvn_package }}.rest.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
|
||||
import {{ cookiecutter.__mvn_package }}.mapper.RoleMapper;
|
||||
import {{ cookiecutter.__mvn_package }}.mapper.entity.RoleExcel;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.ApiController;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.R;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.entity.Role;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.service.RoleService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.poi.ss.util.CellRangeAddressList;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.poi.excel.ExcelReader;
|
||||
import org.dromara.hutool.poi.excel.ExcelUtil;
|
||||
import org.dromara.hutool.poi.excel.ExcelWriter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 角色表(Role)表控制层.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024-07-03 17:49:32
|
||||
*/
|
||||
@Tag(name = "标准接口", description = "由表结构自动生成")
|
||||
@RestController
|
||||
@RequestMapping("db/tRole")
|
||||
@RequiredArgsConstructor
|
||||
public class RoleController extends ApiController {
|
||||
/** 服务对象. */
|
||||
private final RoleService roleService;
|
||||
|
||||
private final RoleMapper roleMapper;
|
||||
|
||||
/**
|
||||
* 分页查询所有数据.
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param role 查询实体
|
||||
* @return 分页数据
|
||||
*/
|
||||
@Operation(
|
||||
summary = "角色表 - 分页查询所有数据",
|
||||
parameters = {
|
||||
@Parameter(name = "page", description = "分页对象"),
|
||||
@Parameter(name = "role", description = "查询实体"),
|
||||
},
|
||||
responses = @ApiResponse(description = "分页数据"),
|
||||
description = "size=-1时查询所有数据,orders配合asc排序")
|
||||
@GetMapping
|
||||
@SaCheckPermission("db:rest:get")
|
||||
public R selectAll(@Nullable PageDTO<Role> page, @Nullable Role role) {
|
||||
QueryWrapper<Role> queryWrapper = new QueryWrapper<>(role);
|
||||
if (role != null && StrUtil.isNotBlank(role.getName())) {
|
||||
queryWrapper.like("name", role.getName());
|
||||
role.setName(null);
|
||||
}
|
||||
return success(this.roleService.page(page, queryWrapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据.
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 单条数据
|
||||
*/
|
||||
@Operation(
|
||||
summary = "角色表 - 通过主键查询单条数据",
|
||||
parameters = {
|
||||
@Parameter(name = "id", description = "主键", schema = @Schema(type = "string")),
|
||||
},
|
||||
responses = @ApiResponse(description = "单条数据"))
|
||||
@GetMapping("{id}")
|
||||
@SaCheckPermission("db:rest:get")
|
||||
public R selectOne(@PathVariable Serializable id) {
|
||||
return success(this.roleService.getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增数据.
|
||||
*
|
||||
* @param role 实体对象
|
||||
* @return 新增结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "角色表 - 新增数据",
|
||||
parameters = {
|
||||
@Parameter(name = "role", description = "实体对象"),
|
||||
},
|
||||
responses = @ApiResponse(description = "新增结果"))
|
||||
@PostMapping
|
||||
@SaCheckPermission("db:rest:post")
|
||||
public R insert(@RequestBody Role role) {
|
||||
return success(this.roleService.save(role));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增数据.
|
||||
*
|
||||
* @param roles 实体对象
|
||||
* @return 新增结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "角色表 - 批量新增数据",
|
||||
parameters = {
|
||||
@Parameter(name = "roles", description = "实体对象"),
|
||||
},
|
||||
responses = @ApiResponse(description = "新增结果"))
|
||||
@PostMapping("s")
|
||||
@SaCheckPermission("db:rest:post")
|
||||
public R inserts(@RequestBody List<Role> roles) {
|
||||
return success(this.roleService.saveBatch(roles));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改数据.
|
||||
*
|
||||
* @param role 实体对象
|
||||
* @return 修改结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "角色表 - 修改数据",
|
||||
parameters = {@Parameter(name = "role", description = "实体对象")},
|
||||
responses = @ApiResponse(description = "修改结果"),
|
||||
description = "不存在的对象会新增")
|
||||
@PutMapping
|
||||
@SaCheckPermission("db:rest:put")
|
||||
public R update(@RequestBody Role role) {
|
||||
return success(this.roleService.saveOrUpdate(role));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量修改数据.
|
||||
*
|
||||
* @param roles 实体对象
|
||||
* @return 修改结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "角色表 - 批量修改数据",
|
||||
parameters = {@Parameter(name = "roles", description = "实体对象")},
|
||||
responses = @ApiResponse(description = "修改结果"),
|
||||
description = "不存在的对象会新增")
|
||||
@PutMapping("s")
|
||||
@SaCheckPermission("db:rest:put")
|
||||
public R updates(@RequestBody List<Role> roles) {
|
||||
return success(this.roleService.saveOrUpdateBatch(roles));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据.
|
||||
*
|
||||
* @param idList 主键结合
|
||||
* @return 删除结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "角色表 - 删除数据",
|
||||
parameters = {
|
||||
@Parameter(name = "idList", description = "主键结合", schema = @Schema(type = "string"))
|
||||
},
|
||||
responses = @ApiResponse(description = "删除结果"),
|
||||
description = "主键用逗号拼接")
|
||||
@DeleteMapping
|
||||
@SaCheckPermission("db:rest:del")
|
||||
public R delete(@RequestParam("idList") List<Long> idList) {
|
||||
return success(this.roleService.removeByIds(idList));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出符合条件的数据
|
||||
*
|
||||
* @param role 查询条件
|
||||
* @param response 响应内容
|
||||
*/
|
||||
@GetMapping("export")
|
||||
@SaCheckPermission("db:rest:export")
|
||||
public void excelExport(@Nullable Role role, HttpServletResponse response) throws IOException {
|
||||
List<Role> list = this.roleService.list(new QueryWrapper<>(role));
|
||||
List<RoleExcel> excels = this.roleMapper.toExcel(list);
|
||||
ExcelWriter writer = ExcelUtil.getWriter();
|
||||
// 表头
|
||||
writer.addHeaderAlias("id", "唯一标识");
|
||||
writer.addHeaderAlias("name", "名称");
|
||||
writer.addHeaderAlias("value", "英文值");
|
||||
writer.addHeaderAlias("status", "状态");
|
||||
writer.addHeaderAlias("description", "描述");
|
||||
writer.addHeaderAlias("createBy", "创建人");
|
||||
writer.addHeaderAlias("tenant", "租户");
|
||||
// 可选项
|
||||
if (!excels.isEmpty()) {
|
||||
writer.addSelect(new CellRangeAddressList(1, excels.size(), 3, 3), "可用", "禁用");
|
||||
} else {
|
||||
writer.addSelect(new CellRangeAddressList(1, 1, 3, 3), "可用", "禁用");
|
||||
}
|
||||
// 默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之
|
||||
writer.setOnlyAlias(true);
|
||||
// 写入数据
|
||||
writer.write(excels, true);
|
||||
writer.autoSizeColumnAll();
|
||||
// 写到响应流
|
||||
response.setContentType(writer.getContentType());
|
||||
response.setHeader(
|
||||
HttpHeaders.CONTENT_DISPOSITION, writer.getDisposition("租户租户", StandardCharsets.UTF_8));
|
||||
ServletOutputStream out = response.getOutputStream();
|
||||
writer.flush(out);
|
||||
// 关闭流
|
||||
IoUtil.closeQuietly(writer, out);
|
||||
}
|
||||
|
||||
@PostMapping("import")
|
||||
@SaCheckPermission("db:rest:import")
|
||||
public R excelImport(@RequestParam("file") MultipartFile[] files) throws IOException {
|
||||
if (files == null || files.length < 1) throw new RuntimeException("文件上传失败");
|
||||
InputStream in = files[0].getInputStream();
|
||||
ExcelReader reader = ExcelUtil.getReader(in);
|
||||
// 表头
|
||||
reader.addHeaderAlias("唯一标识", "id");
|
||||
reader.addHeaderAlias("名称", "name");
|
||||
reader.addHeaderAlias("英文值", "value");
|
||||
reader.addHeaderAlias("状态", "status");
|
||||
reader.addHeaderAlias("描述", "description");
|
||||
reader.addHeaderAlias("创建人", "createBy");
|
||||
reader.addHeaderAlias("租户", "tenant");
|
||||
List<RoleExcel> excels = reader.readAll(RoleExcel.class);
|
||||
List<Role> tenants = roleMapper.toEntity(excels);
|
||||
// 关闭流
|
||||
IoUtil.closeQuietly(reader, in);
|
||||
return success(roleService.saveOrUpdateBatch(tenants));
|
||||
}
|
||||
}
|
@ -0,0 +1,253 @@
|
||||
package {{ cookiecutter.__mvn_package }}.rest.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
|
||||
import {{ cookiecutter.__mvn_package }}.mapper.TenantMapper;
|
||||
import {{ cookiecutter.__mvn_package }}.mapper.entity.TenantExcel;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.ApiController;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.api.R;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.entity.Tenant;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.service.TenantService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.poi.ss.util.CellRangeAddressList;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.poi.excel.ExcelReader;
|
||||
import org.dromara.hutool.poi.excel.ExcelUtil;
|
||||
import org.dromara.hutool.poi.excel.ExcelWriter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 租户表(Tenant)表控制层.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024-07-03 17:49:29
|
||||
*/
|
||||
@Tag(name = "标准接口", description = "由表结构自动生成")
|
||||
@RestController
|
||||
@RequestMapping("db/tTenant")
|
||||
@RequiredArgsConstructor
|
||||
public class TenantController extends ApiController {
|
||||
/** 服务对象. */
|
||||
private final TenantService tenantService;
|
||||
|
||||
private final TenantMapper tenantMapper;
|
||||
|
||||
/**
|
||||
* 分页查询所有数据.
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param tenant 查询实体
|
||||
* @return 分页数据
|
||||
*/
|
||||
@Operation(
|
||||
summary = "租户表 - 分页查询所有数据",
|
||||
parameters = {
|
||||
@Parameter(name = "page", description = "分页对象"),
|
||||
@Parameter(name = "tenant", description = "查询实体"),
|
||||
},
|
||||
responses = @ApiResponse(description = "分页数据"),
|
||||
description = "size=-1时查询所有数据,orders配合asc排序")
|
||||
@GetMapping
|
||||
@SaCheckPermission("db:rest:get")
|
||||
public R selectAll(
|
||||
@Nullable PageDTO<Tenant> page, @Nullable Tenant tenant, @Nullable String mention) {
|
||||
QueryWrapper<Tenant> queryWrapper = new QueryWrapper<>(tenant);
|
||||
if (tenant != null && StrUtil.isNotBlank(tenant.getName())) {
|
||||
queryWrapper.like("name", tenant.getName());
|
||||
tenant.setName(null);
|
||||
}
|
||||
if (StrUtil.isNotBlank(mention)) {
|
||||
queryWrapper.like("name", mention);
|
||||
}
|
||||
return success(this.tenantService.page(page, queryWrapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据.
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 单条数据
|
||||
*/
|
||||
@Operation(
|
||||
summary = "租户表 - 通过主键查询单条数据",
|
||||
parameters = {
|
||||
@Parameter(name = "id", description = "主键", schema = @Schema(type = "string")),
|
||||
},
|
||||
responses = @ApiResponse(description = "单条数据"))
|
||||
@GetMapping("{id}")
|
||||
@SaCheckPermission("db:rest:get")
|
||||
public R selectOne(@PathVariable Serializable id) {
|
||||
return success(this.tenantService.getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增数据.
|
||||
*
|
||||
* @param tenant 实体对象
|
||||
* @return 新增结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "租户表 - 新增数据",
|
||||
parameters = {
|
||||
@Parameter(name = "tenant", description = "实体对象"),
|
||||
},
|
||||
responses = @ApiResponse(description = "新增结果"))
|
||||
@PostMapping
|
||||
@SaCheckPermission("db:rest:post")
|
||||
public R insert(@RequestBody Tenant tenant) {
|
||||
return success(this.tenantService.save(tenant));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增数据.
|
||||
*
|
||||
* @param tenants 实体对象
|
||||
* @return 新增结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "租户表 - 批量新增数据",
|
||||
parameters = {
|
||||
@Parameter(name = "tenants", description = "实体对象"),
|
||||
},
|
||||
responses = @ApiResponse(description = "新增结果"))
|
||||
@PostMapping("s")
|
||||
@SaCheckPermission("db:rest:post")
|
||||
public R inserts(@RequestBody List<Tenant> tenants) {
|
||||
return success(this.tenantService.saveBatch(tenants));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改数据.
|
||||
*
|
||||
* @param tenant 实体对象
|
||||
* @return 修改结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "租户表 - 修改数据",
|
||||
parameters = {@Parameter(name = "tenant", description = "实体对象")},
|
||||
responses = @ApiResponse(description = "修改结果"),
|
||||
description = "不存在的对象会新增")
|
||||
@PutMapping
|
||||
@SaCheckPermission("db:rest:put")
|
||||
public R update(@RequestBody Tenant tenant) {
|
||||
return success(this.tenantService.saveOrUpdate(tenant));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量修改数据.
|
||||
*
|
||||
* @param tenants 实体对象
|
||||
* @return 修改结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "租户表 - 批量修改数据",
|
||||
parameters = {@Parameter(name = "tenants", description = "实体对象")},
|
||||
responses = @ApiResponse(description = "修改结果"),
|
||||
description = "不存在的对象会新增")
|
||||
@PutMapping("s")
|
||||
@SaCheckPermission("db:rest:put")
|
||||
public R updates(@RequestBody List<Tenant> tenants) {
|
||||
return success(this.tenantService.saveOrUpdateBatch(tenants));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据.
|
||||
*
|
||||
* @param idList 主键结合
|
||||
* @return 删除结果
|
||||
*/
|
||||
@Operation(
|
||||
summary = "租户表 - 删除数据",
|
||||
parameters = {
|
||||
@Parameter(name = "idList", description = "主键结合", schema = @Schema(type = "string"))
|
||||
},
|
||||
responses = @ApiResponse(description = "删除结果"),
|
||||
description = "主键用逗号拼接")
|
||||
@DeleteMapping
|
||||
@SaCheckPermission("db:rest:del")
|
||||
public R delete(@RequestParam("idList") List<Long> idList) {
|
||||
return success(this.tenantService.removeByIds(idList));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出符合条件的数据
|
||||
*
|
||||
* @param tenant 查询条件
|
||||
* @param response 响应内容
|
||||
*/
|
||||
@GetMapping("export")
|
||||
@SaCheckPermission("db:rest:export")
|
||||
public void excelExport(@Nullable Tenant tenant, HttpServletResponse response)
|
||||
throws IOException {
|
||||
List<Tenant> list = this.tenantService.list(new QueryWrapper<>(tenant));
|
||||
List<TenantExcel> excels = this.tenantMapper.toExcel(list);
|
||||
ExcelWriter writer = ExcelUtil.getWriter();
|
||||
// 表头
|
||||
writer.addHeaderAlias("id", "唯一标识");
|
||||
writer.addHeaderAlias("name", "名称");
|
||||
writer.addHeaderAlias("shared", "共享租户");
|
||||
writer.addHeaderAlias("type", "类型");
|
||||
writer.addHeaderAlias("status", "状态");
|
||||
writer.addHeaderAlias("description", "描述");
|
||||
writer.addHeaderAlias("createBy", "创建人");
|
||||
// 可选项
|
||||
if (!excels.isEmpty()) {
|
||||
writer.addSelect(new CellRangeAddressList(1, excels.size(), 3, 3), "企业", "个人");
|
||||
writer.addSelect(new CellRangeAddressList(1, excels.size(), 4, 4), "可用", "禁用");
|
||||
} else {
|
||||
writer.addSelect(new CellRangeAddressList(1, 1, 3, 3), "企业", "个人");
|
||||
writer.addSelect(new CellRangeAddressList(1, 1, 4, 4), "可用", "禁用");
|
||||
}
|
||||
// 默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之
|
||||
writer.setOnlyAlias(true);
|
||||
// 写入数据
|
||||
writer.write(excels, true);
|
||||
writer.autoSizeColumnAll();
|
||||
// 写到响应流
|
||||
response.setContentType(writer.getContentType());
|
||||
response.setHeader(
|
||||
HttpHeaders.CONTENT_DISPOSITION, writer.getDisposition("租户租户", StandardCharsets.UTF_8));
|
||||
ServletOutputStream out = response.getOutputStream();
|
||||
writer.flush(out);
|
||||
// 关闭流
|
||||
IoUtil.closeQuietly(writer, out);
|
||||
}
|
||||
|
||||
@PostMapping("import")
|
||||
@SaCheckPermission("db:rest:import")
|
||||
public R excelImport(@RequestParam("file") MultipartFile[] files) throws IOException {
|
||||
if (files == null || files.length < 1) throw new RuntimeException("文件上传失败");
|
||||
InputStream in = files[0].getInputStream();
|
||||
ExcelReader reader = ExcelUtil.getReader(in);
|
||||
// 表头
|
||||
reader.addHeaderAlias("唯一标识", "id");
|
||||
reader.addHeaderAlias("名称", "name");
|
||||
reader.addHeaderAlias("共享租户", "shared");
|
||||
reader.addHeaderAlias("类型", "type");
|
||||
reader.addHeaderAlias("状态", "status");
|
||||
reader.addHeaderAlias("描述", "description");
|
||||
reader.addHeaderAlias("创建人", "createBy");
|
||||
List<TenantExcel> excels = reader.readAll(TenantExcel.class);
|
||||
List<Tenant> tenants = tenantMapper.toEntity(excels);
|
||||
// 关闭流
|
||||
IoUtil.closeQuietly(reader, in);
|
||||
return success(tenantService.saveOrUpdateBatch(tenants));
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package {{ cookiecutter.__mvn_package }}.rest.dao;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import {{ cookiecutter.__mvn_package }}.rest.entity.Account;
|
||||
import {{ cookiecutter.__mvn_package }}.web.MybatisCache;
|
||||
import org.apache.ibatis.annotations.CacheNamespace;
|
||||
|
||||
/**
|
||||
* 凭据表(Account)表数据库访问层.
|
||||
*
|
||||
* @author zweiandlen
|
||||
* @since 2024-07-03 17:49:35
|
||||
*/
|
||||
@CacheNamespace(implementation = MybatisCache.class, eviction = MybatisCache.class)
|
||||
public interface AccountDao extends BaseMapper<Account> {}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user