一文搞懂:localhost和局域网 IP 的核心区别与使用场景
前端项目运行时给出的 localhost:3000 和 192.168.1.41:3000 本质上指向同一项目服务,但适用场景和访问范围不同,具体区别及选择建议如下: 一、核心区别 维度 localh
原理
plus.downloader.createDownload 将文件下载到本地;下载完成后通过 plus.runtime.openFile 打开。Download 目录(需要存储权限)。_doc 持久目录(无需额外权限)。使用方法(代码)
src/views/w-success.vue:323。// src/views/w-success.vue:323-388
const downloadByPlus = async () => {
const p = (window as any).plus;
if (!p) { alert("当前不在App环境"); return; }
if (isDownloading.value) return;
isDownloading.value = true;
downloadStatus.value = "原生下载中...";
try {
const fileOnly = getFileName(downloadUrl);
const isAndroid = p.os && p.os.name === "Android";
const isiOS = p.os && p.os.name === "iOS";
const filename = isAndroid
? `file:///storage/emulated/0/Download/${fileOnly}`
: isiOS
? `_doc/${fileOnly}`
: `_downloads/${fileOnly}`;
const startDownload = () =>
p.downloader.createDownload(
downloadUrl,
{ filename },
(d: any, status: number) => {
isDownloading.value = false;
if (status === 200) {
let localPath = "";
try { localPath = p.io.convertLocalFileSystemURL(d.filename); } catch (err) { localPath = ""; }
downloadStatus.value = localPath ? `下载完成,路径:${localPath}` : "下载完成";
try { p.runtime.openFile(d.filename); } catch (e: any) { downloadStatus.value = `${downloadStatus.value},打开失败: ${e?.message || e}`; }
} else {
downloadStatus.value = `下载失败,状态码:${status}`;
}
}
).start();
if (isAndroid) {
try {
await new Promise((resolve, reject) => {
p.android.requestPermissions(
["android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.READ_EXTERNAL_STORAGE"],
() => resolve(null),
(err: any) => reject(err)
);
});
} catch (err: any) {
isDownloading.value = false;
downloadStatus.value = `缺少存储权限: ${err?.message || err}`;
return;
}
}
startDownload();
} catch (e: any) {
isDownloading.value = false;
downloadStatus.value = `原生下载失败: ${e?.message || e}`;
}
};
保存路径
/storage/emulated/0/Download/<文件名>(绝对路径显示为 file:///storage/emulated/0/Download/<文件名>)。_doc/<文件名>(绝对路径通过 plus.io.convertLocalFileSystemURL 转换后显示为 file:///.../Documents/<文件名> 等沙箱路径)。平台要求
WRITE_EXTERNAL_STORAGE、READ_EXTERNAL_STORAGE 权限;代码中已请求(src/views/w-success.vue:356-371)。http://,需在原生打包的 ATS 配置中允许该域名的非 HTTPS 访问;文件仅可写入应用沙箱。实现飘动红旗的效果整体分两部,一是利用三角函数的周期性让红旗摆动起来,二是根据每个片元的摆动幅度来计算对应位置的阴影。
这是我在一个园区项目中收到的需求,在此记录及分享实现过程。
这里使用gltf模型作为红旗,因为需要获得平滑的摆动效果,因此使用的模型面数较多,同时为了旗子与旗杆可以使用相同的坐标位置,我将模型的定位锚地放到了左上角(见下图,来自建模软件blender)。同样的,飘动的功能也可以手动创建Cesium中polygon或rectangle实体来完成,核心部分与使用gltf模型无异。
创建基础场景
Cesium.Ion.defaultAccessToken = "your token";
const viewer = new Cesium.Viewer("cesiumContainer");
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(116.39122232836966, 39.90701265936752, 4.813199462406734),
orientation: {
heading: Cesium.Math.toRadians(26.754499635799313),
pitch: Cesium.Math.toRadians(5.094600937875728),
roll: 0,
},
});
const modelPosition = [116.39124568344667, 39.90705858625655, 6]
//绘制旗子
const flag = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(...modelPosition),
model: {
uri: '../source/models/旗子/旗子.gltf',
},
});
//绘制旗杆
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(modelPosition[0], modelPosition[1]),
ellipse: {
semiMinorAxis: 0.01,
semiMajorAxis: 0.01,
extrudedHeight: modelPosition[2] + 0.05,
material: Cesium.Color.fromCssColorString('#eee'),
},
});
请注意,下文中所有着色器的坐标控制都是基于模型自身的局部坐标系。如果使用不同的模型,可能需要根据模型的具体坐标系统调整相关参数。
const customShader = new Cesium.CustomShader({
uniforms: {
// 自增变量,让动画持续执行
u_time: {
type: Cesium.UniformType.FLOAT,
value: 0.0
},
u_texture: {
type: Cesium.UniformType.SAMPLER_2D,
value: new Cesium.TextureUniform({
url: "../source/models/旗子/hongqi.jpg",
})
}
},
varyings: {
// 旗子的片元偏移量需要传递给片元着色器计算阴影,因此定义为varying变量
v_offset: Cesium.VaryingType.FLOAT
},
vertexShaderText: `
void vertexMain(VertexInput vsInput, inout czm_modelVertexOutput vsOutput) {
// 根据模型uv坐标的x和y坐标来确定摆动的频率(对应sin曲线的波频)
// 这里控制波频时,分别用到了x和y轴坐标,并让x坐标权重大于y坐标,使得摆动更加自然
// 最后乘以0.13是为了控制摆动的幅度(对应sin曲线的波高)
float offset = sin(vsInput.attributes.texCoord_0.x * 8.0 + vsInput.attributes.texCoord_0.y * 1.5 - u_time) * 0.13;
v_offset = offset - offset * smoothstep(0.4, 0.0, vsInput.attributes.texCoord_0.x);
// 为片元赋予新的x坐标,新的x坐标为原始x坐标加上摆动偏移量
vsOutput.positionMC.x += vsOutput.positionMC.x + v_offset;
// 因为旗子在x方向上有前后摆动,因此在视觉上z轴应当适当缩短一些
vsOutput.positionMC.z *= 0.95;
}`,
fragmentShaderText: `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
}`
})
flag.model.customShader = customShader;
viewer.clock.onTick.addEventListener(function () {
// 使动画持续进行
customShader.uniforms.u_time.value += 0.1;
});
从上图可以看出,虽然旗子已经实现了摆动效果,但与预期不符:靠近旗杆的一侧本应保持不动。接下来进一步优化顶点着色器代码。
void vertexMain(VertexInput vsInput, inout czm_modelVertexOutput vsOutput) {
float offset = sin(vsInput.attributes.texCoord_0.x * 8.0 + vsInput.attributes.texCoord_0.y * 1.5 - u_time) * 0.13;
// 这是关键的一步,使用平滑阶梯函数smoothstep函数来控制摆动的范围
// smoothstep(0.4, 0.0, vsInput.attributes.texCoord_0.x)表达式在uv的x轴坐标靠近起点时返回1,到达x轴的0.4时返回0
// 再用offset减去offset乘以smoothstep函数的结果,就可以得到在x轴坐标靠近0时,offset值为0的效果,往x轴的0.4靠近时再渐渐回到完全的摆动
// 关于smoothstep函数的更多信息,请参考https://zhuanlan.zhihu.com/p/157758600
v_offset = offset - offset * smoothstep(0.4, 0.0, vsInput.attributes.texCoord_0.x);
vsOutput.positionMC.x += vsOutput.positionMC.x + v_offset;
vsOutput.positionMC.z *= 0.95;
}
此时smoothstep函数已经把旗杆一侧固定住了。
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
// 这一步是为了uv贴图的正确映射
fsInput.attributes.texCoord_0.y *= -1.0;
// (1.0 - v_offset * 3.0)表达式用来决定片元的亮度,往x轴的正方向偏移越大,这个表达式输出的值越小,则越暗
// v_offset * 3.0是为了放大偏移量从而增大明暗对比
// 贴图颜色乘以亮度系数就是最终色(这里控制亮度系数的范围在0-1之间,不去增加凸起部分的亮度会更逼真一些)
vec4 color = texture(u_texture,fsInput.attributes.texCoord_0) * min((1.0 - v_offset * 2.0),1.0);
material.diffuse=vec3(color.rgb);
}
上图可以看出,旗子凹陷部分已经有了阴影,但是此时阴影和片元的偏移程度为线性关系,阴影处的对比不够强烈,下面分享另一种阴影算法。
接下来通过实现阴影随片元偏移量的指数级增长来增强阴影部分的对比度,使效果更加逼真。
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
// 计算片元偏移量的平方用于后续的阴影计算
float offsetSquared = v_offset * v_offset;
// 获取片元偏移量的符号,用于在后续的计算中保留偏移量的正负性
float offsetSign = v_offset >= 0.0 ? 1.0 : -1.0;
fsInput.attributes.texCoord_0.y *= -1.0;
// 1.0 - offsetSquared * offsetSign * 30.0表达式用来决定片元的亮度,原理与上面的着色器一致
// 不过此时片元的亮度与偏移量为指数级增长关系,会在阴影区域获得更加大的反差,增强逼真度
vec4 color = texture(u_texture, fsInput.attributes.texCoord_0) * min(1.0 - offsetSquared * offsetSign * 30.0, 1.0);
material.diffuse=vec3(color.rgb);
}
比刚才逼真多了
const customShader = new Cesium.CustomShader({
uniforms: {
u_time: {
type: Cesium.UniformType.FLOAT,
value: 0.0
},
u_texture: {
type: Cesium.UniformType.SAMPLER_2D,
value: new Cesium.TextureUniform({
url: "../source/models/旗子/hongqi.jpg",
})
}
},
varyings: {
v_offset: Cesium.VaryingType.FLOAT
},
vertexShaderText: `
void vertexMain(VertexInput vsInput, inout czm_modelVertexOutput vsOutput) {
// 根据模型uv坐标的x和y坐标来确定摆动的频率(对应sin曲线的波频)
// 这里控制波频时,分别用到了x和y轴坐标,并让x坐标权重大于y坐标,使得摆动更加自然
// 最后乘以0.13是为了控制摆动的幅度(对应sin曲线的波高)
float offset = sin(vsInput.attributes.texCoord_0.x * 8.0 + vsInput.attributes.texCoord_0.y * 1.5 - u_time) * 0.13;
// 这是关键的一步,使用平滑阶梯函数smoothstep函数来控制摆动的范围
// smoothstep(0.4, 0.0, vsInput.attributes.texCoord_0.x)表达式在uv的x轴坐标靠近起点时返回1,到达x轴的0.4时返回0
// 再用offset减去offset乘以smoothstep函数的结果,就可以得到在x轴坐标靠近0时,offset值为0的效果
// 关于smoothstep函数的更多信息,请参考https://zhuanlan.zhihu.com/p/157758600
v_offset = offset - offset * smoothstep(0.4, 0.0, vsInput.attributes.texCoord_0.x);
// 为片元赋予新的x坐标,新的x坐标为原始x坐标加上摆动偏移量
vsOutput.positionMC.x += vsOutput.positionMC.x + v_offset;
// 因为旗子在x方向上有前后摆动,因此在视觉上z轴应当适当缩短一些
vsOutput.positionMC.z *= 0.95;
}`,
fragmentShaderText: `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
// 计算片元偏移量的平方用于后续的阴影计算
float offsetSquared = v_offset * v_offset;
// 获取片元偏移量的符号,用于在后续的计算中保留偏移量的正负性
float offsetSign = v_offset >= 0.0 ? 1.0 : -1.0;
fsInput.attributes.texCoord_0.y *= -1.0;
// 1.0 - offsetSquared * offsetSign * 30.0表达式用来决定片元的亮度,原理与上面的着色器一致
// 不过此时片元的亮度与偏移量为指数级增长关系,会在阴影区域获得更加大的反差,增强逼真度
vec4 color = texture(u_texture, fsInput.attributes.texCoord_0) * min(1.0 - offsetSquared * offsetSign * 30.0, 1.0);
material.diffuse=vec3(color.rgb);
}`
})
摆动核心
用三角函数把 UV 坐标映射成随时间变化的偏移量,再用 smoothstep 把旗杆侧固定,就能让红旗只在自由端飘动。
阴影核心
把顶点着色器算出的偏移量 v_offset 传进片元着色器,用“1 − 偏移量² × 符号 × 放大系数”做指数级压暗,褶皱阴影逼真立体。
扩展和思考
祝玩旗愉快!
npm install @wangeditor/editor
npm install @wangeditor/editor-for-vue
<template>
<div style="border: 1px solid #ccc">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editor"
:defaultConfig="toolbarConfig"
:mode="mode"
/>
<Editor
style="height: 500px; overflow-y: hidden"
v-model="html"
:defaultConfig="editorConfig"
:mode="mode"
@onCreated="onCreated"
@onChange="onChange"
/>
</div>
</template>
<script>
// import Location from "@/utils/location";
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
import { Boot, IModuleConf, DomEditor } from "@wangeditor/editor";
import { getToken } from "@/utils/auth";
import MyPainter from "./geshi";
const menu1Conf = {
key: "geshi", // 定义 menu key :要保证唯一、不重复(重要)
factory() {
return new MyPainter(); // 把 `YourMenuClass` 替换为你菜单的 class
},
};
const module = {
// JS 语法
menus: [menu1Conf],
// 其他功能,下文讲解...
};
Boot.registerModule(module);
export default {
components: { Editor, Toolbar },
props: {
relationKey: {
type: String,
default: "",
},
},
created() {
console.log(this.editorConfig.MENU_CONF.uploadImage.meta.activityKey);
},
data() {
return {
// 富文本实例
editor: null,
// 富文本正文内容
html: "",
// 编辑器模式
mode: "default", // or 'simple'
// 工具栏配置
toolbarConfig: {
//新增菜单
insertKeys: {
index: 32,
keys: ["geshi"],
},
//去掉网络上传图片和视频
excludeKeys: ["insertImage", "insertVideo"],
},
// 编辑栏配置
editorConfig: {
placeholder: "请输入相关内容......",
// 菜单配置
MENU_CONF: {
// ===================
// 上传图片配置
// ===================
uploadImage: {
// 文件名称
fieldName: "contentAttachImage",
server: Location.serverPath + "/editor-upload/upload-image",
headers: {
Authorization: "Bearer " + getToken(),
},
meta: {
activityKey: this.relationKey,
},
// 单个文件的最大体积限制,默认为 20M
maxFileSize: 20 * 1024 * 1024,
// 最多可上传几个文件,默认为 100
maxNumberOfFiles: 10,
// 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
allowedFileTypes: ["image/*"],
// 跨域是否传递 cookie ,默认为 false
withCredentials: true,
// 超时时间,默认为 10 秒
timeout: 5 * 1000,
// 自定义插入图片操作
customInsert: (res, insertFn) => {
if (res.errno == -1) {
this.$message.error("上传失败!");
return;
}
insertFn(Location.serverPath + res.data.url, "", "");
this.$message.success("上传成功!");
},
},
// =====================
// 上传视频配置
// =====================
uploadVideo: {
// 文件名称
fieldName: "contentAttachVideo",
server: Location.serverPath + "/editor-upload/upload-video",
headers: {
Authorization: "Bearer " + getToken(),
},
meta: {
activityKey: this.relationKey,
},
// 单个文件的最大体积限制,默认为 60M
maxFileSize: 60 * 1024 * 1024,
// 最多可上传几个文件,默认为 100
maxNumberOfFiles: 3,
// 选择文件时的类型限制,默认为 ['video/*'] 。如不想限制,则设置为 []
allowedFileTypes: ["video/*"],
// 跨域是否传递 cookie ,默认为 false
withCredentials: true,
// 超时时间,默认为 10 秒
timeout: 15 * 1000,
// 自定义插入图片操作
customInsert: (res, insertFn) => {
if (res.errno == -1) {
this.$message.error("上传失败!");
return;
}
insertFn(Location.serverPath + res.data.url, "", "");
this.$message.success("上传成功!");
},
},
},
},
// ===== data field end =====
};
},
methods: {
// =============== Editor 事件相关 ================
// 1. 创建 Editor 实例对象
onCreated(editor) {
this.editor = Object.seal(editor); // 一定要用 Object.seal() ,否则会报错
this.$nextTick(() => {
const toolbar = DomEditor.getToolbar(this.editor);
const curToolbarConfig = toolbar.getConfig();
console.log("【 curToolbarConfig 】-39", curToolbarConfig);
});
},
// 2. 失去焦点事件
onChange(editor) {
this.$emit("change", this.html);
},
// =============== Editor操作API相关 ==============
insertText(insertContent) {
const editor = this.editor; // 获取 editor 实例
if (editor == null) {
return;
}
// 执行Editor的API插入
editor.insertText(insertContent);
},
// =============== 组件交互相关 ==================
// closeEditorBeforeComponent() {
// this.$emit("returnEditorContent", this.html);
// },
closeContent(){
this.html=''
},
// ========== methods end ===============
},
mounted() {
// ========== mounted end ===============
},
beforeDestroy() {
const editor = this.editor;
if (editor == null) {
return;
}
editor.destroy();
console.log("销毁编辑器!");
},
};
</script>
<style lang="scss" scoped>
// 对默认的p标签进行穿透
::v-deep .editorStyle .w-e-text-container [data-slate-editor] p {
margin: 0 !important;
}
</style>
<style src="@wangeditor/editor/dist/css/style.css"></style>
自定义上传图片接口
uploadImage: {
// 文件名称
fieldName: "contentAttachImage",
// server: '/api/v1/public/uploadFile',
headers: {
Authorization: "Bearer " + getToken(),
},
meta: {
activityKey: this.relationKey,
},
// 单个文件的最大体积限制,默认为 20M
maxFileSize: 20 * 1024 * 1024,
// 最多可上传几个文件,默认为 100
maxNumberOfFiles: 10,
// 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
allowedFileTypes: ["image/*"],
// 跨域是否传递 cookie ,默认为 false
withCredentials: true,
// 超时时间,默认为 10 秒
timeout: 5 * 1000,
这里设置
customUpload: async (file, insertFn) => {
console.log(file, "file");
let formData = new FormData()
const sub = "order";
formData.append('file', file)
formData.append("sub", sub);
formData.append("type", "1");
let res = await getUploadImg(formData)
insertFn(res.data.full_path, '', '');
},
customInsert: (res, insertFn) => {
if (res.errno == -1) {
this.$message.error("上传失败!");
return;
}
// insertFn(res.data.url, "", "");
this.$message.success("上传成功!");
},
},
import {
SlateEditor,
SlateText,
SlateElement,
SlateTransforms,
DomEditor,
// Boot,
} from "@wangeditor/editor";
// Boot.registerMenu(menu1Conf);
import { Editor } from "slate";
export default class MyPainter {
constructor() {
this.title = "格式刷"; // 自定义菜单标题
// 这里是设置格式刷的样式图片跟svg都可以,但是注意要图片大小要小一点,因为要应用到鼠标手势上
this.iconSvg = ``;
this.tag = "button"; //注入的菜单类型
this.savedMarks = null; //保存的样式
this.domId = null; //这个可要可不要
this.editor = null; //编辑器示例
this.parentStyle = null; //储存父节点样式
this.mark = "";
this.marksNeedToRemove = []; // 增加 mark 的同时,需要移除哪些 mark (互斥,不能共存的)
}
clickHandler(e) {
console.log(e, "e"); //无效
}
//添加或者移除鼠标事件
addorRemove = (type) => {
const dom = document.body;
if (type === "add") {
dom.addEventListener("mousedown", this.changeMouseDown);
dom.addEventListener("mouseup", this.changeMouseup);
} else {
//赋值完需要做的清理工作
this.savedMarks = undefined;
dom.removeEventListener("mousedown", this.changeMouseDown);
dom.removeEventListener("mouseup", this.changeMouseup);
document.querySelector("#w-e-textarea-1").style.cursor = "auto";
}
};
//处理重复键名值不同的情况
handlerRepeatandNotStyle = (styles) => {
const addStyles = styles[0];
const notVal = [];
for (const style of styles) {
for (const key in style) {
const value = style[key];
if (!addStyles.hasOwnProperty(key)) {
addStyles[key] = value;
} else {
if (addStyles[key] !== value) {
notVal.push(key);
}
}
}
}
for (const key of notVal) {
delete addStyles[key];
}
return addStyles;
};
// 获取当前选中范围的父级节点
getSelectionParentEle = (type, func) => {
if (this.editor) {
const parentEntry = SlateEditor.nodes(this.editor, {
match: (node) => SlateElement.isElement(node),
});
let styles = [];
for (const [node] of parentEntry) {
styles.push(this.editor.toDOMNode(node).style); //将node对应的DOM对应的style对象加入到数组
}
styles = styles.map((item) => {
//处理不为空的style
const newItem = {};
for (const key in item) {
const val = item[key];
if (val !== "") {
newItem[key] = val;
}
}
return newItem;
});
type === "get"
? func(type, this.handlerRepeatandNotStyle(styles))
: func(type);
}
};
//获取或者设置父级样式
getorSetparentStyle = (type, style) => {
if (type === "get") {
this.parentStyle = style; //这里是个样式对象 例如{textAlign:'center'}
} else {
SlateTransforms.setNodes(
this.editor,
{ ...this.parentStyle },
{
mode: "highest", // 针对最高层级的节点
}
);
}
};
//这里是将svg转换为Base64格式
addmouseStyle = () => {
const icon = ``; // 这里是给鼠标手势添加图标
// 将字符串编码为Base64格式
const base64String = btoa(icon);
// 生成数据URI
const dataUri = `data:image/svg+xml;base64,${base64String}`;
// 将数据URI应用于鼠标图标
document.querySelector(
"#w-e-textarea-1"
).style.cursor = `url('${dataUri}'), auto`;
};
changeMouseDown = () => {}; //鼠标落下
changeMouseup = () => {
//鼠标抬起
if (this.editor) {
const editor = this.editor;
const selectTxt = editor.getSelectionText(); //获取文本是否为null
if (this.savedMarks && selectTxt) {
//先改变父节点样式
this.getSelectionParentEle("set", this.getorSetparentStyle);
// 获取所有 text node
const nodeEntries = SlateEditor.nodes(editor, {
//nodeEntries返回的是一个迭代器对象
match: (n) => SlateText.isText(n), //这里是筛选一个节点是否是 text
universal: true, //当universal为 true 时,Editor.nodes会遍历整个文档,包括根节点和所有子节点,以匹配满足条件的节点。当universal为 false 时,Editor.nodes只会在当前节点的直接子节点中进行匹配。
});
// 先清除选中节点的样式
for (const node of nodeEntries) {
const n = node[0]; //{text:xxxx}
const keys = Object.keys(n);
keys.forEach((key) => {
if (key === "text") {
// 保留 text 属性
return;
}
// 其他属性,全部清除
SlateEditor.removeMark(editor, key);
});
}
// 再赋值新样式
for (const key in this.savedMarks) {
if (Object.hasOwnProperty.call(this.savedMarks, key)) {
const value = this.savedMarks[key];
editor.addMark(key, value);
}
}
this.addorRemove("remove");
}
}
};
getValue(editor) {
// return "MyPainter"; // 标识格式刷菜单
const mark = this.mark;
console.log(mark, "mark");
const curMarks = Editor.marks(editor);
// 当 curMarks 存在时,说明用户手动设置,以 curMarks 为准
if (curMarks) {
return curMarks[mark];
} else {
const [match] = Editor.nodes(editor, {
// @ts-ignore
match: (n) => n[mark] === true,
});
return !!match;
}
}
isActive(editor, val) {
const isMark = this.getValue(editor);
return !!isMark;
// return !!DomEditor.getSelectedNodeByType(editor, "geshi");
// return false;
}
isDisabled(editor) {
//是否禁用
return false;
}
exec(editor, value) {
//当菜单点击后触发
// console.log(!this.isActive());
console.log(value, "value");
this.editor = editor;
this.domId = editor.id.split("-")[1]
? `w-e-textarea-${editor.id.split("-")[1]}`
: undefined;
if (this.isDisabled(editor)) return;
const { mark, marksNeedToRemove } = this;
if (value) {
// 已,则取消
editor.removeMark(mark);
} else {
// 没有,则执行
editor.addMark(mark, true);
this.savedMarks = SlateEditor.marks(editor); // 获取当前选中文本的样式
this.getSelectionParentEle("get", this.getorSetparentStyle); //获取父节点样式并赋值
// this.addmouseStyle(); //点击之后给鼠标添加样式
this.addorRemove("add"); //处理添加和移除事件函数
// 移除互斥、不能共存的 marks
if (marksNeedToRemove) {
marksNeedToRemove.forEach((m) => editor.removeMark(m));
}
}
if (
editor.isEmpty() ||
editor.getHtml() == "<p><br></p>" ||
editor.getSelectionText() == ""
)
return; //这里是对没有选中或者没内容做的处理
}
}
<el-form-item label="内容">
<WangEdit v-model="form.content" ref="wangEdit" @change="change"></WangEdit>
</el-form-item>
// js
const WangEdit = () => import("@/views/compoments/WangEdit.vue");
export default {
name: "Notice",
components: {
WangEdit,
},
data(){
return{
form:{
}
}
},
methods: {
change(val) {
console.log(val,'aa');
this.form.content=val
},
// 取消按钮
cancel() {
this.open = false;
this.form={};
this.$refs.wangEdit.closeContent();
},
}
转载:wangEditor5在vue中自定义菜单栏--格式刷,上传图片,视频功能_vue.js_liang04273-Vue