mirror of
https://gitee.com/zhijiantianya/yudao-cloud.git
synced 2025-12-30 01:12:26 +00:00
Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/yudao-cloud
# Conflicts: # yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateService.java # yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateServiceImpl.java
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,39 +1,58 @@
|
||||
package cn.iocoder.yudao.framework.common.util.json.databind;
|
||||
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 基于时间戳的 LocalDateTime 序列化器
|
||||
*
|
||||
* @author 老五
|
||||
*/
|
||||
@Slf4j
|
||||
public class TimestampLocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
|
||||
|
||||
public static final TimestampLocalDateTimeSerializer INSTANCE = new TimestampLocalDateTimeSerializer();
|
||||
|
||||
private static final Map<Class<?>, Map<String, Field>> FIELD_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
// 情况一:有 JsonFormat 自定义注解,则使用它。https://github.com/YunaiV/ruoyi-vue-pro/pull/1019
|
||||
String fieldName = gen.getOutputContext().getCurrentName();
|
||||
if (fieldName != null) {
|
||||
Class<?> clazz = gen.getOutputContext().getCurrentValue().getClass();
|
||||
Field field = ReflectUtil.getField(clazz, fieldName);
|
||||
JsonFormat[] jsonFormats = field.getAnnotationsByType(JsonFormat.class);
|
||||
if (jsonFormats.length > 0) {
|
||||
String pattern = jsonFormats[0].pattern();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
gen.writeString(formatter.format(value));
|
||||
return;
|
||||
Object currentValue = gen.getOutputContext().getCurrentValue();
|
||||
if (currentValue != null) {
|
||||
Class<?> clazz = currentValue.getClass();
|
||||
Map<String, Field> fieldMap = FIELD_CACHE.computeIfAbsent(clazz, this::buildFieldMap);
|
||||
Field field = fieldMap.get(fieldName);
|
||||
// 进一步修复:https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1480
|
||||
if (field != null && field.isAnnotationPresent(JsonFormat.class)) {
|
||||
JsonFormat jsonFormat = field.getAnnotation(JsonFormat.class);
|
||||
try {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(jsonFormat.pattern());
|
||||
gen.writeString(formatter.format(value));
|
||||
return;
|
||||
} catch (Exception ex) {
|
||||
log.warn("[serialize][({}#{}) 使用 JsonFormat pattern 失败,尝试使用默认的 Long 时间戳]",
|
||||
clazz.getName(), fieldName, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,4 +60,26 @@ public class TimestampLocalDateTimeSerializer extends JsonSerializer<LocalDateTi
|
||||
gen.writeNumber(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建字段映射(缓存)
|
||||
*
|
||||
* @param clazz 类
|
||||
* @return 字段映射
|
||||
*/
|
||||
private Map<String, Field> buildFieldMap(Class<?> clazz) {
|
||||
Map<String, Field> fieldMap = new HashMap<>();
|
||||
for (Field field : ReflectUtil.getFields(clazz)) {
|
||||
String fieldName = field.getName();
|
||||
JsonProperty jsonProperty = field.getAnnotation(JsonProperty.class);
|
||||
if (jsonProperty != null) {
|
||||
String value = jsonProperty.value();
|
||||
if (StrUtil.isNotEmpty(value) && ObjUtil.notEqual("\u0000", value)) {
|
||||
fieldName = value;
|
||||
}
|
||||
}
|
||||
fieldMap.put(fieldName, field);
|
||||
}
|
||||
return fieldMap;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ public class BpmTaskController {
|
||||
|
||||
@GetMapping("manager-page")
|
||||
@Operation(summary = "获取全部任务的分页", description = "用于【流程任务】菜单")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:task:mananger-query')")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:task:manager-query')")
|
||||
public CommonResult<PageResult<BpmTaskRespVO>> getTaskManagerPage(@Valid BpmTaskPageReqVO pageVO) {
|
||||
PageResult<HistoricTaskInstance> pageResult = taskService.getTaskPage(getLoginUserId(), pageVO);
|
||||
if (CollUtil.isEmpty(pageResult.getList())) {
|
||||
|
||||
@@ -34,6 +34,12 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
|
||||
public BpmParallelMultiInstanceBehavior(Activity activity,
|
||||
AbstractBpmnActivityBehavior innerActivityBehavior) {
|
||||
super(activity, innerActivityBehavior);
|
||||
// 关联 Pull Request:https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1483
|
||||
// 在解析/构造阶段基于 activityId 初始化与 activity 绑定且不变的字段,避免在运行期修改 Behavior 实例状态
|
||||
super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
|
||||
super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(activity.getId());
|
||||
// 从 execution.getVariable() 读取当前所有任务处理的人的 key
|
||||
super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(activity.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,12 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
|
||||
|
||||
public BpmSequentialMultiInstanceBehavior(Activity activity, AbstractBpmnActivityBehavior innerActivityBehavior) {
|
||||
super(activity, innerActivityBehavior);
|
||||
// 关联 Pull Request:https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1483
|
||||
// 在解析/构造阶段基于 activityId 初始化与 activity 绑定且不变的字段,避免在运行期修改 Behavior 实例状态
|
||||
super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
|
||||
super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(activity.getId());
|
||||
// 从 execution.getVariable() 读取当前所有任务处理的人的 key
|
||||
super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(activity.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,10 +94,6 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
|
||||
super.executeOriginalBehavior(execution, multiInstanceRootExecution, loopCounter);
|
||||
return;
|
||||
}
|
||||
// 参见 https://gitee.com/zhijiantianya/yudao-cloud/issues/IC239F
|
||||
super.collectionExpression = null;
|
||||
super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
|
||||
super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
|
||||
super.executeOriginalBehavior(execution, multiInstanceRootExecution, loopCounter);
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,7 @@ public class BpmTaskCandidateInvoker {
|
||||
});
|
||||
}
|
||||
|
||||
@DataPermission(enable = false) // 忽略数据权限,避免因为过滤,导致找不到候选人
|
||||
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId,
|
||||
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
|
||||
// 如果是 CallActivity 子流程,不进行计算候选人
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
|
||||
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
|
||||
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceCopyService;
|
||||
import org.flowable.bpmn.model.FlowElement;
|
||||
import org.flowable.engine.delegate.DelegateExecution;
|
||||
@@ -40,8 +41,9 @@ public class BpmCopyTaskDelegate implements JavaDelegate {
|
||||
}
|
||||
// 2. 执行抄送
|
||||
FlowElement currentFlowElement = execution.getCurrentFlowElement();
|
||||
processInstanceCopyService.createProcessInstanceCopy(userIds, null, execution.getProcessInstanceId(),
|
||||
currentFlowElement.getId(), currentFlowElement.getName(), null);
|
||||
FlowableUtils.execute(execution.getTenantId(), () ->
|
||||
processInstanceCopyService.createProcessInstanceCopy(userIds, null, execution.getProcessInstanceId(),
|
||||
currentFlowElement.getId(), currentFlowElement.getName(), null));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ public enum CodegenFrontTypeEnum {
|
||||
|
||||
VUE3_VBEN5_EP_SCHEMA(50), // Vue3 VBEN5 + EP + schema 模版
|
||||
VUE3_VBEN5_EP_GENERAL(51), // Vue3 VBEN5 + EP 标准模版
|
||||
|
||||
VUE3_ADMIN_UNIAPP_WOT(60), // Vue3 Admin + Uniapp + WOT 标准模版
|
||||
;
|
||||
|
||||
/**
|
||||
|
||||
@@ -72,6 +72,14 @@ public class FileController {
|
||||
return success(fileService.createFile(createReqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得文件")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('infra:file:query')")
|
||||
public CommonResult<FileRespVO> getFile(@RequestParam("id") Long id) {
|
||||
return success(BeanUtils.toBean(fileService.getFile(id), FileRespVO.class));
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除文件")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
|
||||
@@ -4,12 +4,14 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenCreateListReqVO;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.DatabaseTableRespVO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenColumnMapper;
|
||||
import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenTableMapper;
|
||||
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
|
||||
@@ -17,7 +19,9 @@ import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
|
||||
import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
|
||||
import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenBuilder;
|
||||
import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenEngine;
|
||||
import cn.iocoder.yudao.module.infra.service.db.DataSourceConfigService;
|
||||
import cn.iocoder.yudao.module.infra.service.db.DatabaseTableService;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.generator.config.po.TableField;
|
||||
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
@@ -45,6 +49,8 @@ public class CodegenServiceImpl implements CodegenService {
|
||||
|
||||
@Resource
|
||||
private DatabaseTableService databaseTableService;
|
||||
@Resource
|
||||
private DataSourceConfigService dataSourceConfigService;
|
||||
|
||||
@Resource
|
||||
private CodegenTableMapper codegenTableMapper;
|
||||
@@ -284,8 +290,11 @@ public class CodegenServiceImpl implements CodegenService {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取数据源对应的数据库类型
|
||||
DataSourceConfigDO dataSourceConfig = dataSourceConfigService.getDataSourceConfig(table.getDataSourceConfigId());
|
||||
DbType dbType = JdbcUtils.getDbType(dataSourceConfig.getUrl());
|
||||
// 执行生成
|
||||
return codegenEngine.execute(table, columns, subTables, subColumnsList);
|
||||
return codegenEngine.execute(dbType, table, columns, subTables, subColumnsList);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -33,6 +33,7 @@ import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
|
||||
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
|
||||
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenVOTypeEnum;
|
||||
import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableTable;
|
||||
import com.google.common.collect.Maps;
|
||||
@@ -96,7 +97,7 @@ public class CodegenEngine {
|
||||
.build();
|
||||
|
||||
/**
|
||||
* 后端的配置模版
|
||||
* 前端的配置模版
|
||||
*
|
||||
* key1:UI 模版的类型 {@link CodegenFrontTypeEnum#getType()}
|
||||
* key2:模板在 resources 的地址
|
||||
@@ -137,6 +138,16 @@ public class CodegenEngine {
|
||||
vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue"))
|
||||
.put(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType(), vue3TemplatePath("api/api.ts"),
|
||||
vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
|
||||
.put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("api/api.ts"),
|
||||
vue3UniappFilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
|
||||
.put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/index.vue"),
|
||||
vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/index.vue"))
|
||||
.put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("components/search-form.vue"),
|
||||
vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/components/search-form.vue"))
|
||||
.put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/form/index.vue"),
|
||||
vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/form/index.vue"))
|
||||
.put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/detail/index.vue"),
|
||||
vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/detail/index.vue"))
|
||||
// VUE3_VBEN2_ANTD_SCHEMA
|
||||
.put(CodegenFrontTypeEnum.VUE3_VBEN2_ANTD_SCHEMA.getType(), vue3VbenTemplatePath("views/data.ts"),
|
||||
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/${classNameVar}.data.ts"))
|
||||
@@ -300,16 +311,17 @@ public class CodegenEngine {
|
||||
/**
|
||||
* 生成代码
|
||||
*
|
||||
* @param dbType 数据库类型
|
||||
* @param table 表定义
|
||||
* @param columns table 的字段定义数组
|
||||
* @param subTables 子表数组,当且仅当主子表时使用
|
||||
* @param subColumnsList subTables 的字段定义数组
|
||||
* @return 生成的代码,key 是路径,value 是对应代码
|
||||
*/
|
||||
public Map<String, String> execute(CodegenTableDO table, List<CodegenColumnDO> columns,
|
||||
public Map<String, String> execute(DbType dbType, CodegenTableDO table, List<CodegenColumnDO> columns,
|
||||
List<CodegenTableDO> subTables, List<List<CodegenColumnDO>> subColumnsList) {
|
||||
// 1.1 初始化 bindMap 上下文
|
||||
Map<String, Object> bindingMap = initBindingMap(table, columns, subTables, subColumnsList);
|
||||
Map<String, Object> bindingMap = initBindingMap(dbType, table, columns, subTables, subColumnsList);
|
||||
// 1.2 获得模版
|
||||
Map<String, String> templates = getTemplates(table.getFrontType());
|
||||
|
||||
@@ -387,8 +399,8 @@ public class CodegenEngine {
|
||||
* @return 格式化后的代码
|
||||
*/
|
||||
private String prettyCode(String content, String vmPath) {
|
||||
// Vue 界面:去除字段后面多余的 , 逗号,解决前端的 Pretty 代码格式检查的报错(需要排除 vben5)
|
||||
if (!StrUtil.contains(vmPath, "vben5")) {
|
||||
// Vue 界面:去除字段后面多余的 , 逗号,解决前端的 Pretty 代码格式检查的报错(需要排除 vben5、vue3_admin_uniapp)
|
||||
if (!StrUtil.containsAny(vmPath, "vben5", "vue3_admin_uniapp")) {
|
||||
content = content.replaceAll(",\n}", "\n}").replaceAll(",\n }", "\n }");
|
||||
}
|
||||
// Vue 界面:去除多的 dateFormatter,只有一个的情况下,说明没使用到
|
||||
@@ -415,10 +427,11 @@ public class CodegenEngine {
|
||||
return content;
|
||||
}
|
||||
|
||||
private Map<String, Object> initBindingMap(CodegenTableDO table, List<CodegenColumnDO> columns,
|
||||
private Map<String, Object> initBindingMap(DbType dbType, CodegenTableDO table, List<CodegenColumnDO> columns,
|
||||
List<CodegenTableDO> subTables, List<List<CodegenColumnDO>> subColumnsList) {
|
||||
// 创建 bindingMap
|
||||
Map<String, Object> bindingMap = new HashMap<>(globalBindingMap);
|
||||
bindingMap.put("dbType", dbType);
|
||||
bindingMap.put("table", table);
|
||||
bindingMap.put("columns", columns);
|
||||
bindingMap.put("primaryColumn", CollectionUtils.findFirst(columns, CodegenColumnDO::getPrimaryKey)); // 主键字段
|
||||
@@ -617,6 +630,15 @@ public class CodegenEngine {
|
||||
"src/" + path;
|
||||
}
|
||||
|
||||
private static String vue3AdminUniappTemplatePath(String path) {
|
||||
return "codegen/vue3_admin_uniapp/" + path + ".vm";
|
||||
}
|
||||
|
||||
private static String vue3UniappFilePath(String path) {
|
||||
return "yudao-ui-${sceneEnum.basePackage}-uniapp/" + // 顶级目录
|
||||
"src/" + path;
|
||||
}
|
||||
|
||||
private static String vue3VbenFilePath(String path) {
|
||||
return "yudao-ui-${sceneEnum.basePackage}-vben/" + // 顶级目录
|
||||
"src/" + path;
|
||||
|
||||
@@ -62,6 +62,7 @@ public interface FileService {
|
||||
* @return 编号
|
||||
*/
|
||||
Long createFile(FileCreateReqVO createReqVO);
|
||||
FileDO getFile(Long id);
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
|
||||
@@ -152,6 +152,11 @@ public class FileServiceImpl implements FileService {
|
||||
return file.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileDO getFile(Long id) {
|
||||
return validateFileExists(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteFile(Long id) throws Exception {
|
||||
// 校验存在
|
||||
|
||||
@@ -1,3 +1,24 @@
|
||||
## 通用变量定义
|
||||
#set ($functionNames = ['查询', '创建', '更新', '删除', '导出'])
|
||||
#set ($functionOps = ['query', 'create', 'update', 'delete', 'export'])
|
||||
##
|
||||
## 宏定义:生成按钮 SQL(通用部分)
|
||||
#macro(insertButtonSql $parentIdVar)
|
||||
#foreach ($functionName in $functionNames)
|
||||
#set ($index = $foreach.count - 1)
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'${table.classComment}${functionName}', '${permissionPrefix}:${functionOps.get($index)}', 3, $foreach.count, ${parentIdVar},
|
||||
'', '', '', 0
|
||||
);
|
||||
#end
|
||||
#end
|
||||
##
|
||||
## ======================= MySQL / OceanBase =======================
|
||||
#if ($dbType.name() == 'MYSQL' || $dbType.name() == 'OCEAN_BASE')
|
||||
-- 菜单 SQL
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
@@ -9,12 +30,9 @@ VALUES (
|
||||
);
|
||||
|
||||
-- 按钮父菜单ID
|
||||
-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
|
||||
SELECT @parentId := LAST_INSERT_ID();
|
||||
|
||||
-- 按钮 SQL
|
||||
#set ($functionNames = ['查询', '创建', '更新', '删除', '导出'])
|
||||
#set ($functionOps = ['query', 'create', 'update', 'delete', 'export'])
|
||||
#foreach ($functionName in $functionNames)
|
||||
#set ($index = $foreach.count - 1)
|
||||
INSERT INTO system_menu(
|
||||
@@ -25,4 +43,139 @@ VALUES (
|
||||
'${table.classComment}${functionName}', '${permissionPrefix}:${functionOps.get($index)}', 3, $foreach.count, @parentId,
|
||||
'', '', '', 0
|
||||
);
|
||||
#end
|
||||
#end
|
||||
##
|
||||
## ======================= Oracle / 达梦 DM =======================
|
||||
#elseif ($dbType.name() == 'ORACLE' || $dbType.name() == 'DM')
|
||||
-- 菜单 SQL
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status, component_name
|
||||
)
|
||||
VALUES (
|
||||
'${table.classComment}管理', '', 2, 0, ${table.parentMenuId},
|
||||
'${simpleClassName_strikeCase}', '', '${table.moduleName}/${table.businessName}/index', 0, '${table.className}'
|
||||
);
|
||||
|
||||
-- 按钮父菜单ID
|
||||
-- 说明:Oracle/达梦 使用序列获取上一个插入的 ID
|
||||
DECLARE
|
||||
v_parent_id NUMBER;
|
||||
BEGIN
|
||||
SELECT system_menu_seq.CURRVAL INTO v_parent_id FROM DUAL;
|
||||
-- 按钮 SQL
|
||||
#foreach ($functionName in $functionNames)
|
||||
#set ($index = $foreach.count - 1)
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'${table.classComment}${functionName}', '${permissionPrefix}:${functionOps.get($index)}', 3, $foreach.count, v_parent_id,
|
||||
'', '', '', 0
|
||||
);
|
||||
#end
|
||||
END;
|
||||
/
|
||||
##
|
||||
## ======================= PostgreSQL / 人大金仓 KingbaseES =======================
|
||||
#elseif ($dbType.name() == 'POSTGRE_SQL' || $dbType.name() == 'KINGBASE_ES')
|
||||
-- 菜单 SQL
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status, component_name
|
||||
)
|
||||
VALUES (
|
||||
'${table.classComment}管理', '', 2, 0, ${table.parentMenuId},
|
||||
'${simpleClassName_strikeCase}', '', '${table.moduleName}/${table.businessName}/index', 0, '${table.className}'
|
||||
);
|
||||
|
||||
-- 按钮父菜单ID
|
||||
-- 说明:PostgreSQL/KingbaseES 使用 lastval() 获取上一个插入的序列值
|
||||
DO $$
|
||||
DECLARE
|
||||
v_parent_id BIGINT;
|
||||
BEGIN
|
||||
v_parent_id := lastval();
|
||||
-- 按钮 SQL
|
||||
#foreach ($functionName in $functionNames)
|
||||
#set ($index = $foreach.count - 1)
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'${table.classComment}${functionName}', '${permissionPrefix}:${functionOps.get($index)}', 3, $foreach.count, v_parent_id,
|
||||
'', '', '', 0
|
||||
);
|
||||
#end
|
||||
END $$;
|
||||
##
|
||||
## ======================= SQL Server =======================
|
||||
#elseif ($dbType.name() == 'SQL_SERVER' || $dbType.name() == 'SQL_SERVER2005')
|
||||
-- 菜单 SQL
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status, component_name
|
||||
)
|
||||
VALUES (
|
||||
'${table.classComment}管理', '', 2, 0, ${table.parentMenuId},
|
||||
'${simpleClassName_strikeCase}', '', '${table.moduleName}/${table.businessName}/index', 0, '${table.className}'
|
||||
);
|
||||
|
||||
-- 按钮父菜单ID
|
||||
-- 说明:SQL Server 使用 SCOPE_IDENTITY() 获取上一个插入的自增 ID
|
||||
DECLARE @parentId BIGINT;
|
||||
SET @parentId = SCOPE_IDENTITY();
|
||||
|
||||
-- 按钮 SQL
|
||||
#foreach ($functionName in $functionNames)
|
||||
#set ($index = $foreach.count - 1)
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'${table.classComment}${functionName}', '${permissionPrefix}:${functionOps.get($index)}', 3, $foreach.count, @parentId,
|
||||
'', '', '', 0
|
||||
);
|
||||
#end
|
||||
##
|
||||
## ======================= 不支持的数据库类型 =======================
|
||||
#else
|
||||
-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
-- 注意:当前数据库类型 ${dbType.name()} 暂不支持自动生成菜单 SQL
|
||||
-- 请参考以下 MySQL 语法,手动修改为您的数据库对应的语法
|
||||
-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
-- 菜单 SQL
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status, component_name
|
||||
)
|
||||
VALUES (
|
||||
'${table.classComment}管理', '', 2, 0, ${table.parentMenuId},
|
||||
'${simpleClassName_strikeCase}', '', '${table.moduleName}/${table.businessName}/index', 0, '${table.className}'
|
||||
);
|
||||
|
||||
-- 按钮父菜单ID
|
||||
-- TODO: 请根据您的数据库类型,修改获取上一个插入 ID 的方式
|
||||
-- MySQL: SELECT @parentId := LAST_INSERT_ID();
|
||||
-- Oracle: SELECT system_menu_seq.CURRVAL INTO v_parent_id FROM DUAL;
|
||||
-- PostgreSQL: SELECT lastval() INTO v_parent_id;
|
||||
-- SQL Server: SET @parentId = SCOPE_IDENTITY();
|
||||
SELECT @parentId := LAST_INSERT_ID();
|
||||
|
||||
-- 按钮 SQL
|
||||
#foreach ($functionName in $functionNames)
|
||||
#set ($index = $foreach.count - 1)
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'${table.classComment}${functionName}', '${permissionPrefix}:${functionOps.get($index)}', 3, $foreach.count, @parentId,
|
||||
'', '', '', 0
|
||||
);
|
||||
#end
|
||||
#end
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
<template>
|
||||
<!-- 搜索框入口 -->
|
||||
<view @click="visible = true">
|
||||
<wd-search :placeholder="placeholder" hide-cancel disabled />
|
||||
</view>
|
||||
|
||||
<!-- 搜索弹窗 -->
|
||||
<wd-popup v-model="visible" position="top" @close="visible = false">
|
||||
<view class="yd-search-form-container" :style="{ paddingTop: `#[[${]]#getNavbarHeight()#[[}]]#px` }">
|
||||
#set ($hasDict = 0)
|
||||
#foreach ($column in $columns)
|
||||
#if ($hasDict == 0 && $column.listOperation && $column.dictType && "" != $column.dictType)
|
||||
#set ($hasDict = 1)
|
||||
#end
|
||||
#end
|
||||
#foreach($column in $columns)
|
||||
#if ($column.listOperation)
|
||||
#set ($dictType = $column.dictType)
|
||||
#set ($javaField = $column.javaField)
|
||||
#set ($javaType = $column.javaType)
|
||||
#set ($listOperationCondition = $column.listOperationCondition)
|
||||
#set ($comment = $column.columnComment)
|
||||
#set ($dictMethod = "getDictOptions")
|
||||
#if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
|
||||
#set ($dictMethod = "getIntDictOptions")
|
||||
#elseif ($javaType == "String")
|
||||
#set ($dictMethod = "getStrDictOptions")
|
||||
#elseif ($javaType == "Boolean")
|
||||
#set ($dictMethod = "getBoolDictOptions")
|
||||
#end
|
||||
#if ($column.htmlType == "input")
|
||||
<view class="yd-search-form-item">
|
||||
<view class="yd-search-form-label">
|
||||
${comment}
|
||||
</view>
|
||||
<wd-input
|
||||
v-model="formData.${javaField}"
|
||||
placeholder="请输入${comment}"
|
||||
clearable
|
||||
/>
|
||||
</view>
|
||||
#elseif ($column.htmlType == "datetime" && $listOperationCondition == "BETWEEN")
|
||||
#set ($AttrName = $javaField.substring(0,1).toUpperCase() + ${javaField.substring(1)})
|
||||
<view class="yd-search-form-item">
|
||||
<view class="yd-search-form-label">
|
||||
${comment}
|
||||
</view>
|
||||
<view class="yd-search-form-date-range-container">
|
||||
<view @click="visible${AttrName}[0] = true">
|
||||
<view class="yd-search-form-date-range-picker">
|
||||
{{ formatDate(formData.${javaField}?.[0]) || '开始日期' }}
|
||||
</view>
|
||||
</view>
|
||||
-
|
||||
<view @click="visible${AttrName}[1] = true">
|
||||
<view class="yd-search-form-date-range-picker">
|
||||
{{ formatDate(formData.${javaField}?.[1]) || '结束日期' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<wd-datetime-picker-view v-if="visible${AttrName}[0]" v-model="temp${AttrName}[0]" type="date" />
|
||||
<view v-if="visible${AttrName}[0]" class="yd-search-form-date-range-actions">
|
||||
<wd-button size="small" plain @click="visible${AttrName}[0] = false">
|
||||
取消
|
||||
</wd-button>
|
||||
<wd-button size="small" type="primary" @click="handle${AttrName}0Confirm">
|
||||
确定
|
||||
</wd-button>
|
||||
</view>
|
||||
<wd-datetime-picker-view v-if="visible${AttrName}[1]" v-model="temp${AttrName}[1]" type="date" />
|
||||
<view v-if="visible${AttrName}[1]" class="yd-search-form-date-range-actions">
|
||||
<wd-button size="small" plain @click="visible${AttrName}[1] = false">
|
||||
取消
|
||||
</wd-button>
|
||||
<wd-button size="small" type="primary" @click="handle${AttrName}1Confirm">
|
||||
确定
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
#elseif (($column.htmlType == "select" || $column.htmlType == "radio") && $dictType && "" != $dictType)
|
||||
<view class="yd-search-form-item">
|
||||
<view class="yd-search-form-label">
|
||||
${comment}
|
||||
</view>
|
||||
<wd-radio-group v-model="formData.${javaField}" shape="button">
|
||||
<wd-radio :value="-1">
|
||||
全部
|
||||
</wd-radio>
|
||||
<wd-radio
|
||||
v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</wd-radio>
|
||||
</wd-radio-group>
|
||||
</view>
|
||||
#else
|
||||
<view class="yd-search-form-item">
|
||||
<view class="yd-search-form-label">
|
||||
${comment}
|
||||
</view>
|
||||
<wd-input
|
||||
v-model="formData.${javaField}"
|
||||
placeholder="请输入${comment}"
|
||||
clearable
|
||||
/>
|
||||
</view>
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
<view class="yd-search-form-actions">
|
||||
<wd-button class="flex-1" plain @click="handleReset">
|
||||
重置
|
||||
</wd-button>
|
||||
<wd-button class="flex-1" type="primary" @click="handleSearch">
|
||||
搜索
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
#set ($hasDict = 0)
|
||||
#set ($hasGetDictOptions = 0)
|
||||
#set ($hasGetIntDictOptions = 0)
|
||||
#set ($hasGetStrDictOptions = 0)
|
||||
#set ($hasGetBoolDictOptions = 0)
|
||||
#set ($hasDateTimeBetween = 0)
|
||||
#foreach($column in $columns)
|
||||
#if ($column.listOperation && $column.dictType && "" != $column.dictType)
|
||||
#set ($hasDict = 1)
|
||||
#if ($column.htmlType == "select" || $column.htmlType == "radio")
|
||||
#if ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short")
|
||||
#set ($hasGetIntDictOptions = 1)
|
||||
#elseif ($column.javaType == "String")
|
||||
#set ($hasGetStrDictOptions = 1)
|
||||
#elseif ($column.javaType == "Boolean")
|
||||
#set ($hasGetBoolDictOptions = 1)
|
||||
#else
|
||||
#set ($hasGetDictOptions = 1)
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#if ($hasDateTimeBetween == 0 && $column.listOperation && $column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
|
||||
#set ($hasDateTimeBetween = 1)
|
||||
#end
|
||||
#end
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { getNavbarHeight } from '@/utils'
|
||||
#if ($hasDateTimeBetween == 1)
|
||||
import { formatDate, formatDateRange } from '@/utils/date'
|
||||
#end
|
||||
#if ($hasDict == 1)
|
||||
#set ($dictImportNames = "getDictLabel, ")
|
||||
#if ($hasGetDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getDictOptions, ")
|
||||
#end
|
||||
#if ($hasGetIntDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getIntDictOptions, ")
|
||||
#end
|
||||
#if ($hasGetStrDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getStrDictOptions, ")
|
||||
#end
|
||||
#if ($hasGetBoolDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getBoolDictOptions, ")
|
||||
#end
|
||||
#set ($dictImportNames = $dictImportNames.trim())
|
||||
#set ($dictImportNames = $dictImportNames.substring(0, $dictImportNames.length() - 1))
|
||||
import { $dictImportNames } from '@/hooks/useDict'
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
#end
|
||||
|
||||
const emit = defineEmits<{
|
||||
search: [data: Record<string, any>]
|
||||
reset: []
|
||||
}>()
|
||||
|
||||
const visible = ref(false)
|
||||
const formData = reactive({
|
||||
#foreach($column in $columns)
|
||||
#if ($column.listOperation)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
|
||||
${column.javaField}: [undefined, undefined] as [number | undefined, number | undefined],
|
||||
#elseif ($column.dictType && "" != $column.dictType)
|
||||
#if(${javaType} == "string")
|
||||
${column.javaField}: -1 as -1 | string, // -1 表示全部
|
||||
#elseif(${javaType} == "boolean")
|
||||
${column.javaField}: -1 as -1 | boolean, // -1 表示全部
|
||||
#else
|
||||
${column.javaField}: -1, // -1 表示全部
|
||||
#end
|
||||
#elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
|
||||
${column.javaField}: undefined as number | undefined,
|
||||
#elseif(${javaType} == "boolean")
|
||||
${column.javaField}: undefined as boolean | undefined,
|
||||
#else
|
||||
${column.javaField}: undefined as string | undefined,
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
})
|
||||
|
||||
/** 搜索条件 placeholder 拼接 */
|
||||
const placeholder = computed(() => {
|
||||
const conditions: string[] = []
|
||||
#foreach($column in $columns)
|
||||
#if ($column.listOperation)
|
||||
#set ($dictType = $column.dictType)
|
||||
#set ($javaField = $column.javaField)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#set ($comment = $column.columnComment)
|
||||
#if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
|
||||
if (formData.${javaField}?.[0] && formData.${javaField}?.[1]) {
|
||||
conditions.push(`${comment}:#[[${]]#formatDate(formData.${javaField}[0])#[[}]]#~#[[${]]#formatDate(formData.${javaField}[1])#[[}]]#`)
|
||||
}
|
||||
#elseif ($dictType && "" != $dictType)
|
||||
if (formData.${javaField} !== -1) {
|
||||
conditions.push(`${comment}:#[[${]]#getDictLabel(DICT_TYPE.${dictType.toUpperCase()}, formData.${javaField})#[[}]]#`)
|
||||
}
|
||||
#else
|
||||
#if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte" || ${javaType} == "boolean")
|
||||
if (formData.${javaField} !== undefined) {
|
||||
conditions.push(`${comment}:#[[${]]#formData.${javaField}#[[}]]#`)
|
||||
}
|
||||
#else
|
||||
if (formData.${javaField}) {
|
||||
conditions.push(`${comment}:#[[${]]#formData.${javaField}#[[}]]#`)
|
||||
}
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
return conditions.length > 0 ? conditions.join(' | ') : '搜索${table.classComment}'
|
||||
})
|
||||
|
||||
#if ($hasDateTimeBetween == 1)
|
||||
// 时间范围选择器状态
|
||||
#foreach($column in $columns)
|
||||
#if ($column.listOperation && $column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
|
||||
#set ($javaField = $column.javaField)
|
||||
#set ($AttrName = $javaField.substring(0,1).toUpperCase() + ${javaField.substring(1)})
|
||||
const visible${AttrName} = ref<[boolean, boolean]>([false, false])
|
||||
const temp${AttrName} = ref<[number, number]>([Date.now(), Date.now()])
|
||||
|
||||
/** ${column.columnComment}[0]确认 */
|
||||
function handle${AttrName}0Confirm() {
|
||||
formData.${javaField} = [temp${AttrName}.value[0], formData.${javaField}?.[1]]
|
||||
visible${AttrName}.value[0] = false
|
||||
}
|
||||
|
||||
/** ${column.columnComment}[1]确认 */
|
||||
function handle${AttrName}1Confirm() {
|
||||
formData.${javaField} = [formData.${javaField}?.[0], temp${AttrName}.value[1]]
|
||||
visible${AttrName}.value[1] = false
|
||||
}
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
|
||||
/** 搜索 */
|
||||
function handleSearch() {
|
||||
visible.value = false
|
||||
emit('search', {
|
||||
...formData,
|
||||
#foreach($column in $columns)
|
||||
#if ($column.listOperation)
|
||||
#if ($column.dictType && "" != $column.dictType)
|
||||
${column.javaField}: formData.${column.javaField} === -1 ? undefined : formData.${column.javaField},
|
||||
#elseif ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
|
||||
${column.javaField}: formatDateRange(formData.${column.javaField}),
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
})
|
||||
}
|
||||
|
||||
/** 重置 */
|
||||
function handleReset() {
|
||||
#foreach($column in $columns)
|
||||
#if ($column.listOperation)
|
||||
#if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
|
||||
formData.${column.javaField} = [undefined, undefined]
|
||||
#elseif ($column.dictType && "" != $column.dictType)
|
||||
formData.${column.javaField} = -1
|
||||
#else
|
||||
formData.${column.javaField} = undefined
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
visible.value = false
|
||||
emit('reset')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<view class="yd-page-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<wd-navbar
|
||||
title="${table.classComment}详情"
|
||||
left-arrow placeholder safe-area-inset-top fixed
|
||||
@click-left="handleBack"
|
||||
/>
|
||||
|
||||
<!-- 详情内容 -->
|
||||
<view>
|
||||
<wd-cell-group border>
|
||||
#foreach($column in $columns)
|
||||
#if ($column.primaryKey || $column.listOperationResult || $column.createOperation || $column.updateOperation)
|
||||
#set ($javaField = $column.javaField)
|
||||
#set ($comment = $column.columnComment)
|
||||
#if ($column.dictType && "" != $column.dictType)
|
||||
<wd-cell title="${comment}">
|
||||
<dict-tag :type="DICT_TYPE.${column.dictType.toUpperCase()}" :value="formData?.${javaField}" />
|
||||
</wd-cell>
|
||||
#elseif ($column.javaType == "LocalDateTime")
|
||||
<wd-cell title="${comment}" :value="formatDateTime(formData?.${javaField}) || '-'" />
|
||||
#else
|
||||
<wd-cell title="${comment}" :value="formData?.${javaField} ?? '-'" />
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
</wd-cell-group>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<view class="yd-detail-footer">
|
||||
<view class="yd-detail-footer-actions">
|
||||
<wd-button
|
||||
v-if="hasAccessByCodes(['${permissionPrefix}:update'])"
|
||||
class="flex-1" type="warning" @click="handleEdit"
|
||||
>
|
||||
编辑
|
||||
</wd-button>
|
||||
<wd-button
|
||||
v-if="hasAccessByCodes(['${permissionPrefix}:delete'])"
|
||||
class="flex-1" type="error" :loading="deleting" @click="handleDelete"
|
||||
>
|
||||
删除
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
#set ($hasDict = 0)
|
||||
#foreach($column in $columns)
|
||||
#if ($hasDict == 0 && $column.dictType && "" != $column.dictType)
|
||||
#set ($hasDict = 1)
|
||||
#end
|
||||
#end
|
||||
#set ($hasDateTime = 0)
|
||||
#foreach($column in $columns)
|
||||
#if ($hasDateTime == 0 && $column.javaType == "LocalDateTime")
|
||||
#set ($hasDateTime = 1)
|
||||
#end
|
||||
#end
|
||||
import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useToast } from 'wot-design-uni'
|
||||
import { delete${simpleClassName}, get${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { useAccess } from '@/hooks/useAccess'
|
||||
import { navigateBackPlus } from '@/utils'
|
||||
#if ($hasDict == 1)
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
#end
|
||||
#if ($hasDateTime == 1)
|
||||
import { formatDateTime } from '@/utils/date'
|
||||
#end
|
||||
|
||||
const props = defineProps<{
|
||||
id?: number | any
|
||||
}>()
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
const { hasAccessByCodes } = useAccess()
|
||||
const toast = useToast()
|
||||
const formData = ref<${simpleClassName}>()
|
||||
const deleting = ref(false)
|
||||
|
||||
/** 返回上一页 */
|
||||
function handleBack() {
|
||||
navigateBackPlus('/pages-${table.moduleName}/${table.businessName}/index')
|
||||
}
|
||||
|
||||
/** 加载${table.classComment}详情 */
|
||||
async function getDetail() {
|
||||
if (!props.id) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
toast.loading('加载中...')
|
||||
formData.value = await get${simpleClassName}(props.id)
|
||||
} finally {
|
||||
toast.close()
|
||||
}
|
||||
}
|
||||
|
||||
/** 编辑${table.classComment} */
|
||||
function handleEdit() {
|
||||
uni.navigateTo({
|
||||
url: `/pages-${table.moduleName}/${table.businessName}/form/index?id=#[[${]]#props.id#[[}]]#`,
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除${table.classComment} */
|
||||
function handleDelete() {
|
||||
if (!props.id) {
|
||||
return
|
||||
}
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除该${table.classComment}吗?',
|
||||
success: async (res) => {
|
||||
if (!res.confirm) {
|
||||
return
|
||||
}
|
||||
deleting.value = true
|
||||
try {
|
||||
await delete${simpleClassName}(props.id)
|
||||
toast.success('删除成功')
|
||||
setTimeout(() => {
|
||||
handleBack()
|
||||
}, 500)
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<view class="yd-page-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<wd-navbar
|
||||
:title="getTitle"
|
||||
left-arrow placeholder safe-area-inset-top fixed
|
||||
@click-left="handleBack"
|
||||
/>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<view>
|
||||
<wd-form ref="formRef" :model="formData" :rules="formRules">
|
||||
<wd-cell-group border>
|
||||
#foreach($column in $columns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey)
|
||||
#set ($dictType = $column.dictType)
|
||||
#set ($javaField = $column.javaField)
|
||||
#set ($javaType = $column.javaType)
|
||||
#set ($comment = $column.columnComment)
|
||||
#set ($dictMethod = "getDictOptions")
|
||||
#if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
|
||||
#set ($dictMethod = "getIntDictOptions")
|
||||
#elseif ($javaType == "String")
|
||||
#set ($dictMethod = "getStrDictOptions")
|
||||
#elseif ($javaType == "Boolean")
|
||||
#set ($dictMethod = "getBoolDictOptions")
|
||||
#end
|
||||
## 优先判断是否有字典,有字典则使用 radio-group
|
||||
#if (($column.htmlType == "select" || $column.htmlType == "radio") && $dictType && "" != $dictType)
|
||||
<wd-cell title="${comment}" title-width="180rpx" prop="${javaField}" center>
|
||||
<wd-radio-group v-model="formData.${javaField}" shape="button">
|
||||
<wd-radio
|
||||
v-for="dict in $dictMethod(DICT_TYPE.${dictType.toUpperCase()})"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</wd-radio>
|
||||
</wd-radio-group>
|
||||
</wd-cell>
|
||||
## 数字类型(无字典)
|
||||
#elseif (${javaType.toLowerCase()} == "long" || ${javaType.toLowerCase()} == "integer" || ${javaType.toLowerCase()} == "short" || ${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal" || ${javaType.toLowerCase()} == "byte")
|
||||
<wd-cell title="${comment}" title-width="180rpx" prop="${javaField}" center>
|
||||
<wd-input-number
|
||||
v-model="formData.${javaField}"
|
||||
:min="0"
|
||||
/>
|
||||
</wd-cell>
|
||||
## 布尔类型
|
||||
#elseif (${javaType.toLowerCase()} == "boolean")
|
||||
<wd-cell title="${comment}" title-width="180rpx" prop="${javaField}" center>
|
||||
<wd-switch v-model="formData.${javaField}" />
|
||||
</wd-cell>
|
||||
## 日期时间类型
|
||||
#elseif (${javaType.toLowerCase()} == "date" || ${javaType.toLowerCase()} == "localdate" || ${javaType.toLowerCase()} == "localdatetime")
|
||||
#set ($pickerType = "date")
|
||||
#if (${javaType.toLowerCase()} == "localdatetime")
|
||||
#set ($pickerType = "datetime")
|
||||
#end
|
||||
<wd-datetime-picker
|
||||
v-model="formData.${javaField}"
|
||||
type="${pickerType}"
|
||||
label="${comment}"
|
||||
label-width="180rpx"
|
||||
prop="${javaField}"
|
||||
/>
|
||||
## 文本域
|
||||
#elseif ($column.htmlType == "textarea")
|
||||
<wd-textarea
|
||||
v-model="formData.${javaField}"
|
||||
label="${comment}"
|
||||
label-width="180rpx"
|
||||
placeholder="请输入${comment}"
|
||||
:maxlength="200"
|
||||
show-word-limit
|
||||
clearable
|
||||
/>
|
||||
## 默认:文本输入
|
||||
#else
|
||||
<wd-input
|
||||
v-model="formData.${javaField}"
|
||||
label="${comment}"
|
||||
label-width="180rpx"
|
||||
prop="${javaField}"
|
||||
clearable
|
||||
placeholder="请输入${comment}"
|
||||
/>
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
</wd-cell-group>
|
||||
</wd-form>
|
||||
</view>
|
||||
|
||||
<!-- 底部保存按钮 -->
|
||||
<view class="yd-detail-footer">
|
||||
<wd-button
|
||||
type="primary"
|
||||
block
|
||||
:loading="formLoading"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
保存
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
|
||||
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
|
||||
#set ($primaryTsType = "number")
|
||||
#else
|
||||
#set ($primaryTsType = "string")
|
||||
#end
|
||||
#set ($hasDict = 0)
|
||||
#set ($hasGetDictOptions = 0)
|
||||
#set ($hasGetIntDictOptions = 0)
|
||||
#set ($hasGetStrDictOptions = 0)
|
||||
#set ($hasGetBoolDictOptions = 0)
|
||||
#foreach ($column in $columns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
&& ($column.htmlType == "select" || $column.htmlType == "radio")
|
||||
&& $column.dictType && "" != $column.dictType)
|
||||
#set ($hasDict = 1)
|
||||
#if ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short")
|
||||
#set ($hasGetIntDictOptions = 1)
|
||||
#elseif ($column.javaType == "String")
|
||||
#set ($hasGetStrDictOptions = 1)
|
||||
#elseif ($column.javaType == "Boolean")
|
||||
#set ($hasGetBoolDictOptions = 1)
|
||||
#else
|
||||
#set ($hasGetDictOptions = 1)
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useToast } from 'wot-design-uni'
|
||||
import { create${simpleClassName}, get${simpleClassName}, update${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
#if ($hasDict == 1)
|
||||
#set ($dictImportNames = "")
|
||||
#if ($hasGetDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getDictOptions, ")
|
||||
#end
|
||||
#if ($hasGetIntDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getIntDictOptions, ")
|
||||
#end
|
||||
#if ($hasGetStrDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getStrDictOptions, ")
|
||||
#end
|
||||
#if ($hasGetBoolDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getBoolDictOptions, ")
|
||||
#end
|
||||
#set ($dictImportNames = $dictImportNames.trim())
|
||||
#set ($dictImportNames = $dictImportNames.substring(0, $dictImportNames.length() - 1))
|
||||
import { $dictImportNames } from '@/hooks/useDict'
|
||||
#end
|
||||
import { navigateBackPlus } from '@/utils'
|
||||
#if ($hasDict == 1)
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
#end
|
||||
|
||||
const props = defineProps<{
|
||||
id?: ${primaryTsType} | any
|
||||
}>()
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
const toast = useToast()
|
||||
const getTitle = computed(() => props.id ? '编辑${table.classComment}' : '新增${table.classComment}')
|
||||
const formLoading = ref(false)
|
||||
const formData = ref<${simpleClassName}>({
|
||||
#foreach($column in $columns)
|
||||
#if (($column.createOperation || $column.updateOperation) || $column.primaryKey)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#set ($javaFieldLower = $column.javaField.toLowerCase())
|
||||
#set ($optional = $column.nullable || $column.primaryKey || $javaFieldLower == "createtime" || $javaFieldLower == "updatetime")
|
||||
#if ($column.primaryKey)
|
||||
${column.javaField}: undefined,
|
||||
#elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
|
||||
${column.javaField}: 0,
|
||||
#elseif(${javaType} == "boolean")
|
||||
${column.javaField}: false,
|
||||
#elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime")
|
||||
${column.javaField}: undefined,
|
||||
#else
|
||||
${column.javaField}: '',
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
})
|
||||
const formRules = {
|
||||
#foreach($column in $columns)
|
||||
#set ($javaFieldLower = $column.javaField.toLowerCase())
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey
|
||||
&& $javaFieldLower != "createtime" && $javaFieldLower != "updatetime")
|
||||
${column.javaField}: [{ required: true, message: '${column.columnComment}不能为空' }],
|
||||
#end
|
||||
#end
|
||||
}
|
||||
const formRef = ref()
|
||||
|
||||
/** 返回上一页 */
|
||||
function handleBack() {
|
||||
navigateBackPlus('/pages-${table.moduleName}/${table.businessName}/index')
|
||||
}
|
||||
|
||||
/** 加载${table.classComment}详情 */
|
||||
async function getDetail() {
|
||||
if (!props.id) {
|
||||
return
|
||||
}
|
||||
formData.value = await get${simpleClassName}(props.id)
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
const { valid } = await formRef.value.validate()
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
|
||||
formLoading.value = true
|
||||
try {
|
||||
if (props.id) {
|
||||
await update${simpleClassName}(formData.value)
|
||||
toast.success('修改成功')
|
||||
} else {
|
||||
await create${simpleClassName}(formData.value)
|
||||
toast.success('新增成功')
|
||||
}
|
||||
setTimeout(() => {
|
||||
handleBack()
|
||||
}, 500)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<view class="yd-page-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<wd-navbar
|
||||
title="${table.classComment}管理"
|
||||
left-arrow placeholder safe-area-inset-top fixed
|
||||
@click-left="handleBack"
|
||||
/>
|
||||
|
||||
<!-- 搜索组件 -->
|
||||
<SearchForm @search="handleQuery" @reset="handleReset" />
|
||||
|
||||
<!-- ${table.classComment}列表 -->
|
||||
<view class="p-24rpx">
|
||||
<view
|
||||
v-for="item in list"
|
||||
:key="item.${primaryColumn.javaField}"
|
||||
class="mb-24rpx overflow-hidden rounded-12rpx bg-white shadow-sm"
|
||||
@click="handleDetail(item)"
|
||||
>
|
||||
<view class="p-24rpx">
|
||||
#set ($titleField = "")
|
||||
#set ($statusField = "")
|
||||
#set ($statusDictType = "")
|
||||
#foreach($column in $columns)
|
||||
#if ($titleField == "" && !$column.primaryKey && $column.listOperationResult)
|
||||
#set ($titleField = $column.javaField)
|
||||
#set ($titleComment = $column.columnComment)
|
||||
#end
|
||||
#if ($statusField == "" && $column.listOperationResult && $column.dictType && "" != $column.dictType)
|
||||
#set ($statusField = $column.javaField)
|
||||
#set ($statusDictType = $column.dictType)
|
||||
#end
|
||||
#end
|
||||
#if ($titleField == "")
|
||||
#set ($titleField = $primaryColumn.javaField)
|
||||
#end
|
||||
<view class="mb-16rpx flex items-center justify-between">
|
||||
<view class="text-32rpx text-[#333] font-semibold">
|
||||
{{ item.${titleField} }}
|
||||
</view>
|
||||
#if($statusField != "")
|
||||
<dict-tag :type="DICT_TYPE.${statusDictType.toUpperCase()}" :value="item.${statusField}" />
|
||||
#end
|
||||
</view>
|
||||
#foreach($column in $columns)
|
||||
#if ($column.listOperationResult && !$column.primaryKey && $column.javaField != $titleField && $column.javaField != $statusField)
|
||||
#set ($javaField = $column.javaField)
|
||||
#set ($comment = $column.columnComment)
|
||||
#set ($dictType = $column.dictType)
|
||||
#set ($javaType = $column.javaType)
|
||||
#if ($dictType && "" != $dictType)
|
||||
<view class="mb-12rpx flex items-center text-28rpx text-[#666]">
|
||||
<text class="mr-8rpx text-[#999]">${comment}:</text>
|
||||
<dict-tag :type="DICT_TYPE.${dictType.toUpperCase()}" :value="item.${javaField}" />
|
||||
</view>
|
||||
#elseif ($javaType == "LocalDateTime")
|
||||
<view class="mb-12rpx flex items-center text-28rpx text-[#666]">
|
||||
<text class="mr-8rpx text-[#999]">${comment}:</text>
|
||||
<text class="line-clamp-1">{{ formatDateTime(item.${javaField}) || '-' }}</text>
|
||||
</view>
|
||||
#else
|
||||
<view class="mb-12rpx flex items-center text-28rpx text-[#666]">
|
||||
<text class="mr-8rpx text-[#999]">${comment}:</text>
|
||||
<text class="line-clamp-1">{{ item.${javaField} }}</text>
|
||||
</view>
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view v-if="loadMoreState !== 'loading' && list.length === 0" class="py-100rpx text-center">
|
||||
<wd-status-tip image="content" tip="暂无${table.classComment}数据" />
|
||||
</view>
|
||||
<wd-loadmore
|
||||
v-if="list.length > 0"
|
||||
:state="loadMoreState"
|
||||
@reload="loadMore"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 新增按钮 -->
|
||||
<wd-fab
|
||||
v-if="hasAccessByCodes(['${permissionPrefix}:create'])"
|
||||
position="right-bottom"
|
||||
type="primary"
|
||||
:expandable="false"
|
||||
@click="handleAdd"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
#set ($hasDict = 0)
|
||||
#foreach($column in $columns)
|
||||
#if ($hasDict == 0 && $column.listOperationResult && $column.dictType && "" != $column.dictType)
|
||||
#set ($hasDict = 1)
|
||||
#end
|
||||
#end
|
||||
#set ($hasDateTime = 0)
|
||||
#foreach($column in $columns)
|
||||
#if ($column.listOperationResult)
|
||||
#if ($hasDateTime == 0 && $column.javaType == "LocalDateTime")
|
||||
#set ($hasDateTime = 1)
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import type { LoadMoreState } from '@/http/types'
|
||||
import { onReachBottom } from '@dcloudio/uni-app'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { get${simpleClassName}Page } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { useAccess } from '@/hooks/useAccess'
|
||||
import { navigateBackPlus } from '@/utils'
|
||||
#if ($hasDict == 1)
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
#end
|
||||
#if ($hasDateTime == 1)
|
||||
import { formatDateTime } from '@/utils/date'
|
||||
#end
|
||||
import SearchForm from './components/search-form.vue'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
const { hasAccessByCodes } = useAccess()
|
||||
const total = ref(0)
|
||||
const list = ref<${simpleClassName}[]>([])
|
||||
const loadMoreState = ref<LoadMoreState>('loading')
|
||||
const queryParams = ref({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
})
|
||||
|
||||
/** 返回上一页 */
|
||||
function handleBack() {
|
||||
navigateBackPlus()
|
||||
}
|
||||
|
||||
/** 查询${table.classComment}列表 */
|
||||
async function getList() {
|
||||
loadMoreState.value = 'loading'
|
||||
try {
|
||||
const data = await get${simpleClassName}Page(queryParams.value)
|
||||
list.value = [...list.value, ...data.list]
|
||||
total.value = data.total
|
||||
loadMoreState.value = list.value.length >= total.value ? 'finished' : 'loading'
|
||||
} catch {
|
||||
queryParams.value.pageNo = queryParams.value.pageNo > 1 ? queryParams.value.pageNo - 1 : 1
|
||||
loadMoreState.value = 'error'
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery(data?: Record<string, any>) {
|
||||
queryParams.value = {
|
||||
...data,
|
||||
pageNo: 1,
|
||||
pageSize: queryParams.value.pageSize,
|
||||
}
|
||||
list.value = []
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
function handleReset() {
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 加载更多 */
|
||||
function loadMore() {
|
||||
if (loadMoreState.value === 'finished') {
|
||||
return
|
||||
}
|
||||
queryParams.value.pageNo++
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 新增${table.classComment} */
|
||||
function handleAdd() {
|
||||
uni.navigateTo({
|
||||
url: '/pages-${table.moduleName}/${table.businessName}/form/index',
|
||||
})
|
||||
}
|
||||
|
||||
/** 查看详情 */
|
||||
function handleDetail(item: ${simpleClassName}) {
|
||||
uni.navigateTo({
|
||||
url: `/pages-${table.moduleName}/${table.businessName}/detail/index?id=#[[${]]#item.${primaryColumn.javaField}#[[}]]#`,
|
||||
})
|
||||
}
|
||||
|
||||
/** 触底加载更多 */
|
||||
onReachBottom(() => {
|
||||
loadMore()
|
||||
})
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTa
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.DatabaseTableRespVO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenColumnMapper;
|
||||
import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenTableMapper;
|
||||
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
|
||||
@@ -19,7 +20,9 @@ import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
|
||||
import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
|
||||
import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenBuilder;
|
||||
import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenEngine;
|
||||
import cn.iocoder.yudao.module.infra.service.db.DataSourceConfigService;
|
||||
import cn.iocoder.yudao.module.infra.service.db.DatabaseTableService;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.generator.config.po.TableField;
|
||||
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -62,6 +65,8 @@ public class CodegenServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@MockBean
|
||||
private DatabaseTableService databaseTableService;
|
||||
@MockitoBean
|
||||
private DataSourceConfigService dataSourceConfigService;
|
||||
|
||||
@MockBean
|
||||
private CodegenBuilder codegenBuilder;
|
||||
@@ -453,9 +458,12 @@ public class CodegenServiceImplTest extends BaseDbUnitTest {
|
||||
CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId())
|
||||
.setOrdinalPosition(2));
|
||||
codegenColumnMapper.insert(column02);
|
||||
// mock 数据(DataSourceConfigDO)
|
||||
when(dataSourceConfigService.getDataSourceConfig(eq(table.getDataSourceConfigId())))
|
||||
.thenReturn(randomPojo(DataSourceConfigDO.class, o -> o.setUrl("jdbc:mysql://")));
|
||||
// mock 执行生成
|
||||
Map<String, String> codes = MapUtil.of(randomString(), randomString());
|
||||
when(codegenEngine.execute(eq(table), argThat(columns -> {
|
||||
when(codegenEngine.execute(eq(DbType.MYSQL), eq(table), argThat(columns -> {
|
||||
assertEquals(2, columns.size());
|
||||
assertEquals(column01, columns.get(0));
|
||||
assertEquals(column02, columns.get(1));
|
||||
@@ -494,9 +502,12 @@ public class CodegenServiceImplTest extends BaseDbUnitTest {
|
||||
// mock 数据(sub CodegenColumnDO)
|
||||
CodegenColumnDO subColumn01 = randomPojo(CodegenColumnDO.class, o -> o.setId(1024L).setTableId(subTable.getId()));
|
||||
codegenColumnMapper.insert(subColumn01);
|
||||
// mock 数据(DataSourceConfigDO)
|
||||
when(dataSourceConfigService.getDataSourceConfig(eq(table.getDataSourceConfigId())))
|
||||
.thenReturn(randomPojo(DataSourceConfigDO.class, o -> o.setUrl("jdbc:mysql://")));
|
||||
// mock 执行生成
|
||||
Map<String, String> codes = MapUtil.of(randomString(), randomString());
|
||||
when(codegenEngine.execute(eq(table), argThat(columns -> {
|
||||
when(codegenEngine.execute(eq(DbType.MYSQL), eq(table), argThat(columns -> {
|
||||
assertEquals(2, columns.size());
|
||||
assertEquals(column01, columns.get(0));
|
||||
assertEquals(column02, columns.get(1));
|
||||
|
||||
@@ -4,6 +4,7 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
|
||||
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
|
||||
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -28,7 +29,7 @@ public class CodegenEngineVue2Test extends CodegenEngineAbstractTest {
|
||||
List<CodegenColumnDO> columns = getColumnList("student");
|
||||
|
||||
// 调用
|
||||
Map<String, String> result = codegenEngine.execute(table, columns, null, null);
|
||||
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
|
||||
// 生成测试文件
|
||||
//writeResult(result, resourcesPath + "/vue2_one");
|
||||
// 断言
|
||||
@@ -44,7 +45,7 @@ public class CodegenEngineVue2Test extends CodegenEngineAbstractTest {
|
||||
List<CodegenColumnDO> columns = getColumnList("category");
|
||||
|
||||
// 调用
|
||||
Map<String, String> result = codegenEngine.execute(table, columns, null, null);
|
||||
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
|
||||
// 生成测试文件
|
||||
//writeResult(result, resourcesPath + "/vue2_tree");
|
||||
// 断言
|
||||
@@ -88,7 +89,7 @@ public class CodegenEngineVue2Test extends CodegenEngineAbstractTest {
|
||||
List<CodegenColumnDO> teacherColumns = getColumnList("teacher");
|
||||
|
||||
// 调用
|
||||
Map<String, String> result = codegenEngine.execute(table, columns,
|
||||
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns,
|
||||
Arrays.asList(contactTable, teacherTable), Arrays.asList(contactColumns, teacherColumns));
|
||||
// 生成测试文件
|
||||
//writeResult(result, resourcesPath + path);
|
||||
|
||||
@@ -4,6 +4,7 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
|
||||
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
|
||||
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -28,7 +29,7 @@ public class CodegenEngineVue3Test extends CodegenEngineAbstractTest {
|
||||
List<CodegenColumnDO> columns = getColumnList("student");
|
||||
|
||||
// 调用
|
||||
Map<String, String> result = codegenEngine.execute(table, columns, null, null);
|
||||
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
|
||||
// 生成测试文件
|
||||
//writeResult(result, resourcesPath + "/vue3_one");
|
||||
// 断言
|
||||
@@ -44,7 +45,7 @@ public class CodegenEngineVue3Test extends CodegenEngineAbstractTest {
|
||||
List<CodegenColumnDO> columns = getColumnList("category");
|
||||
|
||||
// 调用
|
||||
Map<String, String> result = codegenEngine.execute(table, columns, null, null);
|
||||
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
|
||||
// 生成测试文件
|
||||
//writeResult(result, resourcesPath + "/vue3_tree");
|
||||
// 断言
|
||||
@@ -88,7 +89,7 @@ public class CodegenEngineVue3Test extends CodegenEngineAbstractTest {
|
||||
List<CodegenColumnDO> teacherColumns = getColumnList("teacher");
|
||||
|
||||
// 调用
|
||||
Map<String, String> result = codegenEngine.execute(table, columns,
|
||||
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns,
|
||||
Arrays.asList(contactTable, teacherTable), Arrays.asList(contactColumns, teacherColumns));
|
||||
// 生成测试文件
|
||||
//writeResult(result, resourcesPath + path);
|
||||
|
||||
@@ -76,7 +76,7 @@ public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> {
|
||||
.in(CouponTemplateDO::getTakeType, canTakeTypes) // 2. 领取方式一致
|
||||
.and(ww -> ww.gt(CouponTemplateDO::getValidEndTime, LocalDateTime.now()) // 3.1 未过期
|
||||
.or().eq(CouponTemplateDO::getValidityType, CouponTemplateValidityTypeEnum.TERM.getType())) // 3.2 领取之后
|
||||
.apply(" (take_count < total_count OR total_count = -1)"); // 4. 剩余数量大于 0,或者无限领取
|
||||
.apply(" (take_count < total_count OR total_count = " + CouponTemplateDO.TOTAL_COUNT_MAX + ")"); // 4. 剩余数量大于 0,或者无限领取
|
||||
}
|
||||
return canTakeConsumer;
|
||||
}
|
||||
|
||||
@@ -285,8 +285,8 @@ public class CouponServiceImpl implements CouponService {
|
||||
// 校验剩余发放数量是否充足(仅在 CouponTakeTypeEnum.USER 用户领取时)
|
||||
// 关联案例:https://t.zsxq.com/mElGQ、https://t.zsxq.com/6pLzr
|
||||
if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeType())
|
||||
&& ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TAKE_LIMIT_COUNT_MAX) // 校验不限制领取数
|
||||
&& couponTemplate.getTakeCount() > couponTemplate.getTotalCount()) { // 已领取数量 >= 总发放数量
|
||||
&& !couponTemplateService.isTotalCountUnlimited(couponTemplate.getTotalCount()) // 校验不限制总发放数量
|
||||
&& couponTemplate.getTakeCount() > couponTemplate.getTotalCount()) { // 已领取数量 > 总发放数量
|
||||
throw exception(COUPON_TEMPLATE_NOT_ENOUGH);
|
||||
}
|
||||
// 校验"固定日期"的有效期类型是否过期
|
||||
@@ -304,7 +304,7 @@ public class CouponServiceImpl implements CouponService {
|
||||
* @param couponTemplate 优惠劵模版
|
||||
*/
|
||||
private void removeTakeLimitUser(Set<Long> userIds, CouponTemplateDO couponTemplate) {
|
||||
if (couponTemplate.getTakeLimitCount() <= 0) {
|
||||
if (couponTemplateService.isTakeLimitCountUnlimited(couponTemplate.getTakeLimitCount())) {
|
||||
return;
|
||||
}
|
||||
// 查询已领过券的用户
|
||||
@@ -360,7 +360,8 @@ public class CouponServiceImpl implements CouponService {
|
||||
}
|
||||
|
||||
// 2.1 过滤领取数量无限制的
|
||||
Set<Long> templateIds = convertSet(templates, CouponTemplateDO::getId, template -> template.getTakeLimitCount() != -1);
|
||||
Set<Long> templateIds = convertSet(templates, CouponTemplateDO::getId,
|
||||
template -> !couponTemplateService.isTakeLimitCountUnlimited(template.getTakeLimitCount()));
|
||||
// 2.2 检查用户领取的数量是否超过限制
|
||||
if (CollUtil.isNotEmpty(templateIds)) {
|
||||
Map<Long, Integer> couponTakeCountMap = this.getTakeCountMapByTemplateIds(templateIds, userId);
|
||||
|
||||
@@ -7,7 +7,7 @@ import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.Cou
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@@ -18,6 +18,23 @@ import java.util.List;
|
||||
*/
|
||||
public interface CouponTemplateService {
|
||||
|
||||
/**
|
||||
* 判断是否不限制每人领取数量
|
||||
*
|
||||
* @param takeLimitCount 每人限领个数
|
||||
* @return 是否不限制
|
||||
*/
|
||||
boolean isTakeLimitCountUnlimited(Integer takeLimitCount);
|
||||
|
||||
/**
|
||||
* 判断是否不限制总发放数量
|
||||
*
|
||||
* @param totalCount 发放数量
|
||||
* @return 是否不限制
|
||||
*/
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
boolean isTotalCountUnlimited(Integer totalCount);
|
||||
|
||||
/**
|
||||
* 创建优惠劵模板
|
||||
*
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package cn.iocoder.yudao.module.promotion.service.coupon;
|
||||
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.product.api.category.ProductCategoryApi;
|
||||
@@ -41,6 +40,16 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
|
||||
@Resource
|
||||
private ProductSpuApi productSpuApi;
|
||||
|
||||
@Override
|
||||
public boolean isTakeLimitCountUnlimited(Integer takeLimitCount) {
|
||||
return CouponTemplateDO.TAKE_LIMIT_COUNT_MAX.equals(takeLimitCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTotalCountUnlimited(Integer totalCount) {
|
||||
return CouponTemplateDO.TOTAL_COUNT_MAX.equals(totalCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createCouponTemplate(CouponTemplateCreateReqVO createReqVO) {
|
||||
// 校验商品范围
|
||||
@@ -59,7 +68,7 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
|
||||
CouponTemplateDO couponTemplate = validateCouponTemplateExists(updateReqVO.getId());
|
||||
// 校验发放数量不能过小(仅在 CouponTakeTypeEnum.USER 用户领取时)
|
||||
if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeType())
|
||||
&& ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TAKE_LIMIT_COUNT_MAX) // 非不限制
|
||||
&& !isTotalCountUnlimited(updateReqVO.getTotalCount()) // 非不限制总发放数量
|
||||
&& updateReqVO.getTotalCount() < couponTemplate.getTakeCount()) {
|
||||
throw exception(COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL, couponTemplate.getTakeCount());
|
||||
}
|
||||
|
||||
@@ -82,7 +82,17 @@ public class MemberUserController {
|
||||
@PreAuthorize("@ss.hasPermission('member:user:query')")
|
||||
public CommonResult<MemberUserRespVO> getUser(@RequestParam("id") Long id) {
|
||||
MemberUserDO user = memberUserService.getUser(id);
|
||||
return success(MemberUserConvert.INSTANCE.convert03(user));
|
||||
if (user == null) {
|
||||
return success(null);
|
||||
}
|
||||
MemberUserRespVO userVO = MemberUserConvert.INSTANCE.convert03(user);
|
||||
if (user.getLevelId() != null) {
|
||||
MemberLevelDO level = memberLevelService.getLevel(userVO.getId());
|
||||
if (level != null) {
|
||||
userVO.setLevelName(level.getName());
|
||||
}
|
||||
}
|
||||
return success(userVO);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
|
||||
@@ -36,6 +36,14 @@ public class LoginLogController {
|
||||
@Resource
|
||||
private LoginLogService loginLogService;
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得登录日志")
|
||||
@PreAuthorize("@ss.hasPermission('system:login-log:query')")
|
||||
public CommonResult<LoginLogRespVO> getLoginLog(Long id) {
|
||||
LoginLogDO loginLog = loginLogService.getLoginLog(id);
|
||||
return success(BeanUtils.toBean(loginLog, LoginLogRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得登录日志分页列表")
|
||||
@PreAuthorize("@ss.hasPermission('system:login-log:query')")
|
||||
|
||||
@@ -61,6 +61,7 @@ public class OperateLogController {
|
||||
@Operation(summary = "导出操作日志")
|
||||
@GetMapping("/export-excel")
|
||||
@PreAuthorize("@ss.hasPermission('system:operate-log:export')")
|
||||
@TransMethodResult
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportOperateLog(HttpServletResponse response, @Valid OperateLogPageReqVO exportReqVO) throws IOException {
|
||||
exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog;
|
||||
|
||||
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
|
||||
import com.fhs.core.trans.anno.Trans;
|
||||
import com.fhs.core.trans.constant.TransType;
|
||||
import com.fhs.core.trans.vo.VO;
|
||||
@@ -31,6 +33,11 @@ public class OperateLogRespVO implements VO {
|
||||
@ExcelProperty("操作人")
|
||||
private String userName;
|
||||
|
||||
@Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1", implementation = Integer.class)
|
||||
@ExcelProperty("用户类型")
|
||||
@DictFormat(DictTypeConstants.USER_TYPE)
|
||||
private Integer userType;
|
||||
|
||||
@Schema(description = "操作模块类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "订单")
|
||||
@ExcelProperty("操作模块类型")
|
||||
private String type;
|
||||
|
||||
@@ -11,10 +11,12 @@ import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogRespVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsLogDO;
|
||||
import cn.iocoder.yudao.module.system.service.sms.SmsLogService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@@ -44,6 +46,15 @@ public class SmsLogController {
|
||||
return success(BeanUtils.toBean(pageResult, SmsLogRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得短信日志")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('system:sms-log:query')")
|
||||
public CommonResult<SmsLogRespVO> getSmsLog(@RequestParam("id") Long id) {
|
||||
SmsLogDO smsLog = smsLogService.getSmsLog(id);
|
||||
return success(BeanUtils.toBean(smsLog, SmsLogRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出短信日志 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('system:sms-log:export')")
|
||||
|
||||
@@ -5,8 +5,10 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthPermissionInfoRespVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthSmsLoginReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthSmsSendReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthSocialLoginReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
|
||||
@@ -26,8 +28,6 @@ public interface AuthConvert {
|
||||
|
||||
AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class);
|
||||
|
||||
AuthLoginRespVO convert(OAuth2AccessTokenDO bean);
|
||||
|
||||
default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
|
||||
return AuthPermissionInfoRespVO.builder()
|
||||
.user(BeanUtils.toBean(user, AuthPermissionInfoRespVO.UserVO.class))
|
||||
|
||||
@@ -91,10 +91,10 @@ public class AliyunSmsClient extends AbstractSmsClient {
|
||||
@Override
|
||||
public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
|
||||
// 1. 执行请求
|
||||
// 参考链接 https://api.aliyun.com/document/Dysmsapi/2017-05-25/QuerySmsTemplate
|
||||
// 参考链接 https://api.aliyun.com/document/Dysmsapi/2017-05-25/GetSmsTemplate
|
||||
TreeMap<String, Object> queryParam = new TreeMap<>();
|
||||
queryParam.put("TemplateCode", apiTemplateId);
|
||||
JSONObject response = request("QuerySmsTemplate", queryParam);
|
||||
JSONObject response = request("GetSmsTemplate", queryParam);
|
||||
|
||||
// 2.1 请求失败
|
||||
String code = response.getStr("Code");
|
||||
|
||||
@@ -4,6 +4,7 @@ import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
|
||||
@@ -216,13 +217,13 @@ public class AdminAuthServiceImpl implements AdminAuthService {
|
||||
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(),
|
||||
OAuth2ClientConstants.CLIENT_ID_DEFAULT, null);
|
||||
// 构建返回结果
|
||||
return AuthConvert.INSTANCE.convert(accessTokenDO);
|
||||
return BeanUtils.toBean(accessTokenDO, AuthLoginRespVO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthLoginRespVO refreshToken(String refreshToken) {
|
||||
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT);
|
||||
return AuthConvert.INSTANCE.convert(accessTokenDO);
|
||||
return BeanUtils.toBean(accessTokenDO, AuthLoginRespVO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -12,6 +12,14 @@ import javax.validation.Valid;
|
||||
*/
|
||||
public interface LoginLogService {
|
||||
|
||||
/**
|
||||
* 获得登录日志
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 登录日志
|
||||
*/
|
||||
LoginLogDO getLoginLog(Long id);
|
||||
|
||||
/**
|
||||
* 获得登录日志分页
|
||||
*
|
||||
|
||||
@@ -21,6 +21,11 @@ public class LoginLogServiceImpl implements LoginLogService {
|
||||
@Resource
|
||||
private LoginLogMapper loginLogMapper;
|
||||
|
||||
@Override
|
||||
public LoginLogDO getLoginLog(Long id) {
|
||||
return loginLogMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<LoginLogDO> getLoginLogPage(LoginLogPageReqVO pageReqVO) {
|
||||
return loginLogMapper.selectPage(pageReqVO);
|
||||
|
||||
@@ -11,16 +11,18 @@ import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.mail.MailTemplateMapper;
|
||||
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
@@ -53,7 +55,7 @@ public class MailTemplateServiceImpl implements MailTemplateService {
|
||||
|
||||
// 插入
|
||||
MailTemplateDO template = BeanUtils.toBean(createReqVO, MailTemplateDO.class)
|
||||
.setParams(parseTemplateContentParams(createReqVO.getContent()));
|
||||
.setParams(parseTemplateTitleAndContentParams(createReqVO.getTitle(), createReqVO.getContent()));
|
||||
mailTemplateMapper.insert(template);
|
||||
return template.getId();
|
||||
}
|
||||
@@ -69,7 +71,7 @@ public class MailTemplateServiceImpl implements MailTemplateService {
|
||||
|
||||
// 更新
|
||||
MailTemplateDO updateObj = BeanUtils.toBean(updateReqVO, MailTemplateDO.class)
|
||||
.setParams(parseTemplateContentParams(updateReqVO.getContent()));
|
||||
.setParams(parseTemplateTitleAndContentParams(updateReqVO.getTitle(), updateReqVO.getContent()));
|
||||
mailTemplateMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@@ -129,7 +131,77 @@ public class MailTemplateServiceImpl implements MailTemplateService {
|
||||
|
||||
@Override
|
||||
public String formatMailTemplateContent(String content, Map<String, Object> params) {
|
||||
return StrUtil.format(content, params);
|
||||
// 1. 先替换模板变量
|
||||
String formattedContent = StrUtil.format(content, params);
|
||||
|
||||
// 关联 Pull Request:https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1461 讨论
|
||||
// 2.1 反转义HTML特殊字符
|
||||
formattedContent = unescapeHtml(formattedContent);
|
||||
// 2.2 处理代码块(确保<pre><code>标签格式正确)
|
||||
formattedContent = formatHtmlCodeBlocks(formattedContent);
|
||||
// 2.3 将最外层的 pre 标签替换为 div 标签
|
||||
formattedContent = replaceOuterPreWithDiv(formattedContent);
|
||||
return formattedContent;
|
||||
}
|
||||
|
||||
private String replaceOuterPreWithDiv(String content) {
|
||||
if (StrUtil.isEmpty(content)) {
|
||||
return content;
|
||||
}
|
||||
// 使用正则表达式匹配所有的 <pre> 标签,包括嵌套的 <code> 标签
|
||||
String regex = "(?s)<pre[^>]*>(.*?)</pre>";
|
||||
Pattern pattern = Pattern.compile(regex);
|
||||
Matcher matcher = pattern.matcher(content);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (matcher.find()) {
|
||||
// 提取 <pre> 标签内的内容
|
||||
String innerContent = matcher.group(1);
|
||||
// 返回 div 标签包裹的内容
|
||||
matcher.appendReplacement(sb, "<div>" + innerContent + "</div>");
|
||||
}
|
||||
matcher.appendTail(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 反转义 HTML 特殊字符
|
||||
*
|
||||
* @param input 输入字符串
|
||||
* @return 反转义后的字符串
|
||||
*/
|
||||
private String unescapeHtml(String input) {
|
||||
if (StrUtil.isEmpty(input)) {
|
||||
return input;
|
||||
}
|
||||
return input
|
||||
.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace(""", "\"")
|
||||
.replace("'", "'")
|
||||
.replace(" ", " ");
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 HTML 中的代码块
|
||||
*
|
||||
* @param content 邮件内容
|
||||
* @return 格式化后的邮件内容
|
||||
*/
|
||||
private String formatHtmlCodeBlocks(String content) {
|
||||
// 匹配 <pre><code> 标签的代码块
|
||||
Pattern codeBlockPattern = Pattern.compile("<pre\\s*.*?><code\\s*.*?>(.*?)</code></pre>", Pattern.DOTALL);
|
||||
Matcher matcher = codeBlockPattern.matcher(content);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (matcher.find()) {
|
||||
// 获取代码块内容
|
||||
String codeBlock = matcher.group(1);
|
||||
// 为代码块添加样式
|
||||
String replacement = "<pre style=\"background-color: #f5f5f5; padding: 10px; border-radius: 5px; overflow-x: auto;\"><code>" + codeBlock + "</code></pre>";
|
||||
matcher.appendReplacement(sb, replacement);
|
||||
}
|
||||
matcher.appendTail(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -137,14 +209,31 @@ public class MailTemplateServiceImpl implements MailTemplateService {
|
||||
return mailTemplateMapper.selectCountByAccountId(accountId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析标题和内容中的参数
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public List<String> parseTemplateTitleAndContentParams(String title, String content) {
|
||||
List<String> titleParams = ReUtil.findAllGroup1(PATTERN_PARAMS, title);
|
||||
List<String> contentParams = ReUtil.findAllGroup1(PATTERN_PARAMS, content);
|
||||
// 合并参数并去重
|
||||
List<String> allParams = new ArrayList<>(titleParams);
|
||||
for (String param : contentParams) {
|
||||
if (!allParams.contains(param)) {
|
||||
allParams.add(param);
|
||||
}
|
||||
}
|
||||
return allParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得邮件模板中的参数,形如 {key}
|
||||
*
|
||||
* @param content 内容
|
||||
* @return 参数列表
|
||||
*/
|
||||
private List<String> parseTemplateContentParams(String content) {
|
||||
List<String> parseTemplateContentParams(String content) {
|
||||
return ReUtil.findAllGroup1(PATTERN_PARAMS, content);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -180,7 +180,13 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
|
||||
.setClientId(clientDO.getClientId()).setScopes(refreshTokenDO.getScopes())
|
||||
.setRefreshToken(refreshTokenDO.getRefreshToken())
|
||||
.setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getAccessTokenValiditySeconds()));
|
||||
accessTokenDO.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号
|
||||
// 优先从 refreshToken 获取租户编号,避免 ThreadLocal 被污染时导致 tenantId 为 null
|
||||
// 可能关联的 issue:https://t.zsxq.com/JIi5G
|
||||
Long tenantId = refreshTokenDO.getTenantId();
|
||||
if (tenantId == null) {
|
||||
tenantId = TenantContextHolder.getTenantId();
|
||||
}
|
||||
accessTokenDO.setTenantId(tenantId);
|
||||
oauth2AccessTokenMapper.insert(accessTokenDO);
|
||||
// 记录到 Redis 中
|
||||
oauth2AccessTokenRedisDAO.set(accessTokenDO);
|
||||
|
||||
@@ -255,6 +255,9 @@ public class MenuServiceImpl implements MenuService {
|
||||
return;
|
||||
}
|
||||
// 如果 id 为空,说明不用比较是否为相同 id 的菜单
|
||||
if (id == null) {
|
||||
throw exception(MENU_NAME_DUPLICATE);
|
||||
}
|
||||
if (!menu.getId().equals(id)) {
|
||||
throw exception(MENU_NAME_DUPLICATE);
|
||||
}
|
||||
@@ -277,7 +280,7 @@ public class MenuServiceImpl implements MenuService {
|
||||
}
|
||||
// 如果 id 为空,说明不用比较是否为相同 id 的菜单
|
||||
if (id == null) {
|
||||
return;
|
||||
throw exception(MENU_COMPONENT_NAME_DUPLICATE);
|
||||
}
|
||||
if (!menu.getId().equals(id)) {
|
||||
throw exception(MENU_COMPONENT_NAME_DUPLICATE);
|
||||
|
||||
@@ -58,6 +58,14 @@ public interface SmsLogService {
|
||||
void updateSmsReceiveResult(Long id, String apiSerialNo, Boolean success,
|
||||
LocalDateTime receiveTime, String apiReceiveCode, String apiReceiveMsg);
|
||||
|
||||
/**
|
||||
* 获得短信日志
|
||||
*
|
||||
* @param id 日志编号
|
||||
* @return 短信日志
|
||||
*/
|
||||
SmsLogDO getSmsLog(Long id);
|
||||
|
||||
/**
|
||||
* 获得短信日志分页
|
||||
*
|
||||
|
||||
@@ -78,6 +78,11 @@ public class SmsLogServiceImpl implements SmsLogService {
|
||||
.receiveTime(receiveTime).apiReceiveCode(apiReceiveCode).apiReceiveMsg(apiReceiveMsg).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmsLogDO getSmsLog(Long id) {
|
||||
return smsLogMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<SmsLogDO> getSmsLogPage(SmsLogPageReqVO pageReqVO) {
|
||||
return smsLogMapper.selectPage(pageReqVO);
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user