Compare commits

3 Commits

21 changed files with 1978 additions and 1107 deletions

View File

@@ -9,7 +9,13 @@
margin: 0;
padding: 0;
}
/* 禁止选中特定段落或组件 */
.no-select {
user-select: none; /* 标准语法 */
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE10+ */
}
html,
body,
#app,

View File

@@ -2055,22 +2055,31 @@ export default {
onAction: () => {
// 插入自定义表格到编辑器中
ed.setContent('');
const customCallback = ed.getParam('clear_custom_action');
if (typeof customCallback === 'function') {
customCallback(ed, vueInstance);
} else {
// 3. 如果没有自定义逻辑,执行默认逻辑
vueInstance.$emit('onClear');
}
}
});
ed.ui.registry.addButton('customBlue', {
text: 'Blue', // 按钮文本
className: 'custom-button-blue', // 添加自定义类
// shortcut: "Ctrl+J",
text: 'Blue',
className: 'custom-button-blue',
onAction: function () {
// 在选中的文本周围包裹 <blue> 标签
var selectedText = ed.selection.getContent();
// 必须获取带 HTML 的内容,否则里面的 em/i 标签在拼接前就丢了
var selectedText = ed.selection.getContent({ format: 'html' });
if (selectedText) {
// 这就是你想要的:直接外层套一个 blue
var wrappedText = `<blue>${selectedText}</blue>`;
// 使用 setContent 强行回写
ed.selection.setContent(wrappedText);
} else {
this.$message.error('请选择要添加蓝色的文本');
}
}
});

View File

@@ -18,7 +18,7 @@
</div>
<!-- {{ $t('system.title') }} -->
</div>
<div class="header-right">
<div class="header-right no-select">
<div class="header-user-con">
<div class="changelang">
<el-dropdown trigger="click" @command="chengelang">

View File

@@ -7,7 +7,7 @@
<img v-if="isProofreading" src="../../assets/img/Online Proofreading.png" alt="" height="24px" style="margin-left: 10px;margin-top: 4px;"/>
</div>
<div class="header-right">
<div class="header-right no-select">
<div class="header-user-con">
<div class="changelang">
<el-dropdown trigger="click" @command="chengelang">

View File

@@ -395,16 +395,19 @@ const en = {
typesettingType2: 'Horizontal A4',
typesettingType1: 'Vertical A4',
AnnotationList: 'Annotation List',
Annotations: 'Annotations',
Annotations: 'Comments',
exportWord: 'Export Word',
exportImg: 'Export PNG',
PaperRotation: 'Paper Rotation',
removeAnnotations: 'Are you sure you want to delete this Annotation?',
removeProofread: 'Are you sure to delete this suggestion?',
removeContent: 'Are you sure you want to delete this content?',
removeimg: 'Are you sure you want to delete this figure?',
removetable: 'Are you sure you want to delete this table?',
reContent: 'Are you sure you want to restore this content?',
uploadImageInfo: 'Figures can only upload files in JPG, JPEG, and PNG formats!',
selectComment: 'Please select the text to add annotations to!',
selectLinkText: 'Please select the target text content before associating it with a figure or table.',
selectWord: 'Please select only a single word',
selectOne: 'Please select only a single paragraph',
alreadyCommented: 'There are already annotations in the text, please select again!',
@@ -422,6 +425,7 @@ const en = {
MoveDown: 'Move Down',
jump: 'Locate',
editAssociation: 'Edit Association',
UnbindAssociation: 'Unbind Association',
},
pendingPayment: {
title: 'Title',
@@ -543,7 +547,24 @@ const en = {
state194: 'Proportion of references from JCR Q2',
state21: 'Probability of article being cited',
}
},
imageTask: {
"title": "Image Task Center",
"completed": "All Tasks Completed",
"preparing": "Preparing {total} images...",
"progress": "Progress: {current} / {total}",
"allDone": "Successfully processed {total} images",
"manualClose": "Tasks completed. Please close manually.",
"parsing": "Parsing...",
"uploading": "Uploading...",
"success": "Success",
"tooLarge": "Too Large (>1MB)",
"error": "Error: {msg}",
"imgLabel": "Img"
}
}

View File

@@ -389,9 +389,12 @@ const zh = {
removeAnnotations: '确定要删除这条批注吗?',
removeProofread: '确定要删除这条建议吗?',
removeContent: '确定要删除这条内容吗?',
removeimg: '确定要删除这张图片吗?',
removetable: '确定要删除这个表格吗?',
reContent: '确定要恢复这条内容吗?',
uploadImageInfo: 'Figures 只能上传 JPG、JPEG 和 PNG 格式的文件',
selectComment: '请选择要添加批注的文本',
selectLinkText: '执行图表关联前,请先选定目标文本内容',
selectWord:'请只选中单个单词!',
selectOne:'请只勾选单个段落!',
alreadyCommented:'文本中已有批注内容请重新选择',
@@ -406,8 +409,9 @@ const zh = {
BatchAddcontent: '批量添加内容',
MoveUp: '上移',
MoveDown: '下移',
jump: 'Locate',
editAssociation: 'Edit Association',
jump: '定位',
editAssociation: '编辑关联',
UnbindAssociation: '取消关联',
},
pendingPayment: {
title: 'Title',
@@ -525,7 +529,22 @@ const zh = {
state193: '参考文献JCR1区比例',
state194: '参考文献JCR2区比例',
state21: '文章被引用概率',
},
imageTask: {
"title": "图片任务中心",
"completed": "所有任务处理完毕",
"preparing": "正在准备 {total} 张图片...",
"progress": "处理进度: {current} / {total}",
"allDone": "已成功处理全部 {total} 张任务",
"manualClose": "任务已结束,请手动关闭窗口",
"parsing": "正在解析...",
"uploading": "正在上传...",
"success": "成功",
"tooLarge": "过大跳过(>1MB)",
"error": "失败: {msg}",
"imgLabel": "图"
}
}

View File

@@ -16,7 +16,7 @@
<div
class="right-side"
style="
width: 285px;
width: 260px;
float: left;
height: 100%;
background-color: #fff;
@@ -67,28 +67,30 @@
@handlePaperclip="handlePaperclip"
@addComment="addCommentSetting"
@goToComment="goToComment"
@edit="handleImageEdit"
@edit="handleFigureAndTableEdit"
@delete="handleFigureAndTableDelete"
@goToListComment="goToListComment"
style="width: 100%; height: 100%; padding: 0 0px; box-sizing: border-box; background-color: #fff"
>
<template slot="catalogue1">
<catalogue
v-if="Main_List.length > 0"
:content="Main_List"
:articleId="articleId"
ref="catalogue"
@goToListComment="goToListComment"
style="width: 100%; height: 100%; padding: 0 0px; box-sizing: border-box; background-color: #fff"
>
</catalogue>
</template>
</common-word-html-type-setting>
<input type="file" ref="fileInput" style="display: none" @change="handleFileChange" />
</div>
</div>
<div style="width: 100%; width: calc(100% - 285px); float: right; height: calc(100% - 0px); background-color: #e4e9ed">
<!-- <div class="toolbar">
<div class="toolbar_item" @click="handleImageAdd('img')">
<img src="@/assets/img/upload.png" style="object-fit: contain" />
<span>Add Figure </span>
</div>
<div class="toolbar_item" @click="handleTableAdd('table')">
<img src="@/assets/img/uploadTable.png" style="object-fit: contain" />
<span>Add Table </span>
</div>
</div> -->
<div style="width: 100%; width: calc(100% - 260px); float: right; height: calc(100% - 0px); background-color: #e4e9ed">
<common-word
:articleId="articleId"
:articleId="articleId"
v-if="htmlContent"
ref="commonWord"
:value="htmlContent"
@@ -96,13 +98,13 @@
:comments="comments"
:wordStyle="wordStyle"
@onDrop="onDrop"
@onLinkUnbind="handleUnbindLink"
@onLinkConfirm="handleConfirmLink"
@saveContent="saveContent"
@editComment="editComment"
@loaded="loadedWord"
@onEdit="onEdit"
@addContent="onAddContent"
@changeSort="changeSort"
@onDelete="onDelete"
@onDeletes="onDeletes"
@@ -234,7 +236,7 @@
<font style="color: #f56c6c; margin-right: 5px">*</font>
Table:
</span>
<common-table
:articleId="articleId"
@getContent="getContent"
@@ -415,7 +417,7 @@
</el-button>
</span>
</el-dialog>
<common-late-x v-if="showLateX" @close="showLateX = false" @save="saveLateX" :LateXInfo="LateXInfo"></common-late-x>
</div>
</template>
@@ -427,6 +429,7 @@ import Tiff from 'tiff.js';
import { mediaUrl } from '@/common/js/commonJS.js'; // 引入通用逻辑
import Tinymce from '@/components/page/components/Tinymce';
import bottomTinymce from '@/components/page/components/Tinymce';
import catalogue from '@/components/page/components/table/catalogue.vue';
export default {
data() {
return {
@@ -465,9 +468,11 @@ export default {
setPositioningImage: 'api/Preaccept/positioningImage',
removePositioningImage: 'api/Preaccept/removeImage',
addImage: 'api/Preaccept/addMainImage',
editImage: 'api/Preaccept/editMainImage',
addTable: 'api/Preaccept/addMainTable',
editTable: 'api/Preaccept/editMainTable'
editImage: 'api/Preaccept/editMainImage',
editTable: 'api/Preaccept/editMainTable',
deleteImage: 'api/Articlemain/removeMainImage',
deleteTable: 'api/Articlemain/removeMainTable'
},
wordStyle: `
// p {
@@ -552,7 +557,8 @@ export default {
},
components: {
Tinymce,
bottomTinymce
bottomTinymce,
catalogue
},
computed: {
combinedValue() {
@@ -582,20 +588,16 @@ export default {
}
},
async created() {
// await this.$api.post('api/Proofread/proofRead', {
// article_id: this.$route.query.id
// });
localStorage.removeItem('scrollPosition');
this.isShowEditComment();
this.getDate();
this.getCommentList();
// this.loadDictionary().catch(console.error);
},
mounted() {},
async activated() {
// await this.$api.post('api/Proofread/proofRead', {
// article_id: this.$route.query.id
// });
this.isShowEditComment();
this.getDate();
this.getCommentList();
@@ -705,38 +707,55 @@ export default {
this.addComment(content);
}
},
handleConfirmLink(selectedMedia) {
// selectedMedia 是你从 el-radio 列表中选中的那一项(包含 ami_id 或 amt_id
const targetId = selectedMedia.select.amt_id || selectedMedia.select.ami_id;
const type = selectedMedia.type; // 'table' 或 'figure'
const tagName = `my${type}`; // 生成 mytable 或 myfigure
// 获取当前段落原本的完整 HTML 内容
let originalContent = selectedMedia.linkData.content;
// 获取用户选中的纯文本片段
const label = selectedMedia.linkData.label;
handleUnbindLink(data) {
const { label, mainId, index, content } = data;
if (!label || !originalContent) return;
const unwrapTag = (str) => {
return str.replace(/<(myfigure|mytable)[^>]*>([\s\S]*?)<\/\1>/gi, '$2');
};
// --- 核心替换逻辑 ---
// 使用正则匹配选中的文字,并用自定义标签包裹它
// 注意:这里需要考虑 label 在 HTML 中可能被拆分的情况,简单处理可直接 replace
const replacement = `<${tagName} data-id="${targetId}">${label}</${tagName}>`;
// 执行替换(仅替换第一次匹配到的,防止全篇误伤)
const newContent = originalContent.replace(label, replacement);
// 2. 执行替换
// 我们只针对传入的这个特定的 label 进行剥壳
const strippedLabel = unwrapTag(label);
console.log('newContent at line 592:', newContent);
this.saveContent(newContent, selectedMedia.linkData.mainId);
// 3. 将 content 中的原标签替换为剥壳后的文字
// 使用 split/join 或者是精准 replace确保只替换这一处
const newContent = content.replace(label, strippedLabel);
this.saveContent(newContent, mainId);
},
handleConfirmLink(selectedMedia) {
const targetId = selectedMedia.select.amt_id || selectedMedia.select.ami_id;
const type = selectedMedia.type;
const tagName = `my${type}`;
let originalContent = selectedMedia.linkData.content;
const label = selectedMedia.linkData.label;
if (!label || !originalContent) return;
const isAlreadyTagged = label.startsWith('<my') && label.endsWith('>');
let newContent = '';
if (isAlreadyTagged) {
const updatedTag = label
.replace(/<my(figure|table)/, `<${tagName}`)
.replace(/data-id=".*?"/, `data-id="${targetId}"`)
.replace(/<\/my(figure|table)>$/, `</${tagName}>`);
newContent = originalContent.replace(label, updatedTag);
} else {
const replacement = `<${tagName} data-id="${targetId}">${label}</${tagName}>`;
newContent = originalContent.replace(label, replacement);
}
this.saveContent(newContent, selectedMedia.linkData.mainId);
},
async saveContent(content, am_id) {
const loading = this.$loading({
lock: true,
text: 'Loading...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
var that = this;
var str = content.replace(/^<p>\s*(.*?)\s*<\/p>$/, '$1').trim();
str = await that.$commonJS.decodeHtml(str);
await that.$api
@@ -746,10 +765,18 @@ this.saveContent(newContent, selectedMedia.linkData.mainId);
})
.then(async (res) => {
if (res.code == 0) {
loading.close();
this.editVisible = false;
this.getDate();
this.refreshCurrentContent('content', am_id, res.data);
this.getCommentList();
} else {
loading.close();
this.$message.error(res.msg);
}
})
.catch((err) => {
loading.close();
this.$message.error(err);
});
},
async saveContentList(content, am_id) {
@@ -759,7 +786,12 @@ this.saveContent(newContent, selectedMedia.linkData.mainId);
message: 'Please enter the content!'
});
}
const loading = this.$loading({
lock: true,
text: 'Loading...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
await this.$api
.post('api/Preaccept/addMoreRow', {
article_id: this.articleId,
@@ -768,10 +800,18 @@ this.saveContent(newContent, selectedMedia.linkData.mainId);
})
.then(async (res) => {
if (res.code == 0) {
loading.close();
this.addContentVisible = false;
this.getDate();
this.refreshCurrentContent('addMoreRow', am_id, res.data);
this.getCommentList();
} else {
loading.close();
this.$message.error(res.msg);
}
})
.catch((err) => {
loading.close();
this.$message.error(err);
});
},
isHeaderRow(rowIndex, table) {
@@ -877,8 +917,10 @@ this.saveContent(newContent, selectedMedia.linkData.mainId);
var am_id;
if (type == 'img') {
am_id = this.Main_List.find((item) => item.ami_id == id).am_id;
} else {
} else if (type == 'table') {
am_id = this.Main_List.find((item) => item.amt_id == id).am_id;
} else {
am_id = id;
}
if (am_id) {
this.goToComment(am_id);
@@ -903,8 +945,10 @@ this.saveContent(newContent, selectedMedia.linkData.mainId);
}
},
async onDelete(dataId) {
var that = this;
var dataInfo = this.Main_List.find((item) => item.am_id == dataId);
var dataIndex = this.Main_List.indexOf(dataInfo);
var type = '';
if (dataInfo.type == 1) {
type = 'img';
@@ -913,53 +957,70 @@ this.saveContent(newContent, selectedMedia.linkData.mainId);
} else if (dataInfo.type == 0) {
type = 'content';
}
var url = '';
switch (type) {
case 'table':
url = that.urlList.removePositioningTable;
break;
case 'img':
url = that.urlList.removePositioningImage;
break;
case 'content':
url = that.urlList.delete;
break;
}
if (dataInfo.type == 0 && dataInfo.content == '') {
await that.$api
.post(url, {
am_id: dataId
})
.then(async (res) => {
if (res.code == 0) {
setTimeout(() => {
that.Main_List.splice(dataIndex, 1);
await this.$confirm(this.$t('commonTable.removeContent'), 'Prompt', {
confirmButtonText: 'Submit',
cancelButtonText: 'Cancel',
type: 'warning'
})
.then(async () => {
var that = this;
var url = '';
switch (type) {
case 'table':
url = that.urlList.removePositioningTable;
break;
case 'img':
url = that.urlList.removePositioningImage;
break;
case 'content':
url = that.urlList.delete;
break;
}
await that.$api
.post(url, {
am_id: dataId
})
.then(async (res) => {
if (res.code == 0) {
setTimeout(() => {
that.getDate();
that.getCommentList();
if (type == 'img') {
that.$refs.commonWordHtmlTypeSetting.refresh('img');
} else {
that.$refs.commonWordHtmlTypeSetting.refresh('table');
}
that.$forceUpdate();
});
}
});
// this.Main_List.splice(
// this.Main_List.findIndex((item) => item.p_main_id == dataId),
// 1
// );
that.getCommentList();
that.$forceUpdate();
});
} else {
this.$message.error(res.msg);
}
})
.catch((err) => {
this.$message.error(err);
});
} else {
await this.$confirm(this.$t('commonTable.removeContent'), 'Prompt', {
confirmButtonText: 'Submit',
cancelButtonText: 'Cancel',
type: 'warning'
})
.catch((err) => {
console.log('err at line 466:', err);
});
.then(async () => {
await that.$api
.post(url, {
am_id: dataId
})
.then(async (res) => {
if (res.code == 0) {
setTimeout(() => {
that.Main_List.splice(dataIndex, 1);
that.$forceUpdate();
if (type == 'img') {
that.$refs.commonWordHtmlTypeSetting.refresh('removeImg', { ami_id: dataInfo.ami_id });
} else if (type == 'table') {
that.$refs.commonWordHtmlTypeSetting.refresh('removeTable', { amt_id: dataInfo.amt_id });
}
that.getCommentList();
that.$forceUpdate();
});
}
});
})
.catch((err) => {
console.log('err at line 466:', err);
});
}
},
async onDeletes(dataId) {
await this.$confirm(this.$t('commonTable.removeContent'), 'Prompt', {
@@ -978,8 +1039,8 @@ this.saveContent(newContent, selectedMedia.linkData.mainId);
setTimeout(() => {
that.getDate();
that.getCommentList();
that.$refs.commonWordHtmlTypeSetting.refresh('img');
that.$refs.commonWordHtmlTypeSetting.refresh('table');
that.$refs.commonWordHtmlTypeSetting.reload();
that.$forceUpdate();
});
}
@@ -994,20 +1055,40 @@ this.saveContent(newContent, selectedMedia.linkData.mainId);
});
},
async changeSort(type, id) {
var that = this;
const index = this.Main_List.findIndex((item) => item.am_id == id);
if(type=='up'&&index==0){
return
}
if(type=='down'&&index==this.Main_List.length-1){
return
}
const load = this.$loading({
lock: true,
text: 'Loading...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
await that.$api
.post(type == 'up' ? '/api/Preaccept/upArticleMain' : '/api/Preaccept/downArticleMain', {
am_id: id
})
.then(async (res) => {
if (res.code == 0) {
setTimeout(() => {
that.getDate();
that.getCommentList();
that.$forceUpdate();
});
load.close();
await this.refreshCurrentContent(`${type}ArticleMain`, id);
that.$forceUpdate();
} else {
load.close();
this.$message.error(res.msg);
}
})
.catch((err) => {
load.close();
this.$message.error(err);
});
},
async addCommentSetting(content) {
@@ -1103,7 +1184,7 @@ this.saveContent(newContent, selectedMedia.linkData.mainId);
if (data && data.remark) {
this.$alert(
`<p style="display:flex;"><img src="${this.remarkImageUrl}" alt="" style="width:20px;height:20px;margin-right:10px;"/><span style=" overflow-wrap: break-word;width:calc(100% - 50px)"> ${data.remark}</span></p>`,
'Annotations',
'Comments',
{
confirmButtonText: 'OK',
dangerouslyUseHTMLString: true, // 启用 HTML 渲染
@@ -1146,6 +1227,12 @@ this.saveContent(newContent, selectedMedia.linkData.mainId);
this.commentVisible = true;
},
async onEditTitle(data) {
const loading = this.$loading({
lock: true,
text: 'Loading...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
var url;
switch (data.value) {
case 0:
@@ -1168,13 +1255,64 @@ this.saveContent(newContent, selectedMedia.linkData.mainId);
am_id: data.mainId
})
.then(async (res) => {
this.getDate();
this.getCommentList();
if (res.code == 0) {
await this.refreshCurrentContent('content', data.mainId, res.data);
loading.close();
this.getCommentList();
}
})
.catch((err) => {
loading.close();
this.$message.error(err.msg);
});
},
async refreshCurrentContent(type, mainId, resData) {
const index = this.Main_List.findIndex((item) => item.am_id == mainId);
if (type == 'upArticleMain') {
if (index == 0) {
return;
}
const item = this.Main_List[index];
// 2. 从原位置删除
this.Main_List.splice(index, 1);
// 3. 插入到前一个位置
this.Main_List.splice(index - 1, 0, item);
return;
}
if (type == 'downArticleMain') {
if (index == this.Main_List.length - 1) {
return;
}
const item = this.Main_List[index];
// 2. 从原位置删除
this.Main_List.splice(index, 1);
// 3. 插入到前一个位置
this.Main_List.splice(index + 1, 0, item);
return;
}
if (index !== -1 && resData) {
var newData = Array.isArray(resData) ? resData : (resData ? [resData] : []);
const updatedItems= newData.map((item) => ({
...item,
checked: false,
getnum: 0,
}));
if (type == 'addRow') {
this.Main_List.splice(index + 1, 0, updatedItems[0]);
} else if (type == 'addMoreRow') {
this.Main_List.splice(index + 1, 0, ...updatedItems);
}else if(type=='positioningImg'||type=='positioningTable'){
this.Main_List.splice(index + 1, 0, ...updatedItems);
}
else {
this.$set(this.Main_List, index, updatedItems[0]);
}
} else {
console.warn(`Item with am_id ${mainId} not found.`);
}
this.$forceUpdate();
},
handlePaperclip() {
this.uploadWordTables = [];
this.$refs.fileInput.click();
@@ -1231,21 +1369,18 @@ this.saveContent(newContent, selectedMedia.linkData.mainId);
},
async onAddRow(mainId) {
await this.$api
.post(this.urlList.addRow, {
am_id: mainId,
article_id: this.articleId
})
.then(async (res) => {
if(res.code == 0){
this.getDate();
this.getCommentList();
}else{
if (res.code == 0) {
this.refreshCurrentContent('addRow', mainId, res.data);
this.getCommentList();
} else {
this.$message.error(res.msg);
}
})
.catch((err) => {
this.$message.error(err.msg);
@@ -1270,8 +1405,6 @@ this.saveContent(newContent, selectedMedia.linkData.mainId);
},
async executeProofreading(data) {
console.log('data at line 1225:', data);
await this.$api
.post(this.urlList.executeProofreading, {
am_id: data.am_id,
@@ -1292,8 +1425,6 @@ this.saveContent(newContent, selectedMedia.linkData.mainId);
});
},
async revokeProofreading(data) {
console.log('data at line 1225:', data);
await this.$api
.post(this.urlList.executeProofreading, {
am_id: data.am_id,
@@ -1313,13 +1444,8 @@ this.saveContent(newContent, selectedMedia.linkData.mainId);
this.$message.error(err.msg);
});
},
async editProofreading(data) {
console.log('data at line 1225:', data);
},
deleteProofreading(data, index) {
console.log('comment at line 480:', data);
},
async editProofreading(data) {},
deleteProofreading(data, index) {},
async cancelSolveComment(data) {
await this.$api
@@ -1363,7 +1489,69 @@ this.saveContent(newContent, selectedMedia.linkData.mainId);
this.lineStyle.visiTitle = 'Add Table';
this.threeVisible = true;
},
handleImageEdit(data, type) {
async handleFigureAndTableDelete(data, type) {
await this.$confirm(this.$t('commonTable.remove' + type), 'Prompt', {
confirmButtonText: 'Submit',
cancelButtonText: 'Cancel',
type: 'warning'
})
.then(async (res) => {
const loading = this.$loading({
lock: true,
text: 'Loading...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
if (type == 'img') {
this.$api
.post(this.urlList.deleteImage, {
ami_id: data.ami_id,
article_id: this.articleId
})
.then(async (res) => {
if (res.status == 1) {
loading.close();
this.$message.success(res.msg);
this.$refs.commonWordHtmlTypeSetting.replacement('img', data.ami_id);
} else {
loading.close();
this.$message.error(res.msg);
}
})
.catch((err) => {
loading.close();
this.$message.error(err.msg);
});
}
if (type == 'table') {
this.$api
.post(this.urlList.deleteTable, {
amt_id: data.amt_id,
article_id: this.articleId
})
.then(async (res) => {
if (res.status == 1) {
loading.close();
this.$refs.commonWordHtmlTypeSetting.replacement('table', data.amt_id);
} else {
loading.close();
this.$message.error(res.msg);
}
})
.catch((err) => {
loading.close();
this.$message.error(err.msg);
});
}
})
.catch((err) => {
// this.$message.error(err.msg);
});
},
handleFigureAndTableEdit(data, type) {
if (type == 'img') {
var extension = data.url.split('.').pop().toLowerCase();
this.picStyle = {};
@@ -1373,17 +1561,15 @@ this.saveContent(newContent, selectedMedia.linkData.mainId);
} else if (type == 'table') {
this.lineStyle = {};
this.lineStyle1 = {};
// 1. 提取处理逻辑
const formattedData = {
...data,
table: JSON.parse(data.table_data),
// 如果 data 中已经包含了 html_data, note, title且不需要特殊处理
// 解构赋值 (...data) 其实已经把它们带进来了。
};
// 1. 提取处理逻辑
const formattedData = {
...data,
table: JSON.parse(data.table_data)
};
// 2. 统一赋值
this.lineStyle = formattedData;
this.lineStyle1 = { ...formattedData }; // 使用浅拷贝确保两个变量指向不同引用(如果需要独立修改)
// 2. 统一赋值
this.lineStyle = formattedData;
this.lineStyle1 = { ...formattedData }; // 使用浅拷贝确保两个变量指向不同引用(如果需要独立修改)
this.lineStyle.visiTitle = 'Edit Table';
this.threeVisible = true;
}
@@ -1448,36 +1634,40 @@ this.lineStyle1 = { ...formattedData }; // 使用浅拷贝确保两个变量指
this.currentId = dataId;
},
async onDrop(event, dataId) {
const loading = this.$loading({
lock: true,
text: 'Loading...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
if (event.dataTransfer.getData('image')) {
const draggedImage = JSON.parse(event.dataTransfer.getData('image'));
const draggedImageIndex = JSON.parse(event.dataTransfer.getData('imageIndex'));
this.$nextTick(async () => {
await this.$api
.post(this.urlList.setPositioningImage, {
am_id: dataId,
ami_id: draggedImage.ami_id
})
.then(async (res) => {
if (res.code == 0) {
this.getDate();
if (res.code == 0) {loading.close()
this.refreshCurrentContent('positioningImg',dataId, res.data);
this.$nextTick(() => {
this.$refs.commonWordHtmlTypeSetting.refresh('img');
this.$refs.commonWordHtmlTypeSetting.refresh('positioningImg', res.data);
});
this.$forceUpdate();
} else {
} else {loading.close()
this.$message.error(res.msg);
}
})
.catch((err) => {
.catch((err) => {loading.close()
this.$message.error(err.msg);
});
});
// this.Main_List.splice(index + 1, 0, draggedImage);
// this.$nextTick(() => {
// this.$refs.commonWordHtmlTypeSetting.changeIsHidden(draggedImageIndex, true, 'img');
// });
} else {
const draggedtable = JSON.parse(event.dataTransfer.getData('table'));
@@ -1488,28 +1678,27 @@ this.lineStyle1 = { ...formattedData }; // 使用浅拷贝确保两个变量指
amt_id: draggedtable.amt_id
})
.then(async (res) => {
if (res.code == 0) {
this.getDate();
if (res.code == 0) {loading.close()
this.refreshCurrentContent('positioningTable',dataId, res.data);
this.$nextTick(() => {
this.$refs.commonWordHtmlTypeSetting.refresh('table');
this.$refs.commonWordHtmlTypeSetting.refresh('positioningTable', res.data);
});
this.$forceUpdate();
} else {
loading.close()
this.$message.error(res.msg);
}
})
.catch((err) => {
loading.close()
this.$message.error(err.msg);
});
});
// const draggedtableIndex = JSON.parse(event.dataTransfer.getData('tableIndex'));
// this.Main_List.splice(index + 1, 0, draggedtable);
// console.log('this.Main_List.splice at line 447:', this.Main_List);
}
// this.getWord();
},
getCommentList() {
this.$api
@@ -1522,13 +1711,10 @@ this.lineStyle1 = { ...formattedData }; // 使用浅拷贝确保两个变量指
});
},
getWord() {
this.htmlContent = 'true';
},
// 获取数据
async getDate() {
this.imagesList = [];
let urlLInk = '';
let urlTask = {};
@@ -1548,7 +1734,7 @@ this.lineStyle1 = { ...formattedData }; // 使用浅拷贝确保两个变量指
.post(urlLInk, urlTask)
.then(async (res) => {
if (res.code == 0) {
this.Main_List = res.data.list.map(e=>{
this.Main_List = res.data.list.map((e) => {
e.checked = false;
return e;
});
@@ -1556,14 +1742,12 @@ this.lineStyle1 = { ...formattedData }; // 使用浅拷贝确保两个变量指
for (let i = 0; i < this.Main_List.length; i++) {
this.Main_List[i].text = this.Main_List[i].content;
this.Main_List[i].getnum = 0;
// this.Main_List[i].checked = false;
}
// setTimeout(async () => {
this.$nextTick(async () => {
await this.getWord();
if (this.$refs.catalogue) {
await this.$refs.catalogue.getCatalogueList();
}
loading.close();
});
// }, 1000);
@@ -1659,8 +1843,6 @@ this.lineStyle1 = { ...formattedData }; // 使用浅拷贝确保两个变量指
// 确定保存图片
async savePic() {
var str = this.picStyle1.note ? await this.$commonJS.decodeHtml(this.picStyle1.note) : '';
var titleStr = this.picStyle1.title ? await this.$commonJS.decodeHtml(this.picStyle1.title) : '';
@@ -1672,6 +1854,12 @@ this.lineStyle1 = { ...formattedData }; // 使用浅拷贝确保两个变量指
this.$message.error('Please enter a title');
return;
}
const loading = this.$loading({
lock: true,
text: 'Loading...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
if (this.picStyle.visiTitle == 'Edit Figure') {
this.$api
.post(this.urlList.editImage, {
@@ -1682,20 +1870,22 @@ this.lineStyle1 = { ...formattedData }; // 使用浅拷贝确保两个变量指
})
.then((res) => {
if (res.code == 0) {
loading.close();
this.$message.success('Successfully edit Figure!');
this.pictVisible = false;
this.$refs.commonWordHtmlTypeSetting.refresh('editImg', res.data.image?{...res.data.image,has_selected:1}:res.data);
this.refreshCurrentContent('editImg',res.data.am_id, res.data);
this.$nextTick(() => {
this.getDate();
this.getCommentList();
this.$forceUpdate();
});
this.$refs.commonWordHtmlTypeSetting.refresh('img');
} else {
loading.close();
this.$message.error(res.msg);
}
})
.catch((err) => {
loading.close();
this.$message.error(err);
});
} else {
@@ -1708,15 +1898,19 @@ this.lineStyle1 = { ...formattedData }; // 使用浅拷贝确保两个变量指
})
.then((res) => {
if (res.code == 0) {
loading.close();
this.$message.success('Successfully Add Figure!');
this.pictVisible = false;
this.getDate();
this.$refs.commonWordHtmlTypeSetting.refresh('img');
this.$refs.commonWordHtmlTypeSetting.refresh('addImg', res.data);
} else {
loading.close();
this.$message.error(res.msg);
}
})
.catch((err) => {
loading.close();
this.$message.error(err);
});
}
@@ -1766,18 +1960,18 @@ this.lineStyle1 = { ...formattedData }; // 使用浅拷贝确保两个变量指
}
if (strTitle != '') {
strTitle = await this.$commonJS.decodeHtml(strTitle);
}else{
} else {
this.$message.error('Please enter a title');
return;
}
if (content && cleanedTableList && cleanedTableList.length > 0) {
const loading = this.$loading({
lock: true,
text: 'Loading...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
lock: true,
text: 'Loading...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
if (this.lineStyle.visiTitle == 'Edit Table') {
this.$api
.post(this.urlList.editTable, {
@@ -1789,21 +1983,22 @@ this.lineStyle1 = { ...formattedData }; // 使用浅拷贝确保两个变量指
})
.then((res) => {
if (res.code == 0) {
loading.close();
this.$message.success('Successfully edit Table!');
this.threeVisible = false;
setTimeout(() => {
this.getDate();
this.$refs.commonWordHtmlTypeSetting.refresh('editTable', res.data.table?{...res.data.table,has_selected:1}:res.data);
this.refreshCurrentContent('editTable',res.data.am_id, res.data);
this.getCommentList();
this.$refs.commonWordHtmlTypeSetting.refresh('table');
});
} else {
this.$message.error(res.msg);
loading.close();
this.$message.error(res.msg);
}
})
.catch((err) => {
this.$message.error(err);
loading.close();
this.$message.error(err);
});
} else {
this.$api
@@ -1816,14 +2011,14 @@ this.lineStyle1 = { ...formattedData }; // 使用浅拷贝确保两个变量指
})
.then((res) => {
if (res.code == 0) {
loading.close();
this.$message.success('Successfully Add Table!');
this.threeVisible = false;
this.getDate();
loading.close();
this.$refs.commonWordHtmlTypeSetting.refresh('table');
this.$refs.commonWordHtmlTypeSetting.refresh('addTable', res.data);
} else {
this.$message.error(res.msg);
loading.close();
this.$message.error(res.msg);
}
})
.catch((err) => {
@@ -1940,7 +2135,7 @@ this.lineStyle1 = { ...formattedData }; // 使用浅拷贝确保两个变量指
this.$message.error('Picture size cannot exceed 20M!');
return false;
}
const isValidFormat = ['image/jpeg', 'image/png', 'image/tiff'].includes(file.type);
if (!isValidFormat) {
this.$message.error(this.$t('commonTable.uploadImageInfo'));
@@ -2337,5 +2532,4 @@ this.lineStyle1 = { ...formattedData }; // 使用浅拷贝确保两个变量指
background-color: #0066990d;
/* display: block !important; */
}
</style>

View File

@@ -1103,7 +1103,7 @@ export default {
if (data && data.remark) {
this.$alert(
`<p style="display:flex;"><img src="${this.remarkImageUrl}" alt="" style="width:20px;height:20px;margin-right:10px;"/><span style=" overflow-wrap: break-word;width:calc(100% - 50px)"> ${data.remark}</span></p>`,
'Annotations',
'Comments',
{
confirmButtonText: 'OK',
dangerouslyUseHTMLString: true, // 启用 HTML 渲染

View File

@@ -124,7 +124,7 @@
:value="tradition"
class="paste-area text-container"
:toolbar="[
'bold italic |customBlue removeBlue|myuppercase myuppercasea Line|subscript superscript|clearButton'
'bold italic |customBlue removeBlue|myuppercase myuppercasea Line MoreSymbols|subscript superscript|clearButton'
]"
style="
line-height: 12px;
@@ -155,7 +155,7 @@
:value="mhooStr"
class="paste-area text-container"
:toolbar="[
'bold italic |customBlue removeBlue|myuppercase myuppercasea Line|subscript superscript|clearButton'
'bold italic |customBlue removeBlue|myuppercase myuppercasea Line MoreSymbols|subscript superscript|clearButton'
]"
style="
line-height: 12px;
@@ -199,7 +199,7 @@
:value="abstract"
class="paste-area text-container"
:toolbar="[
'bold italic |customBlue removeBlue|myuppercase myuppercasea Line|subscript superscript|clearButton'
'bold italic |customBlue removeBlue|myuppercase myuppercasea Line MoreSymbols|subscript superscript|clearButton'
]"
style="
line-height: 12px;
@@ -688,39 +688,92 @@
<div class="bor_style_onli">
<h4>{{ tabsList[5].name }}</h4>
<div style="font-size: 14px">
<div style="margin: 30px 0 40px 0">
Choose Template :
<el-select
v-model="shuTter.board"
placeholder="Please select a template..."
@change="select_tem($event)"
style="width: 225px"
>
<el-option v-for="item in fol_low" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
<el-button type="primary" plain style="width: 150px; margin: 0 5px 0 15px" @click="EstaBlish">
<i class="el-icon-document"></i>
Create Manuscript
</el-button>
</div>
<div style="margin: 30px 0 40px 0">
<h5 style="font-size: 16px; margin: 0 0 30px 0; letter-spacing: -0.5px">Download list</h5>
<p v-if="DLfileList == ''" style="color: #666; font-size: 14px">No Manuscript</p>
<div v-for="(item, index) in DLfileList" target="_blank" class="load_pdf">
<img src="../../assets/img/icon_0.png" />
<span style="color: #333">Typesetting {{ index + 1 }}</span>
<span style="margin-left: 40px; color: #888; font-size: 13px"
>Time : {{ modifDate(item.ctime * 1000) }}</span
>
<a :href="'/tsfile/' + item.url" target="_blank" style="margin-left: 40px">
<i class="el-icon-download" style="color: #66b1ff; font-weight: bold"></i>
</a>
<div style="margin: 30px 0px 0 0">
<h5 style="font-size: 16px; margin: 0 0 30px 0; letter-spacing: -0.5px">Improve information</h5>
<!-- 时间Doi简介信息 -->
<el-form ref="Abs_Form" :model="detailMes" :rules="rules" label-width="150px" style="margin-top: 30px">
<el-form-item label="Doi :" prop="doi">
<span>https://doi.org/10.53388/</span>
<el-input v-model="detailMes.doi" style="margin-left: 10px; width: 325px"> </el-input>
</el-form-item>
<el-form-item label="Date of publication :" prop="pubDate">
<el-date-picker
v-model="detailMes.pubDate"
value-format="yyyy-MM-dd"
type="date"
placeholder=""
@blur="dateBlur(detailMes.pubDate)"
style="width: 100%"
>
</el-date-picker>
</el-form-item>
</el-form>
<div style="text-align: center; margin: 0px 0 0 0">
<el-button type="primary" @click="ZsSaveAbs" class="save-btn" style="margin-top: 10px;">
<i class="el-icon-check"></i>
Save Essential Information
</el-button>
</div>
<!-- <p @click="allLoad()">
<i class="el-icon-download" style="color: #66b1ff;font-weight: bold;"></i>123
</p> -->
</div>
<div style="height: 1px; background-color: #c5e1f1; width: 100%; margin: 80px 0 40px"></div>
<div style="display: flex; /* 开启 flex 布局 */
justify-content: space-between;
padding: 20px;">
<div class="left-panel">
<div style="margin: 0 0 40px 0">
Choose Template :
<el-select v-model="shuTter.board" placeholder="Please select a template..." @change="select_tem($event)" style="width: 225px">
<el-option v-for="item in fol_low" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
<el-button type="primary" plain style="width: 150px; margin: 0 5px 0 15px" @click="EstaBlish">
<i class="el-icon-document"></i> Create Manuscript
</el-button>
</div>
<div style="margin: 30px 0 40px 0">
<h5 style="font-size: 16px; margin: 0 0 30px 0; letter-spacing: -0.5px">Download list</h5>
<p v-if="DLfileList.length == 0" style="color: #666; font-size: 14px">No Manuscript</p>
<div v-for="(item, index) in DLfileList" :key="index" class="load_pdf">
<img src="../../assets/img/icon_0.png" />
<span style="color: #333">Typesetting {{ index + 1 }}</span>
<span style="margin-left: 40px; color: #888; font-size: 13px">Time : {{ modifDate(item.ctime * 1000) }}</span>
<a :href="'/tsfile/' + item.url" target="_blank" style="margin-left: 40px">
<i class="el-icon-download" style="color: #66b1ff; font-weight: bold"></i>
</a>
</div>
</div>
</div>
<div class="right-panel">
<h5 style="font-size: 14px; margin: 0 0 40px 0;">
<el-button
type="success"
icon="el-icon-printer"
style="width: 150px; height: 32px; font-size: 12px;margin-right: 10px;"
:loading="isGenerating"
@click="generatePDF"
>
{{ isGenerating ? 'Generating PDF...' : 'Generate PDF' }}
</el-button>
<span>Click the button below to start high-quality PDF typesetting.</span>
</h5>
<div style="margin: 30px 0 40px 0">
<h5 style="font-size: 16px; margin: 0 0 30px 0; letter-spacing: -0.5px;font-weight: bold;">Download list</h5>
<p v-if="DLfileList.length == 0" style="color: #666; font-size: 14px">No Manuscript</p>
<div v-for="(item, index) in DLfileList" :key="index" class="load_pdf">
<img src="../../assets/img/icon_0.png" />
<span style="color: #333">Typesetting {{ index + 1 }}</span>
<span style="margin-left: 40px; color: #888; font-size: 13px">Time : {{ modifDate(item.ctime * 1000) }}</span>
<a :href="'/tsfile/' + item.url" target="_blank" style="margin-left: 40px">
<i class="el-icon-download" style="color: #66b1ff; font-weight: bold"></i>
</a>
</div>
</div>
</div>
</div>
<div style="height: 1px; background-color: #c5e1f1; width: 100%; margin: 10px 0"></div>
<div style="margin: 30px 0 30px 0">
<h5 style="font-size: 16px; margin: 0 0 30px 0; letter-spacing: -0.5px">Upload the final typeset file</h5>
@@ -767,34 +820,7 @@
</el-upload>
</div>
</div>
<div style="height: 1px; background-color: #c5e1f1; width: 100%; margin: 10px 0"></div>
<div style="margin: 30px 0px 0 0">
<h5 style="font-size: 16px; margin: 0 0 30px 0; letter-spacing: -0.5px">Improve information</h5>
<!-- 时间Doi简介信息 -->
<el-form ref="Abs_Form" :model="detailMes" :rules="rules" label-width="150px" style="margin-top: 30px">
<el-form-item label="Doi :" prop="doi">
<span>https://doi.org/10.53388/</span>
<el-input v-model="detailMes.doi" style="margin-left: 10px; width: 325px"> </el-input>
</el-form-item>
<el-form-item label="Date of publication :" prop="pubDate">
<el-date-picker
v-model="detailMes.pubDate"
value-format="yyyy-MM-dd"
type="date"
placeholder=""
@blur="dateBlur(detailMes.pubDate)"
style="width: 100%"
>
</el-date-picker>
</el-form-item>
</el-form>
<div style="text-align: center; margin: 25px 0 0 0">
<el-button type="primary" @click="ZsSaveAbs" class="save-btn">
<i class="el-icon-check"></i>
Save Essential Information
</el-button>
</div>
</div>
</div>
</div>
</div>
@@ -994,6 +1020,8 @@ import Tinymce from '@/components/page/components/Tinymce';
export default {
data() {
return {
isGenerating: false, // 按钮加载状态
percentage: 0, // 进度条
finalReview: null,
baseUrl: this.Common.baseUrl,
mediaUrl: this.Common.mediaUrl,
@@ -1452,6 +1480,42 @@ export default {
this.getWorldPdf();
},
methods: {
async generatePDF() {
// 1. 验证是否选择了模板
if (!this.shuTter.board) {
this.$message.warning("Please select a template first!");
return;
}
this.isGenerating = true;
this.percentage = 0;
try {
// 模拟进度条逻辑(如果是真实接口,可以根据后端返回或者固定时长模拟)
let timer = setInterval(() => {
if (this.percentage < 90) this.percentage += 10;
}, 500);
// 调用你的生成接口 (假设是 Establish 的升级版)
// const res = await api.createPDF({ board: this.shuTter.board });
// 模拟接口请求耗时
await new Promise(resolve => setTimeout(resolve, 3000));
this.percentage = 100;
clearInterval(timer);
this.$message.success("PDF Generated Successfully!");
// 自动刷新左侧的下载列表
// this.getDLFileList();
} catch (error) {
this.$message.error("Generation failed, please try again.");
} finally {
this.isGenerating = false;
}
},
getArticleFinal(id) {
// api/Finalreview/getRecord
this.$api
@@ -3071,6 +3135,37 @@ export default {
height: 6vh;
min-height: 60px;
}
.left-panel {
flex: 1; /* 左侧占据剩余空间 */
margin-right: 40px;
border-right: 1px dashed #eee; /* 加一条虚线分隔线 */
padding-right: 20px;
}
.right-panel {
width: 45%; /* 右侧固定宽度 */
}
.right-panel h5 {
font-weight: normal;
}
.console-card {
background: #f9f9f9;
border-radius: 8px;
padding: 20px;
border: 1px solid #ebeef5;
}
.load_pdf {
display: flex;
align-items: center;
margin-bottom: 15px;
padding: 10px;
transition: background 0.3s;
}
.load_pdf:hover {
background: #f5f7fa;
}
</style>
<style>
.handle-box {
@@ -3660,4 +3755,5 @@ export default {
padding: 20px 20px 0 !important;
font-weight: bold !important;
}
</style>

View File

@@ -592,7 +592,7 @@ export default {
if (data && data.remark) {
this.$alert(
`<p style="display:flex;"><img src="${this.remarkImageUrl}" alt="" style="width:20px;height:20px;margin-right:10px;"/><span style=" overflow-wrap: break-word;width:calc(100% - 50px)"> ${data.remark}</span></p>`,
'Annotations',
'Comments',
{
confirmButtonText: 'OK',
dangerouslyUseHTMLString: true, // 启用 HTML 渲染

View File

@@ -67,6 +67,10 @@ export default {
},
data() {
return {
uploadNotifications: {},
totalUploadImages: 0,
uploadedImageCount: 0,
uploadNotificationInstance: null, // 全局通知实例
baseUrl: this.Common.baseUrl,
mediaUrl: this.Common.mediaUrl,
typesettingType: 1,
@@ -136,6 +140,15 @@ export default {
mounted() {
this.typesettingType = 1;
this.initTinymce();
const style = document.createElement('style');
style.innerHTML = `
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes slideIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
.status-spinning { display: inline-block; animation: spin 1s linear infinite; }
.upload-item-animate { animation: slideIn 0.3s ease-out forwards; }
.el-progress-bar__inner { transition: width 0.4s ease-in-out !important; }
`;
document.head.appendChild(style);
},
activated() {
this.typesettingType = 1;
@@ -145,9 +158,139 @@ export default {
this.destroyTinymce();
},
methods: {
onClear() {
if (this.uploadNotificationInstance) {
this.uploadNotificationInstance.close(); this.uploadNotificationInstance = null;
this.uploadedImageCount = 0;
this.totalUploadImages = 0;}
},
updateUploadProgressNotification(imgIndex, status = 'processing', message = '') {
// 快捷调用 $t
const t = (key, params) => this.$t(`imageTask.${key}`, params);
if (!this.uploadNotificationInstance) {
this.uploadNotificationInstance = this.$notify({
title: t('title'),
dangerouslyUseHTMLString: true,
message: `
<div id="image-upload-container" style="width: 300px;">
<p id="total-status-text" style="margin: 0 0 12px 0; font-size: 14px; color: #606266; font-weight: 500;">
${t('preparing', { total: this.totalUploadImages })}
</p>
<div class="el-progress el-progress--line" style="margin-bottom: 15px;">
<div class="el-progress-bar">
<div class="el-progress-bar__outer" style="height: 10px; background: #ebeef5; border-radius: 5px;">
<div class="el-progress-bar__inner" style="width: 0%; background: #409EFF; transition: width 0.4s ease-out;"></div>
</div>
</div>
<div class="el-progress__text" style="font-size: 12px; font-weight: bold; margin-top: 2px;">0%</div>
</div>
<ul id="image-individual-status" style="max-height: 400px; overflow-y: auto; padding: 0; margin: 0; list-style: none; border-top: 1px solid #f0f0f0; padding-top: 10px;"></ul>
<p id="manual-close-tip" style="display:none; margin-top:15px; font-size:12px; color:#909399; text-align:right; border-top: 1px dashed #eee; padding-top: 8px;">
${t('manualClose')}
</p>
</div>`,
duration: 0,
position: 'bottom-left',
onClose: () => {
this.uploadNotificationInstance = null;
this.uploadedImageCount = 0;
this.totalUploadImages = 0;
}
});
}
const listContainer = document.getElementById('image-individual-status');
if (!listContainer) return;
let itemEl = document.getElementById(`img-status-${imgIndex}`);
if (!itemEl) {
itemEl = document.createElement('li');
itemEl.id = `img-status-${imgIndex}`;
itemEl.className = 'upload-item-animate';
itemEl.style.cssText =
'font-size: 13px; padding: 8px 5px; display: flex; align-items: center; border-bottom: 1px solid #fafafa;';
itemEl.innerHTML = `
<span style="width: 55px; color: #909399; font-weight: bold;">${t('imgLabel')} ${imgIndex + 1}:</span>
<span class="status-icon" style="margin: 0 10px;"></span>
<span class="status-text" style="flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #666;">...</span>
`;
listContainer.appendChild(itemEl);
listContainer.scrollTop = listContainer.scrollHeight;
}
const statusIcon = itemEl.querySelector('.status-icon');
const statusText = itemEl.querySelector('.status-text');
let isFinished = false;
if (statusIcon && statusText) {
switch (status) {
case 'processing':
statusIcon.innerHTML = '<span class="status-spinning">🔄</span>';
statusText.innerText = t('parsing');
break;
case 'uploading':
statusIcon.innerHTML = '<span class="status-spinning">⬆️</span>';
statusText.innerText = t('uploading');
break;
case 'tooLarge':
statusIcon.innerHTML = '⚠️';
statusText.innerText = t('tooLarge');
itemEl.style.color = '#E6A23C';
isFinished = true;
break;
case 'success':
statusIcon.innerHTML = '✅';
statusText.innerText = t('success');
itemEl.style.color = '#67C23A';
isFinished = true;
break;
case 'fail':
statusIcon.innerHTML = '❌';
statusText.innerText = t('error', { msg: message });
itemEl.style.color = '#F56C6C';
isFinished = true;
break;
}
}
if (isFinished) {
this.uploadedImageCount++;
const progressPercent = Math.min(100, Math.round((this.uploadedImageCount / this.totalUploadImages) * 100));
const barInner = this.uploadNotificationInstance.$el.querySelector('.el-progress-bar__inner');
const barText = this.uploadNotificationInstance.$el.querySelector('.el-progress__text');
const statusTotalText = document.getElementById('total-status-text');
if (barInner) barInner.style.width = `${progressPercent}%`;
if (barText) barText.innerText = `${progressPercent}%`;
if (statusTotalText) {
statusTotalText.innerText = t('progress', { current: this.uploadedImageCount, total: this.totalUploadImages });
}
if (this.uploadedImageCount >= this.totalUploadImages) {
// 更新标题为完成状态
const titleEl = this.uploadNotificationInstance.$el.querySelector('.el-notification__title');
if (titleEl) titleEl.innerText = t('completed');
if (barInner) barInner.style.background = '#67C23A';
const tipEl = document.getElementById('manual-close-tip');
if (tipEl) tipEl.style.display = 'block';
if (statusTotalText) {
statusTotalText.innerText = t('allDone', { total: this.totalUploadImages });
}
}
}
},
openLatexEditor(data) {
this.$emit('openLatexEditor', data);
console.log('at line 254:', '打开数字公式');
},
handleSubmit() {
this.$refs.uploadImage.handleSubmit();
@@ -210,10 +353,10 @@ export default {
const xhr = new XMLHttpRequest();
const formData = new FormData();
// 按照你截图中的参数名,这里假设是 'file'
formData.append('mainImage', blob, `word_img_${index}.png`);
formData.append('file_name', blob, `word_img_${index}.png`);
formData.append('article_id', this.articleId);
xhr.withCredentials = false;
xhr.open('POST', _this.baseUrl + 'api/Preaccept/up_img_mainImage');
xhr.open('POST', _this.baseUrl + 'api/Articlemain/uploadTableImage');
xhr.onload = function () {
if (xhr.status !== 200) {
console.error('HTTP Error: ' + xhr.status);
@@ -221,9 +364,9 @@ export default {
}
try {
const json = JSON.parse(xhr.responseText);
if (json.code === 0) {
if (json.status == 1) {
// 2. 拼接服务器返回的 URL
const finalUrl = _this.mediaUrl + 'articleImage/' + json.data.upurl;
const finalUrl = _this.mediaUrl + 'articleTableImage/' + json.data;
// 3. 找到对应的加载中占位图并替换
const doc = tinymce.activeEditor.getDoc();
const placeholder = doc.querySelector(`img[data-idx="${index}"]`);
@@ -231,6 +374,8 @@ export default {
placeholder.src = finalUrl;
placeholder.removeAttribute('data-idx'); // 任务完成,移除标记
}
} else {
_this.removePlaceholder(index);
}
} catch (e) {
console.error('解析响应失败', e);
@@ -240,6 +385,14 @@ export default {
xhr.send(formData);
},
removePlaceholder(idx) {
const doc = tinymce.activeEditor.getDoc();
const placeholder = doc.querySelector(`img[data-idx="${idx}"]`);
if (placeholder) {
placeholder.remove(); // 直接从 DOM 中删除这个 img 标签
this.$message.error('Upload failed. Removing temporary placeholder.');
}
},
// 辅助工具Base64 转 Blob
dataURLtoBlob(dataurl) {
const arr = dataurl.split(','),
@@ -258,6 +411,10 @@ export default {
var _this = this;
window.tinymce.init({
..._this.tinymceOtherInit,
extended_valid_elements: 'blue[*]',
custom_elements: 'blue',
valid_children: '+blue[#text|i|em|b|strong|span],+body[blue],+p[blue]',
inline: false, // 使用 iframe 模式
selector: `#${this.tinymceId}`,
// noneditable_regexp: "/<wmath>.*?<\/wmath>/g",
@@ -271,8 +428,41 @@ export default {
noneditable_editable_class: 'MathJax',
height: this.height,
content_style: `
*{
font-size: 14px;
}
${tableStyle}
${_this.wordStyle}
blue{
display: inline;
}
myfigure,
mytable {
pointer-events: auto !important; /* 强制允许鼠标点击 */
display: inline-block;
cursor: pointer;
color: rgb(0, 130, 170) !important;
text-shadow: 0 0 3px #09c2fb, 0 0 4px rgba(0, 130, 170, 0.3);
}
myfigure *,
mytable * {
color: inherit !important;
text-shadow: inherit !important;
background: transparent !important; /* 防止内部标签背景干扰 */
}
@keyframes blueGlow {
0%,
100% {
transform: scale(1);
opacity: 0.9;
}
50% {
transform: scale(1.02);
opacity: 1;
}
}
`,
formats: {
bold: { inline: 'b' },
@@ -304,12 +494,12 @@ export default {
// 1. 处理文件名:优先使用原始文件名,没有则生成
let filename = blobInfo.filename() || `upload_${Date.now()}.png`;
// 2. 构造符合你后端要求的参数
formData.append('mainImage', file, filename);
formData.append('file_name', file, filename);
// 优先从路由取 id其次取 data 里的 articleId
formData.append('article_id', _this.articleId);
xhr.withCredentials = false;
// 拼接你的 baseUrl
xhr.open('POST', _this.baseUrl + '/api/Preaccept/up_img_mainImage');
xhr.open('POST', _this.baseUrl + 'api/Articlemain/uploadTableImage');
// 上传进度(可选)
xhr.upload.onprogress = (e) => {
progress((e.loaded / e.total) * 100);
@@ -321,9 +511,9 @@ export default {
}
try {
const json = JSON.parse(xhr.responseText);
if (json.code === 0) {
if (json.status == 1) {
// 3. 按照你的逻辑返回拼接后的完整 URL
const finalUrl = _this.mediaUrl + 'articleImage/' + json.data.upurl;
const finalUrl = _this.mediaUrl + 'articleTableImage/' + json.data;
resolve(finalUrl);
console.log('手动上传成功:', finalUrl);
} else {
@@ -374,15 +564,42 @@ export default {
);
}
});
// 修改 paste 事件中的逻辑
ed.on('paste', (event) => {
ed.on('paste', async (event) => {
const rtf = event.clipboardData.getData('text/rtf');
if (rtf && rtf.includes('\\pict')) {
const extracted = extractHexImagesFromRTF(rtf);
extracted.forEach((img, i) => {
const base64 = _this.hexToBase64Sync(img.hex, img.mimeType);
_this.uploadSingleImage(base64, i);
_this.totalUploadImages = extracted.length; // 设置总数
_this.uploadedImageCount = 0; // 重置已上传数
if (_this.totalUploadImages === 0) return;
// 初始化主通知框
_this.updateUploadProgressNotification(0, 'init'); // 这里的参数只是为了触发通知框的创建
const uploadPromises = extracted.map(async (img, i) => {
const isTooLarge = img.hex.length > 2 * 1024 * 1024; // 1MB 限制
_this.updateUploadProgressNotification(i, 'processing'); // 更新单张图片状态
if (isTooLarge) {
_this.updateUploadProgressNotification(i, 'tooLarge');
return; // 跳过此图片
}
try {
const base64 = _this.hexToBase64Sync(img.hex, img.mimeType);
_this.updateUploadProgressNotification(i, 'uploading'); // 更新为上传中
await _this.uploadSingleImage(base64, i);
_this.updateUploadProgressNotification(i, 'success'); // 上传成功
} catch (err) {
console.error('Upload failed:', err);
_this.updateUploadProgressNotification(i, 'fail', err.message || 'Network error'); // 上传失败
}
});
await Promise.all(uploadPromises);
// Promise.all 完成后, updateUploadProgressNotification 会自动计算 totalCount === uploadedCount 并关闭
}
});
@@ -484,15 +701,16 @@ export default {
},
paste_preprocess: function (plugin, args) {
let imgIdx = 0;
const silentPlaceholder = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
let content = args.content.replace(/(<img[^>]*?)src="file:\/\/\/[^" ]*"/gi, (match, p1) => {
// 保留 data-idx Word 里的尺寸
return `${p1}src="${silentPlaceholder}" class="word-img-placeholder" data-idx="${imgIdx++}"`;
});
let tempDiv = document.createElement('div');
tempDiv.innerHTML = content;
if (tempDiv.querySelector('table')) {
console.log('粘贴的内容包含表格');
if (_this.type == 'table') {
@@ -567,11 +785,14 @@ export default {
window.renderMathJax(_this.tinymceId);
}, 10);
},
clear_custom_action: (editor, vm) => {
vm.onClear();
},
init_instance_callback: (editor) => {
if (_this.value) {
editor.setContent(_this.value);
editor.setContent('<p>' + _this.value + '</p>');
// console.log('at line 489:', ' 页面');
setTimeout(() => {
window.renderMathJax(_this.tinymceId); // 初始化时渲染 MathJax
}, 10);
@@ -648,10 +869,11 @@ export default {
editor.focus(); // 聚焦到编辑器// 触发编辑器内容变化后,如果需要,可能还要设置编辑器的样式
},
//销毁富文本
destroyTinymce() {
destroyTinymce() {this.onClear();
if (window.tinymce.get(this.tinymceId)) {
window.tinymce.get(this.tinymceId).destroy();
}
},
//设置内容
setContent(value) {
@@ -777,5 +999,4 @@ export default {
::v-deep .tox:not(.tox-tinymce-inline) .tox-editor-header {
padding: 0 !important;
}
</style>

View File

@@ -0,0 +1,243 @@
<template>
<transition name="table-fade">
<div v-if="visible" class="modal-mask" @click.self="close">
<div :class="['modal-container', { 'is-img-preview': type === 'img' }]" @click.self="close">
<div class="table-paper-view" @click.stop>
<div class="close-icon-top" @click="close">×</div>
<div v-if="type === 'table' && processedItem" class="thumbnailTableBox wordTableHtml table_Box pMain">
<div class="tableTitle font">
<span v-html="renderText(processedItem.title)"></span>
</div>
<table border="1" class="custom-table-core">
<tbody>
<tr v-for="(row, rIdx) in processedItem.table.tableHeader" :key="'h-' + rIdx" class="table-header-row">
<td v-for="(cell, cIdx) in row" :key="'hc-'+cIdx" :rowspan="cell.rowspan" :colspan="cell.colspan">
<span v-html="cell.text"></span>
</td>
</tr>
<tr v-for="(row, rIdx) in processedItem.table.tableContent"
:key="'c-' + rIdx"
class="table-content-row"
:class="{ oddColor: processedItem.table.oddRowIds.includes(row.rowId) }">
<td v-for="(cell, cIdx) in row" :key="'cc-'+cIdx" :rowspan="cell.rowspan" :colspan="cell.colspan">
<span v-html="cell.text"></span>
</td>
</tr>
</tbody>
</table>
<div class="tableNote font">
<span v-html="renderText(processedItem.note)"></span>
</div>
</div>
<div v-if="type === 'img' && processedItem" class="img-viewer-content">
<div class="img-wrapper">
<img :src="mediaUrl + processedItem.url" class="full-screen-img" @click="close" title="点击图片关闭">
</div>
<div class="imageTitle font" style="margin-top: 0px;">
<span v-html="processedItem.title"></span>
</div>
<div class="imageNote font" style="margin-top: 0px;">
<span v-html="processedItem.note"></span>
</div>
</div>
</div>
</div>
</div>
</transition>
</template>
<script>
import { TableUtils } from '@/common/js/TableUtils';
import { mediaUrl } from '@/common/js/commonJS.js';
export default {
name: 'TablePreviewer',
data() {
return {
visible: false,
loading: false,
processedItem: null,
type: null,
mediaUrl,
};
},
methods: {
async open(type, item) {
this.visible = true;
this.type = type;
if (type === 'table') {
this.loading = true;
setTimeout(() => {
try {
const processed = this.processTableData(item.table);
this.processedItem = Object.freeze({
...item,
table: {
...item.table,
tableHeader: processed.tableHeader,
tableContent: processed.tableContent,
oddRowIds: processed.oddRowIds
}
});
} catch (err) {
console.error("解析表格失败", err);
} finally {
this.loading = false;
}
}, 50);
} else {
this.processedItem = item;
}
// 数学公式渲染
setTimeout(() => {
if (window.renderMathJax) window.renderMathJax();
}, 500);
},
processTableData(rawContent) {
try {
const tableList = typeof rawContent === 'string' ? JSON.parse(rawContent) : rawContent;
const { header, content } = TableUtils.splitTable(tableList);
const { rowData, rowIds } = TableUtils.addRowIdToData(content);
return { tableHeader: header, tableContent: rowData, oddRowIds: rowIds };
} catch (e) {
return { tableHeader: [], tableContent: [], oddRowIds: [] };
}
},
renderText(text) {
if (this.$parent && typeof this.$parent.highlightText === 'function') {
return this.$parent.highlightText(
{ am_id: this.processedItem.am_id, text: text || '' },
this.processedItem.checks || []
);
}
return text || '';
},
close() {
this.visible = false;
this.processedItem = null;
}
}
};
</script>
<style scoped>
.modal-mask {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.75);
z-index: 9999;
overflow-y: auto;
display: block;
padding: 60px 0;
cursor: pointer;
box-sizing: border-box;
}
.modal-container {
display: flex;
justify-content: center;
align-items: flex-start;
width: 100%;
min-height: 100%;
cursor: pointer;
}
/* 统一的白色“纸张”边框容器 */
.table-paper-view {
position: relative;
min-width: 794px; /* A4 宽度参考 */
max-width: 90vw;
background-color: #fff;
padding: 10px 20px;
box-sizing: border-box;
box-shadow: 0 12px 40px rgba(0,0,0,0.5);
border-radius: 4px;
cursor: default;
margin-bottom: 20px;
}
/* 当预览图片时,如果想让框居中,可以给父级加这个 */
.is-img-preview {
align-items: center;
}
.img-wrapper {
display: flex;
justify-content: center;
margin: 15px 0 5px;
}
.full-screen-img {
max-width: 100%; /* 限制在白色框内 */
max-height: 70vh;
display: block;
cursor: zoom-out;
border: 1px solid #eee; /* 给图片本身加个微弱边框 */
}
.close-icon-top {
position: absolute;
right: -50px;
top: 0;
font-size: 40px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
line-height: 1;
}
.close-icon-top:hover {
color: #fff;
}
.custom-table-core {
width: 100%;
border-collapse: collapse;
}
.font {
font-size: 14px;
line-height: 1.6;
display: block;
margin: 10px auto;
}
.tableTitle, .imageTitle {
text-align: center;
font-weight: bold;
color: rgb(210, 90, 90);
font-size: 14px;
}
.tableNote, .imageNote {
color: #333;
margin-top: 15px;
}
.imageNote {
text-align: center;
}
.oddColor td {
background: rgb(250, 231, 232) !important;
}
.table-fade-enter-active, .table-fade-leave-active { transition: opacity 0.3s ease; }
.table-fade-enter, .table-fade-leave-to { opacity: 0; }
</style>

View File

@@ -152,11 +152,11 @@ export default {
this.$nextTick(() => {
setTimeout(() => {
const el = document.querySelector('.math-only');
console.log('el at line 121:', el);
if (window.MathJax && el) {
// MathJax.typesetClear([el]); // 清除旧渲染
window.MathJax.typesetPromise([el]).catch((err) => {
console.warn('MathJax 渲染失败:', err);
});
}
}, 500);

View File

@@ -11,23 +11,23 @@
<div class="list-container">
<el-radio-group v-model="selectedId" class="full-width-radio">
<div
v-for="item in displayList"
v-for="(item, index) in displayList"
:key="item.amt_id || item.ami_id"
:class="['item-card', { active: selectedId === item.amt_id || selectedId === item.ami_id }]"
>
<el-radio :label="item.amt_id || item.ami_id"
><div
v-html="item.title"
v-html="item.title || `${currentTypeText} ${index+1}`"
class="custom-radio-title"
@click.stop="handleClick(item)"
:title="stripHtml(item.title)"
:title="stripHtml(item.title || `${currentTypeText} ${index+1}`)"
></div
></el-radio>
</div>
<el-empty
v-if="displayList.length === 0"
:description="`No ${currentTypeText.toLowerCase()}s found in document`"
:description="`No ${currentTypeText.toLowerCase()}s found`"
:image-size="60"
></el-empty>
</el-radio-group>
@@ -63,6 +63,7 @@ export default {
},
show(data) {
this.selectLinkData = data;
console.log('show at line 34:', data);
// 1. 获取传入的 ID
const inputId = data.selectedId ? Number(data.selectedId) : null;
const exists = this.displayList.some((item) => item.ami_id === inputId || item.amt_id === inputId);

View File

@@ -0,0 +1,262 @@
<template>
<div class="ManuscirptList">
<div class="arrlist">
<ul class="catalogue-ul">
<li class="catalogue-li">
<div
@click="goToListComment(item.am_id, 'content')"
v-for="(item, index) in catalogueList"
:key="index"
:class="['catalogue-item', 'level-' + item.level]"
>
<div class="title-content-wrapper">
<div class="title-content" v-html="item.content"></div>
</div>
</div>
</li>
</ul>
</div>
</div>
</template>
<script>
import { mediaUrl } from '@/common/js/commonJS.js'; // 引入通用逻辑
export default {
props: ['articleId', 'imgWidth', 'imgHeight', 'scale', 'isEdit', 'isShowEdit', 'urlList', 'content'],
watch: {
content: {
handler(newVal, oldVal) {
if (this.content.length > 0) this.getCatalogueList();
},
deep: true
}
},
data() {
return {
catalogueList: [],
isShowComment: false,
isCollapse: false,
isEditComment: false,
isEditing: null, // 用于跟踪当前正在编辑的批注索引
selectedComment: null, // 存储当前选择的批注内容
comments: [], // 存储所有批注
isFresh: false,
currentSelectType: '0',
currentMenu: 1,
mediaUrl,
statusList: [
{ title: 'ALL', type: '0' },
{ title: 'Typed', type: '1' },
{ title: 'Unfettered', type: '2' }
],
images: [],
tables: [],
imagesList: [],
tablesList: [],
tablesHtml: [],
imagesHtml: [],
activeNames: ['images', 'tables']
};
},
directives: {
// 注册一个局部的自定义指令 v-focus
focus: {
// 指令的定义
inserted: function (el) {
console.log('el at line 263:', el);
// 聚焦元素
el.querySelector('textarea').focus();
}
}
},
methods: {
getCatalogueList() {
this.catalogueList = this.content
.filter((item) => {
return item.is_h1 == 1 || item.is_h2 == 1;
})
.map((item) => {
// 增加一个 level 字段进行标记
let level = 1;
if (item.is_h2 == 1) level = 2;
return {
...item,
level: level // 1, 2, 或 3
};
});
this.catalogueList = [
...this.catalogueList
// ,{
// am_id:'References',
// content:'References',
// level:1,
// }
];
this.$forceUpdate();
// this.catalogueList = res.data;
},
goToListComment(id, type) {
this.$emit('goToListComment', id, type);
}
},
async created() {},
mounted() {
this.getCatalogueList();
},
async activated() {
this.getCatalogueList();
}
};
</script>
<style scoped>
.ManuscirptList {
}
/* 模拟 Word 导航标题 */
.word-navigation-header {
padding: 12px 16px 0 16px;
font-size: 14px;
color: #333;
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
}
.word-tabs {
display: flex;
margin-top: 10px;
border-bottom: 1px solid #e1e1e1;
}
.word-tabs span {
padding: 4px 12px;
font-size: 12px;
color: #666;
cursor: pointer;
position: relative;
}
.word-tabs span.active {
color: #2b579a; /* Word 经典蓝色 */
font-weight: bold;
}
.word-tabs span.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
right: 0;
height: 2px;
background-color: #2b579a;
}
/* 列表容器 */
.arrlist {
flex: 1;
overflow-y: auto;
/* padding-top: 8px; */
}
.catalogue-ul {
width: 100%;
list-style: none;
padding: 0;
margin: 0;
}
/* 基础条目样式 - 模拟 Word 悬浮效果 */
.catalogue-item {
position: relative;
width: 100%;
/* padding: 6px 16px; */
cursor: pointer;
display: flex;
align-items: flex-start;
box-sizing: border-box;
transition: background-color 0.1s;
}
.catalogue-item:hover {
background-color: #eff3f9; /* Word 淡淡的选中蓝 */
}
/* Word 样式的左侧小箭头 */
.word-icon-arrow {
width: 0;
height: 0;
border-left: 5px solid #666;
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
margin-right: 8px;
margin-top: 6px;
}
.title-content-wrapper {
flex: 1;
overflow: hidden;
}
/* 重点Word 常用排版字体 */
.title-content {
font-weight: bold !important;
font-style: normal !important;
font-size: 14px !important;
line-height: 1.3;
white-space: nowrap;
padding: 4px 0px;
overflow: hidden;
text-overflow: ellipsis;
/* Times New Roman 增加学术感 */
font-family: 'Charis SIL' !important;
color: #333;
}
/* 保持 v-html 内部样式 */
::v-deep .title-content b {
font-weight: bold;
}
::v-deep .title-content i {
font-style: italic;
}
/* --- Word 风格的层级缩进 --- */
/* 1级标题 */
.level-1 {
padding-left: 0px;
}
.level-1 .title-content {
font-style: italic !important;
}
/* 2级标题 */
.level-2 {
padding-left: 20px;
}
.level-2 .title-content {
font-size: 14px;
color: #888;
}
/* 3级标题 */
.level-3 {
padding-left: 56px;
}
.level-3 .title-content {
font-size: 14px;
color: #666;
}
/* 隐藏滚动条 */
.arrlist::-webkit-scrollbar {
width: 5px;
}
.arrlist::-webkit-scrollbar-thumb {
background: #cdcdcd;
}
</style>

View File

@@ -16,7 +16,7 @@
:value="value"
:typesettingType="typesettingType"
class="paste-area text-container"
:toolbar="!isAutomaticUpdate?['bold italic |customBlue removeBlue|LateX| myuppercase myuppercasea Line MoreSymbols|subscript superscript|clearButton|searchreplace']:['bold italic |customBlue removeBlue| myuppercase myuppercasea Line|subscript superscript|clearButton|searchreplace']"
:toolbar="!isAutomaticUpdate?['bold italic |customBlue removeBlue|LateX| myuppercase myuppercasea Line MoreSymbols|subscript superscript|clearButton|searchreplace']:['bold italic |customBlue removeBlue| myuppercase myuppercasea Line MoreSymbols|subscript superscript|clearButton|searchreplace']"
style="
/* white-space: pre-line; */
line-height: 12px;

View File

@@ -1,40 +1,28 @@
<template>
<div>
<!--uploadWord |customButtonExportWord |customButtonExportImg -->
<!-- image -->
<tinymce
type="table"
:articleId="articleId"
ref="tinymceChild1"
:wordStyle="wordStyle"
@getContent="getContent"
@openLatexEditor="openLatexEditor"
:height="calcDynamicWidth()"
:value="updatedHtml"
:typesettingType="typesettingType"
class="paste-area text-container"
:toolbar="['bold italic|customBlue removeBlue|LateX |myuppercase myuppercasea Line|subscript superscript|table | searchreplace |clearButton']"
style="
/* white-space: pre-line; */
line-height: 12px;
/* max-height: 60vh; */
overflow: auto;
font-size: 12px;
font-size: 14px; /* 字体大小 */
margin-top: 0pt; /* 段前间距 */
margin-bottom: 0pt; /* 段后间距 */
"
></tinymce>
<tinymce
type="table"
:articleId="articleId"
ref="tinymceChild1"
:wordStyle="wordStyle"
@getContent="getContent"
@openLatexEditor="openLatexEditor"
:height="calcDynamicWidth()"
:value="updatedHtml"
:typesettingType="typesettingType"
class="paste-area text-container"
:toolbar="[
'bold italic|customBlue removeBlue|LateX |myuppercase myuppercasea Line MoreSymbols|subscript superscript|table | searchreplace |clearButton'
]"
style="line-height: 12px; overflow: auto; font-size: 14px; margin-top: 0px; margin-bottom: 0px"
></tinymce>
</div>
</template>
<script>
import Tinymce from '@/components/page/components/Tinymce';
export default {
props: ['lineStyle','articleId'],
props: ['lineStyle', 'articleId'],
components: {
Tinymce
},
@@ -79,8 +67,8 @@ export default {
text-align: center;
table-layout: auto;"
>`;
this.tableData.forEach((row,i) => {
modalContent += `<tr class="${this.isHeaderRow(i,this.tableData)?'table-header-row':''}">`;
this.tableData.forEach((row, i) => {
modalContent += `<tr class="${this.isHeaderRow(i, this.tableData) ? 'table-header-row' : ''}">`;
row.forEach((cell) => {
modalContent += `
<td
@@ -94,42 +82,34 @@ export default {
modalContent += `</tr>`;
});
modalContent += `</table></div>`;
// console.log('modalContent at line 91:', modalContent);
this.updatedHtml = modalContent;
}
// this.updatedHtml = newVal.html_data;
} else {
this.updatedHtml = '';
}
},
methods: {
openLatexEditor(data) {
this.$emit('openLatexEditor',data)
console.log('at line 254:', '打开数字公式');
this.$emit('openLatexEditor', data);
},
calcDynamicWidth() {
const parentWidth = window.innerHeight;
const result = parentWidth - 420;
return result;
},
isHeaderRow(rowIndex, table) {
var head = table[0];
return rowIndex < head[0].rowspan;
},
calcDynamicWidth() {
const parentWidth = window.innerHeight; // 获取窗口宽度
const result = parentWidth - 420; // 计算 `100% - 200px`
return result ;
},
isHeaderRow(rowIndex,table) {
var head=table[0]
return rowIndex < head[0].rowspan; // 假设前两行是表头
},
getTableContent(type) {
this.$refs.tinymceChild1.getContent(type);
},
getContent(type, content) {
if (content) {
console.log('content at line 121:', content)
const container = document.createElement('div');
container.innerHTML = content;
this.$commonJS.parseTableToArray(content, (table) => {
this.$emit('getContent', type, { html_data: content, table: table });
});
} else {

View File

@@ -1914,7 +1914,7 @@ export default {
},
// 接收组件传回的关联数据
onLinkConfirm(selectedItem) {
console.log('关联的对象:', selectedItem);
// 在这里处理后续逻辑,比如保存绑定关系
},
handleOpenLink() {

File diff suppressed because it is too large Load Diff

View File

@@ -2,12 +2,12 @@
<!-- v-show="tables.length > 0 || images.length > 0" -->
<div
style="background-color: transparent !important; margin-top: 0px !important; height: 100%; padding: 0 !important; overflow: hidden"
class="ManuscirptList"
class="ManuscirptList no-select"
>
<!-- 图片缩略图区域 -->
<el-menu
default-active="1"
default-active="4"
style="border: none; height: 100%; float: left"
background-color="#f8f8f9"
active-text-color="#006699"
@@ -19,6 +19,10 @@
<i class="el-icon-message-solid" style="margin: 0 auto; color: #fc625d"></i>
<span slot="title" style="line-height: 30px">Annotations</span>
</el-menu-item> -->
<el-menu-item index="4">
<i class="el-icon-document" color="#333639" style="margin: 0 auto"></i>
<span slot="title" style="line-height: 20px">Outline</span>
</el-menu-item>
<el-menu-item index="1">
<i class="el-icon-picture" color="#333639" style="margin: 0 auto"></i>
<span slot="title" style="line-height: 20px">Figures</span>
@@ -28,10 +32,10 @@
<i class="el-icon-s-grid" color="#333639" style="margin: 0 auto"></i>
<span slot="title" style="line-height: 30px">Tables</span>
</el-menu-item>
<el-menu-item index="3">
<!-- <el-menu-item index="3">
<i class="el-icon-delete-solid" color="#333639" style="margin: 0 auto"></i>
<span slot="title" style="line-height: 30px">Recycle Bin</span>
</el-menu-item>
</el-menu-item> -->
</el-menu>
<!-- <ul style="width: 80px; height: 100%; float: left; padding: 6px; box-sizing: border-box" v-if="currentMenu != 3">
<li
@@ -51,7 +55,7 @@
<p>{{ v.title }}</p>
</li>
</ul> -->
<div class="go-mt">
<div class="go-mt" v-show="currentMenu != 4">
<el-radio-group v-model="isCollapse" style="float: right" size="mini" @change="changeIsCollapse">
<el-radio-button :label="false">{{ $t('commonTable.singleRow') }}</el-radio-button>
<el-radio-button :label="true">{{ $t('commonTable.Multicolumn') }}</el-radio-button>
@@ -62,77 +66,8 @@
class="arrlist"
>
<ul style="width: 100%; height: auto">
<!-- <li v-show="currentMenu == 1">
<div style="display: flex; flex-wrap: wrap; gap: 10px; justify-content: start">
<li v-for="(comment, index) in comments" :key="index" class="comment-item" style="width: 100%; padding: 6px 0">
<div @click.prevent="goToComment(comment.am_id)">
<p style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px">
<el-link class="pizhu">
<span style="color: #fc625d; display: flex; align-items: center">
<img
class="isRemark"
src="@/assets/img/isRemark.png"
alt=""
style="width: 15px; height: 15px; margin-right: 6px"
/>
{{ comment.am_id }}</span
></el-link
>
<span style="color: #b8b7b7;font-size: 12px">{{ getTime(comment.ctime) }}</span>
</p>
<div style="display: flex; align-items: center; justify-content: space-between">
<p :style="isEditComment?'width: calc(100% - 40px)':'width: calc(100%)'" style="line-height:18px;min-height: 20px; width: calc(100%);white-space: normal;" v-html="comment.remark">
<span @click="editComment(index)" style="cursor: pointer; color: #007bff; margin-left: 10px;">Edit</span>
</p>
<i
class="el-icon-edit"
v-if="isEditComment"
@click="onAddComment(comment,index)"
style="color: #006699 ;margin-right: 10px;"
></i>
<i
class="el-icon-delete"
v-if="isEditComment"
@click="deleteComment(comment, index)"
style="color: rgb(252, 98, 93)"
></i>
</div>
</div>
</li>
</div>
</li> -->
<li v-show="currentMenu == 1">
<div style="" class="go-content-charts-item-box" :class="isCollapse ? 'double' : 'single'">
<!-- <div class="item_box" style="width: 100%; height: auto; position: relative" >
<el-collapse v-if="isShowEdit">
<el-collapse-item name="1" style="margin-top: 4px">
<template slot="title">
<div style="width: 100%; display: flex; align-items: center; justify-content: space-between">
<span style="color: #777">Tables {{ index + 1 }}</span>
<span v-if="isShowEdit"><i class="el-icon-edit" v-if="isEdit"></i>Comments/ Suggestions</span>
</div>
</template>
<el-input
v-if="isShowEdit"
type="textarea"
placeholder="please input content"
:readonly="!isEdit"
:rows="4"
></el-input>
</el-collapse-item>
</el-collapse>
</div> -->
<div class="item_box" style="width: 100%; height: auto; position: relative">
<div class="list-center go-flex-center go-transition" style="width: 100%" @click="addImage">
<div class="title">
@@ -142,7 +77,6 @@
<div
class="thumbnailBox image list-img"
:style="`
border: 1px solid #ccccccb5;
width: 100%;
height: auto;
@@ -178,13 +112,6 @@
></el-input>
</el-collapse-item>
</el-collapse>
<!-- <p
style="color: #777; margin-top: 6px"
v-else
@click="img.has_selected == 1 ? goToListComment(img.article_image_id, 'img') : ''"
>
<el-link> Figures {{ index + 1 }} </el-link>
</p> -->
</div>
<div
class="item_box"
@@ -198,10 +125,10 @@
>Figure {{ index + 1 }}
<span
class="previewbox"
@click="openPreview(index, 'img')"
@click="openPreview(index, 'img',img)"
v-if="['jpg', 'jpeg', 'png'].includes(img.image.split('.').pop().toLowerCase())"
>
<i class="el-icon-view" style="font-size: 16px"></i>
<i class="el-icon-view" style="font-size: 12px"></i>
</span>
<a :href="mediaUrl + img.image.url" style="color: #000" v-else>
<!-- <el-tooltip class="item" effect="dark" :content="$t('commonTable.preview')" placement="top"> -->
@@ -211,7 +138,10 @@
</span>
<p>
<span style="" @click="edit(img, 'img')">
<i class="el-icon-edit" style="color: #409eff; font-size: 16px"></i>
<i class="el-icon-edit" style="color: #409eff; font-size: 12px"></i>
</span>
<span style="margin-left: 6px" @click="handledelete(img, 'img')">
<i class="el-icon-delete" style="color: #f56c6c; font-size: 12px"></i>
</span>
</p>
</div>
@@ -259,25 +189,14 @@
@click="
img.has_selected == 1
? goToListComment(img.article_image_id, 'img')
: openPreview(index, 'img')
: openPreview(index, 'img',img)
"
:data-img-id="img.article_image_id"
:src="mediaUrl + img.image"
style="width: 140px; height: 100%; object-fit: cover"
/>
</template>
<template v-else-if="img.image.split('.').pop().toLowerCase() === 'tif'">
<img
@click="
img.has_selected == 1
? goToListComment(img.article_image_id, 'img')
: openPreview(index, 'img')
"
:data-img-id="img.article_image_id"
:src="img.dataUrl"
style="width: 140px; height: 100%; object-fit: cover"
/>
</template>
<template v-else>
<div
style="
@@ -404,14 +323,17 @@
<div class="title">
<span
>Table {{ index + 1 }}
<span class="previewbox" @click="openPreview(index, 'table')">
<span class="previewbox" @click="openPreview(index, 'table',table)">
<el-tooltip class="item" effect="dark" :content="$t('commonTable.preview')" placement="top">
<i class="el-icon-view" style="font-size: 16px"></i>
<i class="el-icon-view" style="font-size: 12px"></i>
</el-tooltip> </span
></span>
<p>
<span style="" @click="edit(table, 'table')">
<i class="el-icon-edit" style="color: #409eff; font-size: 16px"></i>
<i class="el-icon-edit" style="color: #409eff; font-size: 12px"></i>
</span>
<span style="margin-left: 6px" @click="handledelete(table, 'table')">
<i class="el-icon-delete" style="color: #f56c6c; font-size: 12px"></i>
</span>
</p>
</div>
@@ -454,7 +376,7 @@
@click="
table.has_selected == 1
? goToListComment(table.article_table_id, 'table')
: openPreview(index, 'table')
: openPreview(index, 'table', table)
"
style="width: 100%; height: 80px; display: flex; justify-content: center; align-items: center"
>
@@ -537,13 +459,22 @@
</div>
</div>
</li>
<li v-show="currentMenu == 4">
<slot name="catalogue1"></slot>
</li>
</ul>
</div>
<DynamicTable
ref="myTableModal"
/>
</div>
</template>
<script>
import DynamicTable from './DynamicTable.vue';
import { mediaUrl } from '@/common/js/commonJS.js'; // 引入通用逻辑
export default {
props: ['articleId', 'imgWidth', 'imgHeight', 'scale', 'isEdit', 'isShowEdit', 'urlList', 'content'],
data() {
@@ -556,7 +487,7 @@ export default {
comments: [], // 存储所有批注
isFresh: false,
currentSelectType: '0',
currentMenu: 1,
currentMenu: 4,
mediaUrl,
@@ -574,6 +505,9 @@ export default {
activeNames: ['images', 'tables']
};
},
components: {
DynamicTable
},
directives: {
// 注册一个局部的自定义指令 v-focus
focus: {
@@ -695,6 +629,9 @@ export default {
edit(data, type) {
this.$emit('edit', data, type);
},
handledelete(data, type) {
this.$emit('delete', data, type);
},
huifu(id) {
this.$emit('huifu', id);
},
@@ -711,6 +648,23 @@ export default {
this.currentSelectType = v.type;
this.filterData();
},
replacement(type, id) {
if (type == 'img') {
const indexInList = this.images.findIndex((img) => img.ami_id == id);
if (indexInList !== -1) {
this.images.splice(indexInList, 1); // 从数组中彻底删除
}
this.filterData('img');
} else if (type == 'table') {
const indexInList = this.tables.findIndex((img) => img.amt_id == id);
if (indexInList !== -1) {
this.tables.splice(indexInList, 1); // 从数组中彻底删除
}
this.filterData('table');
}
},
filterData(type) {
if (type) {
if (type == 'img') {
@@ -751,20 +705,113 @@ export default {
goToListComment(id, type) {
this.$emit('goToListComment', id, type);
},
async refresh(type) {
this.isFresh = false;
async reload(){
this.$nextTick(async () => {
if (type == 'img') {
await this.getWordimgList();
await this.filterData();
} else if (type == 'table') {
await this.getWordTablesList();
await this.filterData();
})
},
async refresh(type, newData) {
var tableIndex;
var imgIndex;
var tableData
if(newData.amt_id ) {
tableIndex = this.tables.findIndex((table) => table.amt_id == newData.amt_id);
tableData = newData.table_data?JSON.parse(newData.table_data):[];
}
if(newData.ami_id) {
imgIndex = this.images.findIndex((img) => img.ami_id == newData.ami_id);
}
this.isFresh = false;
this.$nextTick(async () => {
switch (type) {
case 'addImg':
this.images.push({
...newData,
image: newData.url,
article_image_id: newData.ami_id
});
break;
case 'editImg':
if (imgIndex !== -1) {
this.images[imgIndex] = {
...newData,
image: newData.url,
article_image_id: newData.ami_id
};
}
break;
case 'addTable':
this.tables.push({
...newData,
article_table_id: newData.amt_id,
table: tableData
});
case 'editTable':
if (tableIndex !== -1) {
this.tables[tableIndex] = {
...newData,
article_table_id: newData.amt_id,
table: tableData
};
}
break;
case 'removeImg':
if (imgIndex !== -1) {
this.images[imgIndex].has_selected = 0;
}
break;
case 'positioningImg':
if (imgIndex !== -1) {
this.images[imgIndex].has_selected = 1;
}
break;
case 'removeTable':
if (tableIndex !== -1) {
this.tables[tableIndex].has_selected = 0;
}
break;
case 'positioningTable':
if (tableIndex !== -1) {
this.tables[tableIndex].has_selected = 1;
}
break;
}
await this.filterData();
this.$forceUpdate();
this.isFresh = true;
// this.$emit('loaded', loadedthis.images);
});
},
changeDataIsHidden(val, status, type) {
@@ -811,10 +858,9 @@ export default {
});
}
},
openPreview(index, type) {
this.$nextTick(() => {
document.getElementById(type + '-modal-' + index).style.display = 'flex';
});
openPreview(index, type,data) {
this.$refs.myTableModal.open(type,data);
},
async getWordimgList() {
var that = this;
@@ -836,22 +882,13 @@ export default {
list = res.data.list;
}
if (list.length > 0) {
list.forEach((img, index) => {
var extension = img.image.split('.').pop().toLowerCase();
// if (extension === 'tif' || extension === 'jpg' || extension === 'jpeg' || extension === 'png') {
const modalContent = `<img src="${this.mediaUrl + img.image}" alt="Image ${index}" style="width:100%;" >`;
this.$commonJS.createImageModal(index, modalContent, 'img');
// }
});
}
this.images = list;
this.$emit('loaded', this.images);
});
},
isHeaderRow(rowIndex, table) {
var table =table;
var table = table;
var head = table[0];
@@ -887,40 +924,7 @@ export default {
: [];
}
await this.filterData('table');
if (this.tables.length > 0) {
this.tables.forEach((table, index) => {
var modalContent = `
<div class="wordTableHtml" style="background:#FFF;padding:20px">
<table
border="1"
style="
border-collapse: collapse;
width: 100%;
text-align: center;
table-layout: auto;"
>`;
if (table.table && table.table.length > 0) {
table.table.forEach((row,i) => {
modalContent += `<tr class="${this.isHeaderRow(i, table.table)? 'table-header-row':'' }">`;
row.forEach((cell) => {
modalContent += `
<td
colspan="${cell.colspan || 1}"
rowspan="${cell.rowspan || 1}"
style=""
>
<span>${cell.text}</span>
</td>`;
});
modalContent += `</tr>`;
});
}
modalContent += `</table></div>`;
this.$commonJS.createImageModal(index, modalContent, 'table', '');
});
}
});
}
},
@@ -968,7 +972,7 @@ export default {
border-bottom: 1px solid #000 !important;
}
.el-menu-vertical-demo {
width: 85px;
width: 60px;
}
.el-menu-vertical-demo li {
display: flex;
@@ -1000,7 +1004,7 @@ li {
.go-transition .title {
background-color: #e5e6eb;
color: #767c82;
font-size: 13px;
font-size: 12px;
padding: 2px 10px;
display: flex;
align-items: center;
@@ -1132,7 +1136,7 @@ li {
width: auto !important;
}
.double .go-transition .title {
font-size: 12px !important;
font-size: 11px !important;
padding: 0 2px !important;
line-height: 20px !important;
}

View File

@@ -361,16 +361,16 @@ export default {
this.add_apply = 0;
break;
case 7||16:
// 7文章状态不在审稿中 16同意审稿后14天未进行审稿
case 7:
// 7文章状态不在审稿中
this.add_apply = 1;
break;
case 8:
//拒绝审稿
this.add_apply = -1;
break;
case 13:
//13 邀请审稿超过5天未同意邀请
case 13||16:
//13 邀请审稿超过5天未同意邀请16同意审稿后14天未进行审稿
this.add_apply = 2;
break;
default: