<!-- src/views/incorruptible/case/caseForm.vue -->
<template>
<div class="app-container">
<!-- 页面标题 -->
<div class="page-title">{{ title }}</div>
<el-form
ref="form"
:model="form"
:rules="rules"
label-width="80px"
class="form-container"
>
<el-form-item label="案列标题" prop="title">
<el-input v-model="form.title" placeholder="请输入案列标题" />
</el-form-item>
<el-form-item label="发布日期" prop="publishDate">
<el-date-picker
clearable
v-model="form.publishDate"
type="date"
value-format="yyyy-MM-dd"
placeholder="请选择发布日期"
popper-append-to-body="false"
class="date-picker-zindex"
/>
</el-form-item>
<el-form-item label="案例内容">
<!-- Quill富文本编辑器 -->
<div class="quill-editor-container">
<quill-editor
ref="quillEditor"
v-model="form.content"
:options="editorOptions"
@ready="onEditorReady"
/>
</div>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in dict.type.sys_normal_disable"
:key="dict.value"
:label="dict.value"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-form>
</div>
</template>
<script>
import { getCase, addCase, updateCase } from "@/api/incorruptible/case";
import { quillEditor } from 'vue-quill-editor';
import 'quill/dist/quill.core.css';
import 'quill/dist/quill.snow.css';
import 'quill/dist/quill.bubble.css';
import { getToken } from '@/utils/auth';
import axios from 'axios';
export default {
name: "CaseForm",
components: {
quillEditor
},
dicts: ['sys_normal_disable'],
data() {
return {
articleId: null, // 改为在data中存储
title: "",
form: {
title: null,
publishDate: null,
content: null,
status: null
},
// Quill编辑器配置
editorOptions: {
placeholder: '请输入内容',
theme: 'snow',
modules: {
toolbar: {
container: [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ 'header': 1 }, { 'header': 2 }],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
[{ 'script': 'sub'}, { 'script': 'super' }],
[{ 'indent': '-1'}, { 'indent': '+1' }],
[{ 'direction': 'rtl' }],
[{ 'size': ['small', false, 'large', 'huge'] }],
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
[{ 'color': [] }, { 'background': [] }],
[{ 'font': [] }],
[{ 'align': [] }],
['clean'],
['link', 'image']
],
handlers: {
'image': this.handleImageButtonClick
}
}
}
},
quillInstance: null,
// 表单校验规则
rules: {
title: [{ required: true, message: '案列标题不能为空', trigger: 'blur' }],
publishDate: [{ required: true, message: '发布日期不能为空', trigger: 'change' }],
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
}
};
},
created() {
// 从路由参数获取articleId
this.articleId = this.$route.params.articleId;
// 根据是否有ID确定是新增还是修改
this.title = this.articleId ? "修改廉洁标准案例" : "添加廉洁标准案例";
// 如果是修改,加载案例数据
if (this.articleId) {
this.loadCaseData();
}
},
methods: {
// 加载案例数据
async loadCaseData() {
try {
const response = await getCase(this.articleId);
this.form = response.data;
// 反转义富文本内容
this.form.content = this.unescapeHtmlFromJson(this.form.content);
} catch (error) {
console.error('加载案例数据失败:', error);
this.$modal.msgError('加载案例数据失败');
}
},
// 编辑器准备就绪
onEditorReady(editor) {
this.quillInstance = editor;
},
// 图片按钮点击事件处理
handleImageButtonClick() {
if (!this.quillInstance) return;
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.style.display = 'none';
document.body.appendChild(input);
input.click();
input.onchange = () => {
if (!input.files || !input.files[0]) return;
const file = input.files[0];
if (file.size > 5 * 1024 * 1024) {
this.$modal.msgError('图片大小不能超过5MB');
return;
}
this.uploadImage(file)
.then(imageUrl => {
const range = this.quillInstance.getSelection();
if (range) {
this.quillInstance.insertEmbed(range.index, 'image', imageUrl);
this.quillInstance.setSelection(range.index + 1);
}
})
.catch(error => {
console.error('图片上传失败:', error);
this.$modal.msgError('图片上传失败');
})
.finally(() => {
document.body.removeChild(input);
});
};
},
// 图片上传方法
uploadImage(file) {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append('file', file);
const uploadUrl = process.env.VUE_APP_BASE_API + '/file/upload';
axios.post(uploadUrl, formData, {
headers: {
'Authorization': 'Bearer ' + getToken(),
'Content-Type': 'multipart/form-data'
}
}).then(response => {
const res = response.data;
let imageUrl = '';
if (res.code === 200 && res.data) {
if (typeof res.data === 'string') {
imageUrl = res.data;
} else if (res.data.url) {
imageUrl = res.data.url;
} else if (res.data.value) {
imageUrl = String(res.data.value);
}
if (imageUrl) {
if (!imageUrl.startsWith('http')) {
imageUrl = process.env.VUE_APP_BASE_API + imageUrl;
}
resolve(imageUrl);
} else {
reject('无法解析图片URL');
}
} else {
reject(res.msg || '图片上传失败');
}
}).catch(error => {
console.error('图片上传失败:', error);
reject('上传失败: ' + (error.message || '网络错误'));
});
});
},
// 富文本内容JSON转义处理
escapeHtmlForJson(html) {
if (!html) return '';
let processedHtml = html.replace(/ /g, '\\u00a0');
processedHtml = processedHtml.replace(/^[ \t]+/gm, (match) => {
return '\\u00a0'.repeat(match.length);
});
processedHtml = processedHtml.replace(/([^ \t\n\r])([ \t]{2,})/g, (match, prefix, spaces) => {
return prefix + '\\u00a0'.repeat(spaces.length);
});
processedHtml = processedHtml
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\//g, '\\/')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t')
.replace(/\f/g, '\\f')
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e');
return processedHtml;
},
// JSON转义内容反转义处理
unescapeHtmlFromJson(escapedHtml) {
if (!escapedHtml) return '';
return escapedHtml
.replace(/\\u00a0/g, ' ')
.replace(/\\u003c/g, '<')
.replace(/\\u003e/g, '>')
.replace(/\\"/g, '"')
.replace(/\\\//g, '/')
.replace(/\\\\/g, '\\')
.replace(/\\n/g, '\n')
.replace(/\\r/g, '\r')
.replace(/\\t/g, '\t')
.replace(/\\f/g, '\f');
},
// 提交表单
submitForm() {
this.$refs.form.validate(valid => {
if (valid) {
// 检查富文本内容
if (!this.form.content || this.form.content === '<p><br></p>') {
this.$modal.msgWarning('案例内容不能为空');
return;
}
// 准备数据
const formData = {
...this.form,
content: this.escapeHtmlForJson(this.form.content)
};
// 确定是新增还是修改
const action = this.articleId ? updateCase : addCase;
action(formData)
.then(() => {
this.$modal.msgSuccess(this.articleId ? "修改成功" : "新增成功");
this.$router.back(); // 返回上一页
})
.catch(error => {
console.error('操作失败:', error);
this.$modal.msgError('操作失败');
});
}
});
},
// 取消操作
cancel() {
this.$router.back(); // 返回上一页
}
}
};
</script>
<style scoped>
.app-container {
padding: 20px;
background-color: #f9f9f9;
min-height: 100vh;
}
.page-title {
font-size: 24px;
font-weight: bold;
margin-bottom: 20px;
color: #2c3e50;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.form-container {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.quill-editor-container {
border: 1px solid #DCDFE6;
border-radius: 4px;
overflow: hidden;
background: #fff;
margin-bottom: 20px;
}
.quill-editor-container:hover {
border-color: #C0C4CC;
}
.ql-toolbar {
border-bottom: 1px solid #DCDFE6 !important;
}
.ql-container {
min-height: 300px;
border: none !important;
}
.dialog-footer {
text-align: center;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #eee;
}
.date-picker-zindex {
z-index: 3000 !important;
}
</style>
这里面的富文本,没有正确转译tab空格和空格,我tab空格后刷新tab空格没有了,空格变成了\