引用了开源项目Ruoyi-Cloud-Vue3中的例子
vue create 项目名
vue ui
单页 Web 应用(single page web application,SPA)
渐进式 Web 应用(PWA)
PWA(Progressive Web Apps,渐进式 Web 应用)运用现代的 Web API 以及传统的渐进式增强策略来创建跨平台 Web 应用程序。这些应用无处不在、功能丰富,使其具有与原生应用相同的用户体验优势。这组文档和指南告诉您有关 PWA 的所有信息。
来自 https://developer.mozilla.org/zh-CN/docs/Web/Progressive_web_apps
单文件组件SFC
src下新建文件夹,除components组件文件夹外,可以文件夹下只有一个index.js,通过ES6语法的export向外暴漏提供的对象,然后在main.js中通过import走相对路径获取。
vuex用store管理状态(全局数据)
在main.js中引入
import '@/assets/styles/index.scss' // global css
:to=“{ path: tag.path, query: tag.query, fullPath: tag.fullPath }”
在router文件夹下的index.js中import页面并配置路由
引入组件/页面:import,如果组件文件夹有index.vue就可省略具体文件名,写在文件夹级别就可以。
所有路由嵌套都在pages文件夹下的layout页面?
示例中的自定义对象:
import { createWebHistory, createRouter } from 'vue-router'
import Layout from '@/layout'
/**
* Note: 路由配置项
*
* hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
* alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
* // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
* // 若你想不管路由下面的 children 声明的个数都显示你的根路由
* // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
* redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
* name:'router-name' // 设定路由的名字,一定要填写不然使用时会出现各种问题。也可以在其他路由路径的redirect中直接使用'router-name'
* query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数
* meta : {
noCache: true // 如果设置为true,则不会被 缓存(默认 false)
title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字
icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg
breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示
activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。
name? 用于匹配规则
requireAuth?
}
*/
// 公共路由列表
export const constantRoutes = [
{
path: '/redirect',
component: Layout,
hidden: true,
children: [
{ //路由开头不加/也行
path: '/redirect/:path(.*)',
component: () => import('@/views/redirect/index.vue')
}
]
},
{
path: '',
component: Layout,
redirect: 'index',
children: [
{
path: 'index',
component: () => import('@/views/index'),
name: 'Index',
meta: { title: '首页', icon: 'dashboard', affix: true }
}
]
},
{
path: '/user',
component: Layout,
hidden: true,
redirect: 'noredirect',
children: [
{
path: 'profile',
component: () => import('@/views/system/user/profile/index'),
name: 'Profile',
meta: { title: '个人中心', icon: 'user' }
}
]
}
];
// 动态路由,基于用户权限动态去加载
export const dynamicRoutes = [
{
path: '/system/user-auth',
component: Layout,
hidden: true,
permissions: ['system:user:edit'],
children: [
{
path: 'role/:userId(\\d+)',
component: () => import('@/views/system/user/authRole'),
name: 'AuthRole',
meta: { title: '分配角色', activeMenu: '/system/user' }
}
]
},
{
path: '/system/role-auth',
component: Layout,
hidden: true,
permissions: ['system:role:edit'],
children: [
{
path: 'user/:roleId(\\d+)',
component: () => import('@/views/system/role/authUser'),
name: 'AuthUser',
meta: { title: '分配用户', activeMenu: '/system/role' }
}
]
},
{
path: "/:pathMatch(.*)*",
component: () => import('@/views/error/404'),
hidden: true
},
{
path: '/system/dict-data',
component: Layout,
hidden: true,
permissions: ['system:dict:list'],
children: [
{
path: 'index/:dictId(\\d+)',
component: () => import('@/views/system/dict/data'),
name: 'Data',
meta: { title: '字典数据', activeMenu: '/system/dict' }
}
]
},
{
path: '/monitor/job-log',
component: Layout,
hidden: true,
permissions: ['monitor:job:list'],
children: [
{
path: 'index',
component: () => import('@/views/monitor/job/log'),
name: 'JobLog',
meta: { title: '调度日志', activeMenu: '/monitor/job' }
}
]
},
{
path: '/tool/gen-edit',
component: Layout,
hidden: true,
permissions: ['tool:gen:edit'],
children: [
{
path: 'index/:tableId(\\d+)',
component: () => import('@/views/tool/gen/editTable'),
name: 'GenEdit',
meta: { title: '修改生成配置', activeMenu: '/tool/gen' }
}
]
}
]
// 防止连续点击多次路由报错
let routerPush = Router.prototype.push;
Router.prototype.push = function push(location) {
return routerPush.call(this, location).catch(err => err)
}
const router = createRouter({
history: createWebHistory(),//?
routes: constantRoutes,
scrollBehavior(to, from, savedPosition) {//?
if (savedPosition) {
return savedPosition
} else {
return { top: 0 }
}
},
});
export default router;
cloud版本没有permission
外链示例
{
path: 'http://ruoyi.vip',
meta: { title: '若依官网', icon : "guide" }
}
引入组件的方式:在开头引入,或者在需要的地方用函数表达式引入
在组件中用
定义路由显示的位置。App.vue中就有
默认路由为路由列表中的第一个路由。(ruoyi中默认为主页然后没前端检测没token被拦截跳转)
代表那些不需要动态判断权限的路由,如登录页、404、等通用页面,在@/router/index.js (opens new window)配置对应的公共路由。
代表那些需要根据用户动态判断权限并通过addRoutes
动态添加的页面,在@/store/modules/permission.js (opens new window)加载后端接口路由配置。
可复用 & 组合是什么意思?难道说不是组件而是所有的可复用?框架允许复用的东西?
ref 有三种用法:
1、ref 加在普通的元素上,用this.$refs.(ref值) 获取到的是dom元素
2、ref 加在子组件上,用this. r e f s . ( r e f 值)获取到的是组件实例,可以使用组件的所有方法。在使用方法的时候直接 t h i s . refs.(ref值) 获取到的是组件实例,可以使用组件的所有方法。在使用方法的时候直接this. refs.(ref值)获取到的是组件实例,可以使用组件的所有方法。在使用方法的时候直接this.refs.(ref值).方法() 就可以使用了。
ref值是指html标签中设置属性ref的值。
可以这么理解:vue3中某一对象的$refs属性就是包含所有ref的对象。ref可一试方法、标签等
ref方法干什么的?
comupted
单文件组件的语法糖
都是vue官方提供的功能(路由也是)。
曾经组件中数据是单向传递,现在值可以由vuex统一管理。
React:UI = fn(state)
elm:UI = state + effect
状态管理,其实也是本地数据存储(store)管理。需要存储、操作、获取功能
State | Vuex (vuejs.org)
在index.js中创建并export
在main.js中引入并注册
那么我们如何在 Vue 组件中展示状态呢?由于 Vuex 的状态存储是响应式?的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:
// 创建一个 Counter 组件
const Counter = {
template: `{{ count }}`,
computed: {
count () {
return store.state.count
}
}
}
每当 store.state.count
变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM。
$store.state.count
默认的this可以省
定义方法,传递其他参数可以写在state添加一个对象(最多传入一个state和另一个参数,多个参数只能以一个对象包装进去)
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_EXPIRES_IN: (state, time) => {
state.expires_in = time
},
SET_NAME: (state, name) => {
state.name = name
}
}
根据方法名调用
store.commit('increment')
对共享数据计算一次
util包中的auth.js
import Cookies from 'js-cookie'
const TokenKey = 'Admin-Token'
const ExpiresInKey = 'Admin-Expires-In'
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}
export function getExpiresIn() {
return Cookies.get(ExpiresInKey) || -1
}
export function setExpiresIn(time) {
return Cookies.set(ExpiresInKey, time)
}
export function removeExpiresIn() {
return Cookies.remove(ExpiresInKey)
}
在vscode中使用vue模板:
vue然后按!
子组件就像吕布一样,先被导入,然后在父组件的子组件中注册自己,然后使用
单页应用:页面的dom结构一直在变化,而传统前后端项目,每次返回一个完整网页
el-col不指定span会占据一整行倒是换行。。
interface Tree {
id: number
label: string
children?: Tree[]
}
let id = 1000
const append = (data: Tree) => {
const newChild = { id: id++, label: 'testtest', children: [] }
if (!data.children) {
data.children = []
}
data.children.push(newChild)
}
const renderContent = (
h,
{
node,
data,
store,
}: {
node: Node
data: Tree
store: Node['store']
}
) => {
return h(
'span',
{
},
h('span', null, node.label),
h(
'span',
null,
h(
'a',
{
onClick: () => append(data),
},
'禁用'
)
)
)
}
渲染函数
node
提前把这个类写一遍,是单例模式的对象声明?
setup里不能有export
js变量必须用var或者let声明?
slot就是写个template
节点点击事件经常传入两个参数,node和data
node: 节点本身的代理对象
data:节点的数据
有些属性要带上v-bind才能使用,比如
<el-rate v-model="form.rank" :max="5" show-score allow-half/>
的:max
<h1>{{ blogTitle }}h1>
或者一个渲染函数里:
render() {
return h('h1', {}, this.blogTitle)
}
在这两种情况下,Vue 都会自动保持页面的更新。
Vue通过创建虚拟Dom树的结点VNode来追踪和管理dom
最后的子渲染函数,似乎也可以不用数组,直接传入多个参数
一个嵌套渲染、并渲染图标的例子:
const renderContent = (
h,
{
node,
data,
store,
}: {
node: Node
data: Tree
store: Node['store']
}
) => {
return h(
'span',
{
class: 'custom-tree-node',
},[
h('span', null, node.label),
h('svg', {
class: "svg-icon",
'aria-hidden': "true"
}, h(
'use',
{
'xlink:href': "#icon-dict"
},
null
)),
h(
'span',
null,
h(
'a',
{
// onClick: () => append(data),
},
'禁用'
)
)
]
)
}
如果这时prop时,对象的key含有’:‘、’-'等字符,可以用单引号将键值对中的键括起来
.native修饰符过时
xxx.sync(注:v-bind和sync) v-model:xxxx:
slot-scope #default=“scope”