feat: 支持图片上传功能,以及自定义上传接口和字段配置
This commit is contained in:
parent
9c7fc8e8e6
commit
a8c8a72bd6
@ -4,7 +4,7 @@
|
|||||||
<MdEditor ref="editorRef" v-model="editorState.text" :id="editorState.id" :theme="editorState.theme"
|
<MdEditor ref="editorRef" v-model="editorState.text" :id="editorState.id" :theme="editorState.theme"
|
||||||
:previewTheme="editorState.previewTheme" :style="{ height: editorState.height }"
|
:previewTheme="editorState.previewTheme" :style="{ height: editorState.height }"
|
||||||
:toolbars="editorState.toolbars" :toolbarsExclude="editorState.toolbarsExclude" @onSave="onSave"
|
:toolbars="editorState.toolbars" :toolbarsExclude="editorState.toolbarsExclude" @onSave="onSave"
|
||||||
@onHtmlChanged="getPreviewedHTML">
|
@onHtmlChanged="getPreviewedHTML" @onUploadImg="onUploadImg">
|
||||||
<template #defToolbars>
|
<template #defToolbars>
|
||||||
<NormalToolbar :title="leftSidebarState == 'open' ? '隐藏文件树' : '显示文件树'" @onClick="toggleLeftSideBar">
|
<NormalToolbar :title="leftSidebarState == 'open' ? '隐藏文件树' : '显示文件树'" @onClick="toggleLeftSideBar">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
@ -39,18 +39,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Split>
|
</Split>
|
||||||
|
<UploadImageConfig v-model:visible="uploadImgForm.showFormModal" v-model:uploadImgAPI="uploadImgForm.uploadImgAPI"
|
||||||
|
v-model:uploadImgField="uploadImgForm.uploadImgField" v-model:otherField="uploadImgForm.otherField">
|
||||||
|
</UploadImageConfig>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import 'md-editor-v3/lib/style.css';
|
import 'md-editor-v3/lib/style.css';
|
||||||
import '@vavt/v3-extension/lib/asset/PreviewThemeSwitch.css';
|
import '@vavt/v3-extension/lib/asset/PreviewThemeSwitch.css';
|
||||||
import '@vavt/v3-extension/lib/asset/ExportPDF.css';
|
import '@vavt/v3-extension/lib/asset/ExportPDF.css';
|
||||||
|
import UploadImageConfig from './UI/UploadImageConfig.vue';
|
||||||
|
import scriptjs from 'scriptjs'
|
||||||
import { config, MdEditor, MdCatalog, NormalToolbar } from 'md-editor-v3';
|
import { config, MdEditor, MdCatalog, NormalToolbar } from 'md-editor-v3';
|
||||||
import { ThemeSwitch, PreviewThemeSwitch, ExportPDF } from '@vavt/v3-extension';
|
import { ThemeSwitch, PreviewThemeSwitch, ExportPDF } from '@vavt/v3-extension';
|
||||||
import { lineNumbers } from '@codemirror/view';
|
import { lineNumbers } from '@codemirror/view';
|
||||||
import { ref, reactive, watch } from "vue";
|
import { ref, reactive, watch, nextTick, onMounted } from "vue";
|
||||||
import { Message } from 'view-ui-plus'
|
import { Message } from 'view-ui-plus'
|
||||||
|
|
||||||
|
// fetchScript 用于将其他 JS 脚本加载进来
|
||||||
|
const fetchScript = (url) => new Promise((resolve) => scriptjs(url, () => resolve()));
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
markdownCode: {
|
markdownCode: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -219,6 +227,76 @@ const publish = () => {
|
|||||||
Message.warning('抱歉,该功能暂未开放');
|
Message.warning('抱歉,该功能暂未开放');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uploadImgForm = reactive({
|
||||||
|
// 是否显示表单对话框
|
||||||
|
showFormModal: false,
|
||||||
|
// 上传图片的后端 API 接口
|
||||||
|
uploadImgAPI: localStorage.getItem('imageUploadURLOfEditor') || '',
|
||||||
|
// 上传图片时,FormData 的文件字段
|
||||||
|
uploadImgField: localStorage.getItem('imageUploadFieldOfEditor') || '',
|
||||||
|
// 上传图片时,FormData 里附带的其他字段和数值. example: [{ key: 'token', value: 'uuid' }]
|
||||||
|
otherField: JSON.parse(localStorage.getItem('imageUploadOtherFieldOfEditor')) || [],
|
||||||
|
});
|
||||||
|
watch(() => uploadImgForm.uploadImgAPI, (newAPI) => {
|
||||||
|
localStorage.setItem('imageUploadURLOfEditor', newAPI);
|
||||||
|
})
|
||||||
|
watch(() => uploadImgForm.uploadImgField, (newField) => {
|
||||||
|
localStorage.setItem('imageUploadFieldOfEditor', newField);
|
||||||
|
})
|
||||||
|
watch(() => uploadImgForm.otherField, (newData) => {
|
||||||
|
localStorage.setItem('imageUploadOtherFieldOfEditor', JSON.stringify(newData));
|
||||||
|
})
|
||||||
|
|
||||||
|
const onUploadImg = async (files, callback) => {
|
||||||
|
if (uploadImgForm.uploadImgAPI.trim().length == 0 || uploadImgForm.uploadImgField.trim().length == 0) {
|
||||||
|
Message.warning("请先配置图片上传的后端接口,以及对应的字段名称");
|
||||||
|
uploadImgForm.showFormModal = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await Promise.all(
|
||||||
|
files.map((file) => {
|
||||||
|
return new Promise((rev, rej) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append(uploadImgForm.uploadImgField.trim(), file);
|
||||||
|
uploadImgForm.otherField.forEach(field => {
|
||||||
|
formData.append(field.key.trim(), field.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
fetch(uploadImgForm.uploadImgAPI.trim(), {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
}).then(response => {
|
||||||
|
if (!response.ok) throw new Error(`HTTP 错误: ${response.status}`);
|
||||||
|
return response.json();
|
||||||
|
}).then(data => {
|
||||||
|
rev(data);
|
||||||
|
}).catch(error => {
|
||||||
|
rej(error);
|
||||||
|
Message.error(`上传失败: ${error}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
callback(res.map((item) => item.url));
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
// 加载 jQuery 脚本以便能够使用 jQuery 语法
|
||||||
|
await fetchScript('/static/jquery/jquery.min.js');
|
||||||
|
// 使用 nextTick 确保页面组件已完成挂载和渲染
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
// (这个做法并不规范)找到工具栏中「上传图片」的下拉框,新增一个选项,支持更新图片上传的配置
|
||||||
|
let imageIcon = $('div.md-editor-toolbar-item[title="图片"]').first();
|
||||||
|
let dropdownOverlay = imageIcon.next().children().first();
|
||||||
|
let ulOfDropdownOverlay = dropdownOverlay.children().first();
|
||||||
|
let updateLi = $('<li class="md-editor-menu-item md-editor-menu-item-image" role="menuitem" tabindex="0">更新配置</li>');
|
||||||
|
updateLi.on('click', () => uploadImgForm.showFormModal = true);
|
||||||
|
updateLi.appendTo(ulOfDropdownOverlay);
|
||||||
|
});
|
||||||
|
|
||||||
// 对父组件暴露数据或方法
|
// 对父组件暴露数据或方法
|
||||||
defineExpose({})
|
defineExpose({})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
145
src/components/UI/UploadImageConfig.vue
Normal file
145
src/components/UI/UploadImageConfig.vue
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<template>
|
||||||
|
<Modal v-model="showFormModal" :footer-hide="true">
|
||||||
|
<template #header>
|
||||||
|
<Space>
|
||||||
|
<span>上传图片配置(configuration for uploading image)</span>
|
||||||
|
<Poptip placement="bottom" width="400" word-wrap>
|
||||||
|
<Button type="info" shape="circle" size="small" icon="md-help"></Button>
|
||||||
|
<template #content>
|
||||||
|
<Typography>
|
||||||
|
<blockquote>
|
||||||
|
假设已经拥有一个图片上传的 HTTP 接口。该接口:
|
||||||
|
<ul>
|
||||||
|
<li>接收<code>Content-Type: multipart/form-data</code>数据</li>
|
||||||
|
<li>能够通过<code>response.url</code>得到上传成功的图片 URL</li>
|
||||||
|
</ul>
|
||||||
|
</blockquote>
|
||||||
|
<Title :level="6">API(必填)</Title>
|
||||||
|
<Paragraph>
|
||||||
|
后端接口的 URL。示例 https://api.example.com/upload
|
||||||
|
</Paragraph>
|
||||||
|
<Title :level="6">Field(必填)</Title>
|
||||||
|
<Paragraph>
|
||||||
|
上传图片时,该图片文件在 form-data 中的字段名
|
||||||
|
</Paragraph>
|
||||||
|
<Title :level="6">More Fields(可选)</Title>
|
||||||
|
<Paragraph>
|
||||||
|
其他附带字段名,比如 token,可用于辅助鉴权
|
||||||
|
</Paragraph>
|
||||||
|
</Typography>
|
||||||
|
</template>
|
||||||
|
</Poptip>
|
||||||
|
</Space>
|
||||||
|
</template>
|
||||||
|
<Form ref="uploadImgFormRef" :model="configForm" :label-width="80" style="width: 300px">
|
||||||
|
<FormItem label="API" prop="uploadImgAPI" required>
|
||||||
|
<Tooltip content="上传图片的后端 API 接口" placement="top" max-width="300">
|
||||||
|
<Input type="text" v-model="configForm.uploadImgAPI"
|
||||||
|
placeholder="example: https://api.example.com/upload" style="width: 290px;"></Input>
|
||||||
|
</Tooltip>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="Field" prop="uploadImgField" required>
|
||||||
|
<Tooltip content="上传图片时,该图片文件在 form-data 中的字段名" placement="top" max-width="300">
|
||||||
|
<Input type="text" v-model="configForm.uploadImgField"
|
||||||
|
placeholder="File Field. example: image or file ..." style="width: 290px;"></Input>
|
||||||
|
</Tooltip>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem v-for="(field, index) in configForm.otherField" :key="index" :label="'Field ' + (index + 1)"
|
||||||
|
:prop="'otherField.' + index + '.key'"
|
||||||
|
:rules="{ required: true, message: 'Field Key can not be empty', trigger: 'blur' }">
|
||||||
|
<Space>
|
||||||
|
<Tooltip content="其他附带字段名,比如 token,可用于辅助鉴权" placement="top" max-width="300">
|
||||||
|
<Input type="text" v-model="field.key" placeholder="Field Key" style="width: 80px;"></Input>
|
||||||
|
</Tooltip>
|
||||||
|
<Input type="text" v-model="field.value" placeholder="Field Value" style="width: 200px;"></Input>
|
||||||
|
<Button @click="removeField(index)">Delete</Button>
|
||||||
|
</Space>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem>
|
||||||
|
<Button type="dashed" long @click="addField()" icon="md-add">Add More Field</Button>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem>
|
||||||
|
<Button type="primary" @click="handleSubmit()">Submit</Button>
|
||||||
|
<Button @click="handleReset()" style="margin-left: 8px">Reset</Button>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, watch, useTemplateRef } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
uploadImgAPI: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
uploadImgField: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
// 示例 [{ key: 'token', value: 'uuid' }]
|
||||||
|
otherField: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits([
|
||||||
|
'update:visible',
|
||||||
|
'update:uploadImgAPI',
|
||||||
|
'update:uploadImgField',
|
||||||
|
'update:otherField',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const uploadImgFormRef = useTemplateRef('uploadImgFormRef');
|
||||||
|
|
||||||
|
// 是否弹出对话框。要求双向绑定
|
||||||
|
const showFormModal = ref(props.visible);
|
||||||
|
watch(() => props.visible, (visible) => {
|
||||||
|
showFormModal.value = visible;
|
||||||
|
})
|
||||||
|
watch(showFormModal, (visible) => {
|
||||||
|
emit('update:visible', visible);
|
||||||
|
})
|
||||||
|
|
||||||
|
const configForm = reactive({
|
||||||
|
// 上传图片的后端 API 接口
|
||||||
|
uploadImgAPI: props.uploadImgAPI,
|
||||||
|
// 上传图片时,FormData 的文件字段
|
||||||
|
uploadImgField: props.uploadImgField,
|
||||||
|
// 上传图片时,FormData 里附带的其他字段和数值
|
||||||
|
otherField: JSON.parse(JSON.stringify(props.otherField)),
|
||||||
|
});
|
||||||
|
|
||||||
|
const addField = () => {
|
||||||
|
configForm.otherField.push({ key: '', value: '' });
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeField = (index) => {
|
||||||
|
configForm.otherField.splice(index, 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
uploadImgFormRef.value.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
emit('update:uploadImgAPI', configForm.uploadImgAPI);
|
||||||
|
emit('update:uploadImgField', configForm.uploadImgField);
|
||||||
|
emit('update:otherField', JSON.parse(JSON.stringify(configForm.otherField)));
|
||||||
|
showFormModal.value = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
uploadImgFormRef.value.resetFields();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
Loading…
x
Reference in New Issue
Block a user