主要内容
1 支持多个编辑框:
1.1 新建单个编辑框方法:
用到的组件:
import { Editor, EditorContent } from "@tiptap/vue-2";
// in template
<template>
//...
<editor-content :editor="editor" />
//...
</template>
//in script
<script>
//...
import { Editor, EditorContent } from '@tiptap/vue-3'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
// 插件
extensions: [
StarterKit,
],
// editor内容:string|HTML
content: `Write something...`,
})
},
beforeUnmount() {
this.editor.destroy()
},
}
//...
</script>
1.2 动态创建编辑框:
需要的前置知识:Vue2动态添加组件(见第二节)
- 创建EditorContent组件构造器
- 创建editor实例
- 创建EditorContent组件,editor实例作为参数传入
- 挂载EditorContent组件
// in template
<template>
<button @click="newEditorContent">动态创建编辑框</button>
<div id="editor-container"></div>
</template>
// in script
<script>
import { Editor, EditorContent } from "@tiptap/vue-2";
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
methods:{
newEditorContent() {
var editorContent = Vue.extend(EditorContent);
this.editor =
new Editor({
content: `<p>This is my editor. 🎉</p>`,
extensions: [
StarterKit
]
})
//VueComponent
var newEditor = new editorContent({
propsData: {
editor: this.editor,
},
}).$mount();
//newEditor.$el-DOM
document.getElementById("editor-container").appendChild(newEditor.$el);
},
}
}
</script>
1.3 支持多个编辑框
- storage:数组[]Editor, 用于存放editor实例
- count:记录编辑框个数
- currentEditor:当前聚焦的编辑框
// in template
import { Editor, EditorContent } from "@tiptap/vue-2";
export default {
components: {
EditorContent,
},
data() {
return {
editors: [],
count: 0,
currentEditor: {}
}
},
methods:{
newEditorContent() {
var editorContent = Vue.extend(EditorContent);
let _this = this;
this.editors.push(
new Editor({
content: `<p>This is my editor. 🎉</p>`,
extensions: [
StarterKit
],
onFocus({ editor, event }) {
_this.currentEditor = editor;
},
})
);
//VueComponent
var newEditor = new editorContent({
propsData: {
editor: this.editors[this.count],
},
}).$mount();
//newEditor.$el-DOM
document.getElementById("editor-container").appendChild(newEditor.$el);
},
}
}
2 Vue2动态添加组件
参考:
https://juejin.cn/post/6890072682864476168
https://blog.csdn.net/m0_37941483/article/details/90237575
https://developer.aliyun.com/article/1052928
2.1 Vue.extend(options): 基础Vue构造器
https://www.jianshu.com/p/ed58f070684c
options参数是一个Vue组件对象(data属性必须是函数类型),返回一个扩展实例构造器, (注意是构造器并不是实例本身
源码如下:
/**
* Each instance constructor, including Vue, has a unique
* cid. This enables us to create wrapped "child
* constructors" for prototypal inheritance and cache them.
*/
Vue.cid = 0;
var cid = 1;
/**
* Class inheritance
*/
Vue.extend = function (extendOptions) {
extendOptions = extendOptions || {};
var Super = this;
var SuperId = Super.cid;
var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId];
}
var name = getComponentName(extendOptions) || getComponentName(Super.options);
if (process.env.NODE_ENV !== 'production' && name) {
validateComponentName(name);
}
var Sub = function VueComponent(options) {
this._init(options);
};
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.cid = cid++;
Sub.options = mergeOptions(Super.options, extendOptions);
Sub['super'] = Super;
// For props and computed properties, we define the proxy getters on
// the Vue instances at extension time, on the extended prototype. This
// avoids Object.defineProperty calls for each instance created.
if (Sub.options.props) {
initProps(Sub);
}
if (Sub.options.computed) {
initComputed(Sub);
}
// allow further extension/mixin/plugin usage
Sub.extend = Super.extend;
Sub.mixin = Super.mixin;
Sub.use = Super.use;
// create asset registers, so extended classes
// can have their private assets too.
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type];
});
// enable recursive self-lookup
if (name) {
Sub.options.components[name] = Sub;
}
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options;
Sub.extendOptions = extendOptions;
Sub.sealedOptions = extend({}, Sub.options);
// cache constructor
cachedCtors[SuperId] = Sub;
return Sub;
};
-
Vue.extend 返回方法
VueComponent
,核心代码如下:var Sub = function VueComponent(options) { this._init(options); };
-
_init源码如下(解析与简化版本[1])
let uid = 0; export function initMixin(Vue: Class<Component>) { Vue.prototype._init = function(options?: Object) { const vm: Component = this; vm._uid = uid++; // 当前实例的 _uid 加 1 // a flag to avoid this being observed // 用 _isVue 来标识当前实例是 Vue 实例, 这样做是为了后续被 observed vm._isVue = true; // merge options 合并options if (options && options._isComponent) { // _isComponent 标识当前为 内部Component // 内部Component 的 options 初始化 initInternalComponent(vm, options); } else { // 非内部Component的 options 初始化 vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ); } // 在render中将this指向vm._renderProxy if (process.env.NODE_ENV !== 'production') { initProxy(vm); } else { vm._renderProxy = vm; } // expose real self vm._self = vm; initLifecycle(vm); // 初始化生命周期 initEvents(vm); // 初始化事件 initRender(vm); // 初始化渲染函数 callHook(vm, 'beforeCreate'); // 回调 beforeCreate 钩子函数 initInjections(vm); // resolve injections before data/props // 初始化 vm 的状态 initState(vm); initProvide(vm); // resolve provide after data/props callHook(vm, 'created'); // vm 已经创建好 回调 created 钩子函数 if (vm.$options.el) { // 挂载实例 vm.$mount(vm.$options.el); } }
创建组件:
var componentConstructor = Vue.extend(Component);
var vueComponent = new componentConstructor(options)
入参options用来配置组件属性,返回一个Vue组件实例
2.2 Vue(options).$mount()
手动挂载组件[2]
$mount 方法对组件进行了手动渲染,但它仅仅是被渲染好了,并没有挂载到节点上[3]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Tlj9F7b-1689932814069)(assets/image-20230718171653-sg7xse6.png)]
VueComponent.$mount()
对组件实例挂载,提取this.$el
(DOM元素)为实例根节点$mount()
返回组件实例本身$mount()
不传参数(被挂载的dom)则新建一个div返回,如果传入dom,则会将对应的dom对象替换成新的div,所以el不能是body或者html,因为body和html是不适合替换的。
$mount() 和 appendChildren(Node) 区别
VueComponent.$mount()
对组件实例挂载,提取this.$el
(DOM元素)为实例根节点
appendChildren(Node),需要传入DOM元素,也就是挂载后解析出的this.$el
, 传入非DOM元素会报错:
“TypeError: Failed to execute ‘appendChild’ on ‘Node’: parameter 1 is not of type ‘Node’.”
2.3 练习:自定义弹窗组件,使其可以全局使用
具体分为一下几步:
- 定义一个弹框组件
- 定义一个方法,用来构造弹框组件并将其挂载
- 将方法放在原型上
- 在vue中调用
// MyDialog.vue
<template>
<TransitionRoot appear :show="showDialog" as="template">
<Dialog as="div" class="relative z-10">
<TransitionChild
as="template"
enter="duration-300 ease-out"
enter-from="opacity-0"
enter-to="opacity-100"
leave="duration-200 ease-in"
leave-from="opacity-100"
leave-to="opacity-0"
>
<div class="fixed inset-0 bg-black bg-opacity-25" />
</TransitionChild>
<div class="fixed inset-0 overflow-y-auto">
<div
class="flex min-h-full items-center justify-center p-4 text-center"
>
<TransitionChild
as="template"
enter="duration-300 ease-out"
enter-from="opacity-0 scale-95"
enter-to="opacity-100 scale-100"
leave="duration-200 ease-in"
leave-from="opacity-100 scale-100"
leave-to="opacity-0 scale-95"
>
<DialogPanel
class="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all"
>
<DialogTitle
as="h3"
class="text-lg text-center font-medium leading-6 text-gray-900"
>
{{title.text}}
</DialogTitle>
<div class="mt-2">
<p :class="'text-sm text-gray-500 ' + `text-${align}`" v-for="(contentItem,index) in content" :key="index">
{{contentItem}}
</p>
</div>
<div class="mt-4 flex justify-around">
<button
v-show="cancelText"
type="button"
class="w-1/2 mr-10 inline-flex justify-center rounded-md border border-gray-200 bg-white-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-gray-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
@click="cancelBak"
>
{{cancelText}}
</button>
<button
type="button"
class="w-1/2 inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
@click="confirm"
>
{{buttonText}}
</button>
</div>
</DialogPanel>
</TransitionChild>
</div>
</div>
</Dialog>
</TransitionRoot>
</template>
<script>
import {
TransitionRoot,
TransitionChild,
Dialog,
DialogPanel,
DialogTitle,
DialogDescription,
} from "@headlessui/vue";
export default {
name: "dialog",
components:{Dialog, DialogTitle, DialogDescription, DialogPanel, TransitionRoot, TransitionChild},
data() {
return {
showDialog: false,
buttonText:"我知道了",
cancelText:"",
title:{
text:"提示",
img:""
},
content:"",
callback:null,
};
},
created(){
this.show();
},
methods: {
show() {
this.$nextTick().then(() => (this.showDialog = true));
},
confirm() {
console.log('confirm');
this.callback();
this.cancelBak();
}
},
};
</script>
<style>
</style>
// main.js
import Vue from "vue";
import App from "./App.vue";
import dialog from "./components/MyDialog.vue";
Vue.config.productionTip = false;
function showDialog(options) {
let Dialog = Vue.extend(dialog);
let dialogComponent = new Dialog(options).$mount();
for (let key in options) {
dialogComponent[key] = options[key];
}
document.body.appendChild(dialogComponent.$el);
}
Vue.prototype.$dialog = showDialog;
new Vue({
render: (h) => h(App),
}).$mount("#app");
// 使用
this.$dialog({
title: { img: null, text: "出错了" },
content: "系统异常,请稍后再试",
align: "center",
buttonText: "我知道了",
callback: () => {},
});