cankao-admin/src/views/pages/richedit/DetailDrawer/index.vue

750 lines
21 KiB
Vue
Raw Normal View History

2025-08-03 13:41:47 +08:00
<template>
<div
:class="[isMobileByWidth() ? 'drawer-container-mobile' : 'drawer-container', { 'drawer-container-100': isFull }]">
<el-drawer v-model="model" :modal="false" size="100%" :lock-scroll="false" modal-class="modal-class">
<template #header>
<div class="drawer-header">
<div class="title">{{ readOnly ? '资讯信息浏览' : type == 1 ? '资讯信息编辑' : '资讯信息创建' }}</div>
<div class="btn" v-if="type == 1">
<!-- <el-button type="text"
v-if="!form.deleted && (data.status == 3 || data.status == -1 || data.status == 4 || data.status == 2) && (Session.get('userInfoLocal').userType == '01' || Session.get('userInfoLocal').userType == '02')"
2025-08-03 13:41:47 +08:00
@click="handleEditStatus(!readOnly)">{{ readOnly ?
'编辑' : '浏览' }}</el-button>
<el-button type="text"
v-else-if="!form.deleted && (data.status == 0 || data.status == 1 || data.status == -1)"
2025-08-03 13:41:47 +08:00
@click="handleEditStatus(!readOnly)">{{ readOnly ?
'编辑' : '浏览' }}</el-button> -->
<!-- <el-button type="text" style="color: #ff1818"
v-if="!form.deleted && (data.status == 0 || data.status == 1 || data.status == -1)"
@click="doDeleteNewsFn(data)">删除</el-button> -->
<div v-if="Session.get('userInfoLocal').userType == '00'">
<el-button type="text" v-if="!form.deleted && (data.status == 1 || data.status == -1)"
@click="handleEditStatus(!readOnly)">{{ readOnly ?
'编辑' : '浏览' }}</el-button>
<el-button type="text" style="color: #ff1818"
v-if="!form.deleted && (data.status == 0 || data.status == 1)"
@click="doDeleteNewsFn(data)">删除</el-button>
</div>
<div v-if="Session.get('userInfoLocal').userType == '02'">
<el-button type="text"
v-if="!form.deleted && (data.status == 0 || data.status == 1 || data.status == 3)"
@click="handleEditStatus(!readOnly)">{{ readOnly ?
'编辑' : '浏览' }}</el-button>
<el-button type="text" style="color: #ff1818"
v-if="!form.deleted && (data.status == 0 || data.status == 1)"
@click="doDeleteNewsFn(data)">删除</el-button>
</div>
<div v-if="Session.get('userInfoLocal').userType == '01'">
<el-button type="text" v-if="!form.deleted && (data.status == 4 || data.status == 2)"
@click="handleEditStatus(!readOnly)">{{ readOnly ?
'编辑' : '浏览' }}</el-button>
<el-button type="text" style="color: #ff1818"
v-if="!form.deleted && (data.status == 0 || data.status == 1)"
@click="doDeleteNewsFn(data)">删除</el-button>
</div>
2025-08-03 13:41:47 +08:00
<el-button type="text" @click="clickFull" v-if="!isMobileByWidth()">
2025-08-03 13:41:47 +08:00
<el-icon v-if="!isFull">
<FullScreen />
</el-icon>
<el-icon v-else>
<Switch />
</el-icon>
</el-button>
</div>
</div>
</template>
<el-form class="form" :rules="rules" ref="ruleFormRef" :model="form" label-width="95">
<div class="center">
<div class="step_bk">
<div v-if="readOnly || type === 1">
<strong>报道时间</strong>
<span style="color: #0888d7">{{ data.publishTime && formatDate(new Date(data.publishTime),
'YYYY年mm月dd日 HH:MM') }}</span>
</div>
<el-form-item label="标题" style="margin-top: 20px" prop="title" label-width="52">
<div style="display: flex; width: 800px; align-items: center">
<el-input placeholder="输入文章标题" v-model="form.title" :max="titleTextMax"
:maxlength="titleTextMax" @input="inputChange" :disabled="readOnly"></el-input>
<text style="margin-left: 10px; color: #b3b3b3"> {{ titleTextNum }}/{{ titleTextMax
}}</text>
<icon_ai v-if="form.titleTranslated"></icon_ai>
</div>
</el-form-item>
<el-form-item prop="summary" label="摘要" label-width="52">
<el-input v-model="form.summary" show-word-limit maxlength="300" style="margin-top: 10px"
:rows="5" type="textarea" placeholder="请输入摘要内容" max="300" :disabled="readOnly" />
</el-form-item>
</div>
<div class="step_bk">
<el-form-item label="媒体来源:" prop="tagSourceSelect">
<el-select :disabled="readOnly" v-model="form.tagSourceSelect" filterable
placeholder="选择媒体来源" size="large" style="width: 240px">
<el-option v-for="item in tagSourceList" :key="item.id" :label="item.name"
:value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="概念标签:" prop="tagDiySelect">
<el-select :disabled="readOnly" v-model="form.tagDiySelect" filterable multiple
placeholder="选择概念标签" size="large" style="width: 240px">
<el-option v-for="item in tagDiyList" :key="item.id" :label="item.name"
:value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="行业分类:" prop="industrySelect">
<el-select :disabled="readOnly" v-model="form.industrySelect" filterable multiple
placeholder="选择行业分类" size="large" style="width: 240px">
<el-option v-for="item in industryList" :key="item.id" :label="item.name"
:value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="编辑评分:" prop="industrySelect">
<el-rate v-model="form.rating" clearable :disabled="readOnly" />
</el-form-item>
</div>
<div class="step_bk" style="position: relative">
<icon_ai v-if="form.contentTranslated"
style="position: absolute; right: 50px; top: 20px; z-index: 9999">
</icon_ai>
<editorBox :ueditorData="form.content" @changeMsg="changeMsg" :readOnly="readOnly"></editorBox>
<translate v-if="form.newsInfoId && !isMobileByWidth()" :infoData="form" :key="form.id">
</translate>
</div>
<el-collapse accordion expand-icon-position="left"
style="margin-top:10px;padding: 0 30px;border-top: 0;">
<el-collapse-item name="1">
<template #title="{ isActive }">
<div :class="['title-wrapper', { 'is-active': isActive }]">
<text style="font-size: 16px; font-weight: bold">编辑审核备注</text>
</div>
</template>
<el-input v-model="form.revision" show-word-limit maxlength="300" style="margin-top: 10px"
:rows="5" type="textarea" placeholder="请输入编辑审核备注" max="300" :disabled="readOnly" />
</el-collapse-item>
</el-collapse>
</div>
</el-form>
<template #footer>
<div v-if="!readOnly" style="padding: 10px;display: flex;float: right;gap:10px;padding-right: 40px;">
<el-button @click="submit(1, false)">{{ isMobileByWidth() ? '保存并反馈' : '保存' }}</el-button>
<!-- <div v-if="Session.get('userInfoLocal').userType == '00'">
<el-button v-if="data?.status != 3" type="primary" @click="doApprovalFn()">送审</el-button>
<el-button v-else-if="data?.status == 3" type="primary" @click="doApprovalFn()">撤审</el-button>
</div> -->
<el-button
v-if="data?.status == 2 && !form.deleted && Session.get('userInfoLocal').userType == '01'"
type="primary" style="margin-right: 30px;margin-bottom: 20px;"
@click="doNewsPublishFn(data, 1)">撤稿</el-button>
<el-button type="primary" @click="doNewCheckFn(data)"
v-if="data?.status != 2 && !form.deleted && Session.get('userInfoLocal').userType == '02'">复审</el-button>
<el-button type="primary" @click="submit(0, false)"
v-if="data?.status != 2 && data?.status != -1 && !form.deleted && Session.get('userInfoLocal').userType != '02' && Session.get('userInfoLocal').userType == '01'">
<text>发布</text>
2025-08-03 13:41:47 +08:00
</el-button>
<el-button type="primary" @click="doApprovalFn(0, false)"
v-if="data?.status != 2 && (data?.status == -1 || data?.status == 1) && !form.deleted && Session.get('userInfoLocal').userType != '02'">
<text v-if="Session.get('userInfoLocal').userType == '00'">送审</text>
</el-button>
<el-button type="danger"
v-if="Session.get('userInfoLocal').userType == '02' && (data?.status == 3 || data?.status == 4)"
@click="doNewReturnFn(data)">退改</el-button>
2025-08-03 13:41:47 +08:00
<el-button type="danger" v-if="Session.get('userInfoLocal').userType == '01' && (data?.status == 4)"
@click="doNewReturnFn(data)">退改</el-button>
2025-08-03 13:41:47 +08:00
</div>
2025-08-03 13:41:47 +08:00
</template>
</el-drawer>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref, watch } from 'vue';
import { NextLoading } from '/@/utils/loading';
import { ElMessage, ElStep, ElMessageBox } from 'element-plus';
import { useRoute, useRouter } from 'vue-router';
import editorBox from '/@/components/editorBox.vue';
import uploadImage from '/@/components/upload/image.vue';
import { doNewRevoke, doNewSubmit, getTagSource, getTagConcept, getColumn, doNewsSave, doNewsCreatePublish, getNewsDetail, getColumnVIP, getIndustrySearch } from '/@/api/api';
import { FullScreen, Switch } from '@element-plus/icons-vue';
import { formatDate } from '/@/utils/formatTime';
import icon_ai from '/@/components/icon_ai.vue';
import translate from '/@/components/translate/translate.vue';
import { Session } from '/@/utils/storage';
import { isMobileByWidth } from '/@/utils/Utils'
import { doNewReturn, doNewCheck } from '/@/api/api';
2025-08-03 13:41:47 +08:00
const emit = defineEmits(['doNewsPublishFn', 'handleEditStatus', 'doDeleteNewsFn', 'getData']);
const props = defineProps({
data: {
type: Object,
default: () => { },
},
readOnly: {
type: Boolean,
default: false,
},
type: {
type: Number,
default: 0,
},
});
const model = defineModel();
const loading = ref(false);
const route = useRoute();
const router = useRouter();
const rules = reactive({
title: [{ required: true, message: '请填写文章标题', trigger: 'blur' }],
tagSourceSelect: [{ required: true, message: '请选择来源标签', trigger: 'blur' }],
// columnsVip: [{ required: true, message: '请选择VIP栏目', trigger: 'blur' }],
// tagDiySelect: [{ required: true, message: '请选择标签类型', trigger: 'blur' }],
});
const ruleFormRef = ref();
const form = ref({
type: 0,
column: {
earlyKnow: {
show: true,
},
showEverything: true,
everything: {
type: null,
},
},
rating: 0,
showEverything: true,
});
/********************step1 start *************************/
// 文章标题已经输入的字数
const titleTextNum = ref(0);
// 文章标题最大字数限制
const titleTextMax = ref(50);
function inputChange(text) {
titleTextNum.value = text.length;
}
/********************step1 end *************************/
/********************step2 start *************************/
function changeMsg(value) {
form.value.content = value.html;
form.value.contentText = value.text;
}
/********************step2 end *************************/
/********************step3 start *************************/
// 用户选择的栏目列表
// 栏目选择框数组
// const columnNumList = ref([{}, {}]);
// function addColumnNum() {
// columnNumList.value.push({});
// }
// // 删除新添加的栏目
// function delColunmNum(index: number) {
// columnNumList.value.splice(index, 1);
// }
// 栏目列表(接口获取的待选择列表)
// const columnList1 = ref([]);
// const columnList2 = ref([]);
const columnListVIp = ref([]);
// 获取VIP栏目
async function getColumnVIPFn() {
let { code, data } = await getColumnVIP({
page: 1,
size: 100000,
});
if (code == 200) {
columnListVIp.value = data;
}
}
// // 获取栏目
// async function getColumnFn(type) {
// let { code, data } = await getColumn({
// page: 1,
// size: 100000,
// type,
// });
// if (code == 200) {
// if (type == 0) {
// columnList1.value = data;
// } else {
// columnList2.value = data;
// }
// }
// }
// 用户选择的来源标签(单选)
// const tagSourceSelect = ref();
// 用户选择的标签类型(多选)
// const tagDiySelect = ref([]);
// 自定义标签列表
const tagDiyList = ref([]);
async function getTagDiyFn() {
let { code, data } = await getTagConcept();
if (code == 200) {
tagDiyList.value = data.map((item: any) => {
return {
name: `${item.parentName ? item.parentName + '-' : ''}${item.name ? item.name : ''}`,
id: item.id,
};
});
}
}
// 来源标签列表
const tagSourceList = ref([]);
async function getTagSourceFn() {
let { code, data } = await getTagSource({
page: 1,
size: 100000,
});
if (code == 200) {
tagSourceList.value = data;
}
}
// 行业分类列表
const industryList = ref([]);
async function getTndustryFn() {
let { code, data } = await getIndustrySearch({
page: 1,
size: 100000,
});
if (code == 200) {
industryList.value = data.map((item: any) => {
return {
name: `${item.primaryName || ''}${item.secondaryName ? '-' + item.secondaryName : ''}`,
id: item.id,
};
});
}
}
//展示方式列表
const showTypeList = ref([
{
name: '无特殊样式',
value: null,
},
{
name: '封面banner',
value: 0,
},
{
name: '标题banner',
value: 1,
},
]);
/********************step3 end *************************/
const canSubmit = ref(false);
watch(
() => form.value,
(val) => {
// console.log('🚀 ~ val:', val);
if (form.value.title && form.value.tagSourceSelect && form.value.columnsVip) {
canSubmit.value = true;
}
},
{
immediate: true,
deep: true,
}
);
// 退改
async function doNewReturnFn(item) {
let { code, data } = await doNewReturn({
id: item.id,
});
if (code == 200) {
ElMessage.success('操作成功');
goBack()
}
}
// 复审
async function doNewCheckFn(item) {
let { code, data } = await doNewCheck({
id: item.id,
});
if (code == 200) {
ElMessage.success('操作成功');
goBack()
}
}
2025-08-03 13:41:47 +08:00
const pictureTemp = ref([]);
/**
* 提交创建
* @param type 0发布 1保存
*/
async function submit(type, doNotBack) {
form.value.picture = pictureTemp.value[0]?.url;
if (!form.value.title) {
ElMessage.error('请输入标题');
tabIndex.value = 0;
await ruleFormRef.value.validate();
return;
}
if (!form.value.tagSourceSelect) {
ElMessage.error('请选择来源标签');
tabIndex.value = 2;
await ruleFormRef.value.validate();
return;
}
// if (!form.value.columnsVip) {
// ElMessage.error('请选择VIP栏目');
// tabIndex.value = 2;
// await ruleFormRef.value.validate();
// return;
// }
// if (!form.value.industrySelect) {
// ElMessage.error('请选择分行业类');
// tabIndex.value = 2;
// await ruleFormRef.value.validate();
// return;
// }
// if (form.value.column.earlyKnow.show && !form.value.summary) {
// ElMessage.success('选择展示在早知道栏目需要填写摘要');
// return;
// }
console.log('🚀 ~ submit ~ form.value:', form.value);
var tags = [];
if (form.value.tagSourceSelect) {
tagSourceList.value.forEach((item, index) => {
if (item.id == form.value.tagSourceSelect) {
tags.push(item);
}
});
}
if (form.value.tagDiySelect) {
form.value.tagDiySelect.forEach((item, index) => {
tagDiyList.value.forEach((childItem, childIndex) => {
if (item == childItem.id) {
tags.push(childItem);
}
});
});
}
let vip = {
id: form.value.columnsVipChild ? form.value.columnsVipChild : form.value.columnsVip,
type: form.value.showTypeVip,
};
let par = {
...form.value,
tag: {
source: form.value.tagSourceSelect,
fieldArr: form.value.tagDiySelect,
},
industries: form.value.industrySelect,
};
if (type == 0) {
let str = "请确认已审核该篇资讯,发布后前台将展示本资讯"
if (Session.get('userInfoLocal').userType == '00') {
str = '确认初审完成并提交复核?送审后不支持再次编辑';
if (props.data.status == 3) {
str = '确认撤销该篇资讯审核?';
} else {
str = '确认初审完成并提交复核?送审后不支持再次编辑';
}
} else if (Session.get('userInfoLocal').userType == '01') {
str = '请确认已审核该篇资讯,发布后前台将展示本资讯';
}
ElMessageBox.confirm(str, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
// 发布
let { code, data } = await doNewsCreatePublish(par);
if (code == 200) {
ElMessage.success('操作成功');
goBack();
}
})
.catch(() => { });
} else if (type == 1) {
// 保存
let { code, data } = await doNewsSave(par);
if (code == 200) {
ElMessage.success('操作成功');
if (doNotBack) {
} else {
goBack();
}
}
}
}
const tabIndex = ref(0);
function stepChange(index: number) {
tabIndex.value = index;
}
function goBack() {
model.value = false;
emit('getData');
}
const infoData = ref({});
// 获取新闻详情
async function getInfo() {
console.log('🚀 ~ getInfo ~ route:', route);
let { code, data } = await getNewsDetail({ id: props.data.id });
if (code == 200) {
infoData.value = data;
form.value = data;
if (form.value.deleted) {
emit('handleEditStatus', true);
}
// 如果返回的栏目为空,填上默认字段
if (!form.value.column) {
form.value.column = {
vip: null,
everything: null,
};
}
// 如果返回的栏目为空(早知道),填上默认字段
if (!form.value.column?.earlyKnow) {
form.value.column.earlyKnow = {
show: false,
};
}
form.value.columnsVip = data.column?.vip?.id;
// 媒体来源
form.value.tagSourceSelect = data.tag.source?.id;
// 付费类型,目前这个字段灭用
form.value.type = 0;
// 初始化上传的封面
if (form.value.picture) {
pictureTemp.value = [
{
url: form.value.picture,
},
];
}
// 初始化标题字数
titleTextNum.value = form.value.title.length;
// VIP展示方式
form.value.showTypeVip = data.column.vip?.type;
// 初始化概念标签(多选)
form.value.tagDiySelect = [];
data.tag?.fieldArr?.forEach((item) => {
form.value.tagDiySelect.push(item.id);
});
// 初始化行业分类(多选)
form.value.industrySelect = [];
data.industry.forEach((item) => {
form.value.industrySelect.push(item.id);
});
// 如果频道标签有子标签
if (data.column?.vip?.child) {
vipChange(data.column.vip.id);
form.value.columnsVipChild = data.column.vip.child.id;
}
// 初始化天下事字段
if (!form.value.column.everything) {
form.value.column.everything = {
type: null,
};
}
// if (form.value.showTypeVip == null) {
// form.value.showTypeVip = -1;
// }
// if (form.value.column?.everything?.type == null) {
// form.value.column.everything.type = -1;
// }
form.value.content = form.value.content.replace(/\n{3,}/g, '\n');
}
}
const isShowVipChild = ref(false);
const vipChildList = ref([]);
function vipChange(val) {
let selectItem = {};
columnListVIp.value.forEach((item, index) => {
if (item.id == val) {
selectItem = item;
}
});
// 如果有二级栏目
if (selectItem.children && selectItem.children.length > 0) {
isShowVipChild.value = true;
vipChildList.value = selectItem.children;
form.value.columnsVipChild = vipChildList.value[0].id;
} else {
isShowVipChild.value = false;
form.value.columnsVipChild = null;
}
}
// 页面加载时
onMounted(async () => {
console.log('🚀 ~ file: index.vue:13 ~ onMounted ~ onMounted:');
NextLoading.done();
await getColumnVIPFn();
//如果是编辑进来
if (props.type == 1) {
getInfo();
}
// 自定义标签列表
getTagDiyFn();
// 来源标签列表
getTagSourceFn();
// 栏目列表
// getColumnFn();
// 行业分类列表
getTndustryFn();
});
watch(
() => props.data,
(newVal, oldVal) => {
getInfo();
}
);
function handleEditStatus(val) {
emit('handleEditStatus', val);
}
function doNewsPublishFn(item, status) {
emit('doNewsPublishFn', item, status);
}
function doDeleteNewsFn(item) {
emit('doDeleteNewsFn', item);
}
// 放大
const isFull = ref(false);
function clickFull() {
isFull.value = !isFull.value;
}
async function doApprovalFn() {
let str = '确认初审完成并提交复核?送审后不支持再次编辑';
if (props.data.status == 3) {
str = '确认撤销该篇资讯审核?';
} else {
str = '确认初审完成并提交复核?送审后不支持再次编辑';
}
ElMessageBox.confirm(str, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
let { code } = props.data.status == 3 || props.data.status == 4 ? await doNewRevoke({
2025-08-03 13:41:47 +08:00
id: props.data.id,
}) : await doNewSubmit({
id: props.data.id,
});
if (code == 200) {
ElMessage.success('操作成功');
goBack();
}
})
.catch(() => { });
}
</script>
<style scoped lang="scss">
.drawer-container-mobile {
:deep(.modal-class) {
width: 100%;
inset: 0 0 0 auto !important;
transition: all 0.3s ease-in-out;
}
}
.drawer-container {
:deep(.modal-class) {
width: calc(100% - 560px);
inset: 0 0 0 auto !important;
transition: all 0.3s ease-in-out;
}
}
.drawer-container-100 {
:deep(.modal-class) {
width: calc(100% - 220px);
inset: 0 0 0 auto !important;
}
}
.step_bk {
padding: 20px 30px;
border-bottom: 1px solid #eee;
}
.drawer-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-right: 20px;
.btn {
display: flex;
align-items: center;
}
}
</style>