版本:3.4.0
技术栈:vue3,vueX,vite,yarn
该项目勇于尝试新技术,3.5.0的某个版本将状态管理工具由vuex替换为了Pinia
Vue官方也推荐使用Pinia(甚至官网都把VueX的链接删了hhh)
但状态管理工具上手起来很方便,且本质都是使用LocalStorge等本地存储
编写风格:vue3语法糖
在
<script setup>
里编写代码和组件内容,而不是在export default中编写组件。
这种写法可在vue的官方文档-API-单文件组件中看到,是一种语法糖。
也可以在script标签上声明使用ts。
<script setup lang='ts' name= '组件名'>
interface Tree {
id: number
label: string
children?: Tree[]
</script>
声明const类型的引用(ref),来代替曾经在data()中声明变量的方法:
const deptOptions = ref(undefined);
const title = ref("");
const open= ref(false);
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
userName: undefined,
phonenumber: undefined,
status: undefined,
deptId: undefined
rules: {
userName: [{ required: true, message: "用户名称不能为空", trigger: "blur" }, { min: 2, max: 20, message: "用户名称长度必须介于 2 和 20 之间", trigger: "blur" }],
nickName: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
password: [{ required: true, message: "用户密码不能为空", trigger: "blur" }, { min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" }],
email: [{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
phonenumber: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }]
});
const { queryParams, form, rules } = toRefs(data);
function handleAdd(row) {
if (row != null && row.label) {
form.value.label = row.label;
} else {
form.value.label = '顶级分类';
open.value = true;
title.value = "添加商品管理";
ES6:
const指向对象时类似指针,指向的内存不变,但内部的数据可以变。然后在js中通过变量名.value.属性名
的形式改变值,在template中通过变量名.属性名
获取值。
获得的其实是真正对象的代理(Proxy对象)
let只有局部的作用域,所以声明全局变量时不用
vue教程-createAPP
使用$refs等功能的时候,如果不在组件内,会报错:未定义。要先获得当前实例的代理,然后使用proxy.$refs
const { proxy } = getCurrentInstance();
console.log(form)
ObjectRefImpl {_object: Proxy, _key: 'form', _defaultValue: undefined, __v_isRef: true}
console.log(form.value)
Proxy {searchValue: null, createBy: null, createTime: '2022-04-27 02:37:03', updateBy: null, updateTime: null, …}
ruoyi-cloud-vue3使用了vite、yarn
vite热部署?
import { defineConfig, loadEnv } from 'vite'
import path from 'path'
import createVitePlugins from './vite/plugins'
export default defineConfig(({ mode, command }) => {
const env = loadEnv(mode, process.cwd())
const { VITE_APP_ENV } = env
return {
base: VITE_APP_ENV === 'production' ? '/ruoyi/' : '/',
plugins: createVitePlugins(env, command === 'build'),
resolve: {
alias: {
'~': path.resolve(__dirname, './'),
'@': path.resolve(__dirname, './src')
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
server: {
port: 80,
host: true,
open: true,
proxy: {
'/dev-api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '')
css: {
postcss: {
plugins: [
postcssPlugin: 'internal:charset-removal',
AtRule: {
charset: (atRule) => {
if (atRule.name === 'charset') {
atRule.remove();
生产环境:.env.prop
开发环境:.env.development
环境:
然后通过import 导入(貌似vite.config.js直接用env)
request.js:axios工厂模式创造实例、设置拦截器等。
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 10000
export default service
ES6:
export default 默认暴漏的对象。和单纯的export相比,当其他文件用import导入的名字不用和export导出时的名字一一对应,也可以自己起别名,如:
import request from '@/utils/request'
导航栏是前端的,但左侧菜单栏是从后端拿到的。拿到后还要根据自己权限来看是否要渲染
* 构造树型结构数据
* @param {*} data 数据源
* @param {*} id id字段 默认 'id'
* @param {*} parentId 父节点字段 默认 'parentId'
* @param {*} children 孩子节点字段 默认 'children'
export function handleTree(data, id, parentId, children) {
let config = {
id: id || 'id',
parentId: parentId || 'parentId',
childrenList: children || 'children'
var childrenListMap = {};
var nodeIds = {};
var tree = [];
for (let d of data) {
let parentId = d[config.parentId];
if (childrenListMap[parentId] == null) {
childrenListMap[parentId] = [];
nodeIds[d[config.id]] = d;
childrenListMap[parentId].push(d);
for (let d of data) {
let parentId = d[config.parentId];
if (nodeIds[parentId] == null) {
tree.push(d);
for (let t of tree) {
adaptToChildrenList(t);
function adaptToChildrenList(o) {
if (childrenListMap[o[config.id]] !== null) {
o[config.childrenList] = childrenListMap[o[config.id]];
if (o[config.childrenList]) {
for (let c of o[config.childrenList]) {
adaptToChildrenList(c);
return tree;
项目结构:
- api 为每个功能模块分别封装网络接口
- components 复用的组件
- layout 整体的页面框架,如菜单栏、tabbar、主体部分
- views 主题部份内的页面
- utils
- store
- router
App.vuepermission.jsmain.jssetting.js
App.vue,内有router-view,在里面渲染router.js中配置的第一个路由,即
path: '/redirect',
component: Layout,
hidden: true,
children: [
path: '/redirect/:path(.*)',
component: () => import('@/views/redirect/index.vue')
layout是前端整体框架,包含导航栏、菜单栏、appmain
因为菜单栏从后端拿(根据用户动态显示),路由后面只配置了导航栏的
@/views/redirect/index.vue,好疑惑的写法
<template>
<div></div>
</template>
<script setup>
import { useRoute, useRouter } from 'vue-router'
const route = useRoute();
const router = useRouter();
const { params, query } = route
const { path } = params
router.replace({ path: '/' + path, query })
</script>
似乎是自动跳转并且带参数
默认跳转到ocalhost/login?redirect=/index
index在后面有配置,也是Layout组件。应该是想跳转到index,没登陆被拦截到了login(看参数redirect=/index猜的)
Q:useRoute干啥的,他咋知道参数是redirect=/index?
A:vue自带的拿参数的。 params, query是想用两个名字而已
index的路由
path: '',
component: Layout,
redirect: '/index',
children: [
path: '/index',
component: () => import('@/views/index'),
name: 'Index',
meta: { title: '首页', icon: 'dashboard', affix: true }
Layout的appmain组件放主体内容。
AppMain.vue组件中用
<component :is="Component" :key="route.path"/>
通过子路由加载@/views/index
应该是同通过改变route.path改变主题部分的内容。一开始传入的是index。这里也就是一开始放着若依一大堆简介的地方
以后路由跳转时,route.path也会动态改变,达到改变appmian中component标签的作用
在根目录permisson.js中(好多primission.js),定义router.beforeEach方法,在跳转前判断是否有token。要跳转的路径在router.beforeEach方法的参数next方法里跳转
如果本地没有token,就看是否白名单,白名单内放行不然跳转到登录
如果有token,
设置title
to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
- 如果去login,登录了还去干啥,next给他改成/
- 如果不去login
if (store.getters.roles.length === 0) {
isRelogin.show = true
store.dispatch('GetInfo').then(() => {
isRelogin.show = false
store.dispatch('GenerateRoutes').then(accessRoutes => {
accessRoutes.forEach(route => {
if (!isHttp(route.path)) {
router.addRoute(route)
next({ ...to, replace: true })
}).catch(err => {
store.dispatch('LogOut').then(() => {
ElMessage.error(err)
next({ path: '/' })
api里的login、getInfo等方法只负责发请求,用store调用,然后store把数据存在本地。
用store获取用户信息(角色等)、生成路由,都只有每次路由跳转时做
auth.js只负责GetToken之类的
根据用户权限、角色动态渲染,发生在router.beforeEach方法中,拿到用户信息后
const modules = import.meta.glob('./../../views/**/*.vue')
modules是一个数组。然后export了loadView方法,根据传入的view返回.vue文件
大致思路:
修改store中的sidebarRoutes数组
然后layou页面的sideBar组件会根据sidebarRoutes数组动态渲染标签
根据sidebarRoutes数组动态渲染多个siderbarItem
通过siderbarItem的item属性将当前sidebarRoutes的值传入
siderbarItem如果要渲染子菜单,就在自己里面渲染siderbarItem
其实是对应的
* 是否显示顶部导航
topNav: true,
- layout
- appmain 除去导航栏和侧边导航栏的主体部分,views内容在这里
- navbar 默认固定上放的导航栏
- tagsview appmain上方的小标签,类似浏览器新页面的标签的那个
- sidebar 左侧边栏
- index.vue 包含didebar、navbar、appmain
从左到右建议结构依次是
<div navbar的div>
<hamburger id="hamburger-container" :is-active="getters.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!$store.state.settings.topNav" />
<top-nav id="topmenu-container" class="topmenu-container" v-if="$store.state.settings.topNav" />
<div class="right-menu">
个人中心等
</div>
</div>
默认生成代码的表单的ref都是form,和保存表单内容的form变量重名。
要修改ref和下面的proxy.resetForm(“form”);
v-hasPermi="['forum:item:edit']",默认的权限不对
百度地图开发java源码
RuoYi-Cloud-Ant-Design简介
RuoYi-Cloud-Ant-Design是一套基于若依Cloud微服务版本为后端开发的一套基于Ant
Design
Vue的前端项目
在线体验往下滑↓
(本项目采取的后端)。
拉取项目代码
clone
git@gitee.com:xuezipeng/ruoyi-cloud-ant-design.git
install
开发模式运行
serve
build
eslint规范自动修复
api(存放接口)
assets(存放一些静态资源,图片等)
components(存放组件,由Ant
Design
Pro提供)
config(项目配置文件如全局样式、路由)
core(项目核心文件夹,用于配置一些必备的组件/图标)
layouts(项目布局文件夹,路由以及固定的侧边栏所采用的组件都在这里哦)
若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
采用前后端分离的模式,微服务版本前端(基于 )。
后端采用Spring Boot、Spring Cloud & Alibaba。
注册中心、配置中心选型Nacos,权限认证使用Redis。
流量控制框架选型Sentinel,分布式事务选型Seata。
如需不分离应用,请移步 ,如需分离应用,请移步
阿里云折扣场:,腾讯云秒杀场:
阿里云优惠券:,腾讯云优惠券:
友情链接 Ant Design版本。
com.ruoyi
├── ruoyi-ui // 前端框架 [80]
├── ruoyi-gateway // 网关模块 [8080]
├── ruoyi-auth // 认证中心 [9200]
├── ruoyi-api