Files
tuanwei-admin/src/views/cms/manuscript/components/manuscriptEdit.vue
T
2026-07-20 11:50:47 +08:00

359 lines
8.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="1000"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:confirm-loading="loading"
:title="isUpdate ? '编辑稿件' : '添加稿件'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="
styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '100px' }
"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="标题" name="title">
<a-input
allow-clear
placeholder="请输入稿件标题"
v-model:value="form.title"
/>
</a-form-item>
<a-form-item label="内容" name="content">
<div class="editor-wrapper">
<Toolbar
class="editor-toolbar"
:editor="editorRef"
:defaultConfig="toolbarConfig"
mode="default"
/>
<Editor
v-model="contentHtml"
class="editor-content"
:defaultConfig="editorConfig"
mode="default"
@onCreated="handleCreated"
/>
</div>
<div class="editor-tip">支持直接粘贴内容,粘贴图片时会自动上传。</div>
</a-form-item>
<a-form-item label="封面" name="cover">
<ele-image-upload
v-model:value="coverList"
:limit="1"
:drag="true"
:upload-handler="uploadHandler"
@upload="onUpload"
/>
</a-form-item>
<a-form-item label="排序" name="sortNumber">
<a-input-number
:min="0"
:max="9999"
class="ele-fluid"
placeholder="请输入排序号"
v-model:value="form.sortNumber"
/>
</a-form-item>
<a-form-item label="状态" name="status">
<a-radio-group v-model:value="form.status">
<a-radio :value="1">显示</a-radio>
<a-radio :value="0">隐藏</a-radio>
</a-radio-group>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { shallowRef, ref, reactive, watch, onBeforeUnmount } from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject } from 'ele-admin-pro';
import { addManuscript, updateManuscript } from '@/api/cms/manuscript';
import { Manuscript } from '@/api/cms/manuscript/model';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import { FormInstance } from 'ant-design-vue/es/form';
import { uploadFile } from '@/api/system/file';
import type { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
import type {
IDomEditor,
IEditorConfig,
IToolbarConfig
} from '@wangeditor/editor';
import '@wangeditor/editor/dist/css/style.css';
const isUpdate = ref(false);
const useForm = Form.useForm;
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
visible: boolean;
data?: Manuscript | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
const loading = ref(false);
const maxable = ref(true);
const formRef = ref<FormInstance | null>(null);
const editorRef = shallowRef<IDomEditor>();
const contentHtml = ref('');
const coverList = ref<ItemType[]>([]);
const toolbarConfig: Partial<IToolbarConfig> = {
excludeKeys: ['group-video']
};
const editorConfig: Partial<IEditorConfig> = {
placeholder: '请输入稿件内容,支持粘贴图片自动上传',
MENU_CONF: {
uploadImage: {
allowedFileTypes: ['image/*'],
maxFileSize: 2 * 1024 * 1024,
base64LimitSize: 0,
async customUpload(file: File, insertFn: (url: string) => void) {
if (!file.type.startsWith('image/')) {
message.error('只能上传图片');
return;
}
try {
const result = await uploadFile(file);
insertFn(result.url);
} catch (e: any) {
message.error(e.message || '图片上传失败');
}
}
}
}
};
const form = reactive<Manuscript>({
id: undefined,
title: undefined,
content: undefined,
cover: undefined,
deleted: undefined,
tenantId: undefined,
createTime: undefined,
updateTime: undefined,
userId: undefined,
sortNumber: 0,
status: 1
});
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
const hasEditorContent = (html?: string) => {
const text = String(html || '')
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
.replace(/<[^>]+>/g, '')
.replace(/&nbsp;/gi, '')
.trim();
return !!text || /<img\b/i.test(String(html || ''));
};
const rules = reactive({
title: [
{
required: true,
type: 'string',
whitespace: true,
message: '请输入稿件标题',
trigger: 'blur'
}
],
content: [
{
validator: async () => {
if (!hasEditorContent(contentHtml.value)) {
return Promise.reject(new Error('请输入稿件内容'));
}
return Promise.resolve();
},
trigger: 'change'
}
],
sortNumber: [
{
required: true,
type: 'number',
message: '请输入排序号',
trigger: 'change'
}
]
});
const { resetFields, validate } = useForm(form, rules);
const handleCreated = (editor: IDomEditor) => {
editorRef.value = editor;
};
const uploadHandler = (file: File) => {
const item: ItemType = {
file,
uid: (file as any).uid,
name: file.name
};
if (!file.type.startsWith('image/')) {
message.error('只能选择图片');
return;
}
if (file.size / 1024 / 1024 > 2) {
message.error('大小不能超过 2MB');
return;
}
onUpload(item);
};
const onUpload = (item: ItemType) => {
const { file } = item;
uploadFile(file as File)
.then((data) => {
coverList.value = [
{
uid: data.id,
url: data.url,
status: 'done'
}
];
form.cover = data.url;
message.success('上传成功');
})
.catch((e) => {
message.error(e.message);
});
};
const resetEditor = () => {
contentHtml.value = '';
};
const save = async () => {
if (!formRef.value) {
return;
}
try {
await formRef.value.validate();
loading.value = true;
const formData = {
...form,
content: contentHtml.value
};
const saveOrUpdate = isUpdate.value ? updateManuscript : addManuscript;
const msg = await saveOrUpdate(formData);
message.success(msg);
updateVisible(false);
emit('done');
} catch (e: any) {
if (!e?.errorFields) {
message.error(e.message || '保存失败');
}
} finally {
loading.value = false;
}
};
watch(contentHtml, () => {
form.content = contentHtml.value;
validate('content').catch(() => {});
});
watch(
coverList,
(list) => {
if (!list.length) {
form.cover = '';
}
},
{ deep: true }
);
watch(
() => props.visible,
(visible) => {
if (visible) {
if (props.data) {
assignObject(form, props.data);
contentHtml.value = props.data.content || '';
coverList.value = props.data.cover
? [
{
uid: props.data.id || 1,
url: props.data.cover,
status: 'done'
}
]
: [];
isUpdate.value = true;
} else {
resetFields();
resetEditor();
coverList.value = [];
form.status = 1;
form.sortNumber = 0;
isUpdate.value = false;
}
} else {
resetFields();
resetEditor();
coverList.value = [];
}
},
{ immediate: true }
);
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor != null) {
editor.destroy();
}
});
</script>
<style lang="less" scoped>
.editor-wrapper {
border: 1px solid #d9d9d9;
border-radius: 6px;
overflow: hidden;
}
.editor-toolbar {
border-bottom: 1px solid #f0f0f0;
}
.editor-content {
min-height: 360px;
}
.editor-content :deep(.w-e-text-container) {
min-height: 360px;
}
.editor-tip {
margin-top: 8px;
color: #999;
font-size: 12px;
line-height: 1.5;
}
</style>