背景介绍

若依(RouYi)是一款基于JavaEE技术的企业级快速开发平台, 让开发者用少量代码,快速搭建和开发各种管理系统

开发版本

RuoYi-Vue

ruoyi-vue版本,采用了前后端分离的单体架构设计

软件环境:jdk, mysql, redis, maven, node

技术选型:springboot, springsecurity, mybatis, jwt, vue3, element-plus

官网: RuoYi 若依官方网站 |后台管理系统|权限管理系统|快速开发框架|企业管理系统|开源框架|微服务框架|前后端分离框架|开源后台系统|RuoYi|RuoYi-Vue|RuoYi-Cloud|RuoYi框架|RuoYi开源|RuoYi视频|若依视频|RuoYi开发文档|若依开发文档|Java开源框架|Java|SpringBoot|SrpingBoot2.0|SrpingCloud|Alibaba|MyBatis|Shiro|OAuth2.0|Thymeleaf|BootStrap|Vue|Element-UI||www.ruoyi.vip

代码下载:RuoYi-Vue: 🎉 基于SpringBoot,Spring Security,JWT,Vue & Element 的前后端分离权限管理系统,同时提供了 Vue3 的版本

扩展地址:RuoYi-Vue3: 🎉 (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue3 & Vite、Element Plus 的前后端分离权限管理系统

其他低代码

若依搭建

搭建后端项目

1.通过idea下载代码

代码下载完成后, idea会自动加载`Maven`依赖包,初次加载会比较慢

说明: 模块文件加粗, 证明包加载完毕

说明: 如果包下载失败, 可以先清理, 在重新下载

2.导入SQL与配置

  • 创建数据库`create database ry-vue;`
  • 执行sql脚本文件

  • 在`ruoyi-admin`模块下,修改数据库连接,编辑`application-druid.yml`配置
  • 如果Redis有密码, 修改application.yml配置

3.启动Redis

  • 启动Redis: redis-server.exe redis.windows.conf

4.启动项目

  • 启动项目: 运行`com.ruoyi.RuoYiApplication.java`

搭建前端项目

1.下载项目

2.安装依赖

3.运行项目

入门案例

1.分析原型

  1. 查看原型确定需求
  2. 分析需要的接口, 这里需要5个接口: 条件查询接口,添加接口, 编辑接口, 删除接口,详情接口(回显)
2.建表

  1. 根据原型, 确定表结构
  2. 建表: 执行资料中的 [课程管理] SQL文件
  3. 检查表结构

3.配置代码生成信息

4.下载代码并导入到项目

功能详解

权限控制
1.介绍

若依内置了强大的权限控制系统, 为企业级项目提供了通用的解决方案

RBAC(基于角色的访问控制)是一种广泛使用的访问控制模型, 通过角色来分配和管理用户的菜单权限

2.表关系说明

  1. 通过三种主表, 加两张中间表就可以实现RBAC
  2. 若依为了实现更精细的权限控制, 所以提供了更多的数据表, 以实现不同角色查看相同页面, 但是数据不同的精细控制

3.实际操作

创建菜单

创建角色,并分配权限

创建用户,并关键角色

登录验证

数据字典
1.介绍

若依内置的数据字典, 用于维护系统中常见的静态数据, 如性别, 状态....

优势: 提高数据库存储效率, 提高程序灵活性

2.表关系说明

3.实际操作

目标: 把课程管理的学科字段改为数据字典维护

其他功能
1.参数配置

对系统中的参数进行动态维护, 实现在不更改代码的情况下, 控制系统中的功能

控制登录验证码

  • 修改后, 无需改动代码, 立刻生效

控制注册页面

  • 更改后, 需要在前端登录页, 显示注册按钮
2.通知公告

促进组织内部信息的传递

  • 若依的通知功能是一个半成品, 因为通知这个业务, 每个公司需要的形式都不一样, 所以需要自己二次开发
  • 常见的通知形式有消息弹窗, 邮箱通知等
3.日志管理

轻松追踪用户行为和系统运行的状况

操作日志

登录日志

  • 日志随着时间的积累, 数据会越来越多, 如果需要, 可以考虑通过定时任务, 定期导出日志, 然后清理数据
系统监控

若依提供了强大的监控工具, 帮助开发和运维快速了解系统的性能状态

  • 数据监控是druid提供的功能, 所以需要二次登录, 才可以使用
定时任务

若依为定时任务功能提供了友好方便的web界面, 实现动态管理任务

1.创建任务类

  • 添加完成后要重启服务

2.添加任务规则

  • 执行策略: 如果系统宕机, 定时任务会累积, 服务恢复后, 这些任务如何处理
  • 是否并发: 多个定时任务, 允许并发性能更好, 禁止并发可以保证任务的执行顺序不变

3.启动任务

表单构建

表单构建工具, 只需要开发者通过图形界面和拖拽等操作, 就可以快速构建复杂的表单

1.制作表单并导出

  • 默认会提供提交和重置按钮

2.复制到前端工程

3.创建动态菜单

代码生成

代码生成器:根据数据库表结构自动生成前后端的CRUD代码, 提供三种生成模板

  1. 单表:入门案例中使用的就是单表模式
  2. 树表:是一种展示层级数据的表格,能展开折叠, 清晰的呈现父子关系,便于管理

  1. 主子表:一对多的表关系,后面再讲
系统接口

Swagger, 能够自动生成API的同步在线文档,并提供Web界面进行接口调用和测试

接口文档

准备工作

接口接口

项目结构

后端部分

1.后台服务

2.通用工具

3.框架核心

3.系统模块

4.模块依赖关系

前端部分

表结构

整个ruoyi系统共有32张表. 我们需要熟悉的是下面的19张表

源码阅读

前端代码分析
全局处理code

工具方法

import useDictStore from '@/store/modules/dict'
import { getDicts } from '@/api/system/dict/data'

/**
 * 获取字典数据
 */
export function useDict(...args) {
  const res = ref({});
  return (() => {
    args.forEach((dictType, index) => {
      res.value[dictType] = [];
      const dicts = useDictStore().getDict(dictType);
      if (dicts) {
        // 仓库中有数据,直接使用
        res.value[dictType] = dicts;
      } else {
        // 仓库中没有数据,请求数据
        getDicts(dictType).then(resp => {
          res.value[dictType] = resp.data.map(p => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass }))
          useDictStore().setDict(dictType, res.value[dictType]);
        })
      }
    })
    return toRefs(res.value);
  })()
}

页面文件

<template>
   <el-table>
      <el-table-column label="课程学科" align="center" prop="subject">
        <template #default="scope">
          <!-- 使用字典组件 -->
          <dict-tag :options="coures_subject" :value="scope.row.subject"/>
        </template>
      </el-table-column>
    </el-table>
</template>

<script setup name="Course">
// 拿到字典数据
const { proxy } = getCurrentInstance();
const { coures_subject } = proxy.useDict('coures_subject');
</script>

字典组件

<template>
  <div>
    <template v-for="(item, index) in options">
      <!-- 匹配成功 -->
      <template v-if="values.includes(item.value)">
        <!-- 使用span展示 -->
        <span
          v-if="(item.elTagType == 'default' || item.elTagType == '') && (item.elTagClass == '' || item.elTagClass == null)"
          :key="item.value"
          :index="index"
          :class="item.elTagClass"
        >{{ item.label + " " }}</span>

        <!-- 使用el-tag展示 -->
        <el-tag
          v-else
          :disable-transitions="true"
          :key="item.value + ''"
          :index="index"
          :type="item.elTagType === 'primary' ? '' : item.elTagType"
          :class="item.elTagClass"
        >{{ item.label + " " }}</el-tag>
      </template>
    </template>

    <!-- 匹配失败 -->
    <template v-if="unmatch && showValue">
      {{ unmatchArray | handleArray }}
    </template>
  </div>
</template>

<script setup>
// 记录未匹配的项
const unmatchArray = ref([]);

const props = defineProps({
  // 数据
  options: {
    type: Array,
    default: null,
  },
  // 当前的值
  value: [Number, String, Array],
  // 当未找到匹配的数据时,显示value
  showValue: {
    type: Boolean,
    default: true,
  },
  // 分隔符
  separator: {
    type: String,
    default: ",",
  }
});

/**
 * 作用: 基于props.value计算出一个数组
 * 如果props.value是数组,则直接返回其映射(每个项转换为字符串);
 * 如果是字符串,则使用props.separator作为分隔符将其分割成数组;
 */
const values = computed(() => {
  // 非空拦截
  if (props.value === null || typeof props.value === 'undefined' || props.value === '') return [];
  // 数组或字符串
  return Array.isArray(props.value) ? props.value.map(item => '' + item) : String(props.value).split(props.separator);
});

/**
 * 作用: 用于判断并收集未匹配的项。
 * 遍历values中的每个项,检查是否在props.options中有对应的数据
 * 如果没有找到匹配项,则将其添加到unmatchArray中,并设置标志变量unmatch为true。
 */
const unmatch = computed(() => {
  unmatchArray.value = [];
  // 没有value不显示
  if (props.value === null || typeof props.value === 'undefined' || props.value === '' || props.options.length === 0) return false
  // 传入值为数组
  let unmatch = false // 添加一个标志来判断是否有未匹配项
  values.value.forEach(item => {
    if (!props.options.some(v => v.value === item)) {
      unmatchArray.value.push(item)
      unmatch = true // 如果有未匹配项,将标志设置为true
    }
  })
  return unmatch // 返回标志的值
});


/**
 * 生成字符串
 */
function handleArray(array) {
  // 非空拦截
  if (array.length === 0) return "";
  // 拼接未匹配的数据
  return array.reduce((pre, cur) => {
    return pre + " " + cur;
  });
}
</script>

<style scoped>
.el-tag + .el-tag {
  margin-left: 10px;
}
</style>
后端代码分析
1.BaseController介绍

BaseController: web层通用数据处理, 供controller使用

2.注解介绍

记录用户的操作日志: @Log(title = "课程管理", businessType = BusinessType.UPDATE)

权限控制的注解: @PreAuthorize("@ss.hasPermi('course:course:edit')")

它在方法运行前先验证权限, 权限够放行, 不够就拦截

3.分页查询接口介绍

4.非分页查询接口介绍

5.BaseEntity

所有实体类都继承BaseEntity实体基类

前后端交互流程

1.接口文档: 前后端基于接口文档实现数据交互

2.前端请求流程

3,后端响应数据

二次开发

新建业务模块

1.改名

若依框架修改器是一个可以一键修改RouYi包名和项目名的工具

地址: https://gitee.com/lpf_project/RouYi-MT/releases

1.压缩工程

2.运行修改器

3.配置参数

4.开始执行

5.打开新工程

6.修改启动类文件

2.新建sky-merchant子模块

3.父工程版本锁定

4.sky-admin添加依赖

5.验证一下, 运行项目

菜品管理

利用若依代码生成器(主子表模版), 生成菜品管理的前后端代码

1.准备SQL并导入数据库

2.配置代码生产信息

准备工作

配置菜品管理表

配置菜品口味表

  • 其他两项不做调整
3.下载代码并导入到项目

  • 如果导入后端文件保存, 可以手动导入依赖包 ctrl + enter
列表调整

1.安装ai助手

  • 建议: 先解释代码, 再生成代码

2.页面改造

3.修复图片回显

原因:

  • 本地图片需要拼接地址前缀, 完成图片预览
  • 对于阿里云oss图片来说, 会重复拼接
  • 多添加一个过滤条件就OK了
口味改造

改造效果

代码展示

<template>
  ....
  <el-table-column label="口味名称" prop="name" width="150">
    <template #default="scope">
       <el-select v-model="scope.row.name" placeholder="请选择口味名称" @change="changeFlavorName(scope.row)">
           <el-option
             v-for="dishFlavor in dishFlavorListSelect"
            :label="dishFlavor.name"
            :value="dishFlavor.name"
            :key="dishFlavor.name"
            />
        </el-select>
      </template>
    </el-table-column>

  <el-table-column label="口味列表" prop="value" width="350">
       <template #default="scope"> 
          <el-select v-model="scope.row.value"  placeholder="请输入口味列表" multiple 
               @focus="focusFlavorValue(scope.row)" style="width: 90%;">
             <el-option
                v-for="value in checkValueList"
                :label="value"
                :value="value"
                :key="value"
               />
            </el-select>
        </template>
  </el-table-column>
  ...
</template>

<script setup name="Dish">
  /** 修改按钮操作 */
  function handleUpdate(row) {
    ...
    // 将口味列表的value字符串转数组
    if(dishFlavorList.value) {
      dishFlavorList.value.forEach(item => {
        item.value = JSON.parse(item.value)
      });
    }
  }

  /** 提交按钮 */
  function submitForm() {
    ...
    // 将口味列表的value数据转字符串
    if(form.value.dishFlavorList) {
      form.value.dishFlavorList.forEach(item => {
        item.value = JSON.stringify(item.value)
      });
    }
  }


  // -------------------------------------------------------
  // 定义口味名称和口味列表静态数据
  const dishFlavorListSelect = ref([
    {name: '辣度',value: ["不辣","微辣","中辣","特辣"]},
    {name: '忌口',value: ["不要葱","不要蒜","不要香菜","不要辣"]},
    {name: '甜味',value: ["无糖","少糖","半糖","多糖"]},
  ]);
  // 储存当前选中口味列表的数组
  const checkValueList = ref([]);
  // 口味名称改变时更新口味列表
  function changeFlavorName(row) {
    // 清空当前行
    row.value = []
    // 根据选择的nane查找数据的value
    checkValueList.value = dishFlavorListSelect.value.find(item => item.name == row.name).value;
  }

  // 口味列表获取焦点时更新选中的口味
  function focusFlavorValue(row) {
    // 根据选择的nane查找数据的value
    checkValueList.value = dishFlavorListSelect.value.find(item => item.name == row.name).value;
  }

</script>
页面调整
  1. 浏览器标签页icon, 标题修改
  2. 系统页面中的logo, 标题修改
  3. 去除源码和文档链接
  4. 修改主题和自定义图标
  • 登录阿里图标库, 搜索需要的图标

  • 点击下载

  • 调整颜色和尺寸, 点击SVG下载

  • 嵌入到前端工程, 刷新页面, 重新选择

  1. 登录页的标题和背景图修改
Logo

快速构建 Web 应用程序

更多推荐