feat: 支持图片上传功能,以及自定义上传接口和字段配置

This commit is contained in:
Frankie Huang 2025-05-02 16:03:12 +08:00
parent 9c7fc8e8e6
commit a8c8a72bd6
2 changed files with 225 additions and 2 deletions

View File

@ -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>

View 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>