Merge branch 'master' of https://gitee.com/y_project/RuoYi-Vue
Conflicts:
ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java
ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java
ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java
ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java
ruoyi-generator/src/main/resources/vm/sql/sql.vm
ruoyi-ui/src/utils/index.js
ruoyi-ui/src/utils/ruoyi.js
ruoyi-ui/src/views/tool/gen/editTable.vue
ruoyi-ui/src/views/tool/gen/genInfoForm.vue
ruoyi-ui/src/views/tool/gen/importTable.vue
| | |
| | | public String dateFormat() default "";
|
| | |
|
| | | /**
|
| | | * 如果是字典类型,请设置字典的type值
|
| | | */
|
| | | public String dictType() default "";
|
| | |
|
| | | /**
|
| | | * 读取内容转表达式 (如: 0=男,1=女,2=未知)
|
| | | */
|
| | | public String readConverterExp() default "";
|
| | |
| | | return this.value;
|
| | | }
|
| | | }
|
| | | }
|
| | | } |
| | |
| | | /** 树名称字段 */ |
| | | public static final String TREE_NAME = "treeName"; |
| | | |
| | | /** 上级菜单ID字段 */ |
| | | public static final String PARENT_MENU_ID = "parentMenuId"; |
| | | |
| | | /** 上级菜单名称字段 */ |
| | | public static final String PARENT_MENU_NAME = "parentMenuName"; |
| | | |
| | | /** 数据库字符串类型 */ |
| | | public static final String[] COLUMNTYPE_STR = { "char", "varchar", "narchar", "varchar2", "tinytext", "text", |
| | | "mediumtext", "longtext" }; |
| | |
| | |
|
| | | import java.util.Collection;
|
| | | import java.util.List;
|
| | |
|
| | | import com.ruoyi.common.constant.Constants;
|
| | | import com.ruoyi.common.core.domain.entity.SysDictData;
|
| | | import com.ruoyi.common.core.redis.RedisCache;
|
| | |
| | | }
|
| | |
|
| | | /**
|
| | | * 根据字典类型和字典值获取字典标签
|
| | | * |
| | | * @param dictType 字典类型
|
| | | * @param dictValue 字典值
|
| | | * @return 字典标签
|
| | | */
|
| | | public static String getDictLabel(String dictType, String dictValue)
|
| | | {
|
| | | if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotEmpty(dictValue))
|
| | | {
|
| | | List<SysDictData> datas = getDictCache(dictType);
|
| | | if (StringUtils.isNotEmpty(datas))
|
| | | {
|
| | | for (SysDictData dict : datas)
|
| | | {
|
| | | if (dictValue.equals(dict.getDictValue()))
|
| | | {
|
| | | return dict.getDictLabel();
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | | return dictValue;
|
| | | }
|
| | |
|
| | | /**
|
| | | * 根据字典类型和字典标签获取字典值
|
| | | * |
| | | * @param dictType 字典类型
|
| | | * @param dictLabel 字典标签
|
| | | * @return 字典值
|
| | | */
|
| | | public static String getDictValue(String dictType, String dictLabel)
|
| | | {
|
| | | if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotEmpty(dictLabel))
|
| | | {
|
| | | List<SysDictData> datas = getDictCache(dictType);
|
| | | if (StringUtils.isNotEmpty(datas))
|
| | | {
|
| | | for (SysDictData dict : datas)
|
| | | {
|
| | | if (dictLabel.equals(dict.getDictLabel()))
|
| | | {
|
| | | return dict.getDictValue();
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | | return dictLabel;
|
| | | }
|
| | |
|
| | | /**
|
| | | * 清空字典缓存
|
| | | */
|
| | | public static void clearDictCache()
|
| | |
| | | import com.ruoyi.common.core.text.Convert;
|
| | | import com.ruoyi.common.exception.CustomException;
|
| | | import com.ruoyi.common.utils.DateUtils;
|
| | | import com.ruoyi.common.utils.DictUtils;
|
| | | import com.ruoyi.common.utils.StringUtils;
|
| | | import com.ruoyi.common.utils.reflect.ReflectUtils;
|
| | |
|
| | |
| | | }
|
| | | else if (StringUtils.isNotEmpty(attr.readConverterExp()))
|
| | | {
|
| | | val = reverseByExp(String.valueOf(val), attr.readConverterExp());
|
| | | val = reverseByExp(Convert.toStr(val), attr.readConverterExp());
|
| | | }
|
| | | else if (StringUtils.isNotEmpty(attr.dictType()))
|
| | | {
|
| | | val = reverseDictByExp(attr.dictType(), Convert.toStr(val));
|
| | | }
|
| | | ReflectUtils.invokeSetter(entity, propertyName, val);
|
| | | }
|
| | |
| | | Object value = getTargetValue(vo, field, attr);
|
| | | String dateFormat = attr.dateFormat();
|
| | | String readConverterExp = attr.readConverterExp();
|
| | | String dictType = attr.dictType();
|
| | | if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value))
|
| | | {
|
| | | cell.setCellValue(DateUtils.parseDateToStr(dateFormat, (Date) value));
|
| | | }
|
| | | else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value))
|
| | | {
|
| | | cell.setCellValue(convertByExp(String.valueOf(value), readConverterExp));
|
| | | cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp));
|
| | | }
|
| | | else if (StringUtils.isNotEmpty(dictType))
|
| | | {
|
| | | cell.setCellValue(convertDictByExp(dictType, Convert.toStr(value)));
|
| | | }
|
| | | else
|
| | | {
|
| | |
| | | }
|
| | |
|
| | | /**
|
| | | * 解析字典值
|
| | | * |
| | | * @param dictType 字典类型
|
| | | * @param dictValue 字典值
|
| | | * @return 字典标签
|
| | | */
|
| | | public static String convertDictByExp(String dictType, String dictValue) throws Exception
|
| | | {
|
| | | return DictUtils.getDictLabel(dictType, dictValue);
|
| | | }
|
| | |
|
| | | /**
|
| | | * 反向解析值字典值
|
| | | * |
| | | * @param dictType 字典类型
|
| | | * @param dictValue 字典标签
|
| | | * @return 字典值
|
| | | */
|
| | | public static String reverseDictByExp(String dictType, String dictLabel) throws Exception
|
| | | {
|
| | | return DictUtils.getDictValue(dictType, dictLabel);
|
| | | }
|
| | |
|
| | | /**
|
| | | * 编码文件名
|
| | | */
|
| | | public String encodingFilename(String filename)
|
| | |
| | | package com.ruoyi.framework.config; |
| | | |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
| | | import org.springframework.web.cors.CorsConfiguration; |
| | | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; |
| | | import org.springframework.web.filter.CorsFilter; |
| | | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; |
| | | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; |
| | | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; |
| | |
| | | { |
| | | registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); |
| | | } |
| | | |
| | | /** |
| | | * 跨域配置 |
| | | */ |
| | | @Bean |
| | | public CorsFilter corsFilter() |
| | | { |
| | | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); |
| | | CorsConfiguration config = new CorsConfiguration(); |
| | | config.setAllowCredentials(true); |
| | | // 设置访问源地址 |
| | | config.addAllowedOrigin("*"); |
| | | // 设置访问源请求头 |
| | | config.addAllowedHeader("*"); |
| | | // 设置访问源请求方法 |
| | | config.addAllowedMethod("*"); |
| | | // 对接口配置跨域设置 |
| | | source.registerCorsConfiguration("/**", config); |
| | | return new CorsFilter(source); |
| | | } |
| | | } |
| | |
| | | import org.springframework.security.core.userdetails.UserDetailsService; |
| | | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; |
| | | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; |
| | | import org.springframework.security.web.authentication.logout.LogoutFilter; |
| | | import org.springframework.web.filter.CorsFilter; |
| | | import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter; |
| | | import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl; |
| | | import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl; |
| | |
| | | */ |
| | | @Autowired |
| | | private JwtAuthenticationTokenFilter authenticationTokenFilter; |
| | | |
| | | /** |
| | | * 跨域过滤器 |
| | | */ |
| | | @Autowired |
| | | private CorsFilter corsFilter; |
| | | |
| | | /** |
| | | * 解决 无法直接注入 AuthenticationManager |
| | |
| | | httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); |
| | | // 添加JWT filter |
| | | httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); |
| | | // 添加CORS filter |
| | | httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class); |
| | | httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class); |
| | | } |
| | | |
| | | |
| | |
| | | private void genCode(HttpServletResponse response, byte[] data) throws IOException |
| | | { |
| | | response.reset(); |
| | | response.addHeader("Access-Control-Allow-Origin", "*"); |
| | | response.addHeader("Access-Control-Expose-Headers", "Content-Disposition"); |
| | | response.setHeader("Content-Disposition", "attachment; filename=\"ruoyi.zip\""); |
| | | response.addHeader("Content-Length", "" + data.length); |
| | | response.setContentType("application/octet-stream; charset=UTF-8"); |
| | |
| | | /** 树名称字段 */ |
| | | private String treeName; |
| | | |
| | | /** 上级菜单ID字段 */ |
| | | private String parentMenuId; |
| | | |
| | | /** 上级菜单名称字段 */ |
| | | private String parentMenuName; |
| | | |
| | | public Long getTableId() |
| | | { |
| | | return tableId; |
| | |
| | | this.treeName = treeName; |
| | | } |
| | | |
| | | public String getParentMenuId() |
| | | { |
| | | return parentMenuId; |
| | | } |
| | | |
| | | public void setParentMenuId(String parentMenuId) |
| | | { |
| | | this.parentMenuId = parentMenuId; |
| | | } |
| | | |
| | | public String getParentMenuName() |
| | | { |
| | | return parentMenuName; |
| | | } |
| | | |
| | | public void setParentMenuName(String parentMenuName) |
| | | { |
| | | this.parentMenuName = parentMenuName; |
| | | } |
| | | |
| | | public boolean isTree() |
| | | { |
| | | return isTree(this.tplCategory); |
| | |
| | | } |
| | | return StringUtils.equalsAnyIgnoreCase(javaField, GenConstants.BASE_ENTITY); |
| | | } |
| | | } |
| | | } |
| | |
| | | String treeCode = paramsObj.getString(GenConstants.TREE_CODE); |
| | | String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE); |
| | | String treeName = paramsObj.getString(GenConstants.TREE_NAME); |
| | | String parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID); |
| | | String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME); |
| | | |
| | | genTable.setTreeCode(treeCode); |
| | | genTable.setTreeParentCode(treeParentCode); |
| | | genTable.setTreeName(treeName); |
| | | genTable.setParentMenuId(parentMenuId); |
| | | genTable.setParentMenuName(parentMenuName); |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | import com.ruoyi.generator.domain.GenTable; |
| | | import com.ruoyi.generator.domain.GenTableColumn; |
| | | |
| | | /** |
| | | * 代码生成模板处理 |
| | | * |
| | | * @author ruoyi |
| | | */ |
| | | public class VelocityUtils |
| | | { |
| | | /** 项目空间路径 */ |
| | | private static final String PROJECT_PATH = "main/java"; |
| | | |
| | | /** mybatis空间路径 */ |
| | | private static final String MYBATIS_PATH = "main/resources/mapper"; |
| | | private static final String MYBATIS_PATH = "main/resources/mybatis"; |
| | | |
| | | /** 默认上级菜单,系统工具 */ |
| | | private static final String DEFAULT_PARENT_MENU_ID = "3"; |
| | | |
| | | /** |
| | | * 设置模板变量信息 |
| | |
| | | velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName)); |
| | | velocityContext.put("columns", genTable.getColumns()); |
| | | velocityContext.put("table", genTable); |
| | | setMenuVelocityContext(velocityContext, genTable); |
| | | if (GenConstants.TPL_TREE.equals(tplCategory)) |
| | | { |
| | | setTreeVelocityContext(velocityContext, genTable); |
| | | } |
| | | return velocityContext; |
| | | } |
| | | |
| | | public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) |
| | | { |
| | | String options = genTable.getOptions(); |
| | | JSONObject paramsObj = JSONObject.parseObject(options); |
| | | String parentMenuId = getParentMenuId(paramsObj); |
| | | context.put("parentMenuId", parentMenuId); |
| | | } |
| | | |
| | | public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) |
| | |
| | | } |
| | | |
| | | /** |
| | | * 获取上级菜单ID字段 |
| | | * |
| | | * @param options 生成其他选项 |
| | | * @return 上级菜单ID字段 |
| | | */ |
| | | public static String getParentMenuId(JSONObject paramsObj) |
| | | { |
| | | if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID)) |
| | | { |
| | | return paramsObj.getString(GenConstants.PARENT_MENU_ID); |
| | | } |
| | | return DEFAULT_PARENT_MENU_ID; |
| | | } |
| | | |
| | | /** |
| | | * 获取树编码 |
| | | * |
| | | * @param options 生成其他选项 |
| | |
| | | { |
| | | return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_CODE)); |
| | | } |
| | | return ""; |
| | | return StringUtils.EMPTY; |
| | | } |
| | | |
| | | /** |
| | |
| | | { |
| | | return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_PARENT_CODE)); |
| | | } |
| | | return ""; |
| | | return StringUtils.EMPTY; |
| | | } |
| | | |
| | | /** |
| | |
| | | { |
| | | return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_NAME)); |
| | | } |
| | | return ""; |
| | | return StringUtils.EMPTY; |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | return num; |
| | | } |
| | | } |
| | | } |
| | |
| | | -- 菜单 SQL |
| | | insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) |
| | | values('${functionName}', '3', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', '2018-03-01', 'ry', '2018-03-01', '${functionName}菜单'); |
| | | values('${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', '2018-03-01', 'ry', '2018-03-01', '${functionName}菜单'); |
| | | |
| | | -- 按钮父菜单ID |
| | | SELECT @parentId := LAST_INSERT_ID(); |
| | |
| | | import { parseTime } from './ruoyi' |
| | | |
| | | /** |
| | | * 表格时间格式化 |
| | | */ |
| | | export function formatDate(cellValue) { |
| | | if (cellValue == null || cellValue == "") return ""; |
| | | var date = new Date(cellValue) |
| | | var date = new Date(cellValue) |
| | | var year = date.getFullYear() |
| | | var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1 |
| | | var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() |
| | | var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() |
| | | var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() |
| | | var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() |
| | | var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() |
| | | var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() |
| | | var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds() |
| | | return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds |
| | | } |
| | |
| | | ? val => map[val.toLowerCase()] |
| | | : val => map[val] |
| | | } |
| | | |
| | | |
| | | export const exportDefault = 'export default ' |
| | | |
| | | export const beautifierConf = { |
| | |
| | | export function isNumberStr(str) { |
| | | return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str) |
| | | } |
| | | |
| | |
| | | // 回显数据字典 |
| | | export function selectDictLabel(datas, value) { |
| | | var actions = []; |
| | | Object.keys(datas).map((key) => { |
| | | Object.keys(datas).some((key) => { |
| | | if (datas[key].dictValue == ('' + value)) { |
| | | actions.push(datas[key].dictLabel); |
| | | return false; |
| | | return true; |
| | | } |
| | | }) |
| | | return actions.join(''); |
| | |
| | | }); |
| | | return treeData != '' ? treeData : data; |
| | | } |
| | | |
| | | |
| | |
| | | </el-table> |
| | | </el-tab-pane> |
| | | <el-tab-pane label="生成信息" name="genInfo"> |
| | | <gen-info-form ref="genInfo" :info="info" /> |
| | | <gen-info-form ref="genInfo" :info="info" :menus="menus"/> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | <el-form label-width="100px"> |
| | |
| | | <script> |
| | | import { getGenTable, updateGenTable } from "@/api/tool/gen"; |
| | | import { optionselect as getDictOptionselect } from "@/api/system/dict/type"; |
| | | import { listMenu as getMenuTreeselect } from "@/api/system/menu"; |
| | | import basicInfoForm from "./basicInfoForm"; |
| | | import genInfoForm from "./genInfoForm"; |
| | | import Sortable from 'sortablejs' |
| | | |
| | | export default { |
| | | name: "GenEdit", |
| | | components: { |
| | |
| | | cloumns: [], |
| | | // 字典信息 |
| | | dictOptions: [], |
| | | // 菜单信息 |
| | | menus: [], |
| | | // 表详细信息 |
| | | info: {} |
| | | }; |
| | |
| | | getDictOptionselect().then(response => { |
| | | this.dictOptions = response.data; |
| | | }); |
| | | /** 查询菜单下拉列表 */ |
| | | getMenuTreeselect().then(response => { |
| | | this.menus = this.handleTree(response.data, "menuId"); |
| | | }); |
| | | } |
| | | }, |
| | | methods: { |
| | |
| | | genTable.params = { |
| | | treeCode: genTable.treeCode, |
| | | treeName: genTable.treeName, |
| | | treeParentCode: genTable.treeParentCode |
| | | treeParentCode: genTable.treeParentCode, |
| | | parentMenuId: genTable.parentMenuId |
| | | }; |
| | | updateGenTable(genTable).then(res => { |
| | | this.msgSuccess(res.msg); |
| | |
| | | <el-input v-model="info.functionName" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | <el-col :span="12"> |
| | | <el-form-item prop="functionName"> |
| | | <span slot="label"> |
| | | 上级菜单 |
| | | <el-tooltip content="分配到指定菜单下,例如 系统管理" placement="top"> |
| | | <i class="el-icon-question"></i> |
| | | </el-tooltip> |
| | | </span> |
| | | <treeselect :append-to-body="true" v-model="info.parentMenuId" :options="menus" :normalizer="normalizer" :show-count="true" placeholder="请选择系统菜单"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row v-show="info.tplCategory == 'tree'"> |
| | |
| | | </el-form> |
| | | </template> |
| | | <script> |
| | | import Treeselect from "@riophae/vue-treeselect"; |
| | | import "@riophae/vue-treeselect/dist/vue-treeselect.css"; |
| | | |
| | | export default { |
| | | name: "BasicInfoForm", |
| | | components: { Treeselect }, |
| | | props: { |
| | | info: { |
| | | type: Object, |
| | | default: null |
| | | } |
| | | }, |
| | | menus: { |
| | | type: Array, |
| | | default: [] |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | |
| | | } |
| | | }; |
| | | }, |
| | | created() {} |
| | | created() {}, |
| | | methods: { |
| | | /** 转换菜单数据结构 */ |
| | | normalizer(node) { |
| | | if (node.children && !node.children.length) { |
| | | delete node.children; |
| | | } |
| | | return { |
| | | id: node.menuId, |
| | | label: node.menuName, |
| | | children: node.children |
| | | }; |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | |
| | | <el-row> |
| | | <el-table @row-click="clickRow" ref="table" :data="dbTableList" @selection-change="handleSelectionChange" height="260px"> |
| | | <el-table-column type="selection" width="55"></el-table-column> |
| | | <el-table-column prop="tableName" label="表名称"></el-table-column> |
| | | <el-table-column prop="tableComment" label="表描述"></el-table-column> |
| | | <el-table-column prop="tableName" label="表名称" :show-overflow-tooltip="true"></el-table-column> |
| | | <el-table-column prop="tableComment" label="表描述" :show-overflow-tooltip="true"></el-table-column> |
| | | <el-table-column prop="createTime" label="创建时间"></el-table-column> |
| | | <el-table-column prop="updateTime" label="更新时间"></el-table-column> |
| | | </el-table> |