Ruoyi-cloud-vue3学习
非vue组件写法:在<script setup>里,用js写法而不是暴漏组件的写法<script setup>?不需要暴漏的、不需要其他人用的组件就用js吗》<script setup lang='ts'>interface Tree {id: numberlabel: stringchildren?: Tree[]}</script><scr
总览项目
版本: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只有局部的作用域,所以声明全局变量时不用
使用$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, …}
vite
ruoyi-cloud-vue3使用了vite、yarn
vite热部署?
- 哪些变化可以触发热部署?比如改了label,就刷新才行
vite.config.js
import { defineConfig, loadEnv } from 'vite'
import path from 'path'
import createVitePlugins from './vite/plugins'
// https://vitejs.dev/config/
export default defineConfig(({ mode, command }) => {
const env = loadEnv(mode, process.cwd())
const { VITE_APP_ENV } = env
return {
// 部署生产环境和开发环境下的URL。
// 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
base: VITE_APP_ENV === 'production' ? '/ruoyi/' : '/',
plugins: createVitePlugins(env, command === 'build'),
resolve: {
// https://cn.vitejs.dev/config/#resolve-alias
alias: {
// 设置路径
'~': path.resolve(__dirname, './'),
// 设置别名
'@': path.resolve(__dirname, './src')
},
// https://cn.vitejs.dev/config/#resolve-extensions
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
},
// vite 相关配置
server: {
port: 80,
host: true,
open: true,
proxy: {
// https://cn.vitejs.dev/config/#server-proxy
'/dev-api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '')
}
}
},
//fix:error:stdin>:7356:1: warning: "@charset" must be the first rule in the file
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)
网络请求:封装axios
request.js:axios工厂模式创造实例、设置拦截器等。
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: import.meta.env.VITE_APP_BASE_API,
// 超时
timeout: 10000
})
。。。
export default service
ES6:
export default 默认暴漏的对象。和单纯的export相比,当其他文件用import导入的名字不用和export导出时的名字一一对应,也可以自己起别名,如:
import request from '@/utils/request'
导航栏是前端的,但左侧菜单栏是从后端拿到的。拿到后还要根据自己权限来看是否要渲染
- 权限保存到前端哪儿了?
路由:
util/ruoyi.js
/**
* 构造树型结构数据
* @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;
}
- //用了||的写法来设定默认值,巧妙。看来默认是左边?
- nodeIds默认是对象也可以数组,直接用[ ] ?
- [ ]
前端流程梳理
项目结构:
- src
- api 为每个功能模块分别封装网络接口
- components 复用的组件
- layout 整体的页面框架,如菜单栏、tabbar、主体部分
- views 主题部份内的页面
- error
- system等各个模块
- utils
- store
- router
- App.vue
- permission.js
- main.js
- setting.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
- 这里userouter等都是灰色的,是不需要了嘛?封装到main.js了吗?
因为菜单栏从后端拿(根据用户动态显示),路由后面只配置了导航栏的
@/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
api里的login、getInfo等方法只负责发请求,用store调用,然后store把数据存在本地。if (store.getters.roles.length === 0) { isRelogin.show = true // 判断当前用户是否已拉取完user_info信息 store.dispatch('GetInfo').then(() => { isRelogin.show = false store.dispatch('GenerateRoutes').then(accessRoutes => { // 根据roles权限生成可访问的路由表 accessRoutes.forEach(route => { if (!isHttp(route.path)) { router.addRoute(route) // 动态添加可访问路由表 } }) next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 }) }).catch(err => { store.dispatch('LogOut').then(() => { ElMessage.error(err) next({ path: '/' }) }) })
用store获取用户信息(角色等)、生成路由,都只有每次路由跳转时做
auth.js只负责GetToken之类的
动态渲染侧边栏菜单
根据用户权限、角色动态渲染,发生在router.beforeEach方法中,拿到用户信息后
// 匹配views里面所有的.vue文件
const modules = import.meta.glob('./../../views/**/*.vue')
modules是一个数组。然后export了loadView方法,根据传入的view返回.vue文件
大致思路:
修改store中的sidebarRoutes数组
然后layou页面的sideBar组件会根据sidebarRoutes数组动态渲染标签
根据sidebarRoutes数组动态渲染多个siderbarItem
通过siderbarItem的item属性将当前sidebarRoutes的值传入
siderbarItem如果要渲染子菜单,就在自己里面渲染siderbarItem
根目录下setting.js和layout界面框架
其实是对应的
/**
* 是否显示顶部导航
*/
topNav: true,
- src
- layout
- appmain 除去导航栏和侧边导航栏的主体部分,views内容在这里
- navbar 默认固定上放的导航栏
- tagsview appmain上方的小标签,类似浏览器新页面的标签的那个
- sidebar 左侧边栏
- sidebarItem
- logo
- index.vue 包含didebar、navbar、appmain
navbar
从左到右建议结构依次是
<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>
添加模块
bug集锦
表单输入不进内容
默认生成代码的表单的ref都是form,和保存表单内容的form变量重名。
要修改ref和下面的proxy.resetForm(“form”);
Edge有些按钮不显示
v-hasPermi="['forum:item:edit']",默认的权限不对
更多推荐
所有评论(0)