feat(tinymce): add image upload #170
This commit is contained in:
parent
18ad1bcc6e
commit
3ad1a4f5a6
|
|
@ -16,6 +16,7 @@
|
||||||
- 新增`PageWrapper`组件。并应用于示例页面
|
- 新增`PageWrapper`组件。并应用于示例页面
|
||||||
- 新增标签页折叠功能
|
- 新增标签页折叠功能
|
||||||
- 兼容旧版浏览器
|
- 兼容旧版浏览器
|
||||||
|
- tinymce 新增图片上传·
|
||||||
|
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
|
@ -24,6 +25,7 @@
|
||||||
- 修复表格内存溢出问题
|
- 修复表格内存溢出问题
|
||||||
- 修复`layout` 收缩展开功能在分割模式下失效
|
- 修复`layout` 收缩展开功能在分割模式下失效
|
||||||
- 修复 modal 高度计算错误
|
- 修复 modal 高度计算错误
|
||||||
|
- 修复文件上传错误
|
||||||
|
|
||||||
### 🎫 Chores
|
### 🎫 Chores
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
<template>
|
<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>
|
<textarea :id="tinymceId" ref="elRef" :style="{ visibility: 'hidden' }"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -24,6 +30,8 @@
|
||||||
import { bindHandlers } from './helper';
|
import { bindHandlers } from './helper';
|
||||||
import lineHeight from './lineHeight';
|
import lineHeight from './lineHeight';
|
||||||
import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
|
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';
|
const CDN_URL = 'https://cdn.bootcdn.net/ajax/libs/tinymce/5.5.1';
|
||||||
|
|
||||||
|
|
@ -33,12 +41,15 @@
|
||||||
name: 'Tinymce',
|
name: 'Tinymce',
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: basicProps,
|
props: basicProps,
|
||||||
|
components: { ImgUpload },
|
||||||
emits: ['change', 'update:modelValue'],
|
emits: ['change', 'update:modelValue'],
|
||||||
setup(props, { emit, attrs }) {
|
setup(props, { emit, attrs }) {
|
||||||
const editorRef = ref<any>(null);
|
const editorRef = ref<any>(null);
|
||||||
const tinymceId = ref<string>(snowUuid('tiny-vue'));
|
const tinymceId = ref<string>(snowUuid('tiny-vue'));
|
||||||
const elRef = ref<Nullable<HTMLElement>>(null);
|
const elRef = ref<Nullable<HTMLElement>>(null);
|
||||||
|
|
||||||
|
const { prefixCls } = useDesign('tinymce-container');
|
||||||
|
|
||||||
const tinymceContent = computed(() => {
|
const tinymceContent = computed(() => {
|
||||||
return props.modelValue;
|
return props.modelValue;
|
||||||
});
|
});
|
||||||
|
|
@ -140,7 +151,7 @@
|
||||||
bindHandlers(e, attrs, unref(editorRef));
|
bindHandlers(e, attrs, unref(editorRef));
|
||||||
}
|
}
|
||||||
|
|
||||||
function setValue(editor: any, val: string, prevVal: string) {
|
function setValue(editor: Recordable, val: string, prevVal?: string) {
|
||||||
if (
|
if (
|
||||||
editor &&
|
editor &&
|
||||||
typeof val === 'string' &&
|
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 {
|
return {
|
||||||
|
prefixCls,
|
||||||
containerWidth,
|
containerWidth,
|
||||||
initOptions,
|
initOptions,
|
||||||
tinymceContent,
|
tinymceContent,
|
||||||
tinymceScriptSrc,
|
tinymceScriptSrc,
|
||||||
elRef,
|
elRef,
|
||||||
tinymceId,
|
tinymceId,
|
||||||
|
handleImageUploading,
|
||||||
|
handleDone,
|
||||||
|
editorRef,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped></style>
|
||||||
.tinymce-container {
|
|
||||||
|
<style lang="less">
|
||||||
|
@prefix-cls: ~'@{namespace}-tinymce-container';
|
||||||
|
|
||||||
|
.@{prefix-cls} {
|
||||||
position: relative;
|
position: relative;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
|
|
||||||
.mce-fullscreen {
|
|
||||||
z-index: 10000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-custom-btn-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 6px;
|
|
||||||
right: 6px;
|
|
||||||
|
|
||||||
&.fullscreen {
|
|
||||||
position: fixed;
|
|
||||||
z-index: 10000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-upload-btn {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
<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>
|
||||||
|
|
@ -1,18 +1,13 @@
|
||||||
import { PropType } from 'vue';
|
import { PropType } from 'vue';
|
||||||
|
import { propTypes } from '/@/utils/propTypes';
|
||||||
|
|
||||||
export const basicProps = {
|
export const basicProps = {
|
||||||
options: {
|
options: {
|
||||||
type: Object as PropType<any>,
|
type: Object as PropType<any>,
|
||||||
default: {},
|
default: {},
|
||||||
},
|
},
|
||||||
value: {
|
value: propTypes.string,
|
||||||
type: String as PropType<string>,
|
modelValue: propTypes.string,
|
||||||
// default: ''
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: String as PropType<string>,
|
|
||||||
// default: ''
|
|
||||||
},
|
|
||||||
// 高度
|
// 高度
|
||||||
height: {
|
height: {
|
||||||
type: [Number, String] as PropType<string | number>,
|
type: [Number, String] as PropType<string | number>,
|
||||||
|
|
@ -26,4 +21,5 @@ export const basicProps = {
|
||||||
required: false,
|
required: false,
|
||||||
default: 'auto',
|
default: 'auto',
|
||||||
},
|
},
|
||||||
|
showImageUpload: propTypes.bool.def(true),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,13 @@
|
||||||
|
|
||||||
<template #overlay>
|
<template #overlay>
|
||||||
<Menu @click="handleMenuClick">
|
<Menu @click="handleMenuClick">
|
||||||
<MenuItem key="doc" :text="t('layout.header.dropdownItemDoc')" icon="gg:loadbar-doc" />
|
<MenuItem
|
||||||
<MenuDivider v-if="getShowDoc" />
|
key="doc"
|
||||||
|
:text="t('layout.header.dropdownItemDoc')"
|
||||||
|
icon="gg:loadbar-doc"
|
||||||
|
v-if="getShowDoc"
|
||||||
|
/>
|
||||||
|
<MenuDivider />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key="loginOut"
|
key="loginOut"
|
||||||
:text="t('layout.header.dropdownItemLoginOut')"
|
:text="t('layout.header.dropdownItemLoginOut')"
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,6 @@
|
||||||
} from './components';
|
} from './components';
|
||||||
import { useAppInject } from '/@/hooks/web/useAppInject';
|
import { useAppInject } from '/@/hooks/web/useAppInject';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '/@/hooks/web/useDesign';
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'LayoutHeader',
|
name: 'LayoutHeader',
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
export default {
|
export default {
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
upload: 'Upload',
|
upload: 'Upload',
|
||||||
|
imgUpload: 'ImageUpload',
|
||||||
uploaded: 'Uploaded',
|
uploaded: 'Uploaded',
|
||||||
|
|
||||||
operating: 'Operating',
|
operating: 'Operating',
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
export default {
|
export default {
|
||||||
save: '保存',
|
save: '保存',
|
||||||
upload: '上传',
|
upload: '上传',
|
||||||
|
imgUpload: '图片上传',
|
||||||
uploaded: '已上传',
|
uploaded: '已上传',
|
||||||
|
|
||||||
operating: '操作',
|
operating: '操作',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue