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"
|
||||
:previewTheme="editorState.previewTheme" :style="{ height: editorState.height }"
|
||||
:toolbars="editorState.toolbars" :toolbarsExclude="editorState.toolbarsExclude" @onSave="onSave"
|
||||
@onHtmlChanged="getPreviewedHTML">
|
||||
@onHtmlChanged="getPreviewedHTML" @onUploadImg="onUploadImg">
|
||||
<template #defToolbars>
|
||||
<NormalToolbar :title="leftSidebarState == 'open' ? '隐藏文件树' : '显示文件树'" @onClick="toggleLeftSideBar">
|
||||
<template #trigger>
|
||||
@ -39,18 +39,26 @@
|
||||
</div>
|
||||
</template>
|
||||
</Split>
|
||||
<UploadImageConfig v-model:visible="uploadImgForm.showFormModal" v-model:uploadImgAPI="uploadImgForm.uploadImgAPI"
|
||||
v-model:uploadImgField="uploadImgForm.uploadImgField" v-model:otherField="uploadImgForm.otherField">
|
||||
</UploadImageConfig>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import 'md-editor-v3/lib/style.css';
|
||||
import '@vavt/v3-extension/lib/asset/PreviewThemeSwitch.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 { ThemeSwitch, PreviewThemeSwitch, ExportPDF } from '@vavt/v3-extension';
|
||||
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'
|
||||
|
||||
// fetchScript 用于将其他 JS 脚本加载进来
|
||||
const fetchScript = (url) => new Promise((resolve) => scriptjs(url, () => resolve()));
|
||||
|
||||
const props = defineProps({
|
||||
markdownCode: {
|
||||
type: String,
|
||||
@ -219,6 +227,76 @@ const publish = () => {
|
||||
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({})
|
||||
</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