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

594 lines
14 KiB
Vue
Raw Normal View History

<script setup>
2024-07-05 07:41:43 +00:00
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";
2024-07-03 17:02:02 +00:00
import {useStore} from "vuex";
import axios from "axios";
const route = useRoute();
const router = useRouter();
2024-07-03 17:02:02 +00:00
const store = useStore();
const token = ref('')
const form = ref({
2024-07-03 17:02:02 +00:00
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>