5337 lines
164 KiB
SQL
5337 lines
164 KiB
SQL
-- Runnable three-tier code generation templates
|
||
-- Import this script after sql/db.sql.
|
||
-- Template ids: 9201 backend, 9202 admin_frontend, 9203 frontend
|
||
|
||
SET FOREIGN_KEY_CHECKS = 0;
|
||
|
||
DELETE FROM sys_project_template WHERE template_id IN (9201, 9202, 9203);
|
||
DELETE FROM sys_project_structure WHERE template_id IN (9201, 9202, 9203) OR node_id BETWEEN 920100 AND 920399;
|
||
DELETE FROM sys_template_file WHERE template_id IN (9201, 9202, 9203) OR template_file_id BETWEEN 920100 AND 920399;
|
||
DELETE FROM sys_template WHERE template_id IN (9201, 9202, 9203);
|
||
|
||
INSERT INTO sys_template (template_id, template_name, template_path, template_desc, template_type, template_status, create_by, create_time, remark) VALUES
|
||
(9201, 'Runnable Backend Template', '/runnable/backend', 'Spring Boot + MyBatis Plus runnable backend', 'backend', 0, 'admin', sysdate(), 'three-tier runnable template'),
|
||
(9202, 'Runnable Admin Frontend Template', '/runnable/admin_frontend', 'Vue2 + Element UI runnable admin frontend', 'admin_frontend', 0, 'admin', sysdate(), 'three-tier runnable template'),
|
||
(9203, 'Runnable Portal Frontend Template', '/runnable/frontend', 'Vue2 + Element UI runnable portal frontend', 'frontend', 0, 'admin', sysdate(), 'three-tier runnable template');
|
||
|
||
-- Backend template files
|
||
INSERT INTO sys_template_file (template_file_id, template_id, file_name, module_id, file_path, file_content, create_by, create_time) VALUES
|
||
(920101, 9201, 'pom.xml.vm', NULL, 'pom.xml.vm', '<?xml version="1.0" encoding="UTF-8"?>
|
||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||
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>2.5.9</version>
|
||
<relativePath/>
|
||
</parent>
|
||
|
||
<groupId>${packageName}</groupId>
|
||
<artifactId>${businessName}-service</artifactId>
|
||
<version>1.0.0</version>
|
||
<name>${functionName}</name>
|
||
<description>${functionName} generated by qing template</description>
|
||
|
||
<properties>
|
||
<java.version>1.8</java.version>
|
||
</properties>
|
||
|
||
<dependencies>
|
||
<dependency>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-starter-web</artifactId>
|
||
</dependency>
|
||
|
||
<dependency>
|
||
<groupId>com.baomidou</groupId>
|
||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||
<version>3.5.1</version>
|
||
</dependency>
|
||
|
||
<dependency>
|
||
<groupId>mysql</groupId>
|
||
<artifactId>mysql-connector-java</artifactId>
|
||
<scope>runtime</scope>
|
||
</dependency>
|
||
|
||
<dependency>
|
||
<groupId>org.apache.commons</groupId>
|
||
<artifactId>commons-lang3</artifactId>
|
||
</dependency>
|
||
|
||
<dependency>
|
||
<groupId>cn.hutool</groupId>
|
||
<artifactId>hutool-all</artifactId>
|
||
<version>5.7.20</version>
|
||
</dependency>
|
||
|
||
<dependency>
|
||
<groupId>org.apache.poi</groupId>
|
||
<artifactId>poi-ooxml</artifactId>
|
||
<version>4.1.2</version>
|
||
</dependency>
|
||
|
||
<dependency>
|
||
<groupId>io.swagger</groupId>
|
||
<artifactId>swagger-annotations</artifactId>
|
||
<version>1.6.2</version>
|
||
</dependency>
|
||
|
||
<dependency>
|
||
<groupId>org.projectlombok</groupId>
|
||
<artifactId>lombok</artifactId>
|
||
<optional>true</optional>
|
||
</dependency>
|
||
|
||
<dependency>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-starter-test</artifactId>
|
||
<scope>test</scope>
|
||
</dependency>
|
||
</dependencies>
|
||
|
||
<build>
|
||
<plugins>
|
||
<plugin>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||
</plugin>
|
||
</plugins>
|
||
</build>
|
||
</project>
|
||
', 'admin', sysdate()),
|
||
(920102, 9201, 'README.md.vm', NULL, 'README.md.vm', '# ${functionName}
|
||
|
||
这是由 qing 模板生成的单表 Spring Boot + Vue2 项目。
|
||
|
||
## 后端启动
|
||
|
||
1. 创建 MySQL 数据库,默认库名为 `vip`。
|
||
2. 执行 `sql/${businessName}.sql` 中的建表语句。
|
||
3. 按需修改 `src/main/resources/application.yml` 里的数据库账号密码。
|
||
4. 在项目根目录运行:
|
||
|
||
```bash
|
||
mvn spring-boot:run
|
||
```
|
||
|
||
The service starts at `http://localhost:8080`.
|
||
|
||
## 前端启动
|
||
|
||
```bash
|
||
cd vue
|
||
npm install
|
||
npm run dev
|
||
```
|
||
|
||
前端开发服务默认把 `/api` 代理到 `http://localhost:8080`。
|
||
', 'admin', sysdate()),
|
||
(920103, 9201, 'Application.java.vm', NULL, 'Application.java.vm', 'package ${packageName};
|
||
|
||
import org.mybatis.spring.annotation.MapperScan;
|
||
import org.springframework.boot.SpringApplication;
|
||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||
|
||
@MapperScan("${packageName}.mapper")
|
||
@SpringBootApplication
|
||
public class ${ClassName}Application
|
||
{
|
||
public static void main(String[] args)
|
||
{
|
||
SpringApplication.run(${ClassName}Application.class, args);
|
||
}
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920104, 9201, 'Result.java.vm', NULL, 'Result.java.vm', 'package ${packageName}.common;
|
||
|
||
import java.io.Serializable;
|
||
|
||
public class Result<T> implements Serializable
|
||
{
|
||
private static final long serialVersionUID = 1L;
|
||
|
||
private String code;
|
||
private String msg;
|
||
private T data;
|
||
|
||
public Result()
|
||
{
|
||
}
|
||
|
||
public Result(String code, String msg, T data)
|
||
{
|
||
this.code = code;
|
||
this.msg = msg;
|
||
this.data = data;
|
||
}
|
||
|
||
public static <T> Result<T> success()
|
||
{
|
||
return new Result<T>("200", "操作成功", null);
|
||
}
|
||
|
||
public static <T> Result<T> success(T data)
|
||
{
|
||
return new Result<T>("200", "操作成功", data);
|
||
}
|
||
|
||
public static <T> Result<T> error(String msg)
|
||
{
|
||
return new Result<T>("500", msg, null);
|
||
}
|
||
|
||
public String getCode()
|
||
{
|
||
return code;
|
||
}
|
||
|
||
public void setCode(String code)
|
||
{
|
||
this.code = code;
|
||
}
|
||
|
||
public String getMsg()
|
||
{
|
||
return msg;
|
||
}
|
||
|
||
public void setMsg(String msg)
|
||
{
|
||
this.msg = msg;
|
||
}
|
||
|
||
public T getData()
|
||
{
|
||
return data;
|
||
}
|
||
|
||
public void setData(T data)
|
||
{
|
||
this.data = data;
|
||
}
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920105, 9201, 'MybatisPlusConfig.java.vm', NULL, 'MybatisPlusConfig.java.vm', 'package ${packageName}.config;
|
||
|
||
import com.baomidou.mybatisplus.annotation.DbType;
|
||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||
import org.springframework.context.annotation.Bean;
|
||
import org.springframework.context.annotation.Configuration;
|
||
|
||
@Configuration
|
||
public class MybatisPlusConfig
|
||
{
|
||
@Bean
|
||
public MybatisPlusInterceptor mybatisPlusInterceptor()
|
||
{
|
||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||
return interceptor;
|
||
}
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920106, 9201, 'CorsConfig.java.vm', NULL, 'CorsConfig.java.vm', 'package ${packageName}.config;
|
||
|
||
import ${packageName}.security.PortalAuthInterceptor;
|
||
import javax.annotation.Resource;
|
||
import org.springframework.context.annotation.Configuration;
|
||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||
|
||
@Configuration
|
||
public class CorsConfig implements WebMvcConfigurer
|
||
{
|
||
@Resource
|
||
private PortalAuthInterceptor portalAuthInterceptor;
|
||
|
||
@Override
|
||
public void addCorsMappings(CorsRegistry registry)
|
||
{
|
||
registry.addMapping("/**")
|
||
.allowedOriginPatterns("*")
|
||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||
.allowedHeaders("*")
|
||
.allowCredentials(true)
|
||
.maxAge(3600);
|
||
}
|
||
|
||
@Override
|
||
public void addInterceptors(InterceptorRegistry registry)
|
||
{
|
||
registry.addInterceptor(portalAuthInterceptor)
|
||
.addPathPatterns("/**")
|
||
.excludePathPatterns("/auth/login", "/auth/admin/login", "/business-blocks/**", "/files/**", "/error");
|
||
}
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920107, 9201, 'FileController.java.vm', NULL, 'FileController.java.vm', 'package ${packageName}.controller;
|
||
|
||
import ${packageName}.common.Result;
|
||
import java.io.IOException;
|
||
import java.net.MalformedURLException;
|
||
import java.nio.file.Files;
|
||
import java.nio.file.Path;
|
||
import java.nio.file.Paths;
|
||
import java.nio.file.StandardCopyOption;
|
||
import java.util.HashMap;
|
||
import java.util.Map;
|
||
import java.util.UUID;
|
||
import org.springframework.core.io.Resource;
|
||
import org.springframework.core.io.UrlResource;
|
||
import org.springframework.http.HttpHeaders;
|
||
import org.springframework.http.ResponseEntity;
|
||
import org.springframework.util.StringUtils;
|
||
import org.springframework.web.bind.annotation.GetMapping;
|
||
import org.springframework.web.bind.annotation.PathVariable;
|
||
import org.springframework.web.bind.annotation.PostMapping;
|
||
import org.springframework.web.bind.annotation.RequestMapping;
|
||
import org.springframework.web.bind.annotation.RequestParam;
|
||
import org.springframework.web.bind.annotation.RestController;
|
||
import org.springframework.web.multipart.MultipartFile;
|
||
|
||
@RestController
|
||
@RequestMapping("/files")
|
||
public class FileController
|
||
{
|
||
private final Path uploadRoot = Paths.get("uploads");
|
||
|
||
@PostMapping("/upload")
|
||
public Result<Map<String, String>> upload(@RequestParam("file") MultipartFile file) throws IOException
|
||
{
|
||
Files.createDirectories(uploadRoot);
|
||
String originalName = StringUtils.cleanPath(file.getOriginalFilename() == null ? "file" : file.getOriginalFilename());
|
||
String suffix = "";
|
||
int dotIndex = originalName.lastIndexOf(''.'');
|
||
if (dotIndex >= 0)
|
||
{
|
||
suffix = originalName.substring(dotIndex);
|
||
}
|
||
String fileName = UUID.randomUUID().toString().replace("-", "") + suffix;
|
||
Files.copy(file.getInputStream(), uploadRoot.resolve(fileName), StandardCopyOption.REPLACE_EXISTING);
|
||
|
||
Map<String, String> data = new HashMap<String, String>();
|
||
data.put("name", originalName);
|
||
data.put("url", "/files/" + fileName);
|
||
return Result.success(data);
|
||
}
|
||
|
||
@GetMapping("/{fileName:.+}")
|
||
public ResponseEntity<Resource> getFile(@PathVariable String fileName) throws MalformedURLException
|
||
{
|
||
Path filePath = uploadRoot.resolve(fileName).normalize();
|
||
Resource resource = new UrlResource(filePath.toUri());
|
||
if (!resource.exists())
|
||
{
|
||
return ResponseEntity.notFound().build();
|
||
}
|
||
return ResponseEntity.ok()
|
||
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\\"" + resource.getFilename() + "\\"")
|
||
.body(resource);
|
||
}
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920108, 9201, 'application.yml.vm', NULL, 'application.yml.vm', '#set($d = ''$'')
|
||
server:
|
||
port: ${d}{SERVER_PORT:8080}
|
||
|
||
spring:
|
||
application:
|
||
name: ${businessName}-service
|
||
datasource:
|
||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||
url: ${d}{DB_URL:jdbc:mysql://localhost:3306/vip?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8}
|
||
username: ${d}{DB_USERNAME:root}
|
||
password: ${d}{DB_PASSWORD:123456}
|
||
servlet:
|
||
multipart:
|
||
max-file-size: 100MB
|
||
max-request-size: 100MB
|
||
|
||
mybatis-plus:
|
||
mapper-locations: classpath*:mapper/**/*.xml
|
||
configuration:
|
||
map-underscore-to-camel-case: true
|
||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||
', 'admin', sysdate()),
|
||
(920109, 9201, 'entity.java.vm', NULL, 'entity.java.vm', 'package ${packageName}.entity;
|
||
|
||
#foreach ($import in $importList)
|
||
import ${import};
|
||
#end
|
||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||
import com.baomidou.mybatisplus.annotation.TableName;
|
||
import com.baomidou.mybatisplus.annotation.IdType;
|
||
import com.baomidou.mybatisplus.annotation.TableId;
|
||
import com.baomidou.mybatisplus.annotation.TableField;
|
||
import io.swagger.annotations.ApiModel;
|
||
import io.swagger.annotations.ApiModelProperty;
|
||
import lombok.Getter;
|
||
import lombok.Setter;
|
||
import lombok.experimental.Accessors;
|
||
import cn.hutool.core.annotation.Alias;
|
||
|
||
/**
|
||
* ${functionName}对象 ${tableName}
|
||
*
|
||
* @author ${author}
|
||
* @date ${datetime}
|
||
*/
|
||
#if($table.crud || $table.sub)
|
||
#set($Entity="BaseEntity")
|
||
#elseif($table.tree)
|
||
#set($Entity="TreeEntity")
|
||
#end
|
||
@Getter
|
||
@Setter
|
||
@Accessors(chain = true)
|
||
@ApiModel(value = "${ClassName}对象", description = "${functionName}")
|
||
@TableName("${tableName}")
|
||
public class ${ClassName}
|
||
{
|
||
private static final long serialVersionUID = 1L;
|
||
|
||
#foreach ($column in $columns)
|
||
/** $column.columnComment */
|
||
#if($column.list)
|
||
#set($parentheseIndex=$column.columnComment.indexOf("("))
|
||
#if($parentheseIndex != -1)
|
||
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
|
||
#else
|
||
#set($comment=$column.columnComment)
|
||
#end
|
||
#if($column.javaType == ''Date'')
|
||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||
#else
|
||
#end
|
||
#end
|
||
@ApiModelProperty("${column.columnComment}")
|
||
@Alias("${column.columnComment}")
|
||
#if ($column.isPk == "1")
|
||
@TableId(value = "${column.columnName}", type = IdType.AUTO)
|
||
#else
|
||
@TableField("${column.columnName}")
|
||
#end
|
||
private $column.javaType $column.javaField;
|
||
|
||
#end
|
||
#if($table.sub)
|
||
/** $table.subTable.functionName信息 */
|
||
@ApiModelProperty("${table.subTable.functionName}信息")
|
||
private List<${subClassName}> ${subclassName}List;
|
||
|
||
#end
|
||
@Override
|
||
public String toString() {
|
||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||
#foreach ($column in $columns)
|
||
#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]"))
|
||
#set($AttrName=$column.javaField)
|
||
#else
|
||
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
|
||
#end
|
||
.append("${column.javaField}", get${AttrName}())
|
||
#end
|
||
#if($table.sub)
|
||
.append("${subclassName}List", get${subClassName}List())
|
||
#end
|
||
.toString();
|
||
}
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920110, 9201, 'mapper.java.vm', NULL, 'mapper.java.vm', 'package ${packageName}.mapper;
|
||
|
||
import ${packageName}.entity.${ClassName};
|
||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||
|
||
/**
|
||
* ${functionName}Mapper接口
|
||
*
|
||
* @author ${author}
|
||
* @date ${datetime}
|
||
*/
|
||
public interface ${ClassName}Mapper extends BaseMapper<${ClassName}> {
|
||
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920111, 9201, 'service.java.vm', NULL, 'service.java.vm', 'package ${packageName}.service;
|
||
|
||
import ${packageName}.entity.${ClassName};
|
||
import com.baomidou.mybatisplus.extension.service.IService;
|
||
#if($hasTableBusinessActions)
|
||
import java.util.Map;
|
||
#end
|
||
|
||
/**
|
||
* ${functionName}Service接口
|
||
*
|
||
* @author ${author}
|
||
* @date ${datetime}
|
||
*/
|
||
public interface I${ClassName}Service extends IService<${ClassName}> {
|
||
|
||
#if($hasTableBusinessActions)
|
||
#foreach($action in $tableBusinessActions)
|
||
int ${action.code}(Map<String, Object> params);
|
||
|
||
#end
|
||
#end
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920112, 9201, 'serviceImpl.java.vm', NULL, 'serviceImpl.java.vm', 'package ${packageName}.service.impl;
|
||
|
||
import ${packageName}.entity.${ClassName};
|
||
import ${packageName}.mapper.${ClassName}Mapper;
|
||
import ${packageName}.service.I${ClassName}Service;
|
||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||
#if($hasTableBusinessActions)
|
||
import java.math.BigDecimal;
|
||
import java.util.ArrayList;
|
||
import java.util.Date;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import javax.annotation.Resource;
|
||
import org.springframework.jdbc.core.JdbcTemplate;
|
||
import org.springframework.transaction.annotation.Transactional;
|
||
#end
|
||
import org.springframework.stereotype.Service;
|
||
|
||
/**
|
||
* ${functionName}Service业务层处理
|
||
*
|
||
* @author ${author}
|
||
* @date ${datetime}
|
||
*/
|
||
@Service
|
||
public class ${ClassName}ServiceImpl extends ServiceImpl<${ClassName}Mapper, ${ClassName}> implements I${ClassName}Service {
|
||
|
||
#if($hasTableBusinessActions)
|
||
// STRICT_TARGET_TABLE_CONDITIONS
|
||
@Resource
|
||
private JdbcTemplate jdbcTemplate;
|
||
|
||
#foreach($action in $tableBusinessActions)
|
||
@Override
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public int ${action.code}(Map<String, Object> params)
|
||
{
|
||
if (params == null)
|
||
{
|
||
throw new IllegalArgumentException("业务动作参数不能为空");
|
||
}
|
||
#if($action.requestFields)
|
||
#foreach($field in $action.requestFields)
|
||
requireBusinessParam(params, "${field}");
|
||
#end
|
||
#end
|
||
#if($action.ruleChecks)
|
||
#foreach($rule in $action.ruleChecks)
|
||
#set($conditionFields = $rule.conditionFields)
|
||
#if($rule.type == "EXISTS")
|
||
assertExists(params, "${rule.targetTable}", new String[] {#foreach($field in $conditionFields)"${field}"#if($foreach.hasNext), #end#end}, "${rule.message}");
|
||
#elseif($rule.type == "NOT_EXISTS")
|
||
assertNotExists(params, "${rule.targetTable}", new String[] {#foreach($field in $conditionFields)"${field}"#if($foreach.hasNext), #end#end}, "${rule.message}");
|
||
#elseif($rule.type == "FIELD_EQUALS")
|
||
assertFieldEquals(params, "${rule.targetTable}", "${rule.targetField}", "${rule.expectedValue}", new String[] {#foreach($field in $conditionFields)"${field}"#if($foreach.hasNext), #end#end}, "${rule.message}");
|
||
#elseif($rule.type == "FIELD_NOT_EQUALS")
|
||
assertFieldNotEquals(params, "${rule.targetTable}", "${rule.targetField}", "${rule.expectedValue}", new String[] {#foreach($field in $conditionFields)"${field}"#if($foreach.hasNext), #end#end}, "${rule.message}");
|
||
#elseif($rule.type == "FIELD_IN")
|
||
assertFieldIn(params, "${rule.targetTable}", "${rule.targetField}", new String[] {#foreach($value in $rule.expectedValues)"${value}"#if($foreach.hasNext), #end#end}, new String[] {#foreach($field in $conditionFields)"${field}"#if($foreach.hasNext), #end#end}, "${rule.message}");
|
||
#elseif($rule.type == "NUMBER_GTE")
|
||
assertNumberGte(params, "${rule.targetTable}", "${rule.targetField}", "${rule.compareValue}", new String[] {#foreach($field in $conditionFields)"${field}"#if($foreach.hasNext), #end#end}, "${rule.message}");
|
||
#end
|
||
#end
|
||
#end
|
||
int rows = 0;
|
||
#if($action.effects)
|
||
#foreach($effect in $action.effects)
|
||
#if($effect.type == "UPDATE_FIELD")
|
||
#set($conditionFields = $effect.conditionFields)
|
||
rows += executeUpdateFieldEffect(params, "${effect.targetTable}", "${effect.targetField}", "${effect.value}", new String[] {#foreach($field in $conditionFields)"${field}"#if($foreach.hasNext), #end#end});
|
||
#elseif($effect.type == "INSERT_ROW")
|
||
#set($effectIndex = $foreach.index)
|
||
Map<String, Object> insertValues${effectIndex} = new java.util.LinkedHashMap<String, Object>();
|
||
#foreach($entry in $effect.values.entrySet())
|
||
insertValues${effectIndex}.put("${entry.key}", resolveBusinessValue(params, "${entry.value}"));
|
||
#end
|
||
rows += insertRow("${effect.targetTable}", insertValues${effectIndex});
|
||
#elseif($effect.type == "UPDATE_FIELDS")
|
||
#set($conditionFields = $effect.conditionFields)
|
||
#set($effectIndex = $foreach.index)
|
||
Map<String, Object> updateValues${effectIndex} = new java.util.LinkedHashMap<String, Object>();
|
||
#foreach($entry in $effect.values.entrySet())
|
||
updateValues${effectIndex}.put("${entry.key}", resolveBusinessValue(params, "${entry.value}"));
|
||
#end
|
||
rows += updateFields(params, "${effect.targetTable}", updateValues${effectIndex}, new String[] {#foreach($field in $conditionFields)"${field}"#if($foreach.hasNext), #end#end});
|
||
#elseif($effect.type == "SET_STATUS")
|
||
#set($conditionFields = $effect.conditionFields)
|
||
rows += setStatus(params, "${effect.targetTable}", "${effect.targetField}", resolveBusinessValue(params, "${effect.value}"), new String[] {#foreach($field in $conditionFields)"${field}"#if($foreach.hasNext), #end#end});
|
||
#elseif($effect.type == "INCREASE_NUMBER")
|
||
#set($conditionFields = $effect.conditionFields)
|
||
rows += increaseNumberField(params, "${effect.targetTable}", "${effect.targetField}", "${effect.amount}", new String[] {#foreach($field in $conditionFields)"${field}"#if($foreach.hasNext), #end#end});
|
||
#elseif($effect.type == "DECREASE_NUMBER")
|
||
#set($conditionFields = $effect.conditionFields)
|
||
rows += decreaseNumberField(params, "${effect.targetTable}", "${effect.targetField}", "${effect.amount}", "${effect.minValue}", new String[] {#foreach($field in $conditionFields)"${field}"#if($foreach.hasNext), #end#end}, "${effect.message}");
|
||
#end
|
||
#end
|
||
#end
|
||
return rows;
|
||
}
|
||
|
||
#end
|
||
private void requireBusinessParam(Map<String, Object> params, String field)
|
||
{
|
||
if (!params.containsKey(field) || params.get(field) == null || String.valueOf(params.get(field)).trim().isEmpty())
|
||
{
|
||
throw new IllegalArgumentException("缺少业务动作参数:" + field);
|
||
}
|
||
}
|
||
|
||
private void assertExists(Map<String, Object> params, String tableName, String[] conditionFields, String message)
|
||
{
|
||
if (countRows(params, tableName, conditionFields) <= 0)
|
||
{
|
||
throw new IllegalArgumentException(defaultMessage(message, "业务记录不存在"));
|
||
}
|
||
}
|
||
|
||
private void assertNotExists(Map<String, Object> params, String tableName, String[] conditionFields, String message)
|
||
{
|
||
if (countRows(params, tableName, conditionFields) > 0)
|
||
{
|
||
throw new IllegalArgumentException(defaultMessage(message, "业务记录已存在"));
|
||
}
|
||
}
|
||
|
||
private void assertFieldEquals(Map<String, Object> params, String tableName, String targetField,
|
||
String expectedValue, String[] conditionFields, String message)
|
||
{
|
||
Object value = querySingleField(params, tableName, targetField, conditionFields);
|
||
if (value == null || !String.valueOf(resolveBusinessValue(params, expectedValue)).equals(String.valueOf(value)))
|
||
{
|
||
throw new IllegalArgumentException(defaultMessage(message, "业务规则校验失败"));
|
||
}
|
||
}
|
||
|
||
private void assertFieldNotEquals(Map<String, Object> params, String tableName, String targetField,
|
||
String expectedValue, String[] conditionFields, String message)
|
||
{
|
||
Object value = querySingleField(params, tableName, targetField, conditionFields);
|
||
if (value != null && String.valueOf(resolveBusinessValue(params, expectedValue)).equals(String.valueOf(value)))
|
||
{
|
||
throw new IllegalArgumentException(defaultMessage(message, "业务规则校验失败"));
|
||
}
|
||
}
|
||
|
||
private void assertFieldIn(Map<String, Object> params, String tableName, String targetField,
|
||
String[] expectedValues, String[] conditionFields, String message)
|
||
{
|
||
Object value = querySingleField(params, tableName, targetField, conditionFields);
|
||
String current = value == null ? "" : String.valueOf(value);
|
||
for (String expectedValue : expectedValues)
|
||
{
|
||
if (String.valueOf(resolveBusinessValue(params, expectedValue)).equals(current))
|
||
{
|
||
return;
|
||
}
|
||
}
|
||
throw new IllegalArgumentException(defaultMessage(message, "业务规则校验失败"));
|
||
}
|
||
|
||
private void assertNumberGte(Map<String, Object> params, String tableName, String targetField,
|
||
String compareValue, String[] conditionFields, String message)
|
||
{
|
||
Object value = querySingleField(params, tableName, targetField, conditionFields);
|
||
BigDecimal current = toBigDecimal(value, "业务数值无效");
|
||
BigDecimal expected = resolveBusinessNumber(params, compareValue);
|
||
if (current.compareTo(expected) < 0)
|
||
{
|
||
throw new IllegalArgumentException(defaultMessage(message, "业务数值校验失败"));
|
||
}
|
||
}
|
||
|
||
private int insertRow(String tableName, Map<String, Object> values)
|
||
{
|
||
String safeTableName = requireSafeIdentifier(tableName, "业务动作目标表无效");
|
||
if (values == null || values.isEmpty())
|
||
{
|
||
throw new IllegalArgumentException("业务新增字段不能为空");
|
||
}
|
||
StringBuilder fields = new StringBuilder();
|
||
StringBuilder placeholders = new StringBuilder();
|
||
List<Object> args = new ArrayList<Object>();
|
||
for (Map.Entry<String, Object> entry : values.entrySet())
|
||
{
|
||
if (fields.length() > 0)
|
||
{
|
||
fields.append(", ");
|
||
placeholders.append(", ");
|
||
}
|
||
fields.append(requireSafeIdentifier(entry.getKey(), "业务新增字段无效"));
|
||
placeholders.append("?");
|
||
args.add(entry.getValue());
|
||
}
|
||
return jdbcTemplate.update("insert into " + safeTableName + " (" + fields + ") values (" + placeholders + ")",
|
||
args.toArray());
|
||
}
|
||
|
||
private int updateFields(Map<String, Object> params, String tableName, Map<String, Object> values,
|
||
String[] conditionFields)
|
||
{
|
||
String safeTableName = requireSafeIdentifier(tableName, "业务动作目标表无效");
|
||
if (values == null || values.isEmpty())
|
||
{
|
||
throw new IllegalArgumentException("业务更新字段不能为空");
|
||
}
|
||
StringBuilder sets = new StringBuilder();
|
||
List<Object> args = new ArrayList<Object>();
|
||
for (Map.Entry<String, Object> entry : values.entrySet())
|
||
{
|
||
if (sets.length() > 0)
|
||
{
|
||
sets.append(", ");
|
||
}
|
||
sets.append(requireSafeIdentifier(entry.getKey(), "业务更新字段无效")).append(" = ?");
|
||
args.add(entry.getValue());
|
||
}
|
||
String whereClause = buildWhereClause(params, conditionFields);
|
||
appendConditionValues(args, params, conditionFields);
|
||
return jdbcTemplate.update("update " + safeTableName + " set " + sets + " where " + whereClause,
|
||
args.toArray());
|
||
}
|
||
|
||
private int setStatus(Map<String, Object> params, String tableName, String targetField, Object value,
|
||
String[] conditionFields)
|
||
{
|
||
Map<String, Object> values = new java.util.LinkedHashMap<String, Object>();
|
||
values.put(targetField, value);
|
||
return updateFields(params, tableName, values, conditionFields);
|
||
}
|
||
|
||
private int increaseNumberField(Map<String, Object> params, String tableName, String targetField,
|
||
String amountExpression, String[] conditionFields)
|
||
{
|
||
String safeTableName = requireSafeIdentifier(tableName, "业务动作目标表无效");
|
||
String safeTargetField = requireSafeIdentifier(targetField, "业务动作目标字段无效");
|
||
BigDecimal amount = resolveBusinessNumber(params, amountExpression);
|
||
String whereClause = buildWhereClause(params, conditionFields);
|
||
List<Object> args = new ArrayList<Object>();
|
||
args.add(amount);
|
||
appendConditionValues(args, params, conditionFields);
|
||
return jdbcTemplate.update("update " + safeTableName + " set " + safeTargetField + " = "
|
||
+ safeTargetField + " + ? where " + whereClause, args.toArray());
|
||
}
|
||
|
||
private int decreaseNumberField(Map<String, Object> params, String tableName, String targetField,
|
||
String amountExpression, String minValueExpression,
|
||
String[] conditionFields, String message)
|
||
{
|
||
String safeTableName = requireSafeIdentifier(tableName, "业务动作目标表无效");
|
||
String safeTargetField = requireSafeIdentifier(targetField, "业务动作目标字段无效");
|
||
BigDecimal amount = resolveBusinessNumber(params, amountExpression);
|
||
BigDecimal minValue = resolveBusinessNumber(params,
|
||
minValueExpression == null || minValueExpression.length() == 0 ? "0" : minValueExpression);
|
||
String whereClause = buildWhereClause(params, conditionFields);
|
||
List<Object> args = new ArrayList<Object>();
|
||
args.add(amount);
|
||
appendConditionValues(args, params, conditionFields);
|
||
args.add(amount);
|
||
args.add(minValue);
|
||
int rows = jdbcTemplate.update("update " + safeTableName + " set " + safeTargetField + " = "
|
||
+ safeTargetField + " - ? where " + whereClause + " and " + safeTargetField + " - ? >= ?",
|
||
args.toArray());
|
||
if (rows == 0)
|
||
{
|
||
throw new IllegalArgumentException(defaultMessage(message, "业务数值扣减失败"));
|
||
}
|
||
return rows;
|
||
}
|
||
|
||
private int executeUpdateFieldEffect(Map<String, Object> params, String tableName, String targetField,
|
||
String valueExpression, String[] conditionFields)
|
||
{
|
||
String safeTableName = requireSafeIdentifier(tableName, "业务动作目标表无效");
|
||
String safeTargetField = requireSafeIdentifier(targetField, "业务动作目标字段无效");
|
||
String safeValueExpression = requireSafeExpression(valueExpression);
|
||
String whereClause = buildWhereClause(params, conditionFields);
|
||
return jdbcTemplate.update("update " + safeTableName + " set " + safeTargetField + " = "
|
||
+ safeValueExpression + " where " + whereClause, conditionValues(params, conditionFields));
|
||
}
|
||
|
||
private String buildWhereClause(Map<String, Object> params, String[] conditionFields)
|
||
{
|
||
if (conditionFields == null || conditionFields.length == 0)
|
||
{
|
||
throw new IllegalArgumentException("业务动作条件字段不能为空");
|
||
}
|
||
StringBuilder where = new StringBuilder();
|
||
for (String field : conditionFields)
|
||
{
|
||
String safeField = requireSafeIdentifier(field, "业务动作条件字段无效");
|
||
requireBusinessParam(params, safeField);
|
||
if (where.length() > 0)
|
||
{
|
||
where.append(" and ");
|
||
}
|
||
where.append(safeField).append(" = ?");
|
||
}
|
||
return where.toString();
|
||
}
|
||
|
||
private Object[] conditionValues(Map<String, Object> params, String[] conditionFields)
|
||
{
|
||
Object[] values = new Object[conditionFields.length];
|
||
for (int i = 0; i < conditionFields.length; i++)
|
||
{
|
||
values[i] = params.get(conditionFields[i]);
|
||
}
|
||
return values;
|
||
}
|
||
|
||
private int countRows(Map<String, Object> params, String tableName, String[] conditionFields)
|
||
{
|
||
String safeTableName = requireSafeIdentifier(tableName, "业务规则目标表无效");
|
||
String whereClause = buildWhereClause(params, conditionFields);
|
||
Integer count = jdbcTemplate.queryForObject("select count(1) from " + safeTableName + " where "
|
||
+ whereClause, conditionValues(params, conditionFields), Integer.class);
|
||
return count == null ? 0 : count.intValue();
|
||
}
|
||
|
||
private Object querySingleField(Map<String, Object> params, String tableName, String targetField,
|
||
String[] conditionFields)
|
||
{
|
||
String safeTableName = requireSafeIdentifier(tableName, "业务规则目标表无效");
|
||
String safeTargetField = requireSafeIdentifier(targetField, "业务规则目标字段无效");
|
||
String whereClause = buildWhereClause(params, conditionFields);
|
||
List<Map<String, Object>> rows = jdbcTemplate.queryForList("select " + safeTargetField + " from "
|
||
+ safeTableName + " where " + whereClause + " limit 1", conditionValues(params, conditionFields));
|
||
return rows.isEmpty() ? null : rows.get(0).get(safeTargetField);
|
||
}
|
||
|
||
private void appendConditionValues(List<Object> args, Map<String, Object> params, String[] conditionFields)
|
||
{
|
||
for (Object value : conditionValues(params, conditionFields))
|
||
{
|
||
args.add(value);
|
||
}
|
||
}
|
||
|
||
private Object resolveBusinessValue(Map<String, Object> params, String valueExpression)
|
||
{
|
||
if (valueExpression == null)
|
||
{
|
||
return null;
|
||
}
|
||
if ("#[[${now}]]#".equals(valueExpression))
|
||
{
|
||
return new Date();
|
||
}
|
||
if ("#[[${current_user.id}]]#".equals(valueExpression)
|
||
|| "#[[${current_user_id}]]#".equals(valueExpression))
|
||
{
|
||
requireBusinessParam(params, "__currentUserId");
|
||
return params.get("__currentUserId");
|
||
}
|
||
if (valueExpression.startsWith("#[[${param.]]#") && valueExpression.endsWith("}"))
|
||
{
|
||
String field = valueExpression.substring(8, valueExpression.length() - 1);
|
||
requireBusinessParam(params, field);
|
||
return params.get(field);
|
||
}
|
||
return valueExpression;
|
||
}
|
||
|
||
private BigDecimal resolveBusinessNumber(Map<String, Object> params, String valueExpression)
|
||
{
|
||
return toBigDecimal(resolveBusinessValue(params, valueExpression), "业务数值表达式无效");
|
||
}
|
||
|
||
private BigDecimal toBigDecimal(Object value, String message)
|
||
{
|
||
try
|
||
{
|
||
return new BigDecimal(String.valueOf(value));
|
||
}
|
||
catch (RuntimeException e)
|
||
{
|
||
throw new IllegalArgumentException(message);
|
||
}
|
||
}
|
||
|
||
private String defaultMessage(String message, String fallback)
|
||
{
|
||
return message == null || message.length() == 0 ? fallback : message;
|
||
}
|
||
|
||
private String requireSafeIdentifier(String value, String message)
|
||
{
|
||
if (value == null || !value.matches("[A-Za-z][A-Za-z0-9_]*"))
|
||
{
|
||
throw new IllegalArgumentException(message);
|
||
}
|
||
return value;
|
||
}
|
||
|
||
private String requireSafeExpression(String valueExpression)
|
||
{
|
||
if (valueExpression == null || !valueExpression.matches("[A-Za-z0-9_ +*/().-]{1,200}")
|
||
|| valueExpression.contains(";") || valueExpression.contains("--"))
|
||
{
|
||
throw new IllegalArgumentException("业务动作表达式无效");
|
||
}
|
||
return valueExpression;
|
||
}
|
||
#end
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920113, 9201, 'controller.java.vm', NULL, 'controller.java.vm', 'package ${packageName}.controller;
|
||
|
||
import cn.hutool.poi.excel.ExcelReader;
|
||
import cn.hutool.poi.excel.ExcelUtil;
|
||
import cn.hutool.poi.excel.ExcelWriter;
|
||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||
import ${packageName}.common.Result;
|
||
import ${packageName}.entity.${ClassName};
|
||
#if($hasQingCurrentUserColumn || $hasTableBusinessActions)
|
||
import ${packageName}.entity.PortalUser;
|
||
import ${packageName}.security.PortalAuthTokenStore;
|
||
#end
|
||
import ${packageName}.service.I${ClassName}Service;
|
||
import java.io.InputStream;
|
||
import java.net.URLEncoder;
|
||
import java.util.LinkedHashMap;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import javax.annotation.Resource;
|
||
import javax.servlet.ServletOutputStream;
|
||
#if($hasQingCurrentUserColumn || $hasTableBusinessActions)
|
||
import javax.servlet.http.HttpServletRequest;
|
||
#end
|
||
import javax.servlet.http.HttpServletResponse;
|
||
import org.apache.commons.lang3.StringUtils;
|
||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||
import org.springframework.web.bind.annotation.GetMapping;
|
||
import org.springframework.web.bind.annotation.PathVariable;
|
||
import org.springframework.web.bind.annotation.PostMapping;
|
||
import org.springframework.web.bind.annotation.RequestBody;
|
||
import org.springframework.web.bind.annotation.RequestParam;
|
||
import org.springframework.web.bind.annotation.RestController;
|
||
import org.springframework.web.multipart.MultipartFile;
|
||
|
||
/**
|
||
* ${functionName}Controller
|
||
*
|
||
* @author ${author}
|
||
* @date ${datetime}
|
||
*/
|
||
@RestController
|
||
public class ${ClassName}Controller
|
||
{
|
||
@Resource
|
||
private I${ClassName}Service ${className}Service;
|
||
|
||
#if($hasQingCurrentUserColumn || $hasTableBusinessActions)
|
||
@Resource
|
||
private PortalAuthTokenStore tokenStore;
|
||
|
||
#end
|
||
|
||
#if($allowAdd == ''1'' || $allowEdit == ''1'')
|
||
@PostMapping("/${moduleName}/${businessName}")
|
||
public Result<Void> save(@RequestBody ${ClassName} ${className})
|
||
{
|
||
#if($allowAdd == ''1'' && $allowEdit == ''1'')
|
||
${className}Service.saveOrUpdate(${className});
|
||
#elseif($allowAdd == ''1'')
|
||
${className}Service.save(${className});
|
||
#else
|
||
${className}Service.updateById(${className});
|
||
#end
|
||
return Result.success();
|
||
}
|
||
#end
|
||
|
||
#if($allowRemove == ''1'')
|
||
@DeleteMapping("/${moduleName}/${businessName}/{${pkColumn.javaField}}")
|
||
public Result<Void> delete(@PathVariable ${pkColumn.javaType} ${pkColumn.javaField})
|
||
{
|
||
${className}Service.removeById(${pkColumn.javaField});
|
||
return Result.success();
|
||
}
|
||
|
||
@PostMapping("/${moduleName}/${businessName}/del/batch")
|
||
public Result<Void> deleteBatch(@RequestBody List<${pkColumn.javaType}> ${pkColumn.javaField}s)
|
||
{
|
||
${className}Service.removeByIds(${pkColumn.javaField}s);
|
||
return Result.success();
|
||
}
|
||
#end
|
||
|
||
@GetMapping("/${moduleName}/${businessName}")
|
||
public Result<List<${ClassName}>> findAll()
|
||
{
|
||
return Result.success(${className}Service.list());
|
||
}
|
||
|
||
@GetMapping("/${moduleName}/${businessName}/{${pkColumn.javaField}}")
|
||
public Result<${ClassName}> findOne(@PathVariable ${pkColumn.javaType} ${pkColumn.javaField})
|
||
{
|
||
return Result.success(${className}Service.getById(${pkColumn.javaField}));
|
||
}
|
||
|
||
@GetMapping("/${moduleName}/${businessName}/page")
|
||
public Result<Page<${ClassName}>> findPage(@RequestParam(required = false, defaultValue = "1") Integer pageNum,
|
||
@RequestParam(required = false, defaultValue = "10") Integer pageSize,
|
||
@RequestParam(required = false) Map<String, Object> params#if($hasQingCurrentUserColumn),
|
||
HttpServletRequest request#end)
|
||
{
|
||
QueryWrapper<${ClassName}> queryWrapper = new QueryWrapper<${ClassName}>();
|
||
#if($hasQingCurrentUserColumn)
|
||
if (isCurrentUserOnly(params))
|
||
{
|
||
PortalUser currentUser = tokenStore.getPortalUser(resolveToken(request));
|
||
if (currentUser == null)
|
||
{
|
||
return Result.success(new Page<${ClassName}>(pageNum, pageSize));
|
||
}
|
||
queryWrapper.eq("${qingCurrentUserColumn.columnName}", currentUser.getUserId());
|
||
}
|
||
#end
|
||
#foreach ($column in $columns)
|
||
#if($column.isQuery == "1")
|
||
#set($queryType=$column.queryType)
|
||
#set($javaField=$column.javaField)
|
||
#set($columnName=$column.columnName)
|
||
#if($queryType == "EQ")
|
||
queryWrapper.eq(hasValue(params.get("${javaField}")), "${columnName}", params.get("${javaField}"));
|
||
#elseif($queryType == "NE")
|
||
queryWrapper.ne(hasValue(params.get("${javaField}")), "${columnName}", params.get("${javaField}"));
|
||
#elseif($queryType == "GT")
|
||
queryWrapper.gt(hasValue(params.get("${javaField}")), "${columnName}", params.get("${javaField}"));
|
||
#elseif($queryType == "GTE")
|
||
queryWrapper.ge(hasValue(params.get("${javaField}")), "${columnName}", params.get("${javaField}"));
|
||
#elseif($queryType == "LT")
|
||
queryWrapper.lt(hasValue(params.get("${javaField}")), "${columnName}", params.get("${javaField}"));
|
||
#elseif($queryType == "LTE")
|
||
queryWrapper.le(hasValue(params.get("${javaField}")), "${columnName}", params.get("${javaField}"));
|
||
#elseif($queryType == "LIKE")
|
||
queryWrapper.like(hasValue(params.get("${javaField}")), "${columnName}", params.get("${javaField}"));
|
||
#else
|
||
queryWrapper.eq(hasValue(params.get("${javaField}")), "${columnName}", params.get("${javaField}"));
|
||
#end
|
||
#end
|
||
#end
|
||
queryWrapper.orderByDesc("${pkColumn.columnName}");
|
||
return Result.success(${className}Service.page(new Page<${ClassName}>(pageNum, pageSize), queryWrapper));
|
||
}
|
||
|
||
@GetMapping("/${moduleName}/${businessName}/export")
|
||
public void export(HttpServletResponse response) throws Exception
|
||
{
|
||
List<${ClassName}> list = ${className}Service.list();
|
||
ExcelWriter writer = ExcelUtil.getWriter(true);
|
||
writer.write(list, true);
|
||
|
||
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
|
||
String fileName = URLEncoder.encode("${functionName}信息表", "UTF-8");
|
||
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
|
||
|
||
ServletOutputStream out = response.getOutputStream();
|
||
writer.flush(out, true);
|
||
out.close();
|
||
writer.close();
|
||
}
|
||
|
||
#if($allowAdd == ''1'')
|
||
@PostMapping("/${moduleName}/${businessName}/import")
|
||
public Result<Void> imp(MultipartFile file) throws Exception
|
||
{
|
||
InputStream inputStream = file.getInputStream();
|
||
ExcelReader reader = ExcelUtil.getReader(inputStream);
|
||
List<${ClassName}> list = reader.readAll(${ClassName}.class);
|
||
${className}Service.saveBatch(list);
|
||
return Result.success();
|
||
}
|
||
#end
|
||
|
||
#if($hasTableBusinessActions)
|
||
#foreach($action in $tableBusinessActions)
|
||
#if($action.method == "GET")
|
||
@GetMapping("${action.path}")
|
||
public Result<Integer> ${action.code}(@RequestParam(required = false) Map<String, Object> params,
|
||
HttpServletRequest request)
|
||
#elseif($action.method == "PUT")
|
||
@org.springframework.web.bind.annotation.PutMapping("${action.path}")
|
||
public Result<Integer> ${action.code}(@RequestBody(required = false) Map<String, Object> params,
|
||
HttpServletRequest request)
|
||
#elseif($action.method == "DELETE")
|
||
@DeleteMapping("${action.path}")
|
||
public Result<Integer> ${action.code}(@RequestBody(required = false) Map<String, Object> params,
|
||
HttpServletRequest request)
|
||
#else
|
||
@PostMapping("${action.path}")
|
||
public Result<Integer> ${action.code}(@RequestBody(required = false) Map<String, Object> params,
|
||
HttpServletRequest request)
|
||
#end
|
||
{
|
||
try
|
||
{
|
||
Map<String, Object> actionParams = prepareBusinessActionParams(params, request, new String[] {#foreach($field in $action.currentUserFields)"${field}"#if($foreach.hasNext), #end#end});
|
||
return Result.success(${className}Service.${action.code}(actionParams));
|
||
}
|
||
catch (RuntimeException e)
|
||
{
|
||
return Result.error(e.getMessage());
|
||
}
|
||
}
|
||
|
||
#end
|
||
private Map<String, Object> prepareBusinessActionParams(Map<String, Object> params, HttpServletRequest request,
|
||
String[] currentUserFields)
|
||
{
|
||
Map<String, Object> actionParams = params == null
|
||
? new LinkedHashMap<String, Object>()
|
||
: new LinkedHashMap<String, Object>(params);
|
||
PortalUser currentUser = tokenStore.getPortalUser(resolveToken(request));
|
||
if (currentUser == null)
|
||
{
|
||
throw new IllegalArgumentException("请先登录");
|
||
}
|
||
actionParams.put("__currentUserId", currentUser.getUserId());
|
||
if (currentUserFields != null)
|
||
{
|
||
for (String field : currentUserFields)
|
||
{
|
||
if (StringUtils.isNotBlank(field))
|
||
{
|
||
if ("username".equals(field) || "user_name".equals(field))
|
||
{
|
||
actionParams.put(field, currentUser.getUsername());
|
||
}
|
||
else
|
||
{
|
||
actionParams.put(field, currentUser.getUserId());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return actionParams;
|
||
}
|
||
|
||
#end
|
||
private boolean hasValue(Object value)
|
||
{
|
||
return value != null && StringUtils.isNotBlank(String.valueOf(value));
|
||
}
|
||
|
||
#if($hasQingCurrentUserColumn)
|
||
private boolean isCurrentUserOnly(Map<String, Object> params)
|
||
{
|
||
return params != null && "true".equalsIgnoreCase(String.valueOf(params.get("__currentUserOnly")));
|
||
}
|
||
|
||
#end
|
||
#if($hasQingCurrentUserColumn || $hasTableBusinessActions)
|
||
private String resolveToken(HttpServletRequest request)
|
||
{
|
||
String authorization = request.getHeader("Authorization");
|
||
if (authorization != null && authorization.startsWith("Bearer "))
|
||
{
|
||
return authorization.substring("Bearer ".length());
|
||
}
|
||
return request.getHeader("X-Portal-Token");
|
||
}
|
||
#end
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920114, 9201, 'mapper.xml.vm', NULL, 'mapper.xml.vm', '<?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="${packageName}.mapper.${ClassName}Mapper">
|
||
|
||
<!-- 通用查询映射结果 -->
|
||
<resultMap type="${packageName}.entity.${ClassName}" id="BaseResultMap">
|
||
#foreach ($column in $columns)
|
||
#if ($column.isPk == "1")
|
||
<id column="${column.columnName}" property="${column.javaField}"/>
|
||
#else
|
||
<result column="${column.columnName}" property="${column.javaField}"/>
|
||
#end
|
||
#end
|
||
</resultMap>
|
||
|
||
<!-- 通用查询结果列 -->
|
||
<sql id="Base_Column_List">
|
||
#foreach ($column in $columns)
|
||
${column.columnName}#if($foreach.count != $columns.size()),#end
|
||
|
||
#end
|
||
</sql>
|
||
|
||
</mapper>
|
||
', 'admin', sysdate()),
|
||
(920115, 9201, 'sql.vm', NULL, 'sql.vm', '-- ${functionName} table
|
||
-- Generated by qing template at ${datetime}
|
||
|
||
${table.createTableSql}
|
||
|
||
create table if not exists t_admin (
|
||
admin_id bigint not null auto_increment comment ''Admin ID'',
|
||
username varchar(64) not null comment ''Username'',
|
||
password_hash varchar(64) not null comment ''SHA-256 password hash'',
|
||
nickname varchar(64) default null comment ''Nickname'',
|
||
status char(1) default ''0'' comment ''Status, 0 normal 1 disabled'',
|
||
create_time datetime default current_timestamp comment ''Create time'',
|
||
primary key (admin_id),
|
||
unique key uk_t_admin_username (username)
|
||
) engine=innodb default charset=utf8mb4 comment=''Admin user table'';
|
||
|
||
insert into t_admin (username, password_hash, nickname, status)
|
||
select ''admin'',
|
||
''8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92'',
|
||
''Administrator'',
|
||
''0''
|
||
where not exists (select 1 from t_admin where username = ''admin'');
|
||
|
||
create table if not exists t_user (
|
||
user_id bigint not null auto_increment comment ''User ID'',
|
||
username varchar(64) not null comment ''Username'',
|
||
password_hash varchar(64) not null comment ''SHA-256 password hash'',
|
||
nickname varchar(64) default null comment ''Nickname'',
|
||
status char(1) default ''0'' comment ''Status, 0 normal 1 disabled'',
|
||
create_time datetime default current_timestamp comment ''Create time'',
|
||
primary key (user_id),
|
||
unique key uk_t_user_username (username)
|
||
) engine=innodb default charset=utf8mb4 comment=''Portal user table'';
|
||
|
||
insert into t_user (username, password_hash, nickname, status)
|
||
select ''user'',
|
||
''8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92'',
|
||
''User'',
|
||
''0''
|
||
where not exists (select 1 from t_user where username = ''user'');
|
||
', 'admin', sysdate()),
|
||
(920116, 9201, 'PortalUser.java.vm', NULL, 'PortalUser.java.vm', 'package ${packageName}.entity;
|
||
|
||
import com.baomidou.mybatisplus.annotation.IdType;
|
||
import com.baomidou.mybatisplus.annotation.TableId;
|
||
import com.baomidou.mybatisplus.annotation.TableName;
|
||
import lombok.Getter;
|
||
import lombok.Setter;
|
||
import lombok.experimental.Accessors;
|
||
|
||
@Getter
|
||
@Setter
|
||
@Accessors(chain = true)
|
||
@TableName("t_user")
|
||
public class PortalUser
|
||
{
|
||
@TableId(value = "user_id", type = IdType.AUTO)
|
||
private Long userId;
|
||
|
||
private String username;
|
||
|
||
private String passwordHash;
|
||
|
||
private String nickname;
|
||
|
||
private String status;
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920117, 9201, 'PortalUserMapper.java.vm', NULL, 'PortalUserMapper.java.vm', 'package ${packageName}.mapper;
|
||
|
||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||
import ${packageName}.entity.PortalUser;
|
||
|
||
public interface PortalUserMapper extends BaseMapper<PortalUser>
|
||
{
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920118, 9201, 'PortalAuthTokenStore.java.vm', NULL, 'PortalAuthTokenStore.java.vm', 'package ${packageName}.security;
|
||
|
||
import ${packageName}.entity.AdminUser;
|
||
import ${packageName}.entity.PortalUser;
|
||
import java.util.Map;
|
||
import java.util.UUID;
|
||
import java.util.concurrent.ConcurrentHashMap;
|
||
import org.springframework.stereotype.Component;
|
||
|
||
@Component
|
||
public class PortalAuthTokenStore
|
||
{
|
||
private final Map<String, Object> sessions = new ConcurrentHashMap<String, Object>();
|
||
|
||
public String issue(Object user)
|
||
{
|
||
String token = UUID.randomUUID().toString().replace("-", "");
|
||
sessions.put(token, user);
|
||
return token;
|
||
}
|
||
|
||
public Object getUser(String token)
|
||
{
|
||
if (token == null || token.trim().isEmpty())
|
||
{
|
||
return null;
|
||
}
|
||
return sessions.get(token);
|
||
}
|
||
|
||
public PortalUser getPortalUser(String token)
|
||
{
|
||
Object user = getUser(token);
|
||
return user instanceof PortalUser ? (PortalUser) user : null;
|
||
}
|
||
|
||
public AdminUser getAdminUser(String token)
|
||
{
|
||
Object user = getUser(token);
|
||
return user instanceof AdminUser ? (AdminUser) user : null;
|
||
}
|
||
|
||
public boolean validate(String token)
|
||
{
|
||
return getUser(token) != null;
|
||
}
|
||
|
||
public void revoke(String token)
|
||
{
|
||
if (token != null)
|
||
{
|
||
sessions.remove(token);
|
||
}
|
||
}
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920119, 9201, 'PortalAuthInterceptor.java.vm', NULL, 'PortalAuthInterceptor.java.vm', 'package ${packageName}.security;
|
||
|
||
import java.io.IOException;
|
||
import javax.annotation.Resource;
|
||
import javax.servlet.http.HttpServletRequest;
|
||
import javax.servlet.http.HttpServletResponse;
|
||
import org.springframework.stereotype.Component;
|
||
import org.springframework.web.servlet.HandlerInterceptor;
|
||
|
||
@Component
|
||
public class PortalAuthInterceptor implements HandlerInterceptor
|
||
{
|
||
@Resource
|
||
private PortalAuthTokenStore tokenStore;
|
||
|
||
@Override
|
||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
||
throws IOException
|
||
{
|
||
if ("OPTIONS".equalsIgnoreCase(request.getMethod()))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
String token = resolveToken(request);
|
||
if (tokenStore.validate(token))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
response.setStatus(401);
|
||
response.setCharacterEncoding("UTF-8");
|
||
response.setContentType("application/json;charset=UTF-8");
|
||
response.getWriter().write("{\\"code\\":\\"401\\",\\"msg\\":\\"请先登录\\",\\"data\\":null}");
|
||
return false;
|
||
}
|
||
|
||
private String resolveToken(HttpServletRequest request)
|
||
{
|
||
String authorization = request.getHeader("Authorization");
|
||
if (authorization != null && authorization.startsWith("Bearer "))
|
||
{
|
||
return authorization.substring("Bearer ".length());
|
||
}
|
||
return request.getHeader("X-Portal-Token");
|
||
}
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920120, 9201, 'PortalAuthController.java.vm', NULL, 'PortalAuthController.java.vm', 'package ${packageName}.controller;
|
||
|
||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||
import ${packageName}.common.Result;
|
||
import ${packageName}.entity.PortalUser;
|
||
import ${packageName}.mapper.PortalUserMapper;
|
||
import ${packageName}.security.PortalAuthTokenStore;
|
||
import java.nio.charset.StandardCharsets;
|
||
import java.security.MessageDigest;
|
||
import java.util.HashMap;
|
||
import java.util.Map;
|
||
import javax.annotation.Resource;
|
||
import javax.servlet.http.HttpServletRequest;
|
||
import org.springframework.web.bind.annotation.GetMapping;
|
||
import org.springframework.web.bind.annotation.PostMapping;
|
||
import org.springframework.web.bind.annotation.RequestBody;
|
||
import org.springframework.web.bind.annotation.RequestMapping;
|
||
import org.springframework.web.bind.annotation.RestController;
|
||
|
||
@RestController
|
||
@RequestMapping("/auth")
|
||
public class PortalAuthController
|
||
{
|
||
@Resource
|
||
private PortalUserMapper portalUserMapper;
|
||
|
||
@Resource
|
||
private PortalAuthTokenStore tokenStore;
|
||
|
||
@PostMapping("/login")
|
||
public Result<Map<String, Object>> login(@RequestBody LoginBody body)
|
||
{
|
||
if (body == null || isBlank(body.getUsername()) || isBlank(body.getPassword()))
|
||
{
|
||
return Result.error("请输入用户名和密码");
|
||
}
|
||
|
||
PortalUser user = portalUserMapper.selectOne(new QueryWrapper<PortalUser>()
|
||
.eq("username", body.getUsername())
|
||
.eq("status", "0")
|
||
.last("limit 1"));
|
||
|
||
if (user == null || !passwordHash(body.getPassword()).equalsIgnoreCase(user.getPasswordHash()))
|
||
{
|
||
return Result.error("账号或密码错误");
|
||
}
|
||
|
||
String token = tokenStore.issue(user);
|
||
user.setPasswordHash(null);
|
||
|
||
Map<String, Object> data = new HashMap<String, Object>();
|
||
data.put("token", token);
|
||
data.put("user", user);
|
||
return Result.success(data);
|
||
}
|
||
|
||
@GetMapping("/me")
|
||
public Result<PortalUser> me(HttpServletRequest request)
|
||
{
|
||
PortalUser user = tokenStore.getPortalUser(resolveToken(request));
|
||
if (user == null)
|
||
{
|
||
return Result.error("请先登录");
|
||
}
|
||
user.setPasswordHash(null);
|
||
return Result.success(user);
|
||
}
|
||
|
||
@PostMapping("/logout")
|
||
public Result<Void> logout(HttpServletRequest request)
|
||
{
|
||
tokenStore.revoke(resolveToken(request));
|
||
return Result.success();
|
||
}
|
||
|
||
private String resolveToken(HttpServletRequest request)
|
||
{
|
||
String authorization = request.getHeader("Authorization");
|
||
if (authorization != null && authorization.startsWith("Bearer "))
|
||
{
|
||
return authorization.substring("Bearer ".length());
|
||
}
|
||
return request.getHeader("X-Portal-Token");
|
||
}
|
||
|
||
private String passwordHash(String password)
|
||
{
|
||
try
|
||
{
|
||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||
byte[] hash = digest.digest(password.getBytes(StandardCharsets.UTF_8));
|
||
StringBuilder builder = new StringBuilder();
|
||
for (byte item : hash)
|
||
{
|
||
builder.append(String.format("%02x", item));
|
||
}
|
||
return builder.toString();
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
throw new IllegalStateException("Password hash failed", e);
|
||
}
|
||
}
|
||
|
||
private boolean isBlank(String value)
|
||
{
|
||
return value == null || value.trim().isEmpty();
|
||
}
|
||
|
||
public static class LoginBody
|
||
{
|
||
private String username;
|
||
private String password;
|
||
|
||
public String getUsername()
|
||
{
|
||
return username;
|
||
}
|
||
|
||
public void setUsername(String username)
|
||
{
|
||
this.username = username;
|
||
}
|
||
|
||
public String getPassword()
|
||
{
|
||
return password;
|
||
}
|
||
|
||
public void setPassword(String password)
|
||
{
|
||
this.password = password;
|
||
}
|
||
}
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920121, 9201, 'AdminUser.java.vm', NULL, 'AdminUser.java.vm', 'package ${packageName}.entity;
|
||
|
||
import com.baomidou.mybatisplus.annotation.IdType;
|
||
import com.baomidou.mybatisplus.annotation.TableId;
|
||
import com.baomidou.mybatisplus.annotation.TableName;
|
||
import lombok.Getter;
|
||
import lombok.Setter;
|
||
import lombok.experimental.Accessors;
|
||
|
||
@Getter
|
||
@Setter
|
||
@Accessors(chain = true)
|
||
@TableName("t_admin")
|
||
public class AdminUser
|
||
{
|
||
@TableId(value = "admin_id", type = IdType.AUTO)
|
||
private Long adminId;
|
||
|
||
private String username;
|
||
|
||
private String passwordHash;
|
||
|
||
private String nickname;
|
||
|
||
private String status;
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920122, 9201, 'AdminUserMapper.java.vm', NULL, 'AdminUserMapper.java.vm', 'package ${packageName}.mapper;
|
||
|
||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||
import ${packageName}.entity.AdminUser;
|
||
|
||
public interface AdminUserMapper extends BaseMapper<AdminUser>
|
||
{
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920123, 9201, 'AdminAuthController.java.vm', NULL, 'AdminAuthController.java.vm', 'package ${packageName}.controller;
|
||
|
||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||
import ${packageName}.common.Result;
|
||
import ${packageName}.entity.AdminUser;
|
||
import ${packageName}.mapper.AdminUserMapper;
|
||
import ${packageName}.security.PortalAuthTokenStore;
|
||
import java.nio.charset.StandardCharsets;
|
||
import java.security.MessageDigest;
|
||
import java.util.HashMap;
|
||
import java.util.Map;
|
||
import javax.annotation.Resource;
|
||
import javax.servlet.http.HttpServletRequest;
|
||
import org.springframework.web.bind.annotation.GetMapping;
|
||
import org.springframework.web.bind.annotation.PostMapping;
|
||
import org.springframework.web.bind.annotation.RequestBody;
|
||
import org.springframework.web.bind.annotation.RequestMapping;
|
||
import org.springframework.web.bind.annotation.RestController;
|
||
|
||
@RestController
|
||
@RequestMapping("/auth/admin")
|
||
public class AdminAuthController
|
||
{
|
||
@Resource
|
||
private AdminUserMapper adminUserMapper;
|
||
|
||
@Resource
|
||
private PortalAuthTokenStore tokenStore;
|
||
|
||
@PostMapping("/login")
|
||
public Result<Map<String, Object>> login(@RequestBody LoginBody body)
|
||
{
|
||
if (body == null || isBlank(body.getUsername()) || isBlank(body.getPassword()))
|
||
{
|
||
return Result.error("请输入用户名和密码");
|
||
}
|
||
|
||
AdminUser user = adminUserMapper.selectOne(new QueryWrapper<AdminUser>()
|
||
.eq("username", body.getUsername())
|
||
.eq("status", "0")
|
||
.last("limit 1"));
|
||
|
||
if (user == null || !passwordHash(body.getPassword()).equalsIgnoreCase(user.getPasswordHash()))
|
||
{
|
||
return Result.error("账号或密码错误");
|
||
}
|
||
|
||
String token = tokenStore.issue(user);
|
||
user.setPasswordHash(null);
|
||
|
||
Map<String, Object> data = new HashMap<String, Object>();
|
||
data.put("token", token);
|
||
data.put("user", user);
|
||
return Result.success(data);
|
||
}
|
||
|
||
@GetMapping("/me")
|
||
public Result<AdminUser> me(HttpServletRequest request)
|
||
{
|
||
AdminUser user = tokenStore.getAdminUser(resolveToken(request));
|
||
if (user == null)
|
||
{
|
||
return Result.error("请先登录");
|
||
}
|
||
user.setPasswordHash(null);
|
||
return Result.success(user);
|
||
}
|
||
|
||
@PostMapping("/logout")
|
||
public Result<Void> logout(HttpServletRequest request)
|
||
{
|
||
tokenStore.revoke(resolveToken(request));
|
||
return Result.success();
|
||
}
|
||
|
||
private String resolveToken(HttpServletRequest request)
|
||
{
|
||
String authorization = request.getHeader("Authorization");
|
||
if (authorization != null && authorization.startsWith("Bearer "))
|
||
{
|
||
return authorization.substring("Bearer ".length());
|
||
}
|
||
return request.getHeader("X-Admin-Token");
|
||
}
|
||
|
||
private String passwordHash(String password)
|
||
{
|
||
try
|
||
{
|
||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||
byte[] hash = digest.digest(password.getBytes(StandardCharsets.UTF_8));
|
||
StringBuilder builder = new StringBuilder();
|
||
for (byte item : hash)
|
||
{
|
||
builder.append(String.format("%02x", item));
|
||
}
|
||
return builder.toString();
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
throw new IllegalStateException("Password hash failed", e);
|
||
}
|
||
}
|
||
|
||
private boolean isBlank(String value)
|
||
{
|
||
return value == null || value.trim().isEmpty();
|
||
}
|
||
|
||
public static class LoginBody
|
||
{
|
||
private String username;
|
||
private String password;
|
||
|
||
public String getUsername()
|
||
{
|
||
return username;
|
||
}
|
||
|
||
public void setUsername(String username)
|
||
{
|
||
this.username = username;
|
||
}
|
||
|
||
public String getPassword()
|
||
{
|
||
return password;
|
||
}
|
||
|
||
public void setPassword(String password)
|
||
{
|
||
this.password = password;
|
||
}
|
||
}
|
||
}
|
||
', 'admin', sysdate());
|
||
|
||
-- Admin frontend template files
|
||
INSERT INTO sys_template_file (template_file_id, template_id, file_name, module_id, file_path, file_content, create_by, create_time) VALUES
|
||
(920201, 9202, 'package.json.vm', NULL, 'package.json.vm', '{
|
||
"name": "${projectFileName}-admin",
|
||
"version": "1.0.0",
|
||
"private": true,
|
||
"scripts": {
|
||
"dev": "vue-cli-service serve --host 0.0.0.0",
|
||
"serve": "vue-cli-service serve --host 0.0.0.0",
|
||
"build": "vue-cli-service build"
|
||
},
|
||
"dependencies": {
|
||
"axios": "^0.27.2",
|
||
"core-js": "^3.8.3",
|
||
"echarts": "5.4.0",
|
||
"element-ui": "^2.15.14",
|
||
"vue": "^2.6.14",
|
||
"vue-router": "^3.6.5"
|
||
},
|
||
"devDependencies": {
|
||
"@vue/cli-service": "^4.5.19",
|
||
"vue-template-compiler": "^2.6.14"
|
||
}
|
||
}', 'admin', sysdate()),
|
||
(920202, 9202, 'babel.config.js.vm', NULL, 'babel.config.js.vm', 'module.exports = {
|
||
presets: [
|
||
"@vue/cli-plugin-babel/preset"
|
||
]
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920203, 9202, 'vue.config.js.vm', NULL, 'vue.config.js.vm', 'const port = process.env.PORT || 8081
|
||
const apiBaseUrl = process.env.VUE_APP_API_BASE_URL || "http://localhost:8080"
|
||
|
||
module.exports = {
|
||
transpileDependencies: [],
|
||
devServer: {
|
||
port,
|
||
proxy: {
|
||
"/api": {
|
||
target: apiBaseUrl,
|
||
changeOrigin: true,
|
||
pathRewrite: {
|
||
"^/api": ""
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920204, 9202, 'index.html.vm', NULL, 'index.html.vm', '<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||
<title>${projectName} Admin</title>
|
||
</head>
|
||
<body>
|
||
<noscript>请启用 JavaScript 后访问。</noscript>
|
||
<div id="app"></div>
|
||
</body>
|
||
</html>', 'admin', sysdate()),
|
||
(920205, 9202, 'admin-main.js.vm', NULL, 'admin-main.js.vm', 'import Vue from "vue"
|
||
import ElementUI from "element-ui"
|
||
import "element-ui/lib/theme-chalk/index.css"
|
||
import App from "./App.vue"
|
||
import router from "./router"
|
||
import request from "./utils/request"
|
||
import dict from "./utils/dict"
|
||
|
||
Vue.config.productionTip = false
|
||
Vue.use(ElementUI)
|
||
Vue.prototype.request = request
|
||
Vue.prototype._dict = dict
|
||
|
||
new Vue({
|
||
router,
|
||
render: h => h(App)
|
||
}).#[[$mount]]#("#app")', 'admin', sysdate()),
|
||
(920206, 9202, 'admin-App.vue.vm', NULL, 'admin-App.vue.vm', '<template>
|
||
<div id="app">
|
||
<router-view v-if="isLoginPage" />
|
||
<div v-else class="admin-shell">
|
||
<aside class="admin-sidebar">
|
||
<router-link class="brand" to="/">
|
||
<span class="brand-mark">ADM</span>
|
||
<span class="brand-text">${projectName}后台</span>
|
||
</router-link>
|
||
|
||
<nav class="sidebar-menu">
|
||
#if($hasAdminNavGroups)
|
||
#foreach($group in $adminNavGroups)
|
||
#if($group.hasChildren)
|
||
<div class="menu-group">
|
||
#if($group.clickable)
|
||
<router-link class="menu-title menu-title-link" :class=''{ active: isSidebarGroupActive(${group.activePathsLiteral}) }'' to="${group.path}" exact>
|
||
<span><i class="el-icon-menu"></i>${group.name}</span>
|
||
<i class="el-icon-arrow-up"></i>
|
||
</router-link>
|
||
#else
|
||
<div class="menu-title" :class=''{ active: isSidebarGroupActive(${group.activePathsLiteral}) }''>
|
||
<span><i class="el-icon-folder-opened"></i>${group.name}</span>
|
||
<i class="el-icon-arrow-up"></i>
|
||
</div>
|
||
#end
|
||
#foreach($child in $group.children)
|
||
<router-link class="sidebar-link is-child" to="${child.path}" exact>
|
||
<span>${child.name}</span>
|
||
</router-link>
|
||
#end
|
||
</div>
|
||
#else
|
||
<router-link class="sidebar-link" to="${group.path}" exact>
|
||
<i class="el-icon-menu"></i>
|
||
<span>${group.name}</span>
|
||
</router-link>
|
||
#end
|
||
#end
|
||
#else
|
||
<router-link class="sidebar-link" to="/empty" exact>
|
||
<i class="el-icon-warning-outline"></i>
|
||
<span>暂无菜单</span>
|
||
</router-link>
|
||
#end
|
||
</nav>
|
||
</aside>
|
||
|
||
<section class="admin-main">
|
||
<header class="topbar">
|
||
<div class="breadcrumb">
|
||
<router-link to="/">首页</router-link>
|
||
<span>/</span>
|
||
<strong>{{ currentTitle }}</strong>
|
||
</div>
|
||
<el-dropdown trigger="click" @command="handleUserCommand">
|
||
<span class="user-entry">
|
||
<span class="avatar">管</span>
|
||
<span>{{ userName }}</span>
|
||
<i class="el-icon-arrow-down"></i>
|
||
</span>
|
||
<el-dropdown-menu slot="dropdown">
|
||
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</el-dropdown>
|
||
</header>
|
||
<main class="content-area">
|
||
<router-view />
|
||
</main>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: "App",
|
||
computed: {
|
||
isLoginPage() {
|
||
return this.#[[$route]]#.path === "/login"
|
||
},
|
||
currentTitle() {
|
||
return this.#[[$route]]#.meta && this.#[[$route]]#.meta.title ? this.#[[$route]]#.meta.title : "系统首页"
|
||
},
|
||
userName() {
|
||
const storedUser = window.localStorage.getItem("admin-user")
|
||
if (storedUser) {
|
||
try {
|
||
const user = JSON.parse(storedUser)
|
||
return user.nickname || user.username || "管理员"
|
||
} catch (e) {
|
||
return "管理员"
|
||
}
|
||
}
|
||
return window.localStorage.getItem("adminUserName") || "管理员"
|
||
}
|
||
},
|
||
methods: {
|
||
isSidebarGroupActive(paths) {
|
||
return Array.isArray(paths) && paths.indexOf(this.#[[$route]]#.path) !== -1
|
||
},
|
||
handleUserCommand(command) {
|
||
if (command === "logout") {
|
||
this.request.post("/auth/admin/logout").finally(() => {
|
||
this.clearAdminSession()
|
||
this.#[[$router]]#.replace("/login")
|
||
})
|
||
}
|
||
},
|
||
clearAdminSession() {
|
||
window.localStorage.removeItem("admin-token")
|
||
window.localStorage.removeItem("admin-user")
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
* {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
margin: 0;
|
||
background: #f3f5fa;
|
||
color: #303133;
|
||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
|
||
}
|
||
|
||
a {
|
||
text-decoration: none;
|
||
}
|
||
|
||
.admin-shell {
|
||
min-height: 100vh;
|
||
display: flex;
|
||
background: #f3f5fa;
|
||
}
|
||
|
||
.admin-sidebar {
|
||
width: 208px;
|
||
min-height: 100vh;
|
||
flex: 0 0 208px;
|
||
overflow-y: auto;
|
||
background: #0f172a;
|
||
color: #d8e6f0;
|
||
}
|
||
|
||
.brand {
|
||
height: 62px;
|
||
padding: 0 16px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
color: #fff;
|
||
background: #111827;
|
||
}
|
||
|
||
.brand-mark {
|
||
display: inline-grid;
|
||
width: 36px;
|
||
height: 36px;
|
||
place-items: center;
|
||
border-radius: 8px;
|
||
color: #ffffff;
|
||
background: #17633b;
|
||
font-size: 12px;
|
||
font-weight: 800;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.brand-text {
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
font-size: 17px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.sidebar-menu {
|
||
padding: 10px 0 24px;
|
||
}
|
||
|
||
.sidebar-link,
|
||
.menu-title {
|
||
min-height: 44px;
|
||
padding: 0 18px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
color: #d8e6f0;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.menu-title {
|
||
justify-content: space-between;
|
||
background: #111f35;
|
||
border-left: 3px solid transparent;
|
||
}
|
||
|
||
.menu-title-link {
|
||
color: #d8e6f0;
|
||
}
|
||
|
||
.menu-title span {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.menu-title.active,
|
||
.menu-title:hover,
|
||
.sidebar-link:hover {
|
||
color: #ffffff;
|
||
background: #1e2f4d;
|
||
}
|
||
|
||
.sidebar-link.is-child {
|
||
padding-left: 48px;
|
||
background: #0b1220;
|
||
}
|
||
|
||
.sidebar-link.router-link-exact-active,
|
||
.sidebar-link.router-link-active {
|
||
background: #2563eb;
|
||
color: #fff;
|
||
}
|
||
|
||
.admin-main {
|
||
min-width: 0;
|
||
flex: 1;
|
||
}
|
||
|
||
.topbar {
|
||
height: 56px;
|
||
padding: 0 18px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
background: #ffffff;
|
||
border-bottom: 1px solid #e5e7eb;
|
||
color: #111827;
|
||
}
|
||
|
||
.breadcrumb {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.breadcrumb a,
|
||
.breadcrumb strong {
|
||
color: #111827;
|
||
}
|
||
|
||
.user-entry {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
color: #334155;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.avatar {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 50%;
|
||
display: inline-grid;
|
||
place-items: center;
|
||
color: #1d4ed8;
|
||
background: #eff6ff;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.content-area {
|
||
min-height: calc(100vh - 56px);
|
||
padding: 8px 10px 28px;
|
||
}
|
||
</style>', 'admin', sysdate()),
|
||
(920207, 9202, 'admin-router.js.vm', NULL, 'admin-router.js.vm', 'import Vue from "vue"
|
||
import Router from "vue-router"
|
||
import Login from "@/views/login/index.vue"
|
||
#if($hasAdminRouteItems)
|
||
#foreach($component in $adminRouteComponents)
|
||
import ${component.componentName} from "${component.componentPath}"
|
||
#end
|
||
#end
|
||
|
||
Vue.use(Router)
|
||
|
||
#if($hasAdminRouteItems)
|
||
const firstPath = "${defaultAdminPath}"
|
||
#else
|
||
const firstPath = "/empty"
|
||
const Empty = { template: "<div class=\"empty-page\">暂无可管理数据</div>" }
|
||
#end
|
||
|
||
const routes = [
|
||
{
|
||
path: "/login",
|
||
name: "Login",
|
||
component: Login,
|
||
meta: { title: "登录" }
|
||
},
|
||
{
|
||
path: "/",
|
||
redirect: firstPath
|
||
}#if($hasAdminRouteItems)#else,
|
||
{
|
||
path: "/empty",
|
||
name: "Empty",
|
||
component: Empty,
|
||
meta: { title: "系统首页" }
|
||
}#end
|
||
#foreach($item in $adminRouteItems),
|
||
{
|
||
path: "${item.path}",
|
||
name: "${item.code}",
|
||
component: ${item.componentName},
|
||
meta: { title: "${item.title}" }
|
||
}
|
||
#end
|
||
]
|
||
|
||
const router = new Router({
|
||
mode: "hash",
|
||
routes
|
||
})
|
||
|
||
router.beforeEach((to, from, next) => {
|
||
const hasToken = Boolean(window.localStorage.getItem("admin-token"))
|
||
if (to.path !== "/login" && !hasToken) {
|
||
next({ path: "/login", query: { redirect: to.fullPath } })
|
||
return
|
||
}
|
||
if (to.path === "/login" && hasToken) {
|
||
next(to.query.redirect || firstPath)
|
||
return
|
||
}
|
||
next()
|
||
})
|
||
|
||
export default router', 'admin', sysdate()),
|
||
(920211, 9202, 'admin-login.vue.vm', NULL, 'admin-login.vue.vm', '<template>
|
||
<div class="login-page">
|
||
<section class="login-visual">
|
||
<div class="login-brand">
|
||
<span class="brand-mark">
|
||
<i></i><i></i><i></i><i></i>
|
||
</span>
|
||
<span>${projectName}后台</span>
|
||
</div>
|
||
<h1>${projectName}</h1>
|
||
<p>统一管理分类、内容、订单与用户数据</p>
|
||
</section>
|
||
<el-form ref="loginForm" :model="form" :rules="rules" class="login-card" @keyup.enter.native="handleLogin">
|
||
<h2>欢迎登录${projectName}后台</h2>
|
||
<p>请输入管理员账号进入后台</p>
|
||
<el-form-item prop="username">
|
||
<el-input v-model="form.username" prefix-icon="el-icon-user" placeholder="请输入账号" />
|
||
</el-form-item>
|
||
<el-form-item prop="password">
|
||
<el-input v-model="form.password" prefix-icon="el-icon-lock" placeholder="请输入密码" show-password />
|
||
</el-form-item>
|
||
<div class="login-options">
|
||
<el-checkbox v-model="form.remember">记住账号</el-checkbox>
|
||
</div>
|
||
<el-button type="primary" :loading="loading" @click="handleLogin">登录</el-button>
|
||
</el-form>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
#if($hasTables)
|
||
#set($firstTable = $tables.get(0))
|
||
const defaultRedirect = "/${firstTable.moduleName}/${firstTable.businessName}"
|
||
#else
|
||
const defaultRedirect = "/empty"
|
||
#end
|
||
|
||
export default {
|
||
name: "AdminLogin",
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
form: {
|
||
username: window.localStorage.getItem("adminUserName") || "admin",
|
||
password: "123456",
|
||
remember: true
|
||
},
|
||
rules: {
|
||
username: [{ required: true, message: "请输入账号", trigger: "blur" }],
|
||
password: [{ required: true, message: "请输入密码", trigger: "blur" }]
|
||
}
|
||
}
|
||
},
|
||
methods: {
|
||
handleLogin() {
|
||
this.#[[$refs]]#.loginForm.validate(valid => {
|
||
if (!valid) {
|
||
return
|
||
}
|
||
this.loading = true
|
||
this.request.post("/auth/admin/login", {
|
||
username: this.form.username,
|
||
password: this.form.password
|
||
}).then(res => {
|
||
if (!res || res.code !== "200" || !res.data || !res.data.token) {
|
||
throw new Error((res && res.msg) || "登录失败")
|
||
}
|
||
window.localStorage.setItem("admin-token", res.data.token)
|
||
window.localStorage.setItem("admin-user", JSON.stringify(res.data.user || {
|
||
username: this.form.username,
|
||
nickname: this.form.username
|
||
}))
|
||
if (this.form.remember) {
|
||
window.localStorage.setItem("adminUserName", this.form.username)
|
||
} else {
|
||
window.localStorage.removeItem("adminUserName")
|
||
}
|
||
this.#[[$message]]#.success("登录成功")
|
||
this.#[[$router]]#.replace(this.#[[$route]]#.query.redirect || defaultRedirect)
|
||
}).catch(error => {
|
||
this.#[[$message]]#.error(error.message || "登录失败")
|
||
}).finally(() => {
|
||
this.loading = false
|
||
})
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.login-page {
|
||
min-height: 100vh;
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) 420px;
|
||
background: #f3f5fa;
|
||
}
|
||
|
||
.login-visual {
|
||
padding: 54px 64px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
background: linear-gradient(135deg, #20a9b5 0%, #147a8f 48%, #001120 100%);
|
||
color: #fff;
|
||
}
|
||
|
||
.login-brand {
|
||
position: absolute;
|
||
top: 32px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.brand-mark {
|
||
width: 30px;
|
||
height: 30px;
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 3px;
|
||
transform: rotate(45deg);
|
||
}
|
||
|
||
.brand-mark i {
|
||
display: block;
|
||
border-radius: 2px;
|
||
}
|
||
|
||
.brand-mark i:nth-child(1) {
|
||
background: #f4c542;
|
||
}
|
||
|
||
.brand-mark i:nth-child(2) {
|
||
background: #22b8cf;
|
||
}
|
||
|
||
.brand-mark i:nth-child(3) {
|
||
background: #2f80ed;
|
||
}
|
||
|
||
.brand-mark i:nth-child(4) {
|
||
background: #42c77b;
|
||
}
|
||
|
||
.login-visual h1 {
|
||
margin: 0;
|
||
font-size: 46px;
|
||
font-weight: 700;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.login-visual p {
|
||
max-width: 520px;
|
||
margin: 16px 0 0;
|
||
color: rgba(255, 255, 255, .82);
|
||
font-size: 18px;
|
||
}
|
||
|
||
.login-card {
|
||
align-self: center;
|
||
margin: 0 48px 0 0;
|
||
padding: 36px 34px;
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
box-shadow: 0 18px 42px rgba(0, 17, 32, .16);
|
||
}
|
||
|
||
.login-card h2 {
|
||
margin: 0;
|
||
color: #17233d;
|
||
font-size: 25px;
|
||
font-weight: 700;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.login-card p {
|
||
margin: 8px 0 24px;
|
||
color: #8a95a6;
|
||
}
|
||
|
||
.login-options {
|
||
margin: -4px 0 18px;
|
||
}
|
||
|
||
.login-card .el-button {
|
||
width: 100%;
|
||
height: 42px;
|
||
border-color: #0f7dbd;
|
||
background: #0f7dbd;
|
||
}
|
||
|
||
@media (max-width: 900px) {
|
||
.login-page {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.login-visual {
|
||
min-height: 220px;
|
||
padding: 36px 28px;
|
||
}
|
||
|
||
.login-brand {
|
||
position: static;
|
||
margin-bottom: 48px;
|
||
}
|
||
|
||
.login-visual h1 {
|
||
font-size: 34px;
|
||
}
|
||
|
||
.login-card {
|
||
margin: 24px;
|
||
}
|
||
}
|
||
</style>
|
||
', 'admin', sysdate()),
|
||
(920208, 9202, 'request.js.vm', NULL, 'request.js.vm', 'import axios from "axios"
|
||
import { Message } from "element-ui"
|
||
|
||
const request = axios.create({
|
||
baseURL: "/api",
|
||
timeout: 30000
|
||
})
|
||
|
||
request.interceptors.request.use(config => {
|
||
const token = window.localStorage.getItem("admin-token")
|
||
if (token) {
|
||
config.headers.Authorization = "Bearer " + token
|
||
}
|
||
return config
|
||
})
|
||
|
||
request.interceptors.response.use(
|
||
response => {
|
||
const data = response.data
|
||
if (data && data.code === "401") {
|
||
clearAdminSession()
|
||
Message.error(data.msg || "请先登录")
|
||
return Promise.reject(new Error(data.msg || "Unauthorized"))
|
||
}
|
||
return data
|
||
},
|
||
error => {
|
||
if (error && error.response && error.response.status === 401) {
|
||
clearAdminSession()
|
||
Message.error("请先登录")
|
||
} else {
|
||
Message.error(error && error.message ? error.message : "请求失败")
|
||
}
|
||
return Promise.reject(error)
|
||
}
|
||
)
|
||
|
||
function clearAdminSession() {
|
||
window.localStorage.removeItem("admin-token")
|
||
window.localStorage.removeItem("admin-user")
|
||
if (window.location.hash.indexOf("#/login") !== 0) {
|
||
window.location.hash = "#/login"
|
||
}
|
||
}
|
||
|
||
export default request
|
||
', 'admin', sysdate()),
|
||
(920209, 9202, 'dict.js.vm', NULL, 'dict.js.vm', 'const emptyDict = []
|
||
|
||
export default {
|
||
getDictDataByType() {
|
||
return emptyDict
|
||
},
|
||
getDictLabel(type, value) {
|
||
return value === undefined || value === null || value === "" ? "-" : value
|
||
}
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920210, 9202, 'admin-index.vue.vm', NULL, 'admin-index.vue.vm', '<template>
|
||
<div class="page-container">
|
||
<section class="query-panel">
|
||
<div class="toolbar query-toolbar">
|
||
#foreach ($column in $columns)
|
||
#if($column.isQuery == "1")
|
||
#set($javaField=$column.javaField)
|
||
#set($columnComment=$column.columnComment)
|
||
#if($column.htmlType == "input")
|
||
<el-input class="filter-item" placeholder="请输入${columnComment}" clearable v-model="queryParams.${javaField}" @keyup.enter.native="load" />
|
||
#elseif($column.htmlType == "select" || $column.htmlType == "radio")
|
||
<el-select class="filter-item" v-model="queryParams.${javaField}" placeholder="请选择${columnComment}" clearable>
|
||
<el-option
|
||
v-for="item in _dict.getDictDataByType(''${column.dictType}'')"
|
||
:key="item.value"
|
||
:label="item.name"
|
||
:value="item.value" />
|
||
</el-select>
|
||
#elseif($column.htmlType == "datetime")
|
||
<el-date-picker class="filter-item" v-model="queryParams.${javaField}" type="date" value-format="yyyy-MM-dd" placeholder="选择${columnComment}" />
|
||
#end
|
||
#end
|
||
#end
|
||
<el-button type="primary" @click="load">搜索</el-button>
|
||
<el-button @click="reset">重置</el-button>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="action-panel">
|
||
<div class="toolbar action-toolbar">
|
||
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增</el-button>
|
||
<el-popconfirm
|
||
confirm-button-text="确定"
|
||
cancel-button-text="取消"
|
||
icon="el-icon-info"
|
||
icon-color="red"
|
||
title="确定批量删除选中的数据吗?"
|
||
@confirm="delBatch">
|
||
<el-button slot="reference" type="danger" icon="el-icon-delete">批量删除</el-button>
|
||
</el-popconfirm>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="table-panel">
|
||
<el-table class="data-table" :data="${businessName}List" stripe @selection-change="handleSelectionChange">
|
||
<el-table-column type="selection" width="55" />
|
||
<el-table-column prop="${pkColumn.javaField}" label="ID" width="90" />
|
||
#foreach ($column in $columns)
|
||
#if($column.isList == "1")
|
||
#set($javaField=$column.javaField)
|
||
#set($columnComment=$column.columnComment)
|
||
#if($column.htmlType == "datetime")
|
||
<el-table-column prop="${javaField}" label="${columnComment}" align="center" width="180" />
|
||
#elseif($column.htmlType == "imageUpload")
|
||
<el-table-column prop="${javaField}" label="${columnComment}" align="center" width="110">
|
||
<template slot-scope="scope">
|
||
<el-image
|
||
v-if="scope.row.${javaField}"
|
||
class="table-image"
|
||
:src="resolveResourceUrl(scope.row.${javaField})"
|
||
:preview-src-list="[resolveResourceUrl(scope.row.${javaField})]" />
|
||
<span v-else>-</span>
|
||
</template>
|
||
</el-table-column>
|
||
#elseif($column.htmlType == "select" || $column.htmlType == "radio")
|
||
<el-table-column prop="${javaField}" label="${columnComment}" align="center">
|
||
<template slot-scope="scope">
|
||
{{ _dict.getDictLabel(''${column.dictType}'', scope.row.${javaField}) }}
|
||
</template>
|
||
</el-table-column>
|
||
#else
|
||
<el-table-column prop="${javaField}" label="${columnComment}" align="center" />
|
||
#end
|
||
#end
|
||
#end
|
||
<el-table-column label="操作" width="190" align="center">
|
||
<template slot-scope="scope">
|
||
<el-button class="row-action edit-action" type="primary" icon="el-icon-edit" circle @click="handleEdit(scope.row)"></el-button>
|
||
<el-popconfirm
|
||
confirm-button-text="确定"
|
||
cancel-button-text="取消"
|
||
icon="el-icon-info"
|
||
icon-color="red"
|
||
title="确定删除这条数据吗?"
|
||
@confirm="del(scope.row.${pkColumn.javaField})">
|
||
<el-button slot="reference" class="row-action delete-action" type="danger" icon="el-icon-delete" circle></el-button>
|
||
</el-popconfirm>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</section>
|
||
|
||
<section class="pagination-panel">
|
||
<el-pagination
|
||
class="pagination"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
:current-page="queryParams.pageNum"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
:page-size="queryParams.pageSize"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
:total="total" />
|
||
</section>
|
||
|
||
<el-dialog :title="form.${pkColumn.javaField} ? ''编辑${functionName}'' : ''新增${functionName}''" :visible.sync="dialogFormVisible" width="520px">
|
||
<el-form ref="form" :model="form" label-width="110px" size="small">
|
||
#foreach ($column in $columns)
|
||
#if($column.isEdit == "1")
|
||
#set($javaField=$column.javaField)
|
||
#set($columnComment=$column.columnComment)
|
||
<el-form-item label="${columnComment}" prop="${javaField}"#if($column.isRequired == "1") :rules="[{ required: true, message: ''${columnComment}不能为空'', trigger: #if($column.htmlType == "select")''change''#else''blur''#end }]"#end>
|
||
#if($column.htmlType == "input")
|
||
<el-input v-model="form.${javaField}" placeholder="请输入${columnComment}" />
|
||
#elseif($column.htmlType == "textarea")
|
||
<el-input v-model="form.${javaField}" type="textarea" placeholder="请输入${columnComment}" />
|
||
#elseif($column.htmlType == "select" || $column.htmlType == "radio")
|
||
<el-select v-model="form.${javaField}" placeholder="请选择${columnComment}" clearable>
|
||
<el-option
|
||
v-for="item in _dict.getDictDataByType(''${column.dictType}'')"
|
||
:key="item.value"
|
||
:label="item.name"
|
||
:value="item.value" />
|
||
</el-select>
|
||
#elseif($column.htmlType == "datetime")
|
||
<el-date-picker v-model="form.${javaField}" type="date" value-format="yyyy-MM-dd" placeholder="选择${columnComment}" />
|
||
#elseif($column.htmlType == "imageUpload")
|
||
<el-upload
|
||
class="avatar-uploader"
|
||
action="/api/files/upload"
|
||
:show-file-list="false"
|
||
:on-success="(res) => uploadSuccess(''${javaField}'', res)">
|
||
<img v-if="form.${javaField}" :src="resolveResourceUrl(form.${javaField})" class="avatar" />
|
||
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
|
||
</el-upload>
|
||
#elseif($column.htmlType == "fileUpload")
|
||
<el-upload
|
||
action="/api/files/upload"
|
||
:on-success="(res) => uploadSuccess(''${javaField}'', res)">
|
||
<el-button size="small" type="primary">点击上传</el-button>
|
||
</el-upload>
|
||
<div v-if="form.${javaField}" class="file-link">{{ form.${javaField} }}</div>
|
||
#end
|
||
</el-form-item>
|
||
#end
|
||
#end
|
||
</el-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button @click="dialogFormVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="save">确定</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: "${BusinessName}",
|
||
data() {
|
||
return {
|
||
${businessName}List: [],
|
||
total: 0,
|
||
queryParams: {
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
#foreach ($column in $columns)
|
||
#if($column.isQuery == "1")
|
||
${column.javaField}: undefined,
|
||
#end
|
||
#end
|
||
},
|
||
form: {},
|
||
dialogFormVisible: false,
|
||
multipleSelection: []
|
||
}
|
||
},
|
||
created() {
|
||
this.load()
|
||
},
|
||
methods: {
|
||
load() {
|
||
this.request.get("/${moduleName}/${businessName}/page", {
|
||
params: this.queryParams
|
||
}).then(res => {
|
||
const page = res.data || {}
|
||
this.${businessName}List = page.records || []
|
||
this.total = page.total || 0
|
||
})
|
||
},
|
||
save() {
|
||
this.#[[$refs]]#.form.validate(valid => {
|
||
if (!valid) {
|
||
return
|
||
}
|
||
this.request.post("/${moduleName}/${businessName}", this.form).then(res => {
|
||
if (res.code === "200") {
|
||
this.#[[$message]]#.success("保存成功")
|
||
this.dialogFormVisible = false
|
||
this.load()
|
||
} else {
|
||
this.#[[$message]]#.error(res.msg || "保存失败")
|
||
}
|
||
})
|
||
})
|
||
},
|
||
handleAdd() {
|
||
this.form = {}
|
||
this.dialogFormVisible = true
|
||
this.#[[$nextTick]]#(() => this.#[[$refs]]#.form && this.#[[$refs]]#.form.clearValidate())
|
||
},
|
||
handleEdit(row) {
|
||
this.form = JSON.parse(JSON.stringify(row))
|
||
this.dialogFormVisible = true
|
||
this.#[[$nextTick]]#(() => this.#[[$refs]]#.form && this.#[[$refs]]#.form.clearValidate())
|
||
},
|
||
del(${pkColumn.javaField}) {
|
||
this.request.delete("/${moduleName}/${businessName}/" + ${pkColumn.javaField}).then(res => {
|
||
if (res.code === "200") {
|
||
this.#[[$message]]#.success("删除成功")
|
||
this.load()
|
||
} else {
|
||
this.#[[$message]]#.error(res.msg || "删除失败")
|
||
}
|
||
})
|
||
},
|
||
handleSelectionChange(val) {
|
||
this.multipleSelection = val
|
||
},
|
||
delBatch() {
|
||
const ids = this.multipleSelection.map(item => item.${pkColumn.javaField})
|
||
if (!ids.length) {
|
||
this.#[[$message]]#.warning("请先选择要删除的数据")
|
||
return
|
||
}
|
||
this.request.post("/${moduleName}/${businessName}/del/batch", ids).then(res => {
|
||
if (res.code === "200") {
|
||
this.#[[$message]]#.success("批量删除成功")
|
||
this.load()
|
||
} else {
|
||
this.#[[$message]]#.error(res.msg || "批量删除失败")
|
||
}
|
||
})
|
||
},
|
||
reset() {
|
||
this.queryParams = {
|
||
pageNum: 1,
|
||
pageSize: 10
|
||
}
|
||
this.load()
|
||
},
|
||
handleSizeChange(pageSize) {
|
||
this.queryParams.pageSize = pageSize
|
||
this.load()
|
||
},
|
||
handleCurrentChange(pageNum) {
|
||
this.queryParams.pageNum = pageNum
|
||
this.load()
|
||
},
|
||
resolveResourceUrl(url) {
|
||
if (!url) {
|
||
return ""
|
||
}
|
||
if (url.indexOf("http://") === 0 || url.indexOf("https://") === 0 || url.indexOf("//") === 0 || url.indexOf("data:") === 0 || url.indexOf("blob:") === 0) {
|
||
return url
|
||
}
|
||
if (url.indexOf("/api/") === 0) {
|
||
return url
|
||
}
|
||
if (url.indexOf("/files/") === 0) {
|
||
return "/api" + url
|
||
}
|
||
return url
|
||
},
|
||
uploadSuccess(field, res) {
|
||
if (res && res.code === "200" && res.data && res.data.url) {
|
||
this.#[[$set]]#(this.form, field, res.data.url)
|
||
} else {
|
||
this.#[[$message]]#.error("上传失败,请重试")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.page-container {
|
||
min-height: calc(100vh - 72px);
|
||
}
|
||
|
||
.query-panel,
|
||
.action-panel,
|
||
.table-panel,
|
||
.pagination-panel {
|
||
margin-bottom: 8px;
|
||
border: 1px solid #edf0f5;
|
||
border-radius: 4px;
|
||
background: #fff;
|
||
box-shadow: 0 1px 8px rgba(26, 39, 56, .08);
|
||
}
|
||
|
||
.query-panel,
|
||
.action-panel {
|
||
padding: 12px 14px;
|
||
}
|
||
|
||
.table-panel {
|
||
padding: 12px 12px 0;
|
||
}
|
||
|
||
.pagination-panel {
|
||
padding: 10px 12px;
|
||
}
|
||
|
||
.toolbar {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
align-items: center;
|
||
}
|
||
|
||
.query-toolbar .el-button--primary {
|
||
border-color: #cde8f0;
|
||
background: #eaf8fb;
|
||
color: #158a9a;
|
||
}
|
||
|
||
.query-toolbar .el-button--default {
|
||
border-color: #f6e6ba;
|
||
background: #fff8e6;
|
||
color: #c58600;
|
||
}
|
||
|
||
.action-toolbar .el-button--primary {
|
||
border-color: #9cd4ed;
|
||
background: #eefaff;
|
||
color: #0d80bd;
|
||
}
|
||
|
||
.action-toolbar .el-button--danger {
|
||
border-color: #f2c2c7;
|
||
background: #fff3f4;
|
||
color: #d64252;
|
||
}
|
||
|
||
.filter-item {
|
||
width: 220px;
|
||
}
|
||
|
||
.data-table {
|
||
width: 100%;
|
||
color: #606266;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.data-table >>> th {
|
||
height: 48px;
|
||
background: #fff;
|
||
color: #7d8794;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.data-table >>> td {
|
||
height: 58px;
|
||
border-color: #f0f2f5;
|
||
}
|
||
|
||
.data-table >>> .el-table__row--striped td {
|
||
background: #fafafa;
|
||
}
|
||
|
||
.pagination {
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
.row-action {
|
||
width: 30px;
|
||
height: 30px;
|
||
padding: 0;
|
||
border: 0;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.edit-action {
|
||
background: #0b7ec2;
|
||
}
|
||
|
||
.delete-action {
|
||
margin-left: 10px;
|
||
background: #e82f3d;
|
||
}
|
||
|
||
.table-image {
|
||
width: 52px;
|
||
height: 52px;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.avatar-uploader .avatar {
|
||
width: 96px;
|
||
height: 96px;
|
||
display: block;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.avatar-uploader-icon {
|
||
width: 96px;
|
||
height: 96px;
|
||
line-height: 96px;
|
||
border: 1px dashed #d9d9d9;
|
||
border-radius: 4px;
|
||
text-align: center;
|
||
color: #8c939d;
|
||
}
|
||
|
||
.file-link {
|
||
margin-top: 6px;
|
||
color: #606266;
|
||
word-break: break-all;
|
||
}
|
||
</style>
|
||
', 'admin', sysdate());
|
||
|
||
-- Portal frontend template files
|
||
INSERT INTO sys_template_file (template_file_id, template_id, file_name, module_id, file_path, file_content, create_by, create_time) VALUES
|
||
(920301, 9203, 'package.json.vm', NULL, 'package.json.vm', '{
|
||
"name": "${projectFileName}-web",
|
||
"version": "1.0.0",
|
||
"private": true,
|
||
"scripts": {
|
||
"dev": "vue-cli-service serve --host 0.0.0.0",
|
||
"serve": "vue-cli-service serve --host 0.0.0.0",
|
||
"build": "vue-cli-service build"
|
||
},
|
||
"dependencies": {
|
||
"axios": "^0.27.2",
|
||
"core-js": "^3.8.3",
|
||
"echarts": "5.4.0",
|
||
"element-ui": "^2.15.14",
|
||
"vue": "^2.6.14",
|
||
"vue-router": "^3.6.5"
|
||
},
|
||
"devDependencies": {
|
||
"@vue/cli-service": "^4.5.19",
|
||
"vue-template-compiler": "^2.6.14"
|
||
}
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920302, 9203, 'babel.config.js.vm', NULL, 'babel.config.js.vm', 'module.exports = {
|
||
presets: [
|
||
"@vue/cli-plugin-babel/preset"
|
||
]
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920303, 9203, 'vue.config.js.vm', NULL, 'vue.config.js.vm', 'const port = process.env.PORT || 8082
|
||
const apiBaseUrl = process.env.VUE_APP_API_BASE_URL || "http://localhost:8080"
|
||
|
||
module.exports = {
|
||
transpileDependencies: [],
|
||
devServer: {
|
||
port,
|
||
proxy: {
|
||
"/api": {
|
||
target: apiBaseUrl,
|
||
changeOrigin: true,
|
||
pathRewrite: {
|
||
"^/api": ""
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
', 'admin', sysdate()),
|
||
(920304, 9203, 'index.html.vm', NULL, 'index.html.vm', '<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||
<title>${functionName}</title>
|
||
</head>
|
||
<body>
|
||
<noscript>请启用 JavaScript 后访问。</noscript>
|
||
<div id="app"></div>
|
||
</body>
|
||
</html>
|
||
', 'admin', sysdate()),
|
||
(920305, 9203, 'frontend-main.js.vm', NULL, 'frontend-main.js.vm', 'import Vue from "vue"
|
||
import ElementUI from "element-ui"
|
||
import "element-ui/lib/theme-chalk/index.css"
|
||
import App from "./App.vue"
|
||
import router from "./router"
|
||
import request from "./api/request"
|
||
import dict from "./utils/dict"
|
||
import "./style.css"
|
||
|
||
Vue.config.productionTip = false
|
||
Vue.use(ElementUI)
|
||
Vue.prototype.request = request
|
||
Vue.prototype._dict = dict
|
||
|
||
new Vue({
|
||
router,
|
||
render: h => h(App)
|
||
}).#[[$mount]]#("#app")', 'admin', sysdate()),
|
||
(920306, 9203, 'frontend-App.vue.vm', NULL, 'frontend-App.vue.vm', '#set($portalProjectName = $projectName)
|
||
#if(!$portalProjectName || $portalProjectName == "")
|
||
#set($portalProjectName = $functionName)
|
||
#end
|
||
#if(!$portalProjectName || $portalProjectName == "")
|
||
#set($portalProjectName = "Portal")
|
||
#end
|
||
#set($portalHomePath = $defaultFrontendPath)
|
||
#if(!$portalHomePath || $portalHomePath == "")
|
||
#set($portalHomePath = "/${moduleName}/${businessName}")
|
||
#end
|
||
<template>
|
||
<div class="portal-shell">
|
||
<template v-if="!isLoginRoute">
|
||
<header class="top-nav">
|
||
<router-link class="brand" to="${portalHomePath}">
|
||
<span class="brand-logo" aria-hidden="true">APP</span>
|
||
<span>${portalProjectName}</span>
|
||
</router-link>
|
||
|
||
<nav class="portal-nav" aria-label="主导航">
|
||
#if($hasFrontendNavGroups)
|
||
#foreach($group in $frontendNavGroups)
|
||
<div class="portal-nav-group#if($group.hasChildren) has-children#end" :class=''{ active: isNavGroupActive(${group.activePathsLiteral}) }''>
|
||
#if($group.clickable)
|
||
<router-link class="portal-nav-parent" to="${group.path}" exact>${group.name}</router-link>
|
||
#else
|
||
<span class="portal-nav-parent portal-nav-directory">${group.name}</span>
|
||
#end
|
||
#if($group.hasChildren)
|
||
<button class="portal-nav-toggle" type="button" aria-label="展开子菜单">⌄</button>
|
||
<div class="portal-nav-dropdown">
|
||
#foreach($child in $group.children)
|
||
<router-link class="portal-nav-child" to="${child.path}" exact>${child.name}</router-link>
|
||
#end
|
||
</div>
|
||
#end
|
||
</div>
|
||
#end
|
||
#else
|
||
<div class="portal-nav-group">
|
||
<router-link class="portal-nav-parent" to="${portalHomePath}" exact>首页</router-link>
|
||
</div>
|
||
#end
|
||
</nav>
|
||
|
||
<div class="user-area">
|
||
<template v-if="currentUser">
|
||
<button class="user-chip" type="button">
|
||
<span class="avatar">{{ userInitial }}</span>
|
||
<span>{{ currentUserName }}</span>
|
||
</button>
|
||
<button class="logout-link" type="button" @click="logout">退出</button>
|
||
</template>
|
||
<button v-else class="login-link" type="button" @click="goLogin">登录</button>
|
||
</div>
|
||
</header>
|
||
|
||
<section class="hero-banner" aria-label="项目横幅">
|
||
<div class="hero-pattern" aria-hidden="true"></div>
|
||
<h1>${portalProjectName}</h1>
|
||
</section>
|
||
</template>
|
||
|
||
<main :class="[''portal-main'', { ''portal-main-login'': isLoginRoute }]">
|
||
<router-view />
|
||
</main>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: "App",
|
||
data() {
|
||
return {
|
||
currentUser: null
|
||
}
|
||
},
|
||
computed: {
|
||
isLoginRoute() {
|
||
return this.#[[$route]]#.path === "/login"
|
||
},
|
||
currentUserName() {
|
||
return this.currentUser && (this.currentUser.nickname || this.currentUser.username || "User")
|
||
},
|
||
userInitial() {
|
||
const name = this.currentUserName || "U"
|
||
return name.slice(0, 1).toUpperCase()
|
||
}
|
||
},
|
||
mounted() {
|
||
this.loadUser()
|
||
window.addEventListener("portal-user-change", this.loadUser)
|
||
},
|
||
beforeDestroy() {
|
||
window.removeEventListener("portal-user-change", this.loadUser)
|
||
},
|
||
methods: {
|
||
isNavGroupActive(paths) {
|
||
return Array.isArray(paths) && paths.indexOf(this.#[[$route]]#.path) !== -1
|
||
},
|
||
loadUser() {
|
||
const raw = localStorage.getItem("portal-user")
|
||
if (!raw) {
|
||
this.currentUser = null
|
||
return
|
||
}
|
||
try {
|
||
this.currentUser = JSON.parse(raw)
|
||
} catch (error) {
|
||
localStorage.removeItem("portal-user")
|
||
this.currentUser = null
|
||
}
|
||
},
|
||
goLogin() {
|
||
this.#[[$router]]#.push({
|
||
path: "/login",
|
||
query: { redirect: this.#[[$route]]#.fullPath }
|
||
})
|
||
},
|
||
logout() {
|
||
this.request.post("/auth/logout").catch(() => {})
|
||
localStorage.removeItem("portal-token")
|
||
localStorage.removeItem("portal-user")
|
||
window.dispatchEvent(new Event("portal-user-change"))
|
||
this.#[[$router]]#.push("/login")
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
* {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
margin: 0;
|
||
background: #f6f7fb;
|
||
color: #1f2937;
|
||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
|
||
}
|
||
|
||
a {
|
||
color: inherit;
|
||
text-decoration: none;
|
||
}
|
||
|
||
button {
|
||
font: inherit;
|
||
}
|
||
|
||
.portal-shell {
|
||
min-height: 100vh;
|
||
background: #f6f7fb;
|
||
}
|
||
|
||
.top-nav {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 20;
|
||
display: grid;
|
||
grid-template-columns: minmax(180px, auto) minmax(0, 1fr) auto;
|
||
align-items: center;
|
||
gap: 24px;
|
||
min-height: 68px;
|
||
padding: 0 32px;
|
||
color: #111827;
|
||
background: #ffffff;
|
||
border-bottom: 1px solid #e5e7eb;
|
||
box-shadow: 0 1px 0 rgba(15, 23, 42, 0.04);
|
||
}
|
||
|
||
.brand,
|
||
.portal-nav,
|
||
.user-area,
|
||
.user-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.brand {
|
||
gap: 10px;
|
||
min-width: 0;
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.brand span:last-child {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.brand-logo {
|
||
display: inline-grid;
|
||
width: 36px;
|
||
height: 36px;
|
||
flex: 0 0 36px;
|
||
place-items: center;
|
||
border-radius: 8px;
|
||
color: #ffffff;
|
||
background: #17633b;
|
||
font-size: 12px;
|
||
font-weight: 800;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.portal-nav {
|
||
justify-content: center;
|
||
gap: 8px;
|
||
min-width: 0;
|
||
height: 100%;
|
||
overflow: visible;
|
||
}
|
||
|
||
.portal-nav-group {
|
||
position: relative;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
flex: 0 0 auto;
|
||
min-height: 68px;
|
||
}
|
||
|
||
.portal-nav-parent,
|
||
.portal-nav-toggle,
|
||
.portal-nav-child {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
border: 0;
|
||
background: transparent;
|
||
font: inherit;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.portal-nav-parent {
|
||
min-height: 36px;
|
||
padding: 0 14px;
|
||
border-radius: 8px;
|
||
color: #1f2937;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.portal-nav-parent:hover,
|
||
.portal-nav-parent.router-link-active,
|
||
.portal-nav-group.active .portal-nav-parent {
|
||
color: #2563eb;
|
||
background: #eef5ff;
|
||
}
|
||
|
||
.portal-nav-directory {
|
||
cursor: default;
|
||
}
|
||
|
||
.portal-nav-toggle {
|
||
justify-content: center;
|
||
width: 28px;
|
||
height: 28px;
|
||
margin-left: -8px;
|
||
border-radius: 6px;
|
||
color: #64748b;
|
||
}
|
||
|
||
.portal-nav-group:hover .portal-nav-toggle,
|
||
.portal-nav-group:focus-within .portal-nav-toggle,
|
||
.portal-nav-group.active .portal-nav-toggle {
|
||
color: #2563eb;
|
||
background: #eef5ff;
|
||
}
|
||
|
||
.portal-nav-dropdown {
|
||
position: absolute;
|
||
top: calc(100% - 8px);
|
||
left: 0;
|
||
z-index: 30;
|
||
display: none;
|
||
min-width: 168px;
|
||
padding: 8px;
|
||
border: 1px solid #dbe4f0;
|
||
border-radius: 8px;
|
||
background: #ffffff;
|
||
box-shadow: 0 16px 34px rgba(15, 23, 42, 0.14);
|
||
}
|
||
|
||
.portal-nav-group:hover .portal-nav-dropdown,
|
||
.portal-nav-group:focus-within .portal-nav-dropdown {
|
||
display: grid;
|
||
gap: 4px;
|
||
}
|
||
|
||
.portal-nav-child {
|
||
min-height: 34px;
|
||
padding: 0 10px;
|
||
border-radius: 6px;
|
||
color: #334155;
|
||
font-size: 14px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.portal-nav-child:hover,
|
||
.portal-nav-child.router-link-active {
|
||
color: #2563eb;
|
||
background: #eef5ff;
|
||
}
|
||
|
||
.user-area {
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
min-width: 130px;
|
||
}
|
||
|
||
.user-chip,
|
||
.login-link,
|
||
.logout-link {
|
||
border: 1px solid #dbe4f0;
|
||
color: #475569;
|
||
background: #ffffff;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.user-chip {
|
||
gap: 8px;
|
||
padding: 0;
|
||
}
|
||
|
||
.avatar {
|
||
display: inline-grid;
|
||
width: 36px;
|
||
height: 36px;
|
||
place-items: center;
|
||
border: 1px solid #bfdbfe;
|
||
border-radius: 50%;
|
||
color: #1d4ed8;
|
||
background: #eff6ff;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.logout-link,
|
||
.login-link {
|
||
min-height: 32px;
|
||
padding: 0 10px;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.logout-link:hover,
|
||
.login-link:hover {
|
||
color: #2563eb;
|
||
background: #f8fbff;
|
||
}
|
||
|
||
.hero-banner {
|
||
position: relative;
|
||
display: grid;
|
||
min-height: 260px;
|
||
place-items: center;
|
||
overflow: hidden;
|
||
background:
|
||
linear-gradient(132deg, rgba(24, 141, 149, 0.95), rgba(44, 120, 215, 0.88)),
|
||
#168d95;
|
||
}
|
||
|
||
.hero-pattern {
|
||
position: absolute;
|
||
inset: 0;
|
||
opacity: 0.58;
|
||
background:
|
||
linear-gradient(42deg, transparent 0 18%, rgba(255, 255, 255, 0.18) 18% 27%, transparent 27% 100%),
|
||
linear-gradient(132deg, transparent 0 48%, rgba(246, 186, 51, 0.32) 48% 55%, transparent 55% 100%);
|
||
}
|
||
|
||
.hero-banner h1 {
|
||
position: relative;
|
||
z-index: 1;
|
||
max-width: min(900px, calc(100% - 40px));
|
||
margin: 0;
|
||
color: #ffffff;
|
||
font-size: 46px;
|
||
font-weight: 600;
|
||
letter-spacing: 0;
|
||
line-height: 1.2;
|
||
text-align: center;
|
||
text-shadow: 0 12px 28px rgba(15, 23, 42, 0.22);
|
||
}
|
||
|
||
.portal-main {
|
||
width: min(1120px, calc(100% - 48px));
|
||
margin: 0 auto;
|
||
padding: 26px 0 44px;
|
||
}
|
||
|
||
.portal-main-login {
|
||
width: 100%;
|
||
min-height: 100vh;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
@media (max-width: 900px) {
|
||
.top-nav {
|
||
grid-template-columns: 1fr auto;
|
||
gap: 10px;
|
||
height: auto;
|
||
min-height: 56px;
|
||
padding: 10px 16px;
|
||
}
|
||
|
||
.portal-nav {
|
||
grid-column: 1 / -1;
|
||
justify-content: flex-start;
|
||
order: 3;
|
||
gap: 6px;
|
||
overflow-x: auto;
|
||
overflow-y: visible;
|
||
}
|
||
|
||
.portal-nav-group {
|
||
min-height: 38px;
|
||
}
|
||
|
||
.portal-nav-parent {
|
||
min-height: 34px;
|
||
padding: 0 12px;
|
||
}
|
||
|
||
.portal-nav-dropdown {
|
||
position: fixed;
|
||
top: 104px;
|
||
left: 16px;
|
||
right: 16px;
|
||
}
|
||
|
||
.hero-banner {
|
||
min-height: 210px;
|
||
}
|
||
|
||
.hero-banner h1 {
|
||
font-size: 34px;
|
||
}
|
||
|
||
.portal-main {
|
||
width: min(100% - 28px, 1120px);
|
||
}
|
||
}
|
||
</style>
|
||
', 'admin', sysdate()),
|
||
(920307, 9203, 'style.css.vm', NULL, 'style.css.vm', 'body {
|
||
margin: 0;
|
||
background: #f6f7fb;
|
||
color: #1f2937;
|
||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
|
||
}
|
||
|
||
.el-button {
|
||
border-radius: 4px;
|
||
}', 'admin', sysdate()),
|
||
(920308, 9203, 'frontend-router.js.vm', NULL, 'frontend-router.js.vm', 'import Vue from "vue"
|
||
import Router from "vue-router"
|
||
import Login from "@/views/login/index.vue"
|
||
#set($portalDefaultPath = $defaultFrontendPath)
|
||
#if(!$portalDefaultPath || $portalDefaultPath == "")
|
||
#set($portalDefaultPath = "/${moduleName}/${businessName}")
|
||
#end
|
||
#if($hasFrontendRouteItems)
|
||
#foreach($component in $frontendRouteComponents)
|
||
import ${component.componentName} from "${component.componentPath}"
|
||
#end
|
||
#else
|
||
import ${BusinessName} from "@/views/${moduleName}/${businessName}/index.vue"
|
||
#end
|
||
|
||
Vue.use(Router)
|
||
|
||
const router = new Router({
|
||
mode: "hash",
|
||
routes: [
|
||
{
|
||
path: "/",
|
||
redirect: "${portalDefaultPath}"
|
||
},
|
||
{
|
||
path: "/login",
|
||
name: "Login",
|
||
component: Login
|
||
},
|
||
#if($hasFrontendRouteItems)
|
||
#foreach($item in $frontendRouteItems)
|
||
{
|
||
path: "${item.path}",
|
||
name: "${item.code}",
|
||
component: ${item.componentName},
|
||
meta: { requiresAuth: ${item.requiresLogin} }
|
||
}#if($foreach.hasNext),#end
|
||
#end
|
||
#else
|
||
{
|
||
path: "/${moduleName}/${businessName}",
|
||
name: "${BusinessName}",
|
||
component: ${BusinessName},
|
||
meta: { requiresAuth: true }
|
||
}
|
||
#end
|
||
]
|
||
})
|
||
|
||
router.beforeEach((to, from, next) => {
|
||
const requiresAuth = to.matched.some(record => record.meta && record.meta.requiresAuth)
|
||
const hasToken = Boolean(localStorage.getItem("portal-token"))
|
||
|
||
if (requiresAuth && !hasToken) {
|
||
next({
|
||
path: "/login",
|
||
query: { redirect: to.fullPath }
|
||
})
|
||
return
|
||
}
|
||
|
||
next()
|
||
})
|
||
|
||
export default router
|
||
', 'admin', sysdate()),
|
||
(920309, 9203, 'frontend-request.js.vm', NULL, 'frontend-request.js.vm', 'import axios from "axios"
|
||
import { Message } from "element-ui"
|
||
|
||
const request = axios.create({
|
||
baseURL: "/api",
|
||
timeout: 30000
|
||
})
|
||
|
||
request.interceptors.request.use(config => {
|
||
const token = localStorage.getItem("portal-token")
|
||
if (token) {
|
||
config.headers.Authorization = "Bearer " + token
|
||
}
|
||
return config
|
||
})
|
||
|
||
request.interceptors.response.use(
|
||
response => {
|
||
const data = response.data
|
||
if (data && data.code === "401") {
|
||
clearSession()
|
||
Message.error(data.msg || "请先登录")
|
||
return Promise.reject(new Error(data.msg || "Unauthorized"))
|
||
}
|
||
return data
|
||
},
|
||
error => {
|
||
if (error && error.response && error.response.status === 401) {
|
||
clearSession()
|
||
Message.error("请先登录")
|
||
} else {
|
||
Message.error(error && error.message ? error.message : "请求失败")
|
||
}
|
||
return Promise.reject(error)
|
||
}
|
||
)
|
||
|
||
function clearSession() {
|
||
localStorage.removeItem("portal-token")
|
||
localStorage.removeItem("portal-user")
|
||
if (window.location.hash.indexOf("#/login") !== 0) {
|
||
window.location.hash = "#/login"
|
||
}
|
||
}
|
||
|
||
export default request
|
||
', 'admin', sysdate()),
|
||
(920310, 9203, 'frontend-api.js.vm', NULL, 'frontend-api.js.vm', 'import request from "./request"
|
||
|
||
export function list${ClassName}(query) {
|
||
return request({
|
||
url: "/${moduleName}/${businessName}/page",
|
||
method: "get",
|
||
params: query
|
||
})
|
||
}
|
||
|
||
export function get${ClassName}(${pkColumn.javaField}) {
|
||
return request({
|
||
url: "/${moduleName}/${businessName}/" + ${pkColumn.javaField},
|
||
method: "get"
|
||
})
|
||
}', 'admin', sysdate()),
|
||
(920311, 9203, 'frontend-index.vue.vm', NULL, 'frontend-index.vue.vm', '<template>
|
||
#set($designed = $hasCurrentFrontendPageDesign)
|
||
#set($queryColumns = $columns)
|
||
#set($tableColumns = $columns)
|
||
#set($formColumns = $columns)
|
||
#set($cardColumns = $columns)
|
||
#set($cardTitleColumn = false)
|
||
#set($cardImageColumn = false)
|
||
#set($showAdd = false)
|
||
#set($showBatchDelete = false)
|
||
#set($showView = false)
|
||
#set($showEdit = false)
|
||
#set($showDelete = false)
|
||
#set($isCardListLayout = false)
|
||
#set($isSplitListLayout = false)
|
||
#set($showQuery = true)
|
||
#set($showToolbar = true)
|
||
#set($showForm = true)
|
||
#set($renderForm = true)
|
||
#set($currentUserOnly = false)
|
||
#set($isDetailPage = $pageDesignIsDetail)
|
||
#set($detailColumns = $columns)
|
||
#set($toolbarBusinessActions = $pageDesignToolbarBusinessActions)
|
||
#set($rowBusinessActions = $pageDesignRowBusinessActions)
|
||
#set($allPageBusinessActions = $pageDesignBusinessActions)
|
||
#set($hasToolbarBusinessActions = $hasPageDesignToolbarBusinessActions)
|
||
#set($hasRowBusinessActions = $hasPageDesignRowBusinessActions)
|
||
#set($topBusinessBlocks = $pageDesignTopBusinessBlocks)
|
||
#set($bottomBusinessBlocks = $pageDesignBottomBusinessBlocks)
|
||
#set($embeddedBusinessBlocks = $pageDesignEmbeddedBusinessBlocks)
|
||
#if($allowAdd == ''1'')
|
||
#set($showAdd = true)
|
||
#end
|
||
#if($allowRemove == ''1'')
|
||
#set($showBatchDelete = true)
|
||
#set($showDelete = true)
|
||
#end
|
||
#if($allowEdit == ''1'')
|
||
#set($showEdit = true)
|
||
#end
|
||
#if($designed)
|
||
#set($queryColumns = $pageDesignQueryColumns)
|
||
#set($tableColumns = $pageDesignTableColumns)
|
||
#set($formColumns = $pageDesignFormColumns)
|
||
#set($cardColumns = $pageDesignCardColumns)
|
||
#set($cardTitleColumn = $pageDesignCardTitleColumn)
|
||
#set($cardImageColumn = $pageDesignCardImageColumn)
|
||
#set($showAdd = $pageDesignAllowAdd)
|
||
#set($showBatchDelete = $pageDesignAllowBatchDelete)
|
||
#set($showView = $pageDesignAllowView)
|
||
#set($showEdit = $pageDesignAllowEdit)
|
||
#set($showDelete = $pageDesignAllowDelete)
|
||
#set($isCardListLayout = $pageDesignIsCardList)
|
||
#set($isSplitListLayout = $pageDesignIsSplitList)
|
||
#set($showQuery = $pageDesignShowQuery)
|
||
#set($showToolbar = $pageDesignShowToolbar)
|
||
#set($showForm = $pageDesignShowForm)
|
||
#set($currentUserOnly = $pageDesignIsCurrentUserList)
|
||
#set($detailColumns = $pageDesignFormColumns)
|
||
#end
|
||
#if(!$showToolbar)
|
||
#set($showAdd = false)
|
||
#set($showBatchDelete = false)
|
||
#end
|
||
#if($isDetailPage)
|
||
#set($showQuery = false)
|
||
#set($showToolbar = false)
|
||
#set($showAdd = false)
|
||
#set($showBatchDelete = false)
|
||
#set($showView = false)
|
||
#set($showEdit = false)
|
||
#set($showDelete = false)
|
||
#set($showForm = false)
|
||
#set($renderForm = false)
|
||
#if($detailColumns.size() == 0)
|
||
#set($detailColumns = $columns)
|
||
#end
|
||
#end
|
||
#set($renderForm = $showForm)
|
||
#if($showAdd || $showEdit)
|
||
#set($renderForm = true)
|
||
#end
|
||
<section class="portal-page">
|
||
#if($hasPageDesignTopBusinessBlocks)
|
||
<section class="embedded-business-blocks top-business-blocks">
|
||
#foreach($block in $topBusinessBlocks)
|
||
<div class="embedded-business-block__item embedded-business-block__item--span-${block.span}">
|
||
<${block.componentName} />
|
||
</div>
|
||
#end
|
||
</section>
|
||
#end
|
||
|
||
<section class="content-board">
|
||
<div class="feature-card">
|
||
<div class="feature-illustration" aria-hidden="true">
|
||
<div class="device-face">
|
||
<span></span>
|
||
<span></span>
|
||
</div>
|
||
</div>
|
||
<div class="feature-copy">
|
||
<span>精选推荐</span>
|
||
<h2>${functionName}</h2>
|
||
<p>围绕资源管理、活动公告和玩家社区内容,快速维护当前模块的数据。</p>
|
||
</div>
|
||
</div>
|
||
|
||
<aside class="notice-panel">
|
||
<div class="notice-tabs">
|
||
<button
|
||
v-for="tab in noticeTabs"
|
||
:key="tab"
|
||
type="button"
|
||
:class="{ active: activeTab === tab }"
|
||
@click="activeTab = tab">
|
||
{{ tab }}
|
||
</button>
|
||
</div>
|
||
<ul class="notice-list">
|
||
<li v-for="notice in notices" :key="notice.title">
|
||
<strong>{{ notice.title }}</strong>
|
||
<span><i class="el-icon-time"></i>{{ notice.time }}</span>
|
||
</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<section class="data-panel#if($isSplitListLayout) split-data-panel#end">
|
||
<div class="panel-heading">
|
||
<div>
|
||
<span class="section-kicker">资源数据</span>
|
||
<h2>${functionName}管理</h2>
|
||
</div>
|
||
<div class="panel-actions">
|
||
#if($showToolbar)
|
||
#if($showAdd)
|
||
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增</el-button>
|
||
#end
|
||
#if($showBatchDelete)
|
||
<el-popconfirm
|
||
confirm-button-text="确定"
|
||
cancel-button-text="取消"
|
||
icon="el-icon-info"
|
||
icon-color="red"
|
||
title="确定批量删除选中的数据吗?"
|
||
@confirm="delBatch">
|
||
<el-button slot="reference" type="danger" icon="el-icon-delete">批量删除</el-button>
|
||
</el-popconfirm>
|
||
#end
|
||
#foreach($action in $toolbarBusinessActions)
|
||
#set($buttonLabel = $action.buttonLabel)
|
||
#if(!$buttonLabel)#set($buttonLabel = $action.name)#end
|
||
#set($buttonType = $action.buttonType)
|
||
#if(!$buttonType)#set($buttonType = "primary")#end
|
||
#set($buttonIcon = $action.buttonIcon)
|
||
#if(!$buttonIcon)#set($buttonIcon = "el-icon-position")#end
|
||
<el-button type="${buttonType}" plain icon="${buttonIcon}" @click="handleBusinessAction(''${action.code}'')">${buttonLabel}</el-button>
|
||
#end
|
||
#end
|
||
</div>
|
||
</div>
|
||
|
||
#if($isDetailPage)
|
||
<div class="detail-view" v-loading="detailLoading">
|
||
<div v-if="detailLoaded && !detailRecord.${pkColumn.javaField}" class="empty-detail">
|
||
<i class="el-icon-document"></i>
|
||
<span>暂无详情数据</span>
|
||
</div>
|
||
<template v-else>
|
||
<dl class="detail-grid">
|
||
#foreach ($column in $detailColumns)
|
||
#set($javaField=$column.javaField)
|
||
#set($columnComment=$column.columnComment)
|
||
#set($remoteSelect=$qingRemoteSelectFields.contains($javaField))
|
||
<div class="detail-field#if($column.htmlType == "textarea") wide#end">
|
||
<dt>${columnComment}</dt>
|
||
<dd>
|
||
#if($column.htmlType == "imageUpload")
|
||
<el-image
|
||
v-if="detailRecord.${javaField}"
|
||
class="detail-image"
|
||
:src="resolveResourceUrl(detailRecord.${javaField})"
|
||
:preview-src-list="[resolveResourceUrl(detailRecord.${javaField})]" />
|
||
<span v-else>-</span>
|
||
#elseif($column.htmlType == "select" || $column.htmlType == "radio")
|
||
#if($remoteSelect)
|
||
{{ getOptionLabel(''${javaField}'', detailRecord.${javaField}) }}
|
||
#else
|
||
{{ _dict.getDictLabel(''${column.dictType}'', detailRecord.${javaField}) || ''-'' }}
|
||
#end
|
||
#else
|
||
{{ formatDetailValue(detailRecord.${javaField}) }}
|
||
#end
|
||
</dd>
|
||
</div>
|
||
#end
|
||
</dl>
|
||
#if($hasRowBusinessActions)
|
||
<div class="detail-actions">
|
||
#foreach($action in $rowBusinessActions)
|
||
#set($buttonLabel = $action.buttonLabel)
|
||
#if(!$buttonLabel)#set($buttonLabel = $action.name)#end
|
||
#set($buttonType = $action.buttonType)
|
||
#if(!$buttonType)#set($buttonType = "primary")#end
|
||
#set($buttonIcon = $action.buttonIcon)
|
||
#if(!$buttonIcon)#set($buttonIcon = "el-icon-position")#end
|
||
<el-button type="${buttonType}" icon="${buttonIcon}" @click="handleBusinessAction(''${action.code}'', detailRecord)">${buttonLabel}</el-button>
|
||
#end
|
||
</div>
|
||
#end
|
||
</template>
|
||
</div>
|
||
#else
|
||
|
||
#if($showQuery)
|
||
<div class="filter-bar">
|
||
#foreach ($column in $queryColumns)
|
||
#set($renderQueryField = false)
|
||
#if($designed)
|
||
#set($renderQueryField = true)
|
||
#elseif($column.isQuery == "1")
|
||
#set($renderQueryField = true)
|
||
#end
|
||
#if($renderQueryField)
|
||
#set($javaField=$column.javaField)
|
||
#set($columnComment=$column.columnComment)
|
||
#set($remoteSelect=$qingRemoteSelectFields.contains($javaField))
|
||
#if($column.htmlType == "input")
|
||
<el-input class="filter-item" placeholder="请输入${columnComment}" clearable v-model="queryParams.${javaField}" @keyup.enter.native="load" />
|
||
#elseif($column.htmlType == "select" || $column.htmlType == "radio")
|
||
<el-select class="filter-item" v-model="queryParams.${javaField}" placeholder="请选择${columnComment}" clearable>
|
||
#if($remoteSelect)
|
||
<el-option
|
||
v-for="item in getSelectOptions(''${javaField}'')"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value" />
|
||
#else
|
||
<el-option
|
||
v-for="item in _dict.getDictDataByType(''${column.dictType}'')"
|
||
:key="item.value"
|
||
:label="item.name"
|
||
:value="item.value" />
|
||
#end
|
||
</el-select>
|
||
#elseif($column.htmlType == "datetime")
|
||
<el-date-picker class="filter-item" v-model="queryParams.${javaField}" type="date" value-format="yyyy-MM-dd" placeholder="选择${columnComment}" />
|
||
#end
|
||
#end
|
||
#end
|
||
<el-button type="primary" icon="el-icon-search" @click="load">搜索</el-button>
|
||
<el-button icon="el-icon-refresh" @click="reset">重置</el-button>
|
||
</div>
|
||
#end
|
||
|
||
#if($isCardListLayout)
|
||
<div class="resource-card-grid">
|
||
<article v-for="item in ${businessName}List" :key="item.${pkColumn.javaField}" class="resource-card-item">
|
||
#if($showBatchDelete)
|
||
<el-checkbox class="resource-card-check" v-model="item.__checked" @change="handleCardSelectionChange" />
|
||
#end
|
||
<div class="resource-card-body">
|
||
#if($cardImageColumn)
|
||
<el-image
|
||
v-if="item.${cardImageColumn.javaField}"
|
||
class="resource-card-image"
|
||
:src="resolveResourceUrl(item.${cardImageColumn.javaField})"
|
||
:preview-src-list="[resolveResourceUrl(item.${cardImageColumn.javaField})]" />
|
||
#end
|
||
#if($cardTitleColumn)
|
||
<h3 class="resource-card-title">{{ item.${cardTitleColumn.javaField} || ''-'' }}</h3>
|
||
#end
|
||
#foreach ($column in $cardColumns)
|
||
#set($renderCardField = false)
|
||
#if($designed)
|
||
#set($renderCardField = true)
|
||
#elseif($column.isList == "1")
|
||
#set($renderCardField = true)
|
||
#end
|
||
#if($renderCardField)
|
||
#set($javaField=$column.javaField)
|
||
#set($columnComment=$column.columnComment)
|
||
#set($remoteSelect=$qingRemoteSelectFields.contains($javaField))
|
||
#if($column.htmlType == "imageUpload")
|
||
<el-image
|
||
v-if="item.${javaField}"
|
||
class="resource-card-image"
|
||
:src="resolveResourceUrl(item.${javaField})"
|
||
:preview-src-list="[resolveResourceUrl(item.${javaField})]" />
|
||
#elseif($column.htmlType == "select" || $column.htmlType == "radio")
|
||
<div class="resource-card-field">
|
||
<span>${columnComment}</span>
|
||
#if($remoteSelect)
|
||
<strong>{{ getOptionLabel(''${javaField}'', item.${javaField}) }}</strong>
|
||
#else
|
||
<strong>{{ _dict.getDictLabel(''${column.dictType}'', item.${javaField}) || ''-'' }}</strong>
|
||
#end
|
||
</div>
|
||
#else
|
||
<div class="resource-card-field">
|
||
<span>${columnComment}</span>
|
||
<strong>{{ item.${javaField} || ''-'' }}</strong>
|
||
</div>
|
||
#end
|
||
#end
|
||
#end
|
||
</div>
|
||
#if($showView || $showEdit || $showDelete || $hasRowBusinessActions)
|
||
<div class="resource-card-actions">
|
||
#if($showView)
|
||
<el-button type="text" icon="el-icon-view" @click="handleView(item)">查看</el-button>
|
||
#end
|
||
#if($showEdit)
|
||
<el-button type="text" icon="el-icon-edit" @click="handleEdit(item)">编辑</el-button>
|
||
#end
|
||
#if($showDelete)
|
||
<el-popconfirm
|
||
confirm-button-text="确定"
|
||
cancel-button-text="取消"
|
||
icon="el-icon-info"
|
||
icon-color="red"
|
||
title="确定删除这条数据吗?"
|
||
@confirm="del(item.${pkColumn.javaField})">
|
||
<el-button slot="reference" type="text" icon="el-icon-delete" class="danger-text">删除</el-button>
|
||
</el-popconfirm>
|
||
#end
|
||
#foreach($action in $rowBusinessActions)
|
||
#set($buttonLabel = $action.buttonLabel)
|
||
#if(!$buttonLabel)#set($buttonLabel = $action.name)#end
|
||
#set($buttonType = $action.buttonType)
|
||
#if(!$buttonType)#set($buttonType = "text")#end
|
||
#set($buttonIcon = $action.buttonIcon)
|
||
#if(!$buttonIcon)#set($buttonIcon = "el-icon-position")#end
|
||
<el-button type="${buttonType}" icon="${buttonIcon}" @click="handleBusinessAction(''${action.code}'', item)">${buttonLabel}</el-button>
|
||
#end
|
||
</div>
|
||
#end
|
||
</article>
|
||
</div>
|
||
#else
|
||
<el-table :data="${businessName}List" border stripe class="resource-table" @selection-change="handleSelectionChange">
|
||
<el-table-column type="selection" width="55" />
|
||
#if(!$designed)
|
||
<el-table-column prop="${pkColumn.javaField}" label="ID" width="90" />
|
||
#end
|
||
#foreach ($column in $tableColumns)
|
||
#set($renderTableField = false)
|
||
#if($designed)
|
||
#set($renderTableField = true)
|
||
#elseif($column.isList == "1")
|
||
#set($renderTableField = true)
|
||
#end
|
||
#if($renderTableField)
|
||
#set($javaField=$column.javaField)
|
||
#set($columnComment=$column.columnComment)
|
||
#set($remoteSelect=$qingRemoteSelectFields.contains($javaField))
|
||
#if($column.htmlType == "datetime")
|
||
<el-table-column prop="${javaField}" label="${columnComment}" align="center" width="180" />
|
||
#elseif($column.htmlType == "imageUpload")
|
||
<el-table-column prop="${javaField}" label="${columnComment}" align="center" width="110">
|
||
<template slot-scope="scope">
|
||
<el-image
|
||
v-if="scope.row.${javaField}"
|
||
class="table-image"
|
||
:src="resolveResourceUrl(scope.row.${javaField})"
|
||
:preview-src-list="[resolveResourceUrl(scope.row.${javaField})]" />
|
||
<span v-else>-</span>
|
||
</template>
|
||
</el-table-column>
|
||
#elseif($column.htmlType == "select" || $column.htmlType == "radio")
|
||
<el-table-column prop="${javaField}" label="${columnComment}" align="center">
|
||
<template slot-scope="scope">
|
||
#if($remoteSelect)
|
||
{{ getOptionLabel(''${javaField}'', scope.row.${javaField}) }}
|
||
#else
|
||
{{ _dict.getDictLabel(''${column.dictType}'', scope.row.${javaField}) }}
|
||
#end
|
||
</template>
|
||
</el-table-column>
|
||
#else
|
||
<el-table-column prop="${javaField}" label="${columnComment}" align="center" />
|
||
#end
|
||
#end
|
||
#end
|
||
#if($showView || $showEdit || $showDelete || $hasRowBusinessActions)
|
||
<el-table-column label="操作" width="230" align="center">
|
||
<template slot-scope="scope">
|
||
#if($showView)
|
||
<el-button type="text" icon="el-icon-view" @click="handleView(scope.row)">查看</el-button>
|
||
#end
|
||
#if($showEdit)
|
||
<el-button type="text" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
|
||
#end
|
||
#if($showDelete)
|
||
<el-popconfirm
|
||
confirm-button-text="确定"
|
||
cancel-button-text="取消"
|
||
icon="el-icon-info"
|
||
icon-color="red"
|
||
title="确定删除这条数据吗?"
|
||
@confirm="del(scope.row.${pkColumn.javaField})">
|
||
<el-button slot="reference" type="text" icon="el-icon-delete" class="danger-text">删除</el-button>
|
||
</el-popconfirm>
|
||
#end
|
||
#foreach($action in $rowBusinessActions)
|
||
#set($buttonLabel = $action.buttonLabel)
|
||
#if(!$buttonLabel)#set($buttonLabel = $action.name)#end
|
||
#set($buttonType = $action.buttonType)
|
||
#if(!$buttonType)#set($buttonType = "text")#end
|
||
#set($buttonIcon = $action.buttonIcon)
|
||
#if(!$buttonIcon)#set($buttonIcon = "el-icon-position")#end
|
||
<el-button type="${buttonType}" icon="${buttonIcon}" @click="handleBusinessAction(''${action.code}'', scope.row)">${buttonLabel}</el-button>
|
||
#end
|
||
</template>
|
||
</el-table-column>
|
||
#end
|
||
</el-table>
|
||
#end
|
||
|
||
<el-pagination
|
||
class="pagination"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
:current-page="queryParams.pageNum"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
:page-size="queryParams.pageSize"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
:total="total" />
|
||
#end
|
||
</section>
|
||
|
||
#if($hasPageDesignBottomBusinessBlocks)
|
||
<section class="embedded-business-blocks bottom-business-blocks">
|
||
#foreach($block in $bottomBusinessBlocks)
|
||
<div class="embedded-business-block__item embedded-business-block__item--span-${block.span}">
|
||
<${block.componentName} />
|
||
</div>
|
||
#end
|
||
</section>
|
||
#end
|
||
|
||
#if($renderForm)
|
||
<el-dialog :title="viewing ? ''查看${functionName}'' : (form.${pkColumn.javaField} ? ''编辑${functionName}'' : ''新增${functionName}'')" :visible.sync="dialogFormVisible" width="520px">
|
||
<el-form ref="form" :model="form" label-width="110px" size="small" :disabled="viewing">
|
||
#foreach ($column in $formColumns)
|
||
#set($renderFormField = false)
|
||
#if($designed)
|
||
#set($renderFormField = true)
|
||
#elseif($column.isEdit == "1")
|
||
#set($renderFormField = true)
|
||
#end
|
||
#if($renderFormField)
|
||
#set($javaField=$column.javaField)
|
||
#set($columnComment=$column.columnComment)
|
||
#set($remoteSelect=$qingRemoteSelectFields.contains($javaField))
|
||
#set($autoIncrementField=$column.isIncrement == "1")
|
||
<el-form-item#if($autoIncrementField) v-if="form.${javaField}"#end label="${columnComment}" prop="${javaField}"#if($column.isRequired == "1" && !$autoIncrementField) :rules="[{ required: true, message: ''${columnComment}不能为空'', trigger: #if($column.htmlType == "select")''change''#else''blur''#end }]"#end>
|
||
#if($column.htmlType == "input")
|
||
<el-input v-model="form.${javaField}"#if($autoIncrementField) disabled#end placeholder="请输入${columnComment}" />
|
||
#elseif($column.htmlType == "textarea")
|
||
<el-input v-model="form.${javaField}" type="textarea" placeholder="请输入${columnComment}" />
|
||
#elseif($column.htmlType == "select" || $column.htmlType == "radio")
|
||
<el-select v-model="form.${javaField}" placeholder="请选择${columnComment}" clearable>
|
||
#if($remoteSelect)
|
||
<el-option
|
||
v-for="item in getSelectOptions(''${javaField}'')"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value" />
|
||
#else
|
||
<el-option
|
||
v-for="item in _dict.getDictDataByType(''${column.dictType}'')"
|
||
:key="item.value"
|
||
:label="item.name"
|
||
:value="item.value" />
|
||
#end
|
||
</el-select>
|
||
#elseif($column.htmlType == "datetime")
|
||
<el-date-picker v-model="form.${javaField}" type="date" value-format="yyyy-MM-dd" placeholder="选择${columnComment}" />
|
||
#elseif($column.htmlType == "imageUpload")
|
||
<el-upload
|
||
class="avatar-uploader"
|
||
action="/api/files/upload"
|
||
:show-file-list="false"
|
||
:on-success="(res) => uploadSuccess(''${javaField}'', res)">
|
||
<img v-if="form.${javaField}" :src="resolveResourceUrl(form.${javaField})" class="avatar" />
|
||
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
|
||
</el-upload>
|
||
#elseif($column.htmlType == "fileUpload")
|
||
<el-upload
|
||
action="/api/files/upload"
|
||
:on-success="(res) => uploadSuccess(''${javaField}'', res)">
|
||
<el-button size="small" type="primary">点击上传</el-button>
|
||
</el-upload>
|
||
<div v-if="form.${javaField}" class="file-link">{{ form.${javaField} }}</div>
|
||
#end
|
||
</el-form-item>
|
||
#end
|
||
#end
|
||
</el-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button @click="dialogFormVisible = false">取消</el-button>
|
||
<el-button v-if="!viewing" type="primary" @click="save">确定</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
#end
|
||
|
||
<el-dialog
|
||
:title="businessActionDialogTitle"
|
||
:visible.sync="businessActionDialogVisible"
|
||
width="520px"
|
||
append-to-body
|
||
:show-close="!businessActionSubmitting"
|
||
:close-on-click-modal="false"
|
||
:close-on-press-escape="!businessActionSubmitting"
|
||
@closed="resetBusinessActionDialog">
|
||
<p v-if="activeBusinessAction && activeBusinessAction.confirmMessage" class="business-action-hint">
|
||
{{ activeBusinessAction.confirmMessage }}
|
||
</p>
|
||
<el-form
|
||
ref="businessActionForm"
|
||
:model="businessActionForm"
|
||
label-width="110px"
|
||
size="small">
|
||
<el-form-item
|
||
v-for="field in businessActionInputFields"
|
||
:key="field.field"
|
||
:label="field.label"
|
||
:prop="field.field"
|
||
:rules="[{ required: true, message: field.label + ''不能为空'', trigger: field.htmlType === ''select'' ? ''change'' : ''blur'' }]">
|
||
<el-date-picker
|
||
v-if="field.htmlType === ''datetime''"
|
||
v-model="businessActionForm[field.field]"
|
||
type="date"
|
||
value-format="yyyy-MM-dd"
|
||
:placeholder="''请选择'' + field.label" />
|
||
<el-select
|
||
v-else-if="field.htmlType === ''select'' && field.dictType"
|
||
v-model="businessActionForm[field.field]"
|
||
clearable
|
||
:placeholder="''请选择'' + field.label">
|
||
<el-option
|
||
v-for="option in getBusinessActionFieldOptions(field)"
|
||
:key="option.value"
|
||
:label="option.label"
|
||
:value="option.value" />
|
||
</el-select>
|
||
<el-input
|
||
v-else-if="field.htmlType === ''textarea''"
|
||
v-model="businessActionForm[field.field]"
|
||
type="textarea"
|
||
:rows="3"
|
||
:placeholder="''请输入'' + field.label" />
|
||
<el-input
|
||
v-else
|
||
v-model="businessActionForm[field.field]"
|
||
:placeholder="''请输入'' + field.label" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button
|
||
:disabled="businessActionSubmitting"
|
||
@click="businessActionDialogVisible = false">
|
||
取消
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
:loading="businessActionSubmitting"
|
||
@click="submitBusinessAction">
|
||
{{ activeBusinessAction && activeBusinessAction.buttonLabel ? activeBusinessAction.buttonLabel : ''确认执行'' }}
|
||
</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
</section>
|
||
</template>
|
||
|
||
<script>
|
||
#foreach($block in $embeddedBusinessBlocks)
|
||
import ${block.componentName} from "${block.relativeImportPath}"
|
||
#end
|
||
|
||
export default {
|
||
name: "${BusinessName}",
|
||
#if($hasPageDesignEmbeddedBusinessBlocks)
|
||
components: {
|
||
#foreach($block in $embeddedBusinessBlocks)
|
||
${block.componentName}#if($foreach.hasNext),#end
|
||
#end
|
||
},
|
||
#end
|
||
data() {
|
||
return {
|
||
${businessName}List: [],
|
||
total: 0,
|
||
queryParams: {
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
#if($currentUserOnly)
|
||
__currentUserOnly: true,
|
||
#end
|
||
#foreach ($column in $queryColumns)
|
||
#set($renderQueryParam = false)
|
||
#if(!$showQuery)
|
||
#set($renderQueryParam = false)
|
||
#elseif($designed)
|
||
#set($renderQueryParam = true)
|
||
#elseif($column.isQuery == "1")
|
||
#set($renderQueryParam = true)
|
||
#end
|
||
#if($renderQueryParam)
|
||
${column.javaField}: undefined,
|
||
#end
|
||
#end
|
||
},
|
||
#if($isDetailPage)
|
||
detailRecord: {},
|
||
detailLoading: false,
|
||
detailLoaded: false,
|
||
#end
|
||
form: {},
|
||
viewing: false,
|
||
dialogFormVisible: false,
|
||
businessActionDialogVisible: false,
|
||
businessActionSubmitting: false,
|
||
activeBusinessAction: null,
|
||
businessActionBasePayload: {},
|
||
businessActionForm: {},
|
||
businessActionInputFields: [],
|
||
multipleSelection: [],
|
||
remoteOptionMap: {},
|
||
remoteSelectConfig: {
|
||
#foreach($remote in $qingRemoteSelects)
|
||
${remote.field}: {
|
||
path: "${remote.path}",
|
||
valueField: "${remote.valueField}",
|
||
labelField: "${remote.labelField}"
|
||
}#if($foreach.hasNext),#end
|
||
#end
|
||
},
|
||
activeTab: "最新公告",
|
||
noticeTabs: ["最新公告", "热门赛事", "热门攻略", "热门帖子"],
|
||
notices: [
|
||
{ title: "《${functionName}》资源内容更新与支持", time: "2025-06-24 16:50:40" },
|
||
{ title: "社区活动与资源维护窗口预告", time: "2025-06-24 16:48:30" },
|
||
{ title: "新内容发布和玩家反馈整理", time: "2025-06-24 10:30:24" },
|
||
{ title: "版本资料、攻略和周边内容同步", time: "2025-06-23 19:20:12" }
|
||
],
|
||
businessActionMap: {
|
||
#foreach($action in $allPageBusinessActions)
|
||
"${action.code}": {
|
||
name: "${action.name}",
|
||
buttonLabel: "${action.buttonLabel}",
|
||
method: "${action.method}",
|
||
path: "${action.path}",
|
||
successMessage: "${action.successMessage}",
|
||
refreshTarget: "${action.refreshTarget}",
|
||
confirmMessage: "${action.confirmMessage}",
|
||
requestFields: [#foreach($field in $action.requestFields)"${field}"#if($foreach.hasNext), #end#end],
|
||
currentUserFields: [#foreach($field in $action.currentUserFields)"${field}"#if($foreach.hasNext), #end#end],
|
||
inputFields: [
|
||
#foreach($field in $action.inputFields)
|
||
{
|
||
field: "${field.field}",
|
||
label: "${field.label}",
|
||
htmlType: "${field.htmlType}",
|
||
dictType: "${field.dictType}"
|
||
}#if($foreach.hasNext),#end
|
||
#end
|
||
]
|
||
}#if($foreach.count != $allPageBusinessActions.size()),#end
|
||
#end
|
||
},
|
||
routeActionMap: {
|
||
#foreach($routeAction in $pageDesignRouteActionEntries)
|
||
"${routeAction.key}": {
|
||
path: "${routeAction.path}",
|
||
paramMap: {#set($paramMap = $routeAction.paramMap)#foreach($param in $paramMap.entrySet())"${param.key}":"${param.value}"#if($foreach.count != $paramMap.size()),#end#end}
|
||
}#if($foreach.count != $pageDesignRouteActionEntries.size()),#end
|
||
#end
|
||
}
|
||
}
|
||
},
|
||
computed: {
|
||
businessActionDialogTitle() {
|
||
if (!this.activeBusinessAction) {
|
||
return "业务操作"
|
||
}
|
||
return this.activeBusinessAction.buttonLabel || this.activeBusinessAction.name || "业务操作"
|
||
}
|
||
},
|
||
created() {
|
||
this.loadRemoteOptions()
|
||
#if($isDetailPage)
|
||
this.loadDetail()
|
||
#else
|
||
this.load()
|
||
#end
|
||
},
|
||
methods: {
|
||
loadRemoteOptions() {
|
||
Object.keys(this.remoteSelectConfig).forEach(field => {
|
||
const config = this.remoteSelectConfig[field]
|
||
this.request.get(config.path, {
|
||
params: { pageNum: 1, pageSize: 999 }
|
||
}).then(res => {
|
||
const data = res.data || {}
|
||
const records = Array.isArray(data) ? data : (data.records || [])
|
||
const options = records.map(item => ({
|
||
value: item[config.valueField],
|
||
label: item[config.labelField]
|
||
}))
|
||
this.#[[$set]]#(this.remoteOptionMap, field, options)
|
||
})
|
||
})
|
||
},
|
||
getSelectOptions(field) {
|
||
return this.remoteOptionMap[field] || []
|
||
},
|
||
getOptionLabel(field, value) {
|
||
const option = this.getSelectOptions(field).find(item => String(item.value) === String(value))
|
||
if (option) {
|
||
return option.label
|
||
}
|
||
return value === undefined || value === null || value === "" ? "-" : value
|
||
},
|
||
#if(!$isDetailPage)
|
||
load() {
|
||
this.request.get("/${moduleName}/${businessName}/page", {
|
||
params: this.queryParams
|
||
}).then(res => {
|
||
const page = res.data || {}
|
||
this.${businessName}List = page.records || []
|
||
this.total = page.total || 0
|
||
})
|
||
},
|
||
#end
|
||
#if($isDetailPage)
|
||
resolveDetailId() {
|
||
const params = this.#[[$route]]# && this.#[[$route]]#.params ? this.#[[$route]]#.params : {}
|
||
return params.id || params.${pkColumn.javaField} || ""
|
||
},
|
||
loadDetail() {
|
||
const detailId = this.resolveDetailId()
|
||
if (!detailId) {
|
||
this.detailRecord = {}
|
||
this.form = {}
|
||
this.detailLoaded = true
|
||
return
|
||
}
|
||
this.detailLoading = true
|
||
this.request.get("/${moduleName}/${businessName}/" + detailId).then(res => {
|
||
this.detailRecord = res.data || {}
|
||
this.form = JSON.parse(JSON.stringify(this.detailRecord))
|
||
this.detailLoaded = true
|
||
}).finally(() => {
|
||
this.detailLoading = false
|
||
})
|
||
},
|
||
formatDetailValue(value) {
|
||
return value === undefined || value === null || value === "" ? "-" : value
|
||
},
|
||
#end
|
||
save() {
|
||
this.#[[$refs]]#.form.validate(valid => {
|
||
if (!valid) {
|
||
return
|
||
}
|
||
this.request.post("/${moduleName}/${businessName}", this.form).then(res => {
|
||
if (res.code === "200") {
|
||
this.#[[$message]]#.success("保存成功")
|
||
this.dialogFormVisible = false
|
||
this.load()
|
||
} else {
|
||
this.#[[$message]]#.error(res.msg || "保存失败")
|
||
}
|
||
})
|
||
})
|
||
},
|
||
handleAdd() {
|
||
if (this.handleConfiguredRoute("toolbar:create")) {
|
||
return
|
||
}
|
||
this.form = {}
|
||
this.viewing = false
|
||
this.dialogFormVisible = true
|
||
this.#[[$nextTick]]#(() => this.#[[$refs]]#.form && this.#[[$refs]]#.form.clearValidate())
|
||
},
|
||
handleView(row) {
|
||
if (this.handleConfiguredRoute("table:view", row)) {
|
||
return
|
||
}
|
||
if (this.handleConfiguredRoute("cardActions:view", row)) {
|
||
return
|
||
}
|
||
this.form = JSON.parse(JSON.stringify(row))
|
||
this.viewing = true
|
||
this.dialogFormVisible = true
|
||
},
|
||
handleEdit(row) {
|
||
if (this.handleConfiguredRoute("table:edit", row)) {
|
||
return
|
||
}
|
||
if (this.handleConfiguredRoute("cardActions:edit", row)) {
|
||
return
|
||
}
|
||
this.form = JSON.parse(JSON.stringify(row))
|
||
this.viewing = false
|
||
this.dialogFormVisible = true
|
||
this.#[[$nextTick]]#(() => this.#[[$refs]]#.form && this.#[[$refs]]#.form.clearValidate())
|
||
},
|
||
del(${pkColumn.javaField}) {
|
||
this.request.delete("/${moduleName}/${businessName}/" + ${pkColumn.javaField}).then(res => {
|
||
if (res.code === "200") {
|
||
this.#[[$message]]#.success("删除成功")
|
||
this.load()
|
||
} else {
|
||
this.#[[$message]]#.error(res.msg || "删除失败")
|
||
}
|
||
})
|
||
},
|
||
handleSelectionChange(val) {
|
||
this.multipleSelection = val
|
||
},
|
||
handleCardSelectionChange() {
|
||
this.multipleSelection = this.${businessName}List.filter(item => item.__checked)
|
||
},
|
||
handleConfiguredRoute(actionKey, row) {
|
||
const action = this.routeActionMap[actionKey]
|
||
if (!action || !action.path) {
|
||
return false
|
||
}
|
||
const path = this.buildConfiguredRoutePath(action.path, action.paramMap || {}, row)
|
||
if (!path) {
|
||
return false
|
||
}
|
||
this.#[[$router]]#.push(path)
|
||
return true
|
||
},
|
||
buildConfiguredRoutePath(routePath, paramMap, row) {
|
||
let path = routePath
|
||
Object.keys(paramMap || {}).forEach(param => {
|
||
const field = paramMap[param]
|
||
const value = row && row[field]
|
||
if (value !== undefined && value !== null && value !== "") {
|
||
path = path.replace(new RegExp(":" + param + "(?=/|$)", "g"), encodeURIComponent(value))
|
||
}
|
||
})
|
||
return this.hasUnresolvedRouteParam(path) ? "" : path
|
||
},
|
||
hasUnresolvedRouteParam(path) {
|
||
return String(path || "").split("/").some(segment => segment.indexOf(":") === 0)
|
||
},
|
||
delBatch() {
|
||
const ids = this.multipleSelection.map(item => item.${pkColumn.javaField})
|
||
if (!ids.length) {
|
||
this.#[[$message]]#.warning("请先选择要删除的数据")
|
||
return
|
||
}
|
||
this.request.post("/${moduleName}/${businessName}/del/batch", ids).then(res => {
|
||
if (res.code === "200") {
|
||
this.#[[$message]]#.success("批量删除成功")
|
||
this.load()
|
||
} else {
|
||
this.#[[$message]]#.error(res.msg || "批量删除失败")
|
||
}
|
||
})
|
||
},
|
||
reset() {
|
||
this.queryParams = {
|
||
pageNum: 1,
|
||
pageSize: 10#if($currentUserOnly),
|
||
__currentUserOnly: true#end
|
||
}
|
||
this.load()
|
||
},
|
||
handleBusinessAction(code, row) {
|
||
if (this.handleConfiguredRoute("business:" + code, row) || this.handleConfiguredRoute("toolbar:" + code, row) || this.handleConfiguredRoute("table:" + code, row)) {
|
||
return
|
||
}
|
||
const action = this.businessActionMap[code]
|
||
if (!action || !action.path) {
|
||
this.#[[$message]]#.warning("动作未配置")
|
||
return
|
||
}
|
||
const payload = this.buildBusinessActionPayload(action, row)
|
||
const missingFields = (action.inputFields || []).filter(field => !this.hasBusinessActionValue(payload[field.field]))
|
||
if (missingFields.length > 0) {
|
||
this.openBusinessActionDialog(action, payload, missingFields)
|
||
return
|
||
}
|
||
if (action.confirmMessage) {
|
||
this.#[[$confirm]]#(action.confirmMessage, "提示", {
|
||
confirmButtonText: "确定",
|
||
cancelButtonText: "取消",
|
||
type: "warning"
|
||
}).then(() => this.executeBusinessAction(action, payload)).catch(() => {})
|
||
return
|
||
}
|
||
this.executeBusinessAction(action, payload).catch(() => {})
|
||
},
|
||
openBusinessActionDialog(action, payload, fields) {
|
||
const form = {}
|
||
fields.forEach(field => {
|
||
form[field.field] = this.hasBusinessActionValue(payload[field.field]) ? payload[field.field] : undefined
|
||
})
|
||
this.activeBusinessAction = action
|
||
this.businessActionBasePayload = Object.assign({}, payload)
|
||
this.businessActionInputFields = fields.slice()
|
||
this.businessActionForm = form
|
||
this.businessActionDialogVisible = true
|
||
this.#[[$nextTick]]#(() => {
|
||
const formRef = this.#[[$refs]]#.businessActionForm
|
||
if (formRef) {
|
||
formRef.clearValidate()
|
||
}
|
||
})
|
||
},
|
||
submitBusinessAction() {
|
||
const formRef = this.#[[$refs]]#.businessActionForm
|
||
if (!formRef || !this.activeBusinessAction) {
|
||
return
|
||
}
|
||
formRef.validate(valid => {
|
||
if (!valid) {
|
||
return
|
||
}
|
||
const action = this.activeBusinessAction
|
||
const payload = Object.assign({}, this.businessActionBasePayload, this.businessActionForm)
|
||
this.businessActionSubmitting = true
|
||
this.executeBusinessAction(action, payload)
|
||
.then(() => {
|
||
this.businessActionDialogVisible = false
|
||
})
|
||
.catch(() => {})
|
||
.finally(() => {
|
||
this.businessActionSubmitting = false
|
||
})
|
||
})
|
||
},
|
||
resetBusinessActionDialog() {
|
||
this.activeBusinessAction = null
|
||
this.businessActionBasePayload = {}
|
||
this.businessActionForm = {}
|
||
this.businessActionInputFields = []
|
||
},
|
||
hasBusinessActionValue(value) {
|
||
if (value === undefined || value === null) {
|
||
return false
|
||
}
|
||
return typeof value !== "string" || value.trim() !== ""
|
||
},
|
||
getBusinessActionFieldOptions(field) {
|
||
if (!field || !field.dictType) {
|
||
return []
|
||
}
|
||
return this._dict.getDictDataByType(field.dictType).map(option => ({
|
||
label: option.name,
|
||
value: option.value
|
||
}))
|
||
},
|
||
executeBusinessAction(action, payload) {
|
||
const method = String(action.method || "POST").toLowerCase()
|
||
const request = method === "get"
|
||
? this.request.get(action.path, { params: payload })
|
||
: this.request[method] ? this.request[method](action.path, payload) : this.request.post(action.path, payload)
|
||
return request.then(res => {
|
||
if (res.code !== "200") {
|
||
return Promise.reject(new Error(res.msg || "操作失败"))
|
||
}
|
||
this.#[[$message]]#.success(action.successMessage || "操作成功")
|
||
this.refreshBusinessActionTarget(action.refreshTarget)
|
||
return res
|
||
}).catch(error => {
|
||
const data = error && error.response ? error.response.data : null
|
||
this.#[[$message]]#.error((data && data.msg) || (error && error.message) || "操作失败")
|
||
return Promise.reject(error)
|
||
})
|
||
},
|
||
refreshBusinessActionTarget(refreshTarget) {
|
||
const target = refreshTarget || "list"
|
||
if (target === "none") {
|
||
return
|
||
}
|
||
if (target === "page") {
|
||
window.location.reload()
|
||
return
|
||
}
|
||
#if($isDetailPage)
|
||
this.loadDetail()
|
||
#else
|
||
this.load()
|
||
#end
|
||
},
|
||
buildBusinessActionPayload(action, row) {
|
||
const payload = {}
|
||
const currentUserFields = new Set(action.currentUserFields || [])
|
||
;(action.requestFields || []).forEach(field => {
|
||
if (currentUserFields.has(field)) {
|
||
return
|
||
}
|
||
const value = this.resolvePayloadValue(field, row)
|
||
if (value !== undefined) {
|
||
payload[field] = value
|
||
}
|
||
})
|
||
return payload
|
||
},
|
||
resolvePayloadValue(field, row) {
|
||
const camelField = this.toCamelCase(field)
|
||
const sources = [row, this.detailRecord, this.form, this.queryParams]
|
||
for (const source of sources) {
|
||
if (!source) {
|
||
continue
|
||
}
|
||
if (source[field] !== undefined) {
|
||
return source[field]
|
||
}
|
||
if (source[camelField] !== undefined) {
|
||
return source[camelField]
|
||
}
|
||
}
|
||
return undefined
|
||
},
|
||
toCamelCase(value) {
|
||
return String(value || "").replace(/_([a-zA-Z0-9])/g, (_, char) => char.toUpperCase())
|
||
},
|
||
handleSizeChange(pageSize) {
|
||
this.queryParams.pageSize = pageSize
|
||
this.load()
|
||
},
|
||
handleCurrentChange(pageNum) {
|
||
this.queryParams.pageNum = pageNum
|
||
this.load()
|
||
},
|
||
resolveResourceUrl(url) {
|
||
if (!url) {
|
||
return ""
|
||
}
|
||
if (url.indexOf("http://") === 0 || url.indexOf("https://") === 0 || url.indexOf("//") === 0 || url.indexOf("data:") === 0 || url.indexOf("blob:") === 0) {
|
||
return url
|
||
}
|
||
if (url.indexOf("/api/") === 0) {
|
||
return url
|
||
}
|
||
if (url.indexOf("/files/") === 0) {
|
||
return "/api" + url
|
||
}
|
||
return url
|
||
},
|
||
uploadSuccess(field, res) {
|
||
if (res && res.code === "200" && res.data && res.data.url) {
|
||
this.#[[$set]]#(this.form, field, res.data.url)
|
||
} else {
|
||
this.#[[$message]]#.error("上传失败,请重试")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.portal-page {
|
||
display: grid;
|
||
gap: 22px;
|
||
}
|
||
|
||
.embedded-business-blocks {
|
||
display: grid;
|
||
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||
gap: 16px;
|
||
}
|
||
|
||
.embedded-business-block__item--span-12 { grid-column: span 12; }
|
||
.embedded-business-block__item--span-6 { grid-column: span 6; }
|
||
.embedded-business-block__item--span-4 { grid-column: span 4; }
|
||
|
||
.content-board {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1.36fr) minmax(320px, 0.64fr);
|
||
gap: 24px;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.feature-card {
|
||
position: relative;
|
||
min-height: 270px;
|
||
overflow: hidden;
|
||
background: #ffffff;
|
||
}
|
||
|
||
@media (max-width: 900px) {
|
||
.embedded-business-block__item {
|
||
grid-column: span 12;
|
||
}
|
||
}
|
||
|
||
.feature-illustration {
|
||
position: absolute;
|
||
inset: 0;
|
||
background:
|
||
linear-gradient(135deg, rgba(10, 66, 99, 0.92), rgba(255, 126, 86, 0.9)),
|
||
#184967;
|
||
}
|
||
|
||
.feature-illustration::before,
|
||
.feature-illustration::after {
|
||
position: absolute;
|
||
display: block;
|
||
content: "";
|
||
}
|
||
|
||
.feature-illustration::before {
|
||
left: 8%;
|
||
top: 18%;
|
||
width: 48%;
|
||
height: 42%;
|
||
border: 8px solid rgba(255, 255, 255, 0.8);
|
||
border-radius: 6px;
|
||
transform: rotate(-8deg);
|
||
}
|
||
|
||
.feature-illustration::after {
|
||
right: 9%;
|
||
bottom: -12%;
|
||
width: 290px;
|
||
height: 290px;
|
||
border-radius: 50%;
|
||
background:
|
||
radial-gradient(circle at 36% 38%, #ffffff 0 10px, transparent 11px),
|
||
radial-gradient(circle at 64% 38%, #ffffff 0 10px, transparent 11px),
|
||
radial-gradient(circle at 50% 58%, transparent 0 38px, #153d58 39px 45px, transparent 46px),
|
||
#39a6d8;
|
||
box-shadow: 0 20px 0 #133c58;
|
||
}
|
||
|
||
.device-face {
|
||
position: absolute;
|
||
left: 24%;
|
||
bottom: 20%;
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 36px);
|
||
gap: 18px;
|
||
padding: 18px;
|
||
border-radius: 48px;
|
||
background: rgba(21, 62, 88, 0.9);
|
||
transform: rotate(7deg);
|
||
}
|
||
|
||
.device-face span {
|
||
width: 36px;
|
||
height: 36px;
|
||
border: 5px solid #f7ebe0;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.feature-copy {
|
||
position: absolute;
|
||
left: 28px;
|
||
bottom: 24px;
|
||
max-width: 420px;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.feature-copy span {
|
||
display: inline-block;
|
||
margin-bottom: 8px;
|
||
color: #ffe1b6;
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.feature-copy h2,
|
||
.feature-copy p,
|
||
.panel-heading h2 {
|
||
margin: 0;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.feature-copy h2 {
|
||
font-size: 28px;
|
||
line-height: 1.25;
|
||
}
|
||
|
||
.feature-copy p {
|
||
margin-top: 8px;
|
||
color: rgba(255, 255, 255, 0.88);
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.notice-panel {
|
||
min-width: 0;
|
||
background: #ffffff;
|
||
}
|
||
|
||
.notice-tabs {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: 10px;
|
||
border-bottom: 1px solid #edf0f5;
|
||
}
|
||
|
||
.notice-tabs button {
|
||
position: relative;
|
||
min-height: 38px;
|
||
border: 0;
|
||
color: #475569;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.notice-tabs button.active {
|
||
color: #19a6ad;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.notice-tabs button.active::after {
|
||
position: absolute;
|
||
right: 8px;
|
||
bottom: -1px;
|
||
left: 8px;
|
||
height: 2px;
|
||
background: #19a6ad;
|
||
content: "";
|
||
}
|
||
|
||
.notice-list {
|
||
display: grid;
|
||
gap: 18px;
|
||
margin: 16px 0 0;
|
||
padding: 0;
|
||
list-style: none;
|
||
}
|
||
|
||
.notice-list li {
|
||
display: grid;
|
||
gap: 7px;
|
||
}
|
||
|
||
.notice-list strong {
|
||
color: #172033;
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
line-height: 1.55;
|
||
}
|
||
|
||
.notice-list span {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
color: #7b8493;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.data-panel,
|
||
.notice-panel {
|
||
padding: 20px;
|
||
border: 1px solid #edf0f5;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.data-panel {
|
||
background: #ffffff;
|
||
}
|
||
|
||
.split-data-panel {
|
||
display: grid;
|
||
grid-template-columns: 260px minmax(0, 1fr);
|
||
gap: 16px;
|
||
}
|
||
|
||
.split-data-panel .panel-heading {
|
||
grid-column: 1 / -1;
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.split-data-panel .filter-bar {
|
||
align-content: flex-start;
|
||
align-self: start;
|
||
flex-direction: column;
|
||
margin-bottom: 0;
|
||
padding: 14px;
|
||
border: 1px solid #edf0f5;
|
||
border-radius: 4px;
|
||
background: #f8fafc;
|
||
}
|
||
|
||
.split-data-panel .filter-item,
|
||
.split-data-panel .filter-bar .el-button {
|
||
width: 100%;
|
||
}
|
||
|
||
.split-data-panel .resource-table,
|
||
.split-data-panel .resource-card-grid,
|
||
.split-data-panel .pagination {
|
||
grid-column: 2;
|
||
}
|
||
|
||
.panel-heading {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 16px;
|
||
margin-bottom: 18px;
|
||
}
|
||
|
||
.section-kicker {
|
||
color: #19a6ad;
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.panel-heading h2 {
|
||
margin-top: 6px;
|
||
color: #172033;
|
||
font-size: 22px;
|
||
}
|
||
|
||
.panel-actions,
|
||
.filter-bar {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
}
|
||
|
||
.filter-bar {
|
||
margin-bottom: 14px;
|
||
}
|
||
|
||
.filter-item {
|
||
width: 220px;
|
||
}
|
||
|
||
.resource-table {
|
||
width: 100%;
|
||
}
|
||
|
||
.resource-card-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||
gap: 14px;
|
||
}
|
||
|
||
.resource-card-item {
|
||
position: relative;
|
||
display: grid;
|
||
gap: 12px;
|
||
min-height: 180px;
|
||
padding: 16px;
|
||
border: 1px solid #edf0f5;
|
||
border-radius: 4px;
|
||
background: #ffffff;
|
||
}
|
||
|
||
.resource-card-check {
|
||
position: absolute;
|
||
top: 12px;
|
||
right: 12px;
|
||
}
|
||
|
||
.resource-card-body {
|
||
display: grid;
|
||
gap: 10px;
|
||
}
|
||
|
||
.resource-card-field {
|
||
display: grid;
|
||
gap: 4px;
|
||
}
|
||
|
||
.resource-card-title {
|
||
margin: 0;
|
||
color: #172033;
|
||
font-size: 17px;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.resource-card-field span {
|
||
color: #7b8493;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.resource-card-field strong {
|
||
color: #172033;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
line-height: 1.45;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.resource-card-image {
|
||
width: 100%;
|
||
height: 132px;
|
||
border-radius: 4px;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.resource-card-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 8px;
|
||
padding-top: 4px;
|
||
border-top: 1px solid #edf0f5;
|
||
}
|
||
|
||
.detail-view {
|
||
min-height: 180px;
|
||
}
|
||
|
||
.detail-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||
gap: 14px 18px;
|
||
margin: 0;
|
||
}
|
||
|
||
.detail-field {
|
||
display: grid;
|
||
gap: 6px;
|
||
padding: 14px 0;
|
||
border-bottom: 1px solid #edf0f5;
|
||
}
|
||
|
||
.detail-field.wide {
|
||
grid-column: 1 / -1;
|
||
}
|
||
|
||
.detail-field dt {
|
||
color: #7b8493;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.detail-field dd {
|
||
margin: 0;
|
||
color: #172033;
|
||
font-size: 15px;
|
||
line-height: 1.65;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.detail-image {
|
||
width: 180px;
|
||
height: 128px;
|
||
border-radius: 4px;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.detail-actions {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
margin-top: 18px;
|
||
padding-top: 16px;
|
||
border-top: 1px solid #edf0f5;
|
||
}
|
||
|
||
.empty-detail {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-height: 120px;
|
||
color: #7b8493;
|
||
}
|
||
|
||
.pagination {
|
||
margin-top: 14px;
|
||
text-align: right;
|
||
}
|
||
|
||
.danger-text {
|
||
color: #f56c6c;
|
||
}
|
||
|
||
.table-image {
|
||
width: 52px;
|
||
height: 52px;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.avatar-uploader .avatar {
|
||
display: block;
|
||
width: 96px;
|
||
height: 96px;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.avatar-uploader-icon {
|
||
width: 96px;
|
||
height: 96px;
|
||
border: 1px dashed #d9d9d9;
|
||
border-radius: 4px;
|
||
color: #8c939d;
|
||
line-height: 96px;
|
||
text-align: center;
|
||
}
|
||
|
||
.file-link {
|
||
margin-top: 6px;
|
||
color: #606266;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.business-action-hint {
|
||
margin: 0 0 18px;
|
||
padding: 10px 12px;
|
||
border-radius: 4px;
|
||
color: #606266;
|
||
background: #f5f7fa;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.business-action-hint + .el-form .el-date-editor,
|
||
.business-action-hint + .el-form .el-select,
|
||
.business-action-hint + .el-form .el-input,
|
||
.el-dialog .el-form .el-date-editor,
|
||
.el-dialog .el-form .el-select {
|
||
width: 100%;
|
||
}
|
||
|
||
@media (max-width: 980px) {
|
||
.content-board {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 720px) {
|
||
.split-data-panel {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.split-data-panel .panel-heading,
|
||
.split-data-panel .resource-table,
|
||
.split-data-panel .resource-card-grid,
|
||
.split-data-panel .pagination {
|
||
grid-column: 1;
|
||
}
|
||
|
||
.panel-heading {
|
||
align-items: flex-start;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.filter-item {
|
||
width: 100%;
|
||
}
|
||
|
||
.notice-tabs {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
}
|
||
</style>
|
||
', 'admin', sysdate()),
|
||
(920312, 9203, 'frontend-login.vue.vm', NULL, 'frontend-login.vue.vm', '#set($portalProjectName = $projectName)
|
||
#if(!$portalProjectName || $portalProjectName == "")
|
||
#set($portalProjectName = $functionName)
|
||
#end
|
||
#if(!$portalProjectName || $portalProjectName == "")
|
||
#set($portalProjectName = "Portal")
|
||
#end
|
||
#set($portalDefaultPath = $defaultFrontendPath)
|
||
#if(!$portalDefaultPath || $portalDefaultPath == "")
|
||
#set($portalDefaultPath = "/${moduleName}/${businessName}")
|
||
#end
|
||
<template>
|
||
<section class="portal-login">
|
||
<div class="login-visual" aria-hidden="true">
|
||
<div class="login-brand">
|
||
<span class="login-logo"></span>
|
||
<strong>${portalProjectName}</strong>
|
||
</div>
|
||
<h1>${portalProjectName}</h1>
|
||
<p>登录后继续访问你的项目功能和个人数据。</p>
|
||
</div>
|
||
|
||
<div class="login-panel">
|
||
<div class="login-heading">
|
||
<span>欢迎回来</span>
|
||
<h2>账号登录</h2>
|
||
</div>
|
||
|
||
<el-form :model="form" label-position="top" class="login-form" @submit.native.prevent>
|
||
<el-form-item label="用户名">
|
||
<el-input v-model="form.username" placeholder="请输入用户名" prefix-icon="el-icon-user" />
|
||
</el-form-item>
|
||
<el-form-item label="密码">
|
||
<el-input v-model="form.password" type="password" placeholder="请输入密码" prefix-icon="el-icon-lock" show-password />
|
||
</el-form-item>
|
||
<el-button type="primary" class="login-button" :loading="loading" @click="handleLogin">登录</el-button>
|
||
</el-form>
|
||
</div>
|
||
</section>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: "PortalLogin",
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
form: {
|
||
username: "user",
|
||
password: "123456"
|
||
}
|
||
}
|
||
},
|
||
methods: {
|
||
handleLogin() {
|
||
if (!this.form.username || !this.form.password) {
|
||
this.#[[$message]]#.warning("请输入用户名和密码")
|
||
return
|
||
}
|
||
|
||
this.loading = true
|
||
this.request.post("/auth/login", this.form)
|
||
.then(res => {
|
||
if (!res || res.code !== "200" || !res.data || !res.data.token) {
|
||
throw new Error((res && res.msg) || "登录失败")
|
||
}
|
||
localStorage.setItem("portal-token", res.data.token)
|
||
localStorage.setItem("portal-user", JSON.stringify(res.data.user || {
|
||
username: this.form.username,
|
||
nickname: this.form.username
|
||
}))
|
||
window.dispatchEvent(new Event("portal-user-change"))
|
||
this.#[[$message]]#.success("登录成功")
|
||
const redirect = this.#[[$route]]#.query.redirect || "${portalDefaultPath}"
|
||
this.#[[$router]]#.push(redirect)
|
||
})
|
||
.catch(error => {
|
||
this.#[[$message]]#.error(error.message || "登录失败")
|
||
})
|
||
.finally(() => {
|
||
this.loading = false
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.portal-login {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) 430px;
|
||
min-height: 100vh;
|
||
background:
|
||
linear-gradient(132deg, rgba(24, 141, 149, 0.95), rgba(44, 120, 215, 0.88)),
|
||
#168d95;
|
||
}
|
||
|
||
.login-visual {
|
||
position: relative;
|
||
display: flex;
|
||
min-width: 0;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
padding: 72px 9vw;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.login-visual::before {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: block;
|
||
content: "";
|
||
opacity: 0.58;
|
||
background:
|
||
linear-gradient(42deg, transparent 0 18%, rgba(255, 255, 255, 0.18) 18% 27%, transparent 27% 100%),
|
||
linear-gradient(132deg, transparent 0 48%, rgba(246, 186, 51, 0.32) 48% 55%, transparent 55% 100%);
|
||
}
|
||
|
||
.login-brand,
|
||
.login-visual h1,
|
||
.login-visual p {
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.login-brand {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-bottom: 46px;
|
||
font-size: 22px;
|
||
}
|
||
|
||
.login-logo {
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 8px 18px 8px 18px;
|
||
background: linear-gradient(135deg, #38d8f0 0 35%, #f3ba28 35% 66%, #5bc269 66%);
|
||
transform: rotate(45deg);
|
||
}
|
||
|
||
.login-visual h1,
|
||
.login-visual p {
|
||
margin: 0;
|
||
}
|
||
|
||
.login-visual h1 {
|
||
max-width: 680px;
|
||
font-size: 52px;
|
||
font-weight: 600;
|
||
letter-spacing: 0;
|
||
line-height: 1.18;
|
||
}
|
||
|
||
.login-visual p {
|
||
max-width: 520px;
|
||
margin-top: 20px;
|
||
color: rgba(255, 255, 255, 0.88);
|
||
font-size: 17px;
|
||
line-height: 1.8;
|
||
}
|
||
|
||
.login-panel {
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
padding: 48px;
|
||
background: #ffffff;
|
||
}
|
||
|
||
.login-heading span {
|
||
color: #168d95;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.login-heading h2 {
|
||
margin: 8px 0 28px;
|
||
color: #111827;
|
||
font-size: 30px;
|
||
font-weight: 700;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.login-form {
|
||
display: grid;
|
||
gap: 2px;
|
||
}
|
||
|
||
.login-button {
|
||
width: 100%;
|
||
height: 42px;
|
||
margin-top: 6px;
|
||
border-color: #168d95;
|
||
background: #168d95;
|
||
}
|
||
|
||
@media (max-width: 880px) {
|
||
.portal-login {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.login-visual {
|
||
min-height: 280px;
|
||
padding: 42px 26px;
|
||
}
|
||
|
||
.login-visual h1 {
|
||
font-size: 36px;
|
||
}
|
||
|
||
.login-panel {
|
||
padding: 34px 24px 44px;
|
||
}
|
||
}
|
||
</style>
|
||
', 'admin', sysdate()),
|
||
(920313, 9203, 'frontend-dict.js.vm', NULL, 'frontend-dict.js.vm', 'const emptyDict = []
|
||
|
||
export default {
|
||
getDictDataByType() {
|
||
return emptyDict
|
||
},
|
||
getDictLabel(type, value) {
|
||
return value === undefined || value === null || value === "" ? "-" : value
|
||
}
|
||
}
|
||
', 'admin', sysdate());
|
||
|
||
-- Backend project structure
|
||
INSERT INTO sys_project_structure (node_id, parent_id, node_name, node_type, module, template_id, table_id, category, sort_order, status, create_by, create_time, update_by, update_time) VALUES
|
||
(920100, 0, '{projectName}-backend', 'folder', NULL, 9201, NULL, NULL, 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920101, 920100, 'pom.xml', 'file', NULL, 9201, -1, 'pom.xml.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920102, 920100, 'README.md', 'file', NULL, 9201, -1, 'README.md.vm', 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920103, 920100, 'sql', 'folder', NULL, 9201, NULL, NULL, 3, '0', 'admin', sysdate(), '', NULL),
|
||
(920104, 920103, '{businessName}.sql', 'file', NULL, 9201, 0, 'sql.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920110, 920100, 'src', 'folder', NULL, 9201, NULL, NULL, 4, '0', 'admin', sysdate(), '', NULL),
|
||
(920111, 920110, 'main', 'folder', NULL, 9201, NULL, NULL, 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920112, 920111, 'java', 'folder', NULL, 9201, NULL, NULL, 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920113, 920111, 'resources', 'folder', NULL, 9201, NULL, NULL, 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920114, 920112, '{packagePath}', 'folder', NULL, 9201, NULL, NULL, 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920115, 920114, '{projectClassName}Application.java', 'file', NULL, 9201, -1, 'Application.java.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920116, 920114, 'common', 'folder', NULL, 9201, NULL, NULL, 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920117, 920116, 'Result.java', 'file', NULL, 9201, -1, 'Result.java.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920118, 920114, 'config', 'folder', NULL, 9201, NULL, NULL, 3, '0', 'admin', sysdate(), '', NULL),
|
||
(920119, 920118, 'MybatisPlusConfig.java', 'file', NULL, 9201, -1, 'MybatisPlusConfig.java.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920120, 920118, 'CorsConfig.java', 'file', NULL, 9201, -1, 'CorsConfig.java.vm', 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920121, 920114, 'controller', 'folder', NULL, 9201, NULL, NULL, 4, '0', 'admin', sysdate(), '', NULL),
|
||
(920122, 920121, 'FileController.java', 'file', NULL, 9201, -1, 'FileController.java.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920123, 920121, '{ClassName}Controller.java', 'file', NULL, 9201, 0, 'controller.java.vm', 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920124, 920114, 'entity', 'folder', NULL, 9201, NULL, NULL, 5, '0', 'admin', sysdate(), '', NULL),
|
||
(920125, 920124, '{ClassName}.java', 'file', NULL, 9201, 0, 'entity.java.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920126, 920114, 'mapper', 'folder', NULL, 9201, NULL, NULL, 6, '0', 'admin', sysdate(), '', NULL),
|
||
(920127, 920126, '{ClassName}Mapper.java', 'file', NULL, 9201, 0, 'mapper.java.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920128, 920114, 'service', 'folder', NULL, 9201, NULL, NULL, 7, '0', 'admin', sysdate(), '', NULL),
|
||
(920129, 920128, 'I{ClassName}Service.java', 'file', NULL, 9201, 0, 'service.java.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920130, 920128, 'impl', 'folder', NULL, 9201, NULL, NULL, 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920131, 920130, '{ClassName}ServiceImpl.java', 'file', NULL, 9201, 0, 'serviceImpl.java.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920132, 920113, 'application.yml', 'file', NULL, 9201, -1, 'application.yml.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920133, 920113, 'mapper', 'folder', NULL, 9201, NULL, NULL, 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920134, 920133, '{moduleName}', 'folder', NULL, 9201, 0, NULL, 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920135, 920134, '{ClassName}Mapper.xml', 'file', NULL, 9201, 0, 'mapper.xml.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920136, 920124, 'PortalUser.java', 'file', NULL, 9201, -1, 'PortalUser.java.vm', 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920137, 920126, 'PortalUserMapper.java', 'file', NULL, 9201, -1, 'PortalUserMapper.java.vm', 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920138, 920114, 'security', 'folder', NULL, 9201, NULL, NULL, 8, '0', 'admin', sysdate(), '', NULL),
|
||
(920139, 920138, 'PortalAuthTokenStore.java', 'file', NULL, 9201, -1, 'PortalAuthTokenStore.java.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920140, 920138, 'PortalAuthInterceptor.java', 'file', NULL, 9201, -1, 'PortalAuthInterceptor.java.vm', 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920141, 920121, 'PortalAuthController.java', 'file', NULL, 9201, -1, 'PortalAuthController.java.vm', 3, '0', 'admin', sysdate(), '', NULL),
|
||
(920142, 920124, 'AdminUser.java', 'file', NULL, 9201, -1, 'AdminUser.java.vm', 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920143, 920126, 'AdminUserMapper.java', 'file', NULL, 9201, -1, 'AdminUserMapper.java.vm', 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920144, 920121, 'AdminAuthController.java', 'file', NULL, 9201, -1, 'AdminAuthController.java.vm', 4, '0', 'admin', sysdate(), '', NULL);
|
||
|
||
-- Admin frontend project structure
|
||
INSERT INTO sys_project_structure (node_id, parent_id, node_name, node_type, module, template_id, table_id, category, sort_order, status, create_by, create_time, update_by, update_time) VALUES
|
||
(920200, 0, '{projectName}-admin', 'folder', NULL, 9202, NULL, NULL, 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920201, 920200, 'package.json', 'file', NULL, 9202, -1, 'package.json.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920202, 920200, 'babel.config.js', 'file', NULL, 9202, -1, 'babel.config.js.vm', 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920203, 920200, 'vue.config.js', 'file', NULL, 9202, -1, 'vue.config.js.vm', 3, '0', 'admin', sysdate(), '', NULL),
|
||
(920204, 920200, 'public', 'folder', NULL, 9202, NULL, NULL, 4, '0', 'admin', sysdate(), '', NULL),
|
||
(920205, 920204, 'index.html', 'file', NULL, 9202, -1, 'index.html.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920206, 920200, 'src', 'folder', NULL, 9202, NULL, NULL, 5, '0', 'admin', sysdate(), '', NULL),
|
||
(920207, 920206, 'main.js', 'file', NULL, 9202, -1, 'admin-main.js.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920208, 920206, 'App.vue', 'file', NULL, 9202, -1, 'admin-App.vue.vm', 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920209, 920206, 'router', 'folder', NULL, 9202, NULL, NULL, 3, '0', 'admin', sysdate(), '', NULL),
|
||
(920210, 920209, 'index.js', 'file', NULL, 9202, -1, 'admin-router.js.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920211, 920206, 'utils', 'folder', NULL, 9202, NULL, NULL, 4, '0', 'admin', sysdate(), '', NULL),
|
||
(920212, 920211, 'request.js', 'file', NULL, 9202, -1, 'request.js.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920213, 920211, 'dict.js', 'file', NULL, 9202, -1, 'dict.js.vm', 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920214, 920206, 'views', 'folder', NULL, 9202, NULL, NULL, 5, '0', 'admin', sysdate(), '', NULL),
|
||
(920218, 920214, 'login', 'folder', NULL, 9202, -1, NULL, 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920219, 920218, 'index.vue', 'file', NULL, 9202, -1, 'admin-login.vue.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920215, 920214, '{moduleName}', 'folder', NULL, 9202, 0, NULL, 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920216, 920215, '{businessName}', 'folder', NULL, 9202, 0, NULL, 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920217, 920216, 'index.vue', 'file', NULL, 9202, 0, 'admin-index.vue.vm', 1, '0', 'admin', sysdate(), '', NULL);
|
||
|
||
-- Portal frontend project structure
|
||
INSERT INTO sys_project_structure (node_id, parent_id, node_name, node_type, module, template_id, table_id, category, sort_order, status, create_by, create_time, update_by, update_time) VALUES
|
||
(920300, 0, '{projectName}-web', 'folder', NULL, 9203, NULL, NULL, 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920301, 920300, 'package.json', 'file', NULL, 9203, -1, 'package.json.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920302, 920300, 'babel.config.js', 'file', NULL, 9203, -1, 'babel.config.js.vm', 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920303, 920300, 'vue.config.js', 'file', NULL, 9203, -1, 'vue.config.js.vm', 3, '0', 'admin', sysdate(), '', NULL),
|
||
(920304, 920300, 'public', 'folder', NULL, 9203, NULL, NULL, 4, '0', 'admin', sysdate(), '', NULL),
|
||
(920305, 920304, 'index.html', 'file', NULL, 9203, -1, 'index.html.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920306, 920300, 'src', 'folder', NULL, 9203, NULL, NULL, 5, '0', 'admin', sysdate(), '', NULL),
|
||
(920307, 920306, 'main.js', 'file', NULL, 9203, -1, 'frontend-main.js.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920308, 920306, 'App.vue', 'file', NULL, 9203, -1, 'frontend-App.vue.vm', 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920309, 920306, 'style.css', 'file', NULL, 9203, -1, 'style.css.vm', 3, '0', 'admin', sysdate(), '', NULL),
|
||
(920310, 920306, 'router', 'folder', NULL, 9203, NULL, NULL, 4, '0', 'admin', sysdate(), '', NULL),
|
||
(920311, 920310, 'index.js', 'file', NULL, 9203, -1, 'frontend-router.js.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920312, 920306, 'api', 'folder', NULL, 9203, NULL, NULL, 5, '0', 'admin', sysdate(), '', NULL),
|
||
(920313, 920312, 'request.js', 'file', NULL, 9203, -1, 'frontend-request.js.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920314, 920312, '{businessName}.js', 'file', NULL, 9203, 0, 'frontend-api.js.vm', 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920321, 920306, 'utils', 'folder', NULL, 9203, NULL, NULL, 6, '0', 'admin', sysdate(), '', NULL),
|
||
(920322, 920321, 'dict.js', 'file', NULL, 9203, -1, 'frontend-dict.js.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920315, 920306, 'views', 'folder', NULL, 9203, NULL, NULL, 7, '0', 'admin', sysdate(), '', NULL),
|
||
(920316, 920315, '{moduleName}', 'folder', NULL, 9203, 0, NULL, 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920317, 920316, '{businessName}', 'folder', NULL, 9203, 0, NULL, 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920318, 920317, 'index.vue', 'file', NULL, 9203, 0, 'frontend-index.vue.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||
(920319, 920315, 'login', 'folder', NULL, 9203, NULL, NULL, 2, '0', 'admin', sysdate(), '', NULL),
|
||
(920320, 920319, 'index.vue', 'file', NULL, 9203, -1, 'frontend-login.vue.vm', 1, '0', 'admin', sysdate(), '', NULL);
|
||
SET FOREIGN_KEY_CHECKS = 1;
|