frontend/src/views/news-management/EditNews.vue

594 lines
14 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.

<script setup>
import {onMounted, ref} from 'vue';
import {
ElButton,
ElForm,
ElFormItem,
ElInput,
ElMessage,
ElMessageBox,
ElOption,
ElSelect,
ElUpload
} from 'element-plus';
import {
AccessibilityHelp,
Alignment,
Autoformat,
AutoLink,
Autosave,
BalloonToolbar,
BlockQuote,
Bold,
ClassicEditor,
CodeBlock,
Essentials,
FindAndReplace,
GeneralHtmlSupport,
Heading,
HorizontalLine,
Indent,
IndentBlock,
Italic,
Link,
Paragraph,
SelectAll,
Style,
Table,
TableCaption,
TableCellProperties,
TableColumnResize,
TableProperties,
TableToolbar,
TextTransformation,
Undo
} from 'ckeditor5';
import translations from 'ckeditor5/translations/zh-cn.js';
import 'ckeditor5/ckeditor5.css';
import {Delete, Plus, Refresh, ZoomIn} from "@element-plus/icons-vue";
import {useRoute, useRouter} from "vue-router";
import {useStore} from "vuex";
import axios from "axios";
const route = useRoute();
const router = useRouter();
const store = useStore();
const token = ref('')
const form = ref({
token: '',
title: '',
author: '',
content: '',
summary: '',
tenant: '',
imagePath: ''
});
const createMode = ref(false);
const componentMode = ref(false);
const modeTitle = ref('');
const fileList = ref([]); // 用于存储上传文件的列表
const basePath = '/api/news'
const uploadUrl = basePath + '/uploadPic'; // 上传的后端地址
const dialogImageUrl = ref('');
const dialogVisible = ref(false); // 用于图片放大预览
const props = defineProps({
mode: {
type: String,
required: false
},
id: {
type: String,
required: false
}
});
const containerStyle = ref('form-container')
const emit = defineEmits(['setNewsDialogInvisible']);
const setNewsDialogInvisible = (changed) => {
emit('setNewsDialogInvisible', changed);
};
//下面是编辑模式需要的一些变量
const id = ref('');
const beforeUpload = (file) => {
// 检查文件类型是否为图片png, jpg, jpeg
const isImage = file.type === 'image/png' || file.type === 'image/jpg' || file.type === 'image/jpeg';
if (!isImage) {
ElMessage.error('只能上传 png/jpg/jpeg 格式的图片文件!');
return false;
}
// 检查文件大小是否超过 5MB
const isLt5M = file.size / 1024 / 1024 < 5;
if (!isLt5M) {
ElMessage.error('上传文件大小不能超过 5MB');
return false;
}
return true;
};
const handleSuccess = (response, file, fileList) => {
file.url = response.url;
};
const handleError = (error, file, fileList) => {
ElMessage.error('图片上传失败,请重试!');
};
const handleChange = (file, fileList) => {
// 处理文件选择变化时的逻辑,确保只选择最新上传的文件
if (fileList.length > 1) {
fileList.splice(0, fileList.length - 1)
}
};
const handlePictureCardPreview = (file) => {
dialogImageUrl.value = file.url
dialogVisible.value = true
}
const handleRemove = (file) => {
fileList.value = fileList.value.filter((item) => item.uid !== file.uid);
console.log(file)
}
const handleCommit = async () => {
// 判断form中的每一个字段都不为空字符串且不为null
if (form.value.title === '' || form.value.author === '' || form.value.content === '' || form.value.summary === '' || form.value.tenant === '') {
await ElMessageBox.alert('请填写完整信息!');
return;
}
if (fileList.value.length === 0) {
await ElMessageBox.alert('请选择图片!');
return;
}
form.value.imagePath = fileList.value[0].url;
if (createMode.value) {
try {
const response = await axios.post(basePath, form.value);
ElMessage.success('添加成功!');
if (componentMode){
setNewsDialogInvisible(true);
}
else{
router.push('/news');
}
} catch (e) {
await ElMessageBox.alert(e.response.data.message);
}
} else {
try {
const response = await axios.put(`${basePath}/${id.value}`, form.value);
ElMessage.success('修改成功!');
if (componentMode){
setNewsDialogInvisible(true);
}
else{
router.push('/news');
}
} catch (e) {
await ElMessageBox.alert(e.response.data.message);
}
}
};
const handleCancel = () => {
if (componentMode){
setNewsDialogInvisible(false);
}
else{
router.push('/news');
}
};
const isLayoutReady = ref(false);
const config = ref(null); // CKEditor needs the DOM tree before calculating the configuration.
const editor = ClassicEditor;
const fetchNewsDetail = async () => {
let params = {
token: token.value,
}
const res = await axios.get(basePath + '/' + id.value, {params});
form.value.title = res.data.title;
form.value.author = res.data.author;
form.value.content = res.data.content;
form.value.summary = res.data.summary;
form.value.tenant = res.data.tenant;
form.value.imagePath = res.data.imagePath;
fileList.value.push({url: res.data.imagePath});
}
onMounted(() => {
token.value = store.getters['authentication/token'];
form.value.token = token.value;
if (props.mode !== undefined){
componentMode.value = true;
containerStyle.value = 'form-container-component'
if (props.mode === 'create'){
createMode.value = true;
modeTitle.value = '添加资讯';
}
else if (props.mode === 'edit'){
createMode.value = false;
id.value = props.id;
modeTitle.value = '修改资讯';
fetchNewsDetail();
}
}
else if (route.query.mode === 'create' || route.params.mode === 'create') {
createMode.value = true;
modeTitle.value = '添加资讯';
} else if (route.query.mode === 'edit' || route.params.mode === 'edit') {
id.value = route.params.id === undefined ? route.query.id : route.params.id;
createMode.value = false;
modeTitle.value = '修改资讯';
fetchNewsDetail();
}
config.value = {
toolbar: {
items: [
'undo',
'redo',
'|',
'heading',
'style',
'|',
'bold',
'italic',
'|',
'link',
'insertTable',
'blockQuote',
'codeBlock',
'|',
'alignment',
'|',
'indent',
'outdent'
],
shouldNotGroupWhenFull: false
},
plugins: [
AccessibilityHelp,
Alignment,
Autoformat,
AutoLink,
Autosave,
BalloonToolbar,
BlockQuote,
Bold,
CodeBlock,
Essentials,
FindAndReplace,
GeneralHtmlSupport,
Heading,
HorizontalLine,
Indent,
IndentBlock,
Italic,
Link,
Paragraph,
SelectAll,
Style,
Table,
TableCaption,
TableCellProperties,
TableColumnResize,
TableProperties,
TableToolbar,
TextTransformation,
Undo
],
balloonToolbar: ['bold', 'italic', '|', 'link'],
heading: {
options: [
{
model: 'paragraph',
title: 'Paragraph',
class: 'ck-heading_paragraph'
},
{
model: 'heading1',
view: 'h1',
title: 'Heading 1',
class: 'ck-heading_heading1'
},
{
model: 'heading2',
view: 'h2',
title: 'Heading 2',
class: 'ck-heading_heading2'
},
{
model: 'heading3',
view: 'h3',
title: 'Heading 3',
class: 'ck-heading_heading3'
},
{
model: 'heading4',
view: 'h4',
title: 'Heading 4',
class: 'ck-heading_heading4'
},
{
model: 'heading5',
view: 'h5',
title: 'Heading 5',
class: 'ck-heading_heading5'
},
{
model: 'heading6',
view: 'h6',
title: 'Heading 6',
class: 'ck-heading_heading6'
}
]
},
htmlSupport: {
allow: [
{
name: /^.*$/,
styles: true,
attributes: true,
classes: true
}
]
},
initialData: '',
language: 'zh-cn',
link: {
addTargetToExternalLinks: true,
defaultProtocol: 'https://',
decorators: {
toggleDownloadable: {
mode: 'manual',
label: 'Downloadable',
attributes: {
download: 'file'
}
}
}
},
menuBar: {
isVisible: true
},
placeholder: '请在此处输入你的文章',
style: {
definitions: [
{
name: 'Article category',
element: 'h3',
classes: ['category']
},
{
name: 'Title',
element: 'h2',
classes: ['document-title']
},
{
name: 'Subtitle',
element: 'h3',
classes: ['document-subtitle']
},
{
name: 'Info box',
element: 'p',
classes: ['info-box']
},
{
name: 'Side quote',
element: 'blockquote',
classes: ['side-quote']
},
{
name: 'Marker',
element: 'span',
classes: ['marker']
},
{
name: 'Spoiler',
element: 'span',
classes: ['spoiler']
},
{
name: 'Code (dark)',
element: 'pre',
classes: ['fancy-code', 'fancy-code-dark']
},
{
name: 'Code (bright)',
element: 'pre',
classes: ['fancy-code', 'fancy-code-bright']
}
]
},
table: {
contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells', 'tableProperties', 'tableCellProperties']
},
translations: [translations]
};
isLayoutReady.value = true;
});
</script>
<template>
<div :class="containerStyle">
<ElForm :model="form" label-width="120px" @submit.prevent="handleCommit">
<h2>{{ modeTitle }}</h2>
<ElFormItem label="新闻标题" required>
<ElInput v-model="form.title" placeholder="请输入新闻标题" required></ElInput>
</ElFormItem>
<ElFormItem label="新闻图片路径" required>
<el-upload
:action="uploadUrl"
:limit="2"
:before-upload="beforeUpload"
:on-success="handleSuccess"
:on-error="handleError"
:on-change="handleChange"
:file-list="fileList"
list-type="picture-card"
auto-upload
v-model:file-list="fileList"
required
>
<template #file="{ file }">
<div>
<img class="el-upload-list__item-thumbnail" :src="file.url" alt=""/>
<span class="el-upload-list__item-actions">
<span
class="el-upload-list__item-preview"
@click="handlePictureCardPreview(file)"
>
<el-icon><zoom-in/></el-icon>
</span>
<span
class="el-upload-list__item-delete"
@click="handleRemove(file)"
>
<el-icon><Delete/></el-icon>
</span>
</span>
</div>
</template>
<el-icon v-if="fileList.length === 0">
<Plus/>
</el-icon>
<el-icon v-else>
<Refresh/>
</el-icon>
</el-upload>
<div class="tip">
请上传大小不超过 <span style="color: red;">5MB</span> 格式为 <span style="color: red;">png/jpg/jpeg</span> 的文件
</div>
</ElFormItem>
<ElFormItem label="新闻内容" required>
<div class="editor-container editor-container_classic-editor editor-container_include-style"
ref="editorContainerElement">
<div class="editor-container__editor">
<div ref="editorElement">
<ckeditor v-if="isLayoutReady" v-model="form.content" :editor="editor" :config="config"/>
</div>
</div>
</div>
</ElFormItem>
<ElFormItem label="作者" required>
<ElInput v-model="form.author" placeholder="请输入作者" required></ElInput>
</ElFormItem>
<ElFormItem label="新闻简介" required>
<ElInput v-model="form.summary" placeholder="请输入新闻简介" required></ElInput>
</ElFormItem>
<ElFormItem label="选择租户" required>
<ElSelect v-model="form.tenant" placeholder="请选择" class="dynamic-width-select">
<ElOption label="Option 1" value="option1"></ElOption>
<ElOption label="Option 2" value="option2"></ElOption>
<ElOption label="Option 3" value="option3"></ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem>
<ElButton type="primary" native-type="submit">确定</ElButton>
<ElButton @click="handleCancel">取消</ElButton>
</ElFormItem>
</ElForm>
</div>
<el-dialog v-model="dialogVisible" class="image-preview">
<img w-full :src="dialogImageUrl" alt="Preview Image"/>
</el-dialog>
</template>
<style scoped>
.form-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: white;
overflow-y: auto; /* 使容器在内容溢出时出现滚动条 */
}
.image-preview {
display: flex;
justify-content: center;
align-items: center;
}
.image-preview img {
max-width: 100%;
height: auto;
}
.tip {
margin-top: 8px;
color: #727272;
font-size: 12px;
}
.el-form {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
width: 55%;
max-height: 90vh; /* 确保表单不会超过视口高度 */
overflow-y: auto; /* 使表单在内容溢出时出现滚动条 */
}
.el-form-item {
margin-bottom: 1rem;
text-align: left;
}
.el-form-item label {
display: block;
margin-bottom: 0.5rem;
}
.el-input,
.el-select {
width: 100%;
}
.el-button--primary {
background-color: #007bff;
color: white;
}
.el-button--primary:hover {
background-color: #0056b3;
}
.dynamic-width-select {
min-width: 30%;
max-width: 100%;
width: auto; /* 使宽度根据内容调整 */
}
.form-container-component {
display: flex;
justify-content: center;
align-items: center;
height: auto;
background: white;
overflow-y: auto; /* 使容器在内容溢出时出现滚动条 */
}
.form-container-component .el-form {
background: white;
padding: 2rem;
width: 100%;
height: 100%;
max-height: 90vh; /* 确保表单不会超过视口高度 */
overflow-y: auto; /* 使表单在内容溢出时出现滚动条 */
}
</style>