物业代码生成

This commit is contained in:
2025-06-18 11:03:42 +08:00
commit 1262d4c745
1881 changed files with 249599 additions and 0 deletions

View File

@@ -0,0 +1 @@
export { default as Tinymce } from './src/editor.vue';

View File

@@ -0,0 +1,222 @@
<script setup lang="ts">
import type { IPropTypes } from '@tinymce/tinymce-vue/lib/cjs/main/ts/components/EditorPropTypes';
import type { Editor as EditorType } from 'tinymce/tinymce';
import type { AxiosProgressEvent, UploadResult } from '#/api';
import { computed, nextTick, ref, shallowRef, useAttrs, watch } from 'vue';
import { preferences, usePreferences } from '@vben/preferences';
import Editor from '@tinymce/tinymce-vue';
import { Spin } from 'ant-design-vue';
import { camelCase } from 'lodash-es';
import { uploadApi } from '#/api';
import {
plugins as defaultPlugins,
toolbar as defaultToolbar,
} from '#/components/tinymce/src/tinymce';
type InitOptions = IPropTypes['init'];
interface Props {
height?: number | string;
options?: Partial<InitOptions>;
plugins?: string;
toolbar?: string;
disabled?: boolean;
}
defineOptions({
name: 'Tinymce',
inheritAttrs: false,
});
const props = withDefaults(defineProps<Props>(), {
height: 400,
options: () => ({}),
plugins: defaultPlugins,
toolbar: defaultToolbar,
disabled: false,
});
const emit = defineEmits<{
mounted: [];
}>();
/**
* https://www.jianshu.com/p/59a9c3802443
* 使用自托管方案本地代替cdn 没有key的限制
* 注意publicPath要以/结尾
*/
const tinymceScriptSrc = `${import.meta.env.VITE_BASE}tinymce/tinymce.min.js`;
const content = defineModel<string>('modelValue', {
default: '',
});
const editorRef = shallowRef<EditorType | null>(null);
const { isDark, locale } = usePreferences();
const skinName = computed(() => {
return isDark.value ? 'oxide-dark' : 'oxide';
});
const contentCss = computed(() => {
return isDark.value ? 'dark' : 'default';
});
/**
* tinymce支持 en zh_CN
*/
const langName = computed(() => {
const lang = preferences.app.locale.replace('-', '_');
if (lang.includes('en_US')) {
return 'en';
}
return 'zh_CN';
});
/**
* 通过v-if来挂载/卸载组件来完成主题切换切换
* 语言切换也需要监听 不监听在切换时候会显示原始<textarea>样式
*/
const init = ref(true);
watch([isDark, locale], async () => {
if (!editorRef.value) {
return;
}
// 相当于手动unmounted清理 非常重要
editorRef.value.destroy();
init.value = false;
// 放在下一次tick来切换
// 需要先加载组件 也就是v-if为true 然后需要拿到editorRef 必须放在setTimeout(相当于onMounted)
await nextTick();
init.value = true;
});
// 加载完毕前显示spin
const loading = ref(true);
const initOptions = computed((): InitOptions => {
const { height, options, plugins, toolbar } = props;
return {
auto_focus: true,
branding: false, // 显示右下角的'使用 TinyMCE 构建'
content_css: contentCss.value,
content_style:
'body { font-family:Helvetica,Arial,sans-serif; font-size:16px }',
contextmenu: 'link image table',
default_link_target: '_blank',
height,
image_advtab: true, // 图片高级选项
image_caption: true,
importcss_append: true,
language: langName.value,
link_title: false,
menubar: 'file edit view insert format tools table help',
noneditable_class: 'mceNonEditable',
/**
* 允许粘贴图片 默认base64格式
* images_upload_handler启用时为上传
*/
paste_data_images: true,
images_file_types: 'jpeg,jpg,png,gif,bmp,webp',
plugins,
quickbars_selection_toolbar:
'bold italic | quicklink h2 h3 blockquote quickimage quicktable',
skin: skinName.value,
toolbar,
toolbar_mode: 'sliding',
...options,
/**
* 覆盖默认的base64行为
* @param blobInfo
* 大坑 不要调用这两个函数 success failure:
* 使用resolve/reject代替
* (PS: 新版已经没有success failure)
*/
images_upload_handler: (blobInfo, progress) => {
return new Promise((resolve, reject) => {
const file = blobInfo.blob();
// const filename = blobInfo.filename();
// 进度条事件
const progressEvent: AxiosProgressEvent = (e) => {
const percent = Math.trunc((e.loaded / e.total!) * 100);
progress(percent);
};
uploadApi(file, { onUploadProgress: progressEvent })
.then((response) => {
const { url } = response as unknown as UploadResult;
console.log('tinymce上传图片:', url);
resolve(url);
})
.catch((error) => {
console.error('tinymce上传图片失败:', error);
// eslint-disable-next-line prefer-promise-reject-errors
reject({ message: error.message, remove: true });
});
});
},
setup: (editor) => {
editorRef.value = editor;
editor.on('init', () => {
emit('mounted');
loading.value = false;
});
},
};
});
const attrs = useAttrs();
/**
* 获取透传的事件 通过v-on绑定
* 可绑定的事件 https://www.tiny.cloud/docs/tinymce/latest/vue-ref/#event-binding
*/
const events = computed(() => {
const onEvents: Record<string, any> = {};
for (const key in attrs) {
if (key.startsWith('on')) {
const eventKey = camelCase(key.split('on')[1]!);
onEvents[eventKey] = attrs[key];
}
}
return onEvents;
});
</script>
<template>
<div class="app-tinymce">
<Spin :spinning="loading">
<Editor
v-if="init"
v-model="content"
:init="initOptions"
:tinymce-script-src="tinymceScriptSrc"
:disabled="disabled"
license-key="gpl"
v-on="events"
/>
</Spin>
</div>
</template>
<style lang="scss">
/***
由于modal/drawer的zIndex升级后为2000
这里会造成遮挡 修改为更高的zIndex
*/
.tox.tox-silver-sink.tox-tinymce-aux {
/** 该样式默认为1300的zIndex */
z-index: 2025;
}
.app-tinymce {
/**
隐藏右上角upgrade按钮
*/
.tox-promotion {
display: none;
}
}
</style>

View File

@@ -0,0 +1,11 @@
// Any plugins you want to setting has to be imported
// Detail plugins list see https://www.tinymce.com/docs/plugins/
// Custom builds see https://www.tinymce.com/download/custom-builds/
// colorpicker/contextmenu/textcolor plugin is now built in to the core editor, please remove it from your editor configuration
// quickbars 快捷栏
export const plugins =
'preview importcss searchreplace autolink autosave save directionality code visualblocks visualchars fullscreen image link media codesample table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount help charmap emoticons accordion';
export const toolbar =
'undo redo | accordion accordionremove | blocks fontfamily fontsize | bold italic underline strikethrough | align numlist bullist | link image | table media | lineheight outdent indent| forecolor backcolor removeformat | charmap emoticons | code fullscreen preview | save print | pagebreak anchor codesample | ltr rtl';