Vue3使用tinymce的配置和坑
Tinymce
·
一、安装依赖
-
- 下载安装依赖
yarn add tinymce -S;
yarn add @tinymce/tinymce-vue -S;
-
- 下载中文语言包和静态文件
- 2.1 这一步网上都有,直接去官网下载语言包,将文件放在public/tinymce/langs中,这样打包后静态文件会被复制到打包后的目录中
- 2.2 还可以添加主题UI等样式,public/tinymce/skins,可以在github找一些相关项目复制下来,可选项
二、 封装组件
-
- tinymce 引入功能
// component/Editor/js/importTinymce.js // 引入node_modules里的tinymce相关文件文件 // eslint-disable-next-line no-unused-vars import 'tinymce/themes/silver' // 编辑器主题,不引入则报错 import 'tinymce/icons/default' // 引入编辑器图标icon,不引入则不显示对应图标 // import 'tinymce/skins/ui/oxide/content.css' // import 'tinymce/skins/content/default/content.css' // // 引入编辑器插件(基本免费插件都在这儿了) import 'tinymce/plugins/advlist' // 高级列表 import 'tinymce/plugins/anchor' // 锚点 import 'tinymce/plugins/autolink' // 自动链接 import 'tinymce/plugins/autoresize' // 编辑器高度自适应,注:plugins里引入此插件时,Init里设置的height将失效 import 'tinymce/plugins/autosave' // 自动存稿 import 'tinymce/plugins/charmap' // 特殊字符 import 'tinymce/plugins/code' // 编辑源码 import 'tinymce/plugins/codesample' // 代码示例 import 'tinymce/plugins/directionality' // 文字方向 // import 'tinymce/plugins/emoticons' // 表情 import 'tinymce/plugins/fullpage/index' // 文档属性 import 'tinymce/plugins/fullscreen' // 全屏 import 'tinymce/plugins/help' // 帮助 import 'tinymce/plugins/hr' // 水平分割线 import 'tinymce/plugins/image' // 插入编辑图片 import 'tinymce/plugins/importcss' // 引入css import 'tinymce/plugins/insertdatetime' // 插入日期时间 import 'tinymce/plugins/link' // 超链接 import 'tinymce/plugins/lists' // 列表插件 import 'tinymce/plugins/media' // 插入编辑媒体 import 'tinymce/plugins/nonbreaking' // 插入不间断空格 import 'tinymce/plugins/pagebreak' // 插入分页符 import 'tinymce/plugins/paste' // 粘贴插件 import 'tinymce/plugins/preview'// 预览 // import 'tinymce/plugins/print'// 打印 import 'tinymce/plugins/quickbars' // 快速工具栏 import 'tinymce/plugins/save' // 保存 import 'tinymce/plugins/searchreplace' // 查找替换 // import 'tinymce/plugins/spellchecker' //拼写检查,暂未加入汉化,不建议使用 // import 'tinymce/plugins/tabfocus' // 切入切出,按tab键切出编辑器,切入页面其他输入框中 import 'tinymce/plugins/table' // 表格 import 'tinymce/plugins/template' // 内容模板 import 'tinymce/plugins/textcolor' // 文字颜色 import 'tinymce/plugins/textpattern' // 快速排版 import 'tinymce/plugins/toc' // 目录生成器 import 'tinymce/plugins/visualblocks' // 显示元素范围 import 'tinymce/plugins/visualchars' // 显示不可见字符 import 'tinymce/plugins/wordcount' // 字数统计
-
- tinymce 配置项
// component/Editor/js/config.js // 导入插件 const buttonPlugins = 'emoticons preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media code codesample table charmap hr pagebreak nonbreaking anchor insertdatetime advlist lists wordcount textpattern autosave paste'; // 导入工具栏 const toolbar = 'fullscreen undo redo restoredraft | cut copy paste pastetext | forecolor backcolor bold italic underline strikethrough link anchor \ table image | alignleft aligncenter alignright alignjustify outdent indent | \ styleselect formatselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat | media charmap emoticons hr pagebreak insertdatetime print preview | code selectall searchreplace visualblocks | indent2em lineheight formatpainter axupimgs'; // 初始化配置 export const init = { selector: '#js_tinymce_editor', height: 550, width: '100%', cleanup: true, language_url: './tinymce/langs/zh-Hans.js', // 引入语言包文件 language: 'zh-Hans', // 语言类型 content_css: true, skin_url: './tinymce/skins/ui/oxide', // 皮肤:浅色 // skin_url: '/tinymce/skins/ui/oxide-dark',//皮肤:暗色 plugins: buttonPlugins, // 插件配置 toolbar: toolbar, // 工具栏配置,设为false则隐藏 body_class: 'panel-body', object_resizing: false, // 图片和表格是否开启在编辑器内部缩放 // menubar: false, // 菜单栏配置,设为false则隐藏,不配置则默认显示全部菜单,也可自定义配置--查看 http://tinymce.ax-z.cn/configure/editor-appearance.php --搜索“自定义菜单” //emoticons_database_url: './tinymce/emoticons/js/emojis.js', fontsize_formats: '12px 14px 16px 18px 20px 22px 24px 28px 32px 36px 48px 56px 72px', // 字体大小 nonbreaking_force_tab: true, // 此选项允许您在用户按下键盘tab键时强制TinyMCE插入三个实体 font_formats: '微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;', // 字体样式 微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif, 宋体=simsun,serif,仿宋体=FangSong,黑体=SimHei,Arial=arial, lineheight_formats: '0.5 0.8 1 1.2 1.5 1.75 2 2.5 3 4 5', // 行高配置,也可配置成"12px 14px 16px 20px"这种形式 branding: false, // tiny技术支持信息是否显示 resize: false, // 编辑器宽高是否可变,false-否,true-高可变,'both'-宽高均可,注意引号 // statusbar: false, //最下方的元素路径和字数统计那一栏是否显示 elementpath: false, // 元素路径是否显示 content_style: 'p {margin-block-start: 0; margin-block-end: 0; color: #606D81; font-size: 14px;}; table { border: 1px}', // 直接自定义可编辑区域的css样式 // content_css: './tinymce/index.css', // 以css文件方式自定义可编辑区域的css样式,css文件需自己创建并引入 paste_data_images: true, // 图片是否可粘贴 images_upload_handler: (blobInfo, success, failure) => { // 需要在setup里面重新写,传入 url }, imagetools_toolbar: 'editimage', // 图片控制的工具栏 }
-
- tinymce 封装组件
// components/Editor/index <template> <div class="tinymce-box"> <Editor v-model="editorContent" :init="initData" :disabled="isDisabled" ref="editorRef" id="js_tinymce_editor" ></Editor> </div> </template> <script setup lang="ts"> import Editor from '@tinymce/tinymce-vue'; import tinymce from 'tinymce/tinymce'; // tinymce默认hidden,不引入则不显示编辑器 import { formatTime } from '@/utils/'; import { uploadImage } from '@/api/tinymce'; const props = defineProps({ modelValue: { type: String, default: '', }, }); // 导入配置文件 import './js/importTinymce'; import { init } from './js/config'; import axios from 'axios'; onMounted(() => { // 配置信息 tinymce.init({}); }); const isDisabled = ref(true); // 初始化配置 const initData = ref( Object.assign({}, init, { init_instance_callback: (editor: any) => { // 初始化编辑器实例时要执行的函数 isDisabled.value = false; }, // 图片上传 images_upload_handler: async (blobInfo: any, success: any, failure: any) => { var form = new FormData(); form.append('file', blobInfo.blob()); const config = { headers: { 'Content-Type': 'multipart/form-data', }, }; axios .post('/my-api/vueadmin/image/upload', form, config) .then((res) => { const data = res.data.data; if (res.data.code === '00000') { success(data.imgUrl); } else { failure('上传失败!'); } }) .catch((err) => { failure('上传失败!'); }); }, }) ); const editorContent = ref(props.modelValue); const emit = defineEmits(['update:modelValue']); // 实时监听内容变化,并同步到父组件 watch( () => editorContent.value, (n) => { debounce(() => { emit('update:modelValue', editorContent.value); }); } ); const timeout = ref(); // 防抖 const debounce = (fn: any, wait = 400) => { if (timeout.value) { clearTimeout(timeout.value); fn(); } timeout.value = setTimeout(fn, wait); }; </script> <style lang="scss" scoped> .tinymce-box { width: 100%; } </style>
三、引用组件
-
- 封装好Tinymce组件后,可以开始应用
注意:如果Tinymce引入的页面是弹窗之类的,存在其自身弹窗层级问题,导致其自身层级低于项目弹窗的层级,这里可以通过加添或修改public/tinymce/skins自定义的样式来覆盖
// views/page/edit/index.js <!-- @format --> <template> <el-card> <el-form ref="formDataRef" :model="formData" :rules="rules" label-width="120px" class="demo-formData" status-icon > <el-form-item label="短信内容" prop="region" style="width: 100%"> <Editor v-if="editDataLoading" v-model="formData.textarea" style="width: 100%" ></Editor> </el-form-item> </el-form> <div class="btn-box"> <el-button type="primary" @click="submitForm(formDataRef)"> 保存 </el-button> <el-button @click="resetForm(formDataRef)">重置</el-button> </div> </el-card> </template> <script setup lang="ts"> import Editor from '@/components/Editor/index.vue'; import type { FormInstance, FormRules } from 'element-plus'; import { IFormData } from '@/api/sms/types'; import { getSmsInfo, saveSms } from '@/api/sms/'; const propsData = defineProps({ drawerData: { type: Object, default: () => { id: ''; }, }, }); const emit = defineEmits(['closeDrawerHandle']); const editDataLoading = ref(false); const initHandle = async () => { if (propsData.drawerData?.id) { const { code, data } = await getSmsInfo(propsData.drawerData.id); if (code === '00000') { console.log(data); formData.textarea = data.textarea; formData.id = data.id; } } nextTick(() => { editDataLoading.value = true; }); }; initHandle(); const formDataRef = ref<FormInstance>(); const formData = reactive<IFormData>({ textarea: '', id: '', }); const rules = reactive<FormRules<IFormData>>({ textarea: [ { required: true, message: '请输入富文本', trigger: 'blur', }, ], }); const submitForm = (formEl: FormInstance | undefined) => { if (!formEl) return; formEl.validate(async (valid, fields) => { if (valid) { console.log(formData); const { code, data } = await saveSms(formData); if (code === '00000') { ElMessage.success(data.msg || '保存成功!'); emit('closeDrawerHandle', true); } } else { console.log(fields); ElMessage.error('保存失败!'); } }); }; const resetForm = (formEl: FormInstance | undefined) => { if (!formEl) return; formEl.resetFields(); }; </script> <style scoped></style>
- 封装好Tinymce组件后,可以开始应用
本地虚拟机wamp存储富文本
-
- 在数据库中创建数据表
DROP TABLE IF EXISTS `tinymce_content`; CREATE TABLE `tinymce_content` ( `id` bigint NOT NULL AUTO_INCREMENT, `textarea` longtext NOT NULL DEFAULT '' COMMENT '富文本内容', `create_time` date NOT NULL DEFAULT '' COMMENT '添加时间', PRIMARY KEY (`id`) USING BTREE, ) ENGINE = InnoDB AUTO_INCREMENT = 0 CHARACTER SET = utf8;
-
- 在数据库中创建图片数据表
DROP TABLE IF EXISTS `tinymce_img`; CREATE TABLE `tinymce_img` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL DEFAULT '' COMMENT '图片名称', `url` varchar(100) NOT NULL DEFAULT '' COMMENT '图片地址', `type` varchar(50) NOT NULL DEFAULT '' COMMENT '图片类型', `blob` mediumblob DEFAULT NULL COMMENT '图片内容(不建议将图片存入数据库)', `create_time` date NOT NULL DEFAULT '' COMMENT '添加时间', PRIMARY KEY (`id`) USING BTREE, ) ENGINE = InnoDB AUTO_INCREMENT = 0 CHARACTER SET = utf8;
-
- 创建好这两张表后,在富文本编辑器中便可以将上传、截图、图片链接源存在项目中,并将图片链接存在tinymce_img中,整个内容存在tinymce_content中
-
- 后端使用PHP框架Yii2,所以图片存在根目录的/web/static/静态目录中,访问地址:http://myproject.com/static/1705046024_finmart.png,主域名是通过wamp配置的代理host访问
-
- PHP存储代码
// /controllers/TinymceImgContent.php
<?php
namespace app\controllers;
use Yii;
use app\models\TinymceImg; // 链接tinymce_img数据库的文件,里面也继承了一些封装的基类,如getDbAll()、create()
use app\controllers\BaseController; //封装一些基类,如response
class ImageController extends BaseController
{
public function actionIndex()
{
return $this->render('index');
}
public function actionUpload()
{
$model = new Images();
// $_FILES 获取文件信息
$fileData = $_FILES['file'];
// 上传文件的临时路径
$tempFile = $fileData['tmp_name'];
// 上传文件的类型
$fileType = $fileData['type'];
// 尺寸大小
$size = $fileData['size'];
// 名称
$name = time() . '_' . $fileData["name"];
// dd($fileData);
// 是否上传的文件
if (is_uploaded_file($tempFile)) {
// 从临时文件中读取内容
$img = file_get_contents($tempFile);
// Yii::$app->getBasePath() 项目的根目录,设置绝对访问地址
$saveLocalUrl = Yii::$app->getBasePath() . "/web/static/" . $name;
$imgLink = 'http://' . $_SERVER['HTTP_HOST'] . "/static/$name";
// move_uploaded_file 将文件存在本地web/static中
move_uploaded_file($tempFile, $saveLocalUrl);
$data = [
// "img" => $img, // 存储在数据库,不建议
"date" => date("Y-m-d H:i:s"),
"name" => $name,
"url" => $imgLink,
"type" => $fileType,
];
$row = $model->create($data);
if (isset($row)) {
$res = ['imgUrl' => $imgLink];
return $this->response($res);
} else {
$this->code = "00001";
$this->msg = "图片保存失败";
return $this->response();
}
}
}
}
更多推荐
已为社区贡献2条内容
所有评论(0)