一、安装依赖
yarn add tinymce -S;
yarn add @tinymce/tinymce-vue -S;
-
- 下载中文语言包和静态文件
- 2.1 这一步网上都有,直接去官网下载语言包,将文件放在public/tinymce/langs中,这样打包后静态文件会被复制到打包后的目录中
- 2.2 还可以添加主题UI等样式,public/tinymce/skins,可以在github找一些相关项目复制下来,可选项
二、 封装组件
-
- tinymce 引入功能
import 'tinymce/themes/silver'
import 'tinymce/icons/default'
import 'tinymce/plugins/advlist'
import 'tinymce/plugins/anchor'
import 'tinymce/plugins/autolink'
import 'tinymce/plugins/autoresize'
import 'tinymce/plugins/autosave'
import 'tinymce/plugins/charmap'
import 'tinymce/plugins/code'
import 'tinymce/plugins/codesample'
import 'tinymce/plugins/directionality'
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'
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/quickbars'
import 'tinymce/plugins/save'
import 'tinymce/plugins/searchreplace'
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 配置项
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', plugins: buttonPlugins, toolbar: toolbar, body_class: 'panel-body',object_resizing: false, fontsize_formats: '12px 14px 16px 18px 20px 22px 24px 28px 32px 36px 48px 56px 72px', nonbreaking_force_tab: true, 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;', lineheight_formats: '0.5 0.8 1 1.2 1.5 1.75 2 2.5 3 4 5', branding: false, resize: false, elementpath: false, content_style: 'p {margin-block-start: 0; margin-block-end: 0; color: #606D81; font-size: 14px;}; table { border: 1px}', paste_data_images: true, images_upload_handler: (blobInfo, success, failure) => {},imagetools_toolbar: 'editimage',
}
-
- tinymce 封装组件
<template><div class="tinymce-box"><Editorv-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'; 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自定义的样式来覆盖
<!-- @format --><template><el-card><el-formref="formDataRef":model="formData":rules="rules"label-width="120px"class="demo-formData"status-icon><el-form-item label="短信内容" prop="region" style="width: 100%"><Editorv-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>
本地虚拟机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存储代码
<?phpnamespace app\controllers;use Yii;
use app\models\TinymceImg;
use app\controllers\BaseController; class ImageController extends BaseController
{public function actionIndex(){return $this->render('index');}public function actionUpload(){$model = new Images();$fileData = $_FILES['file'];$tempFile = $fileData['tmp_name'];$fileType = $fileData['type'];$size = $fileData['size'];$name = time() . '_' . $fileData["name"];if (is_uploaded_file($tempFile)) {$img = file_get_contents($tempFile);$saveLocalUrl = Yii::$app->getBasePath() . "/web/static/" . $name;$imgLink = 'http://' . $_SERVER['HTTP_HOST'] . "/static/$name";move_uploaded_file($tempFile, $saveLocalUrl);$data = ["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();}}}}