feat:整理博客目录结构
This commit is contained in:
@@ -5,8 +5,9 @@ export const routers = [
|
||||
{
|
||||
text: '🌿 Vue',
|
||||
items: [
|
||||
{ text: 'Vue3-Common', link: '/Web/Vue/Vue3-Common' },
|
||||
{ text: 'Vue3架构原理', link: '/Web/Vue/Vue3-Architecture' },
|
||||
{ text: 'Vue3自定义指令', link: '/Web/Vue/Vue3-Directive' },
|
||||
{ text: 'Vue3 defineModel', link: '/Web/Vue/Vue3-DefineModel' },
|
||||
{ text: 'JavaScript知识点整理', link: '/Web/Vue/JavaScript-Guide' },
|
||||
{ text: '前端包管理器', link: '/Web/Vue/Package' },
|
||||
{ text: 'Vite核心原理', link: '/Web/Vue/Vite-Principle' },
|
||||
@@ -27,7 +28,6 @@ export const routers = [
|
||||
{ text: 'SpringBoot注解', link: '/Web/SpringBoot/SpringBoot-Annotation' },
|
||||
{ text: 'SpringBoot3原生镜像', link: '/Web/SpringBoot/SpringBoot3-GraalVM' },
|
||||
{ text: 'SpringBoot技巧', link: '/Web/SpringBoot/SpringBoot-Skills' },
|
||||
{ text: 'SpringBoot Common', link: '/Web/SpringBoot/SpringBoot-Common' },
|
||||
{ text: 'SpringBoot RestClient简介', link: '/Web/SpringBoot/SpringBoot-RestClient' },
|
||||
{ text: 'SpringBoot Redis简介和使用', link: '/Web/SpringBoot/SpringBoot-Redis' },
|
||||
{ text: 'SpringBoot 线程与事件', link: '/Web/SpringBoot/SpringBoot-Thread' }
|
||||
@@ -88,7 +88,7 @@ export const routers = [
|
||||
{
|
||||
text: '🚀 运维部署',
|
||||
items: [
|
||||
{ text: 'OpenObserve部署和使用', link: '/DevOps/OpenObserve' },
|
||||
{ text: '⭐ OpenObserve部署和使用', link: '/DevOps/OpenObserve' },
|
||||
{ text: 'Jenkins部署和使用', link: '/DevOps/Jenkins' },
|
||||
{ text: 'ElasticSearch部署和使用', link: '/DevOps/ElasticSearch' },
|
||||
{ text: 'Docker简介和安装', link: '/DevOps/Docker' },
|
||||
@@ -98,6 +98,13 @@ export const routers = [
|
||||
{
|
||||
text: '🏗️ 项目实战',
|
||||
items: [
|
||||
{
|
||||
text: '🏢 通用模块',
|
||||
items: [
|
||||
{ text: 'Vue3 Common', link: '/Practice/Common/Vue3-Common' },
|
||||
{ text: 'SpringBoot Common', link: '/Practice/Common/SpringBoot-Common' },
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '🏢 公司项目',
|
||||
items: [
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
title: Docker简介和安装
|
||||
date: 2025-12-15
|
||||
isGreat: true
|
||||
---
|
||||
|
||||
# 一、Docker简介
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: OpenObserve部署和使用
|
||||
date: 2025-11-26
|
||||
isGreat: true
|
||||
---
|
||||
|
||||
# 一、基础概念
|
||||
@@ -551,6 +552,40 @@ public class FluentBitLogger : MonoBehaviour
|
||||
docker-compose文件中也要增加9880端口映射。
|
||||
:::
|
||||
|
||||
## 3.5 Vue
|
||||
  发送HTTP数据到FluentBit中:
|
||||
```ts
|
||||
export const sendError = (err, instance, info) => {
|
||||
try {
|
||||
const log = {
|
||||
top: 'vue-ui',
|
||||
level: 'ERROR',
|
||||
info,
|
||||
component: instance?.$options?.name || 'Anonymous',
|
||||
message: err.message,
|
||||
url: location.href
|
||||
}
|
||||
|
||||
fetch('http://ip:9880', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(log)
|
||||
})
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
  利用app.config.errorHandler监听异常:
|
||||
```ts
|
||||
app.config.errorHandler = (err, instance, info) => {
|
||||
sendError(err, instance, info)
|
||||
}
|
||||
```
|
||||
|
||||
# 四、简单使用
|
||||
## 3.1 配置数据流
|
||||
  打开控制台,选择数据流->添加数据流,输入fluent bit配置文件中的流名称,选择数据流类型为Logs。
|
||||
|
||||
@@ -365,7 +365,7 @@ public abstract class AbstractException extends RuntimeException {
|
||||
```
|
||||
:::
|
||||
|
||||
### 3.4 starters模块
|
||||
## 3.4 starters模块
|
||||
  该模块为整个项目的核心模块,在实际项目中,通过引入相应的starter模块,并配合autoconfigure模块中的功能,即可实现快速自动装配。
|
||||
  该模块的pom.xml声明的依赖会注入到实际的项目中。
|
||||
::: danger
|
||||
@@ -373,7 +373,7 @@ public abstract class AbstractException extends RuntimeException {
|
||||
之前的SringBoot2,是在**main->resources->META-INF**目录下新建spring.factories文件。
|
||||
:::
|
||||
|
||||
#### 3.4.1 stater-jdbc
|
||||
### 3.4.1 stater-jdbc
|
||||
  该模块主要提供数据库相关的配置功能。
|
||||
  目前实现的功能有:
|
||||
1. 配置MybatisPlus拦截器,添加乐观锁和分页插件。
|
||||
@@ -450,7 +450,7 @@ public class MybatisMetaObjectHandler implements MetaObjectHandler {
|
||||
  `@ConditionalOnClass(SqlSessionFactory.class)`表示只有存在SqlSessionFactory类时,这个配置类才会生效。即如果没有 MyBatis 相关依赖,这个配置类会被 Spring 完全忽略。
|
||||
  `@Import`用于导入其他配置类或组件到当前配置类中。
|
||||
|
||||
#### 3.4.2 starter-web
|
||||
### 3.4.2 starter-web
|
||||
  该模块主要提供web相关的配置功能。
|
||||
  目前实现的功能有:
|
||||
1. 注册审计拦截器
|
||||
@@ -748,6 +748,162 @@ HandlerInterceptor 拦截器执行顺序:
|
||||
4. afterCompletion():视图渲染后 按注册顺序逆序执行。
|
||||
:::
|
||||
|
||||
### 3.4.3 starter-logging
|
||||
  在`src->main->resources`中添加`logback-spring.xml`文件:
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="true" scanPeriod="10 seconds">
|
||||
|
||||
<!-- 自定义属性 -->
|
||||
<springProperty scope="context" name="fluentdHost" source="logging.fluentd.host" defaultValue="localhost"/>
|
||||
<springProperty scope="context" name="fluentdPort" source="logging.fluentd.port" defaultValue="24224"/>
|
||||
<springProperty scope="context" name="topic" source="spring.application.name" defaultValue="default"/>
|
||||
|
||||
<!-- log路径 -->
|
||||
<property name="log.path" value="logs/${topic}"/>
|
||||
|
||||
<!--控制台日志格式:彩色日志-->
|
||||
<!-- magenta:洋红 -->
|
||||
<!-- boldMagenta:粗红-->
|
||||
<!-- cyan:青色 -->
|
||||
<!-- white:白色 -->
|
||||
<!-- magenta:洋红 -->
|
||||
<property name="CONSOLE_LOG_PATTERN"
|
||||
value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>
|
||||
|
||||
<!--文件日志格式-->
|
||||
<property name="FILE_LOG_PATTERN"
|
||||
value="%date{yyyy-MM-dd HH:mm:ss} |%-5level |%thread |%file:%line |%logger |%msg%n"/>
|
||||
|
||||
<!--编码-->
|
||||
<property name="ENCODING"
|
||||
value="UTF-8"/>
|
||||
|
||||
<!--输出到控制台-->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<!--日志级别-->
|
||||
<level>DEBUG</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<!--日志格式-->
|
||||
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
|
||||
<!--日志字符集-->
|
||||
<charset>${ENCODING}</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--输出到文件-->
|
||||
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!--日志过滤器:此日志文件只记录INFO级别的-->
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>INFO</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<!-- 正在记录的日志文件的路径及文件名 -->
|
||||
<file>${log.path}/log_info.log</file>
|
||||
<encoder>
|
||||
<pattern>${FILE_LOG_PATTERN}</pattern>
|
||||
<charset>${ENCODING}</charset>
|
||||
</encoder>
|
||||
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<!-- 每天日志归档路径以及格式 -->
|
||||
<fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||
<!-- 日志文件的最大大小 -->
|
||||
<maxFileSize>100MB</maxFileSize>
|
||||
<!--日志文件保留天数-->
|
||||
<maxHistory>15</maxHistory>
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
|
||||
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!-- 日志过滤器:此日志文件只记录WARN级别的 -->
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>WARN</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<!-- 正在记录的日志文件的路径及文件名 -->
|
||||
<file>${log.path}/log_warn.log</file>
|
||||
<encoder>
|
||||
<pattern>${FILE_LOG_PATTERN}</pattern>
|
||||
<charset>${ENCODING}</charset> <!-- 此处设置字符集 -->
|
||||
</encoder>
|
||||
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||
<!-- 日志文件的最大大小 -->
|
||||
<maxFileSize>100MB</maxFileSize>
|
||||
<!--日志文件保留天数-->
|
||||
<maxHistory>15</maxHistory>
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
|
||||
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!-- 日志过滤器:此日志文件只记录ERROR级别的 -->
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<!-- 正在记录的日志文件的路径及文件名 -->
|
||||
<file>${log.path}/log_error.log</file>
|
||||
<encoder>
|
||||
<pattern>${FILE_LOG_PATTERN}</pattern>
|
||||
<charset>${ENCODING}</charset> <!-- 此处设置字符集 -->
|
||||
</encoder>
|
||||
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||
<!-- 日志文件的最大大小 -->
|
||||
<maxFileSize>100MB</maxFileSize>
|
||||
<!--日志文件保留天数-->
|
||||
<maxHistory>15</maxHistory>
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
|
||||
<!--输出到FLUENCY-->
|
||||
<appender name="FLUENCY" class="ch.qos.logback.more.appenders.FluencyLogbackAppender">
|
||||
<remoteHost>${fluentdHost}</remoteHost>
|
||||
<port>${fluentdPort}</port>
|
||||
<additionalField>
|
||||
<key>topic</key>
|
||||
<value>${topic}</value>
|
||||
</additionalField>
|
||||
<ignoredField>caller</ignoredField>
|
||||
<useEventTime>true</useEventTime>
|
||||
</appender>
|
||||
|
||||
<!-- 开发环境 -->
|
||||
<springProfile name="dev">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
<!-- 生产环境 -->
|
||||
<springProfile name="prod">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="INFO_FILE"/>
|
||||
<appender-ref ref="WARN_FILE"/>
|
||||
<appender-ref ref="ERROR_FILE"/>
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
<!-- 容器环境 -->
|
||||
<springProfile name="docker">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="FLUENCY"/>
|
||||
</root>
|
||||
</springProfile>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
  该文件会根据当前环境自动添加日志输出节点。
|
||||
|
||||
# 四、项目发布
|
||||
|
||||
## 4.1 发布到Git中
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Vue3-Common
|
||||
title: Vue3 Common
|
||||
date: 2025-12-15 23:25
|
||||
---
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
:::
|
||||
|
||||
# 二、vite.config.ts配置
|
||||
## 2.1 生成Typescript类型文件
|
||||
## 2.1 生成ts类型文件
|
||||
  安装vite-plugin-dts插件
|
||||
|
||||
```cmd
|
||||
@@ -377,6 +377,167 @@ const app = createApp(App)
|
||||
app.use(router)
|
||||
```
|
||||
|
||||
8. 在layout中使用:
|
||||
```vue
|
||||
<template>
|
||||
<aside class="layout-sidebar">
|
||||
<menu-list
|
||||
:route="useRoute()"
|
||||
:routers="menuRoutes"
|
||||
:collapse="isCollapse"
|
||||
class="layout-menu"/>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { MenuList } from 'vue3-common'
|
||||
import menuRoutes from '@/router/menu'
|
||||
import { useRoute } from 'vue-router'
|
||||
</script>
|
||||
```
|
||||
|
||||
  如果需要对路由菜单进行权限处理,可以使用计算属性过滤一遍:
|
||||
```ts
|
||||
const routers = computed(() => {
|
||||
const menuPathList = menuRoutes.map((item) => item.path)
|
||||
const removePathList = menuPathList.filter((item) => !authStore.currentUser.menu.includes(item))
|
||||
|
||||
removePathList.forEach((item) => {
|
||||
router.removeRoute(capitalizeStr(item.replace('/', '')))
|
||||
})
|
||||
|
||||
return menuRoutes.filter((route) => !removePathList.includes(route.path))
|
||||
})
|
||||
```
|
||||
|
||||
::: tip
|
||||
实现原理:
|
||||
```ts
|
||||
import type { RouteComponent, RouteRecordRaw, RouteMeta } from 'vue-router'
|
||||
|
||||
/**
|
||||
* 任意对象类型
|
||||
*/
|
||||
interface ICommonObj {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由meta配置
|
||||
*/
|
||||
interface IRouteMetaConfig {
|
||||
[key: string]: RouteMeta;
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由路径转为名称 /admin/dashboard -> AdminDashboard
|
||||
* @param path
|
||||
*/
|
||||
export const convertPath2Name = (path: string): string => {
|
||||
// 移除路径开头和结尾的斜杠(如果存在)
|
||||
const newPath = path.replace(/^\/|\/$/g, '')
|
||||
|
||||
// 将路径按斜杠分割成多个部分
|
||||
const parts = newPath.split('/')
|
||||
|
||||
// 处理每个部分:将连字符后的字母大写,并移除连字符;处理动态参数(如 :id)
|
||||
return parts.map((part) =>
|
||||
part.split('-').map((subPart) => {
|
||||
// 处理动态参数(如 :id)
|
||||
if (subPart.startsWith(':')) {
|
||||
return subPart.slice(1).charAt(0).toUpperCase() + subPart.slice(2)
|
||||
}
|
||||
// 普通部分
|
||||
return subPart.charAt(0).toUpperCase() + subPart.slice(1)
|
||||
}).join('')).join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据meta的order排序
|
||||
* @param routes 路由列表
|
||||
*/
|
||||
export const sortRoutesByOrder = (routes: RouteRecordRaw[]): RouteRecordRaw[] => {
|
||||
// 排序当前路由的子路由
|
||||
routes.forEach((route) => {
|
||||
if (route.children && route.children.length > 0) {
|
||||
// 如果有子路由,递归排序子路由
|
||||
route.children = sortRoutesByOrder(route.children)
|
||||
}
|
||||
})
|
||||
|
||||
// 对当前路由按 meta.order 排序
|
||||
return routes.sort((a, b) => (a.meta?.order as number || 0) - (b.meta?.order as number || 0))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取路由
|
||||
* @param modules 通过import.meta.glob导入的模块
|
||||
* @param layout 布局组件
|
||||
* @param metaConfig 路由meta配置
|
||||
*/
|
||||
export const getRoutersByModules = (modules: ICommonObj,
|
||||
layout: RouteComponent, metaConfig?: IRouteMetaConfig): RouteRecordRaw[] => {
|
||||
// const modules = import.meta.glob('@/views/admin/**/*.vue')
|
||||
const routes: RouteRecordRaw[] = []
|
||||
|
||||
Object.keys(modules).forEach((filePath) => {
|
||||
// 获取文件的模块路径和文件名
|
||||
const parentPath = filePath.substring(0, filePath.lastIndexOf('/')).replace('/src/views', '')
|
||||
const path = filePath.replace('.vue', '')
|
||||
.replace('/src/views', '')
|
||||
.replace(/\[([^\]]+)]/g, '/:$1')
|
||||
|
||||
// 为每个文件生成对应的路由
|
||||
const route: RouteRecordRaw = {
|
||||
path,
|
||||
name: convertPath2Name(path),
|
||||
component: modules[filePath]
|
||||
}
|
||||
|
||||
// 如果存在meta配置
|
||||
if (metaConfig) {
|
||||
route.meta = metaConfig[path]
|
||||
|
||||
// 设置重定向属性
|
||||
if (route.meta?.redirect) {
|
||||
route.redirect = route.meta?.redirect as any
|
||||
}
|
||||
}
|
||||
|
||||
// 如果该路径已经存在(嵌套路由),则将其作为子路由
|
||||
let parentRoute = routes.find((item) => item.path === parentPath)
|
||||
if (!parentRoute) {
|
||||
parentRoute = {
|
||||
path: parentPath,
|
||||
name: convertPath2Name(parentPath),
|
||||
component: layout,
|
||||
children: []
|
||||
}
|
||||
|
||||
// 如果存在meta配置
|
||||
if (metaConfig) {
|
||||
parentRoute.meta = metaConfig[parentPath]
|
||||
|
||||
// 设置重定向属性
|
||||
if (parentRoute.meta?.redirect) {
|
||||
parentRoute.redirect = parentRoute.meta?.redirect
|
||||
}
|
||||
}
|
||||
|
||||
routes.push(parentRoute)
|
||||
}
|
||||
|
||||
// 如果是子路由,加入到父路由的 `children` 数组
|
||||
parentRoute.children?.push(route)
|
||||
})
|
||||
|
||||
return routes
|
||||
}
|
||||
```
|
||||
|
||||
  核心原理就是利用`import.meta.glob`创建路径->组件的映射,然后结合路由配置表,转为路由对象并添加到`vue router`中。
|
||||
:::
|
||||
|
||||
## 5.2 图标组件
|
||||
1. 安装vite-plugin-svg-icons依赖
|
||||
```cmd
|
||||
93
docs/Web/Vue/Vue3-DefineModel.md
Normal file
93
docs/Web/Vue/Vue3-DefineModel.md
Normal file
@@ -0,0 +1,93 @@
|
||||
---
|
||||
title: Vue3 defineModel
|
||||
date: 2026-06-05
|
||||
---
|
||||
|
||||
# 一、概念
|
||||
  defineModel是对 props + emit的语法糖,返回一个可写的 ref。Vu3.4开始支持。
|
||||
  之前的做法:
|
||||
```ts
|
||||
// 子组件
|
||||
const props = defineProps({
|
||||
modelValue: Number
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
function increment() {
|
||||
emit('update:modelValue', props.modelValue + 1)
|
||||
}
|
||||
```
|
||||
|
||||
  现在做法:
|
||||
```ts
|
||||
const model = defineModel()
|
||||
```
|
||||
|
||||
# 二、简单示例
|
||||
  父组件:
|
||||
```vue
|
||||
<script setup>
|
||||
import Stepper from './Stepper.vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const quantity = ref(1)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p>购买数量:{{ quantity }}</p>
|
||||
<Stepper v-model="quantity" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
  子组件:
|
||||
```vue
|
||||
<script setup>
|
||||
const model = defineModel({
|
||||
type: Number,
|
||||
default: 1
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button @click="quantity--">−</button>
|
||||
<span>{{ quantity }}</span>
|
||||
<button @click="quantity++">+</button>
|
||||
</template>
|
||||
```
|
||||
|
||||
  和`defineProps`定义类似,可以指定类型和默认值。
|
||||
|
||||
# 三、多个v-model
|
||||
  父组件:
|
||||
```vue
|
||||
<UserForm
|
||||
v-model:name="form.name"
|
||||
v-model:age="form.age"
|
||||
/>
|
||||
```
|
||||
|
||||
  子组件:
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const name = defineModel<string>('name', {
|
||||
default: ''
|
||||
})
|
||||
|
||||
const age = defineModel<number>('age', {
|
||||
default: 0
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-model="name" placeholder="姓名" />
|
||||
<input v-model="age" type="number" placeholder="年龄" />
|
||||
</template>
|
||||
```
|
||||
|
||||
::: tip
|
||||
defineProps: 只读,不能改
|
||||
defineModel: 可写
|
||||
:::
|
||||
79
docs/Web/Vue/Vue3-Directive.md
Normal file
79
docs/Web/Vue/Vue3-Directive.md
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: Vue3自定义指令
|
||||
date: 2026-06-05
|
||||
---
|
||||
|
||||
# 一、概念
|
||||
  自定义指令(Directive) 是 Vue 提供的一种机制,用于直接操作 DOM,封装可复用的底层 DOM 行为。它是对模板能力的补充,当现有模板语法(v-if / v-bind / v-on)不够用时,就可以用指令。
|
||||
|
||||
# 二、简单示例
|
||||
  注册:
|
||||
```ts
|
||||
app.directive('demo', {
|
||||
mounted(el, binding) {
|
||||
console.log(el, binding)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
  使用:
|
||||
```vue
|
||||
<div v-demo="'hello'"></div>
|
||||
```
|
||||
|
||||
  使用该指令,就会触发打印效果。
|
||||
|
||||
# 三、生命周期
|
||||
| 钩子 | 说明 |
|
||||
| ----- | ----- |
|
||||
| created | 绑定前 |
|
||||
| beforeMount | 元素挂载前 |
|
||||
| mounted | 元素已挂载 |
|
||||
| beforeUpdate | 更新前 |
|
||||
| updated | 更新后 |
|
||||
| beforeUnmount | 卸载前 |
|
||||
| unmounted | 卸载后 |
|
||||
|
||||
# 四、参数
|
||||
  mounted(el, binding, vnode, prevVnode):
|
||||
| 参数 | 说明 |
|
||||
| ----- | ----- |
|
||||
| el | 当前 DOM 元素 |
|
||||
| binding.value | 指令绑定的值 |
|
||||
| binding.arg | 参数 |
|
||||
| binding.modifiers | 修饰符 |
|
||||
| vnode | 虚拟节点 |
|
||||
|
||||
# 五、封装
|
||||
  在`src`目录新建`directives/**.ts`,定义指令:
|
||||
```ts
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
export default {
|
||||
mounted(el, binding) {
|
||||
const authStore = useAuthStore()
|
||||
const permission = binding.value
|
||||
|
||||
const hasPermission = authStore.currentUser?.permission?.includes(permission)
|
||||
|
||||
if (!hasPermission) {
|
||||
el.parentNode?.removeChild(el)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
  该指令根据用户权限控制DOM是否显示。
|
||||
|
||||
  然后在`main.ts`中注册指令:
|
||||
```ts
|
||||
import permissionDirective from '@/directives/permission'
|
||||
|
||||
app.directive('permission', permissionDirective)
|
||||
```
|
||||
|
||||
  最后在需要的元素中加上`v-permission=""`即可。
|
||||
|
||||
::: tip
|
||||
自定义指令只做DOM相关的事情,比如隐藏DOM,禁止点击、修改样式等等。
|
||||
:::
|
||||
Reference in New Issue
Block a user