提交 3ad1a4f5 编写于 作者: V vben

feat(tinymce): add image upload #170

上级 18ad1bcc
......@@ -16,6 +16,7 @@
- 新增`PageWrapper`组件。并应用于示例页面
- 新增标签页折叠功能
- 兼容旧版浏览器
- tinymce 新增图片上传·
### 🐛 Bug Fixes
......@@ -24,6 +25,7 @@
- 修复表格内存溢出问题
- 修复`layout` 收缩展开功能在分割模式下失效
- 修复 modal 高度计算错误
- 修复文件上传错误
### 🎫 Chores
......
<template>
<div class="tinymce-container" :style="{ width: containerWidth }">
<div :class="prefixCls" :style="{ width: containerWidth }">
<ImgUpload
@uploading="handleImageUploading"
@done="handleDone"
v-if="showImageUpload"
v-show="editorRef"
/>
<textarea :id="tinymceId" ref="elRef" :style="{ visibility: 'hidden' }"></textarea>
</div>
</template>
......@@ -24,6 +30,8 @@
import { bindHandlers } from './helper';
import lineHeight from './lineHeight';
import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
import ImgUpload from './ImgUpload.vue';
import { useDesign } from '/@/hooks/web/useDesign';
const CDN_URL = 'https://cdn.bootcdn.net/ajax/libs/tinymce/5.5.1';
......@@ -33,12 +41,15 @@
name: 'Tinymce',
inheritAttrs: false,
props: basicProps,
components: { ImgUpload },
emits: ['change', 'update:modelValue'],
setup(props, { emit, attrs }) {
const editorRef = ref<any>(null);
const tinymceId = ref<string>(snowUuid('tiny-vue'));
const elRef = ref<Nullable<HTMLElement>>(null);
const { prefixCls } = useDesign('tinymce-container');
const tinymceContent = computed(() => {
return props.modelValue;
});
......@@ -140,7 +151,7 @@
bindHandlers(e, attrs, unref(editorRef));
}
function setValue(editor: any, val: string, prevVal: string) {
function setValue(editor: Recordable, val: string, prevVal?: string) {
if (
editor &&
typeof val === 'string' &&
......@@ -179,45 +190,54 @@
});
}
function handleImageUploading(name: string) {
const editor = unref(editorRef);
if (!editor) return;
const content = editor?.getContent() ?? '';
setValue(editor, `${content}\n${getImgName(name)}`);
}
function handleDone(name: string, url: string) {
const editor = unref(editorRef);
if (!editor) return;
const content = editor?.getContent() ?? '';
const val = content?.replace(getImgName(name), `<img src="${url}"/>`) ?? '';
setValue(editor, val);
}
function getImgName(name: string) {
return `[uploading:${name}]`;
}
return {
prefixCls,
containerWidth,
initOptions,
tinymceContent,
tinymceScriptSrc,
elRef,
tinymceId,
handleImageUploading,
handleDone,
editorRef,
};
},
});
</script>
<style lang="less" scoped>
.tinymce-container {
position: relative;
line-height: normal;
<style lang="less" scoped></style>
.mce-fullscreen {
z-index: 10000;
}
}
<style lang="less">
@prefix-cls: ~'@{namespace}-tinymce-container';
.editor-custom-btn-container {
position: absolute;
top: 6px;
right: 6px;
.@{prefix-cls} {
position: relative;
line-height: normal;
&.fullscreen {
position: fixed;
z-index: 10000;
textarea {
z-index: -1;
visibility: hidden;
}
}
.editor-upload-btn {
display: inline-block;
}
textarea {
z-index: -1;
visibility: hidden;
}
</style>
<template>
<div :class="prefixCls">
<Upload
name="file"
multiple
@change="handleChange"
:action="uploadUrl"
:showUploadList="false"
accept=".jpg,.jpeg,.gif,.png,.webp"
>
<a-button type="primary">{{ t('component.upload.imgUpload') }}</a-button>
</Upload>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { Upload } from 'ant-design-vue';
import { InboxOutlined } from '@ant-design/icons-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useGlobSetting } from '/@/hooks/setting';
import { useI18n } from '/@/hooks/web/useI18n';
export default defineComponent({
name: 'TinymceImageUpload',
components: { Upload, InboxOutlined },
emits: ['uploading', 'done', 'error'],
setup(_, { emit }) {
let uploading = false;
const { uploadUrl } = useGlobSetting();
const { t } = useI18n();
const { prefixCls } = useDesign('tinymce-img-upload');
function handleChange(info: Recordable) {
const file = info.file;
const status = file?.status;
const url = file?.response?.url;
const name = file?.name;
if (status === 'uploading') {
if (!uploading) {
emit('uploading', name);
uploading = true;
}
} else if (status === 'done') {
emit('done', name, url);
uploading = false;
} else if (status === 'error') {
emit('error');
uploading = false;
}
}
return {
prefixCls,
handleChange,
uploadUrl,
t,
};
},
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-tinymce-img-upload';
.@{prefix-cls} {
position: absolute;
top: 4px;
right: 10px;
z-index: 20;
}
</style>
import { PropType } from 'vue';
import { propTypes } from '/@/utils/propTypes';
export const basicProps = {
options: {
type: Object as PropType<any>,
default: {},
},
value: {
type: String as PropType<string>,
// default: ''
},
modelValue: {
type: String as PropType<string>,
// default: ''
},
value: propTypes.string,
modelValue: propTypes.string,
// 高度
height: {
type: [Number, String] as PropType<string | number>,
......@@ -26,4 +21,5 @@ export const basicProps = {
required: false,
default: 'auto',
},
showImageUpload: propTypes.bool.def(true),
};
......@@ -9,8 +9,13 @@
<template #overlay>
<Menu @click="handleMenuClick">
<MenuItem key="doc" :text="t('layout.header.dropdownItemDoc')" icon="gg:loadbar-doc" />
<MenuDivider v-if="getShowDoc" />
<MenuItem
key="doc"
:text="t('layout.header.dropdownItemDoc')"
icon="gg:loadbar-doc"
v-if="getShowDoc"
/>
<MenuDivider />
<MenuItem
key="loginOut"
:text="t('layout.header.dropdownItemLoginOut')"
......
......@@ -84,7 +84,6 @@
} from './components';
import { useAppInject } from '/@/hooks/web/useAppInject';
import { useDesign } from '/@/hooks/web/useDesign';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
export default defineComponent({
name: 'LayoutHeader',
......
export default {
save: 'Save',
upload: 'Upload',
imgUpload: 'ImageUpload',
uploaded: 'Uploaded',
operating: 'Operating',
......
export default {
save: '保存',
upload: '上传',
imgUpload: '图片上传',
uploaded: '已上传',
operating: '操作',
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册