從基礎(chǔ)到高級應(yīng)用詳解Vue3中自定義指令的完整指南
摘要
自定義指令是 Vue.js 中一個(gè)強(qiáng)大而靈活的特性,它允許開發(fā)者直接對 DOM 元素進(jìn)行底層操作。Vue3 在保留自定義指令核心概念的同時(shí),對其 API 進(jìn)行了調(diào)整和優(yōu)化,使其更符合組合式 API 的設(shè)計(jì)理念。本文將深入探討 Vue3 中自定義指令的定義方式、生命周期鉤子、使用場景和最佳實(shí)踐,通過豐富的代碼示例和清晰的流程圖,幫助你徹底掌握這一重要特性。
一、 什么是自定義指令?為什么需要它?
1.1 自定義指令的概念
在 Vue.js 中,指令是帶有 v- 前綴的特殊屬性。除了 Vue 內(nèi)置的指令(如 v-model、v-show、v-if 等),Vue 還允許我們注冊自定義指令,用于對普通 DOM 元素進(jìn)行底層操作。
1.2 使用場景
自定義指令在以下場景中特別有用:
- DOM 操作:焦點(diǎn)管理、文本選擇、元素拖拽
- 輸入限制:格式化輸入內(nèi)容、阻止無效字符
- 權(quán)限控制:根據(jù)權(quán)限顯示/隱藏元素
- 集成第三方庫:與 jQuery 插件、圖表庫等集成
- 性能優(yōu)化:圖片懶加載、無限滾動
- 用戶體驗(yàn):點(diǎn)擊外部關(guān)閉、滾動加載更多
1.3 Vue2 與 Vue3 自定義指令的區(qū)別
| 特性 | Vue2 | Vue3 |
|---|---|---|
| 生命周期鉤子 | bind, inserted, update, componentUpdated, unbind | created, beforeMount, mounted, beforeUpdate, updated, beforeUnmount, unmounted |
| 參數(shù)傳遞 | el, binding, vnode, oldVnode | el, binding, vnode, prevVnode |
| 注冊方式 | 全局 Vue.directive(),局部 directives 選項(xiàng) | 全局 app.directive(),局部 directives 選項(xiàng) |
| 與組合式API集成 | 有限 | 更好,可在 setup 中使用 |
二、 自定義指令的基本結(jié)構(gòu)
2.1 指令的生命周期鉤子
Vue3 中的自定義指令包含一系列生命周期鉤子,這些鉤子在指令的不同階段被調(diào)用:
流程圖:自定義指令生命周期

2.2 鉤子函數(shù)參數(shù)
每個(gè)生命周期鉤子函數(shù)都會接收以下參數(shù):
el:指令綁定的元素,可以直接操作 DOMbinding:一個(gè)對象,包含指令的相關(guān)信息vnode:Vue 編譯生成的虛擬節(jié)點(diǎn)prevVnode:上一個(gè)虛擬節(jié)點(diǎn)(僅在beforeUpdate和updated中可用)
binding 對象結(jié)構(gòu):
{
value: any, // 指令的綁定值,如 v-my-directive="value"
oldValue: any, // 指令綁定的前一個(gè)值
arg: string, // 指令的參數(shù),如 v-my-directive:arg
modifiers: object, // 指令的修飾符對象,如 v-my-directive.modifier
instance: Component, // 使用指令的組件實(shí)例
dir: object // 指令的定義對象
}
三、 定義自定義指令的多種方式
3.1 全局自定義指令
全局指令在整個(gè) Vue 應(yīng)用中都可用。
方式一:使用app.directive()
// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// 定義全局焦點(diǎn)指令
app.directive('focus', {
mounted(el) {
el.focus()
console.log('元素獲得焦點(diǎn)')
}
})
// 定義全局顏色指令(帶參數(shù)和值)
app.directive('color', {
beforeMount(el, binding) {
el.style.color = binding.value
},
updated(el, binding) {
el.style.color = binding.value
}
})
app.mount('#app')
方式二:使用插件形式
// directives/index.js
export const focusDirective = {
mounted(el) {
el.focus()
}
}
export const colorDirective = {
beforeMount(el, binding) {
el.style.color = binding.value
},
updated(el, binding) {
el.style.color = binding.value
}
}
// 注冊所有指令
export function registerDirectives(app) {
app.directive('focus', focusDirective)
app.directive('color', colorDirective)
}
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import { registerDirectives } from './directives'
const app = createApp(App)
registerDirectives(app)
app.mount('#app')
3.2 局部自定義指令
局部指令只在特定組件中可用。
選項(xiàng)式 API
<template>
<div>
<input v-focus-local placeholder="局部焦點(diǎn)指令" />
<p v-color-local="textColor">這個(gè)文本顏色會變化</p>
<button @click="changeColor">改變顏色</button>
</div>
</template>
<script>
export default {
data() {
return {
textColor: 'red'
}
},
methods: {
changeColor() {
this.textColor = this.textColor === 'red' ? 'blue' : 'red'
}
},
directives: {
// 局部焦點(diǎn)指令
'focus-local': {
mounted(el) {
el.focus()
}
},
// 局部顏色指令
'color-local': {
beforeMount(el, binding) {
el.style.color = binding.value
},
updated(el, binding) {
el.style.color = binding.value
}
}
}
}
</script>
組合式 API
<template>
<div>
<input v-focus-local placeholder="局部焦點(diǎn)指令" />
<p v-color-local="textColor">這個(gè)文本顏色會變化</p>
<button @click="changeColor">改變顏色</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const textColor = ref('red')
const changeColor = () => {
textColor.value = textColor.value === 'red' ? 'blue' : 'red'
}
// 局部自定義指令
const vFocusLocal = {
mounted(el) {
el.focus()
}
}
const vColorLocal = {
beforeMount(el, binding) {
el.style.color = binding.value
},
updated(el, binding) {
el.style.color = binding.value
}
}
</script>
四、 完整生命周期示例
讓我們通過一個(gè)完整的示例來演示所有生命周期鉤子的使用:
<template>
<div class="demo-container">
<h2>自定義指令完整生命周期演示</h2>
<div>
<button @click="toggleDisplay">{{ isVisible ? '隱藏' : '顯示' }}元素</button>
<button @click="changeMessage">改變消息</button>
<button @click="changeColor">改變顏色</button>
</div>
<div v-if="isVisible" v-lifecycle-demo:arg.modifier="directiveValue"
class="demo-element" :style="{ color: elementColor }">
{{ message }}
</div>
<div class="log-container">
<h3>生命周期日志:</h3>
<div v-for="(log, index) in logs" :key="index" class="log-item">
{{ log }}
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const isVisible = ref(false)
const message = ref('Hello, Custom Directive!')
const elementColor = ref('#333')
const logs = ref([])
const directiveValue = reactive({
text: '指令值對象',
count: 0
})
// 添加日志函數(shù)
const addLog = (hookName, el, binding) => {
const log = `[${new Date().toLocaleTimeString()}] ${hookName}: value=${JSON.stringify(binding.value)}, arg=${binding.arg}`
logs.value.push(log)
// 保持日志數(shù)量不超過20條
if (logs.value.length > 20) {
logs.value.shift()
}
}
// 完整的生命周期指令
const vLifecycleDemo = {
created(el, binding) {
addLog('created', el, binding)
console.log('created - 指令創(chuàng)建,元素還未掛載')
},
beforeMount(el, binding) {
addLog('beforeMount', el, binding)
console.log('beforeMount - 元素掛載前')
el.style.transition = 'all 0.3s ease'
},
mounted(el, binding) {
addLog('mounted', el, binding)
console.log('mounted - 元素掛載完成')
console.log('修飾符:', binding.modifiers)
console.log('參數(shù):', binding.arg)
// 添加動畫效果
el.style.opacity = '0'
el.style.transform = 'translateY(-20px)'
setTimeout(() => {
el.style.opacity = '1'
el.style.transform = 'translateY(0)'
}, 100)
},
beforeUpdate(el, binding) {
addLog('beforeUpdate', el, binding)
console.log('beforeUpdate - 元素更新前')
},
updated(el, binding) {
addLog('updated', el, binding)
console.log('updated - 元素更新完成')
// 更新時(shí)的動畫
el.style.backgroundColor = '#e3f2fd'
setTimeout(() => {
el.style.backgroundColor = ''
}, 500)
},
beforeUnmount(el, binding) {
addLog('beforeUnmount', el, binding)
console.log('beforeUnmount - 元素卸載前')
// 卸載動畫
el.style.opacity = '1'
el.style.transform = 'translateY(0)'
el.style.opacity = '0'
el.style.transform = 'translateY(-20px)'
},
unmounted(el, binding) {
addLog('unmounted', el, binding)
console.log('unmounted - 元素卸載完成')
}
}
const toggleDisplay = () => {
isVisible.value = !isVisible.value
}
const changeMessage = () => {
message.value = `消息已更新 ${Date.now()}`
directiveValue.count++
}
const changeColor = () => {
const colors = ['#ff4444', '#44ff44', '#4444ff', '#ff44ff', '#ffff44']
elementColor.value = colors[Math.floor(Math.random() * colors.length)]
}
</script>
<style scoped>
.demo-container {
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
.demo-element {
padding: 20px;
margin: 20px 0;
border: 2px solid #42b983;
border-radius: 8px;
background: #f9f9f9;
}
.log-container {
margin-top: 20px;
padding: 15px;
background: #f5f5f5;
border-radius: 8px;
max-height: 400px;
overflow-y: auto;
}
.log-item {
padding: 5px 10px;
margin: 2px 0;
background: white;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 12px;
}
button {
margin: 5px;
padding: 8px 16px;
background: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #369870;
}
</style>
五、 實(shí)用自定義指令示例
5.1 點(diǎn)擊外部關(guān)閉指令
<template>
<div class="click-outside-demo">
<h2>點(diǎn)擊外部關(guān)閉演示</h2>
<button @click="showDropdown = !showDropdown">
切換下拉菜單 {{ showDropdown ? '▲' : '▼' }}
</button>
<div v-if="showDropdown" v-click-outside="closeDropdown" class="dropdown">
<div class="dropdown-item">菜單項(xiàng) 1</div>
<div class="dropdown-item">菜單項(xiàng) 2</div>
<div class="dropdown-item">菜單項(xiàng) 3</div>
</div>
<div v-if="showModal" v-click-outside="closeModal" class="modal">
<div class="modal-content">
<h3>模態(tài)框</h3>
<p>點(diǎn)擊模態(tài)框外部可以關(guān)閉</p>
<button @click="showModal = false">關(guān)閉</button>
</div>
</div>
<button @click="showModal = true" style="margin-left: 10px;">
打開模態(tài)框
</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const showDropdown = ref(false)
const showModal = ref(false)
// 點(diǎn)擊外部關(guān)閉指令
const vClickOutside = {
mounted(el, binding) {
el._clickOutsideHandler = (event) => {
// 檢查點(diǎn)擊是否在元素外部
if (!(el === event.target || el.contains(event.target))) {
binding.value(event)
}
}
document.addEventListener('click', el._clickOutsideHandler)
},
unmounted(el) {
document.removeEventListener('click', el._clickOutsideHandler)
}
}
const closeDropdown = () => {
showDropdown.value = false
}
const closeModal = () => {
showModal.value = false
}
</script>
<style scoped>
.dropdown {
position: absolute;
top: 100%;
left: 0;
background: white;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
z-index: 1000;
margin-top: 5px;
}
.dropdown-item {
padding: 10px 20px;
cursor: pointer;
border-bottom: 1px solid #eee;
}
.dropdown-item:hover {
background: #f5f5f5;
}
.dropdown-item:last-child {
border-bottom: none;
}
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
}
.modal-content {
background: white;
padding: 30px;
border-radius: 8px;
max-width: 400px;
width: 90%;
}
</style>
5.2 輸入限制指令
<template>
<div class="input-restriction-demo">
<h2>輸入限制指令演示</h2>
<div class="input-group">
<label>僅數(shù)字輸入:</label>
<input v-number-only v-model="numberInput" placeholder="只能輸入數(shù)字" />
<span>值: {{ numberInput }}</span>
</div>
<div class="input-group">
<label>最大長度限制:</label>
<input v-limit-length="10" v-model="limitedInput" placeholder="最多10個(gè)字符" />
<span>值: {{ limitedInput }}</span>
</div>
<div class="input-group">
<label>禁止特殊字符:</label>
<input v-no-special-chars v-model="noSpecialInput" placeholder="不能輸入特殊字符" />
<span>值: {{ noSpecialInput }}</span>
</div>
<div class="input-group">
<label>自動格式化手機(jī)號:</label>
<input v-phone-format v-model="phoneInput" placeholder="輸入手機(jī)號" />
<span>值: {{ phoneInput }}</span>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const numberInput = ref('')
const limitedInput = ref('')
const noSpecialInput = ref('')
const phoneInput = ref('')
// 僅數(shù)字輸入指令
const vNumberOnly = {
mounted(el) {
el.addEventListener('input', (e) => {
e.target.value = e.target.value.replace(/[^\d]/g, '')
// 觸發(fā) v-model 更新
e.dispatchEvent(new Event('input'))
})
}
}
// 長度限制指令
const vLimitLength = {
mounted(el, binding) {
const maxLength = binding.value
el.setAttribute('maxlength', maxLength)
el.addEventListener('input', (e) => {
if (e.target.value.length > maxLength) {
e.target.value = e.target.value.slice(0, maxLength)
e.dispatchEvent(new Event('input'))
}
})
}
}
// 禁止特殊字符指令
const vNoSpecialChars = {
mounted(el) {
el.addEventListener('input', (e) => {
e.target.value = e.target.value.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '')
e.dispatchEvent(new Event('input'))
})
}
}
// 手機(jī)號格式化指令
const vPhoneFormat = {
mounted(el) {
el.addEventListener('input', (e) => {
let value = e.target.value.replace(/\D/g, '')
if (value.length > 3 && value.length <= 7) {
value = value.replace(/(\d{3})(\d+)/, '$1-$2')
} else if (value.length > 7) {
value = value.replace(/(\d{3})(\d{4})(\d+)/, '$1-$2-$3')
}
e.target.value = value
e.dispatchEvent(new Event('input'))
})
}
}
</script>
<style scoped>
.input-restriction-demo {
padding: 20px;
}
.input-group {
margin: 15px 0;
}
label {
display: inline-block;
width: 150px;
font-weight: bold;
}
input {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
margin: 0 10px;
width: 200px;
}
span {
color: #666;
font-size: 14px;
}
</style>
5.3 權(quán)限控制指令
<template>
<div class="permission-demo">
<h2>權(quán)限控制指令演示</h2>
<div class="user-info">
<label>當(dāng)前用戶角色:</label>
<select v-model="currentRole" @change="updatePermissions">
<option value="guest">游客</option>
<option value="user">普通用戶</option>
<option value="editor">編輯者</option>
<option value="admin">管理員</option>
</select>
</div>
<div class="permission-list">
<h3>可用功能:</h3>
<button v-permission="'view'" class="feature-btn">
?? 查看內(nèi)容
</button>
<button v-permission="'edit'" class="feature-btn">
?? 編輯內(nèi)容
</button>
<button v-permission="'delete'" class="feature-btn">
??? 刪除內(nèi)容
</button>
<button v-permission="'admin'" class="feature-btn">
?? 系統(tǒng)管理
</button>
<button v-permission="['edit', 'delete']" class="feature-btn">
?? 批量操作
</button>
</div>
<div class="current-permissions">
<h3>當(dāng)前權(quán)限:</h3>
<ul>
<li v-for="permission in currentPermissions" :key="permission">
{{ permission }}
</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
// 角色權(quán)限映射
const rolePermissions = {
guest: ['view'],
user: ['view', 'edit'],
editor: ['view', 'edit', 'delete'],
admin: ['view', 'edit', 'delete', 'admin']
}
const currentRole = ref('user')
const currentPermissions = ref(['view', 'edit'])
// 權(quán)限控制指令
const vPermission = {
mounted(el, binding) {
checkPermission(el, binding)
},
updated(el, binding) {
checkPermission(el, binding)
}
}
// 檢查權(quán)限函數(shù)
const checkPermission = (el, binding) => {
const requiredPermissions = Array.isArray(binding.value)
? binding.value
: [binding.value]
const hasPermission = requiredPermissions.some(permission =>
currentPermissions.value.includes(permission)
)
if (!hasPermission) {
el.style.display = 'none'
} else {
el.style.display = 'inline-block'
}
}
// 更新權(quán)限
const updatePermissions = () => {
currentPermissions.value = rolePermissions[currentRole.value] || []
}
</script>
<style scoped>
.permission-demo {
padding: 20px;
max-width: 600px;
margin: 0 auto;
}
.user-info {
margin: 20px 0;
}
select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
margin-left: 10px;
}
.permission-list {
margin: 30px 0;
}
.feature-btn {
display: inline-block;
padding: 12px 20px;
margin: 5px;
background: #42b983;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s;
}
.feature-btn:hover {
background: #369870;
}
.current-permissions {
margin-top: 30px;
padding: 15px;
background: #f5f5f5;
border-radius: 8px;
}
.current-permissions ul {
list-style: none;
padding: 0;
}
.current-permissions li {
padding: 5px 10px;
background: white;
margin: 5px 0;
border-radius: 4px;
border-left: 4px solid #42b983;
}
</style>
六、 高級技巧與最佳實(shí)踐
6.1 指令參數(shù)動態(tài)化
<template>
<div>
<input v-tooltip="tooltipConfig" placeholder="懸浮顯示提示" />
<div v-pin="pinConfig" class="pinned-element">
可動態(tài)配置的固定元素
</div>
<button @click="updateConfig">更新配置</button>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
// 動態(tài)提示指令
const vTooltip = {
mounted(el, binding) {
const config = binding.value
el.title = config.text
el.style.cursor = config.cursor || 'help'
if (config.position) {
el.dataset.position = config.position
}
},
updated(el, binding) {
const config = binding.value
el.title = config.text
}
}
// 動態(tài)固定指令
const vPin = {
mounted(el, binding) {
updatePinPosition(el, binding)
},
updated(el, binding) {
updatePinPosition(el, binding)
}
}
const updatePinPosition = (el, binding) => {
const config = binding.value
el.style.position = 'fixed'
el.style[config.side] = config.distance + 'px'
el.style.zIndex = config.zIndex || 1000
}
const tooltipConfig = reactive({
text: '這是一個(gè)動態(tài)提示',
cursor: 'help',
position: 'top'
})
const pinConfig = reactive({
side: 'top',
distance: 20,
zIndex: 1000
})
const updateConfig = () => {
tooltipConfig.text = `更新后的提示 ${Date.now()}`
pinConfig.side = pinConfig.side === 'top' ? 'bottom' : 'top'
pinConfig.distance = Math.random() * 100 + 20
}
</script>
6.2 指令組合與復(fù)用
// directives/composable.js
export function useClickHandlers() {
return {
mounted(el, binding) {
el._clickHandler = binding.value
el.addEventListener('click', el._clickHandler)
},
unmounted(el) {
el.removeEventListener('click', el._clickHandler)
}
}
}
export function useHoverHandlers() {
return {
mounted(el, binding) {
el._mouseenterHandler = binding.value.enter
el._mouseleaveHandler = binding.value.leave
if (el._mouseenterHandler) {
el.addEventListener('mouseenter', el._mouseenterHandler)
}
if (el._mouseleaveHandler) {
el.addEventListener('mouseleave', el._mouseleaveHandler)
}
},
unmounted(el) {
if (el._mouseenterHandler) {
el.removeEventListener('mouseenter', el._mouseenterHandler)
}
if (el._mouseleaveHandler) {
el.removeEventListener('mouseleave', el._mouseleaveHandler)
}
}
}
}
// 組合指令
export const vInteractive = {
mounted(el, binding) {
const { click, hover } = binding.value
if (click) {
el.addEventListener('click', click)
el._clickHandler = click
}
if (hover) {
el.addEventListener('mouseenter', hover.enter)
el.addEventListener('mouseleave', hover.leave)
el._hoverHandlers = hover
}
},
unmounted(el) {
if (el._clickHandler) {
el.removeEventListener('click', el._clickHandler)
}
if (el._hoverHandlers) {
el.removeEventListener('mouseenter', el._hoverHandlers.enter)
el.removeEventListener('mouseleave', el._hoverHandlers.leave)
}
}
}
七、 總結(jié)
7.1 核心要點(diǎn)回顧
- 生命周期鉤子:Vue3 提供了 7 個(gè)生命周期鉤子,覆蓋了指令的完整生命周期
- 參數(shù)傳遞:通過
binding對象可以訪問指令的值、參數(shù)、修飾符等信息 - 多種定義方式:支持全局注冊和局部注冊,兼容選項(xiàng)式 API 和組合式 API
- 靈活性:指令可以接收動態(tài)參數(shù)、對象值,支持復(fù)雜的交互邏輯
7.2 最佳實(shí)踐
- 命名規(guī)范:使用小寫字母和連字符命名指令
- 內(nèi)存管理:在
unmounted鉤子中清理事件監(jiān)聽器和定時(shí)器 - 性能優(yōu)化:避免在指令中進(jìn)行昂貴的 DOM 操作
- 可復(fù)用性:將通用指令提取為獨(dú)立模塊
- 類型安全:為指令提供 TypeScript 類型定義
7.3 適用場景
- DOM 操作:焦點(diǎn)管理、元素定位、動畫控制
- 輸入處理:格式化、驗(yàn)證、限制
- 用戶交互:點(diǎn)擊外部、滾動加載、拖拽
- 權(quán)限控制:基于角色的元素顯示/隱藏
- 第三方集成:包裝現(xiàn)有的 JavaScript 庫
自定義指令是 Vue.js 生態(tài)中一個(gè)非常強(qiáng)大的特性,合理使用可以極大地提高代碼的復(fù)用性和可維護(hù)性。
以上就是從基礎(chǔ)到高級應(yīng)用詳解Vue3中自定義指令的完整指南的詳細(xì)內(nèi)容,更多關(guān)于Vue3自定義指令的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在同一個(gè)Vue項(xiàng)目中的不同vue和HTML頁面之間進(jìn)行跳轉(zhuǎn)方式
這篇文章主要介紹了在同一個(gè)Vue項(xiàng)目中的不同vue和HTML頁面之間進(jìn)行跳轉(zhuǎn)方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
Vue項(xiàng)目優(yōu)化的一些實(shí)戰(zhàn)策略
代碼優(yōu)化不僅僅局限在業(yè)務(wù)邏輯這塊,像是代碼復(fù)用、效率等等都是我們可以加以改進(jìn)的地方,這篇文章主要給大家介紹了關(guān)于Vue項(xiàng)目優(yōu)化的一些實(shí)戰(zhàn)策略,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05
如何使用Vue3設(shè)計(jì)實(shí)現(xiàn)一個(gè)Model組件淺析
v-model在Vue里面是一個(gè)語法糖,數(shù)據(jù)的雙向綁定,本質(zhì)上還是通過 自定義標(biāo)簽的attribute傳遞和接受,下面這篇文章主要給大家介紹了關(guān)于如何使用Vue3設(shè)計(jì)實(shí)現(xiàn)一個(gè)Model組件的相關(guān)資料,需要的朋友可以參考下2022-08-08
Vue中Video標(biāo)簽播放解析后短視頻去水印無響應(yīng)解決
這篇文章主要為大家介紹了Vue中使用Video標(biāo)簽播放?<解析后的短視頻>去水印視頻無響應(yīng)的解決方式,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04

