Compare commits

14 Commits

37 changed files with 6066 additions and 5403 deletions

View File

@@ -56,5 +56,6 @@
"less-loader": "^5.0.0",
"sass-loader": "^7.3.1",
"vue-template-compiler": "^2.6.10"
}
},
"packageManager": "pnpm@10.28.1+sha512.7d7dbbca9e99447b7c3bf7a73286afaaf6be99251eb9498baefa7d406892f67b879adb3a1d7e687fc4ccc1a388c7175fbaae567a26ab44d1067b54fcb0d6a316"
}

View File

@@ -19,8 +19,8 @@ const service = axios.create({
// baseURL: 'https://submission.tmrjournals.com/', //正式 记得切换
// baseURL: 'http://www.tougao.com/', //测试本地 记得切换
// baseURL: 'http://192.168.110.110/tougao/public/index.php/',
baseURL: '/api', //本地
// baseURL: '/', //正式
// baseURL: '/api', //本地
baseURL: '/', //正式
});

View File

@@ -1,8 +1,21 @@
:root {
--primary-comment-deep: #ffcf30;
--primary-comment-main: #87CEEB;
--primary-comment-light: #ffcf30;
--primary-comment-bg: #ffe796;
--primary-comment-font-deep: #ff9814;
}
* {
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,
@@ -24,9 +37,179 @@ a {
text-decoration: none
}
.w-auto {
width: auto !important;
}
.w-full {
width: 100% !important;
}
/* 动态宽度类 */
.dynamic-width {
width: calc(100% - var(--minus-w, 300px)) !important;
}
.base-flex {
display: flex !important;
/* 垂直对齐:如果不传 --ai则不设置或保持默认 */
align-items: var(--fl-ai, initial) !important;
/* 水平对齐:如果不传 --jc则不设置 */
justify-content: var(--fl-jc, initial) !important;
/* 换行:如果不传 --wrap则不设置 */
flex-wrap: var(--fl-wrap, initial) !important;
/* 方向:如果不传 --direction则不设置 */
flex-direction: var(--fl-direction, initial) !important;
/* 间隙:这是 Flex 布局中非常好用的属性,建议加上 */
gap: var(--gap, initial) !important;
}
/* 2. 快捷全居中类(最常用,默认就是全居中) */
.base-flex-center {
display: flex ;
align-items: var(--fl-ai, center);
justify-content: var(--fl-jc, center);
}
.base-pos {
/* 定位类型:默认 absolute可选 relative, fixed, sticky */
position: var(--pos, absolute);
/* 默认 initial只有在 style 里传了参,对应的方位才会生效 */
top: var(--p-t, initial);
right: var(--p-r, initial);
bottom: var(--p-b, initial);
left: var(--p-l, initial);
}
.abs-left {
left: var(--left-dist, 10px);
}
.base-font-size {
font-size: var(--f-s, 14px) !important;
color: var(--f-c, #333) !important;
}
/* common.css */
.b-box {
box-sizing: border-box !important;
}
.base-border {
--b-w: 1px;
--b-s: solid;
--b-c: #d8d8d8;
border-top: var(--bt, 0) var(--b-s) var(--b-c);
border-right: var(--br, 0) var(--b-s) var(--b-c);
border-bottom: var(--bb, 0) var(--b-s) var(--b-c);
border-left: var(--bl, 0) var(--b-s) var(--b-c);
}
.base-border-all {
--b-w: 1px;
/* 宽度 */
--b-s: solid;
/* 样式 */
--b-c: #d8d8d8;
/* 颜色 */
border: var(--b-w) var(--b-s) var(--b-c);
}
/* common.css */
.base-padding {
/* 定义默认值(如果没传参,默认是 0 */
--p-t: 0px;
--p-r: 0px;
--p-b: 0px;
--p-l: 0px;
padding-top: var(--p-t);
padding-right: var(--p-r);
padding-bottom: var(--p-b);
padding-left: var(--p-l);
}
.base-padding-all {
padding: var(--p);
/* 默认四周 10px也可以通过 --p 传参 */
}
.base-bg {
background-color: var(--bg, #fff);
}
/* 专门用于“必须生效”场景的类 */
.base-bg-imp {
background-color: var(--bg, #fff) !important;
}
.base-margin {
--m-t: 0px;
--m-r: 0px;
--m-b: 0px;
--m-l: 0px;
margin-top: var(--m-t);
margin-right: var(--m-r);
margin-bottom: var(--m-b);
margin-left: var(--m-l);
}
.base-margin-all {
margin: var(--m);
}
.float-var {
float: var(--f, none) var(--f-imp, );
}
.clearfix::after {
content: "";
display: block;
clear: both;
}
.page-container {
min-width: 1200px;
}
.content-box {
position: absolute;
left: 260px;
@@ -1149,9 +1332,9 @@ a {
mso-border-top-alt: none !important;
border-bottom: none !important;
mso-border-bottom-alt: none !important;
border: 1px dashed #dcdfe6 !important;
/* border: 1px dashed #dcdfe6 !important;
border-left: 1px dashed #dcdfe6 !important;
border-right: 1px dashed #dcdfe6 !important;
border-right: 1px dashed #dcdfe6 !important; */
word-break: keep-all !important;
/* text-align: justify !important; */
}
@@ -1283,7 +1466,7 @@ a {
}
.word-container table tbody tr td {
text-align: left !important;
text-align: center !important;
border-left: none !important;
mso-border-left-alt: none !important;
border-right: none !important;
@@ -1292,9 +1475,9 @@ a {
mso-border-top-alt: none !important;
border-bottom: none !important;
mso-border-bottom-alt: none !important;
border: 1px dashed #dcdfe6 !important;
/* border: 1px dashed #dcdfe6 !important;
border-left: 1px dashed #dcdfe6 !important;
border-right: 1px dashed #dcdfe6 !important;
border-right: 1px dashed #dcdfe6 !important; */
word-break: keep-all !important;
white-space: pre-wrap !important;
/* text-align: justify !important; */
@@ -1330,12 +1513,7 @@ a {
mos-line-height: 20px !important;
}
.word-container table tr:first-child td {
/* border-top: 1pt solid #000 !important;
mso-border-top-alt: 0.5pt solid #000 !important;
border-bottom: 1pt solid #000 !important;
mso-border-bottom-alt: 0.5pt solid #000 !important; */
}
.word-container table tr:first-child td {}
.word-container table tr:last-of-type {
border-bottom: 1pt solid #000 !important;
@@ -1408,18 +1586,13 @@ a {
color: #fff !important;
/* 设置字体颜色 */
fill: #fff !important;
/* font-size:16px!important; */
/* 设置字体颜色 */
}
.tinymce-custom-button-addrow {
font-weight: bold !important;
background-color: #cbccd1 !important;
/* color: #fff !important; */
/* 设置字体颜色 */
/* fill: #fff !important; */
/* font-size:16px!important; */
/* 设置字体颜色 */
}
.tinymce-custom-button-save svg {
@@ -1452,11 +1625,13 @@ wmath {
z-index: 99999 !important;
position: fixed !important;
}
.sticky-header {
position: sticky;
top: 0;
z-index: 1000; /* 保证在上层 */
background-color: transparent;
z-index: 1000;
/* 保证在上层 */
background-color: transparent;
/* padding: 10px; */
box-shadow: 0 2px 2px rgba(221, 221, 221, 0.2);
}
}

BIN
src/assets/img/icon_pdf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

102
src/common/js/TableUtils.js Normal file
View File

@@ -0,0 +1,102 @@
/**
* 表格数据处理工具
*/
export const TableUtils = {
/**
* 判断是否为表头行
* @param {number} rowIndex
* @param {Array} table
*/
isHeaderRow(rowIndex, table) {
if (!table || table.length === 0) return false;
const head = table[0];
// 健壮性检查确保第一行第一个单元格存在且有rowspan
const headerSpan = (head && head[0] && head[0].rowspan) ? head[0].rowspan : 1;
return rowIndex < headerSpan;
},
/**
* 拆分表头和表体
*/
splitTable(tableList) {
if (!Array.isArray(tableList) || tableList.length === 0) {
return { header: [], content: [] };
}
const header = [];
const content = [];
let cellIdCounter = 0;
tableList.forEach((row, rowIndex) => {
if (Array.isArray(row)) {
row.forEach((cell) => {
if (cell && typeof cell === 'object') {
cell.cellId = `cell-${cellIdCounter++}`;
}
});
}
if (this.isHeaderRow(rowIndex, tableList)) {
header.push(row);
} else {
content.push(row);
}
});
return { header, content };
},
/**
* 处理合并单元格后的逻辑行 ID用于斑马纹等
*/
addRowIdToData(content) {
if (!content || content.length === 0) return { rowData: [], rowIds: [] };
const data = JSON.parse(JSON.stringify(content));
const rowIdMap = {};
const usedRows = new Set();
let idCounter = 0;
// 1. 建立逻辑行映射
for (let i = 0; i < data.length; i++) {
if (usedRows.has(i)) continue;
const rowId = `row-${idCounter++}`;
rowIdMap[i] = rowId;
usedRows.add(i);
const row = data[i];
for (let j = 0; j < row.length; j++) {
const cell = row[j];
if (cell && cell.rowspan > 1) {
for (let k = 1; k < cell.rowspan; k++) {
const nextRowIndex = i + k;
if (nextRowIndex < data.length && !rowIdMap[nextRowIndex]) {
rowIdMap[nextRowIndex] = rowId;
usedRows.add(nextRowIndex);
}
}
}
}
}
// 2. 注入 rowId 并提取唯一 ID 列表
const seenIds = [];
data.forEach((row, i) => {
const rowId = rowIdMap[i];
row.rowId = rowId; // 直接赋值给行对象
row.forEach(cell => {
if (cell) cell.rowId = rowId;
});
if (rowId && !seenIds.includes(rowId)) {
seenIds.push(rowId);
}
});
// 取奇数或偶数 ID 用于斑马纹(根据你的需求 index % 2 === 0
const rowIds = seenIds.filter((_, index) => index % 2 === 0);
return { rowData: data, rowIds };
}
};

View File

@@ -147,47 +147,53 @@ export default {
replaceWMathContent(inputHtml, callback) {
// 使用正则表达式查找所有 <wmath> 标签,并提取 data-latex 的内容
var str = inputHtml.replace(/<wmath data-latex="([^"]+)">[^<]*<\/wmath>/g, function (match, latexContent) {
// 返回 <wmath> 标签,内容替换为 data-latex 的值
return `<wmath data-latex="${latexContent}">${latexContent}</wmath>`;
// 正则逻辑:匹配整个 wmath 标签,并捕获内部所有的属性字符串
var str = inputHtml.replace(/<wmath ([^>]+)>[^<]*<\/wmath>/g, function (match, allProps) {
// 1. 从所有属性中提取 data-latex 的值
const latexMatch = allProps.match(/data-latex="([^"]+)"/);
const latexContent = latexMatch ? latexMatch[1] : '';
// 2. 从所有属性中提取 data-wrap 的值
const wrapMatch = allProps.match(/data-wrap="([^"]+)"/);
const wrapAttr = wrapMatch ? ` data-wrap="${wrapMatch[1]}"` : '';
// 3. 重新组装:只保留 data-latex 和 data-wrap
// 注意:这里去掉了多余的 prefix/suffix确保标签“干净”
return `<wmath${wrapAttr} data-latex="${latexContent}">${latexContent}</wmath>`;
});
// 调用回调函数并传递处理后的结果
callback(str);
// 输出结果到控制台
// console.log('Processed HTML:', str);
}
,
},
// **解析 MathJax 公式,获取 LaTeX**
async extractMathJaxLatex(cell, callback) {
return new Promise((resolve, reject) => {
// Step 1: First, process the math content and extract LaTeX from <wmath> tags
let updatedContent = cell.innerHTML; // Start with the cell's inner HTML
// Step 1: 获取初始 HTML
let updatedContent = cell.innerHTML;
// Find all <wmath> elements
// 查找所有 <wmath> 元素
const wmathElements = cell.querySelectorAll('wmath');
wmathElements.forEach((element) => {
// Get the LaTeX content from the data-latex attribute
const latexContent = element.getAttribute('data-latex');
// Replace the <wmath> tag with its LaTeX content wrapped in $$...$$
updatedContent = updatedContent.replace(element.outerHTML, `<wmath data-latex="${latexContent}">${latexContent}</wmath>`);
wmathElements.forEach((element) => {
// 1. 提取 latex 内容
const latexContent = element.getAttribute('data-latex') || '';
// 2. 提取 wrap 模式 (只提取,不包裹)
const wrapMode = element.getAttribute('data-wrap') || 'block';
// 3. 重新组装标签:只保留属性,标签内部只放原始 latex 文本
// 这里不加 $ 或 $$,保持数据的原始性
const newWmathTag = `<wmath data-wrap="${wrapMode}" data-latex="${latexContent}">${latexContent}</wmath>`;
// 4. 执行替换
updatedContent = updatedContent.replace(element.outerHTML, newWmathTag);
});
// Step 2: Now extract content without the outer <span> tags
// Step 2: 提取去掉外层 span 的内容
updatedContent = this.extractContentWithoutOuterSpan(updatedContent);
// Step 3: Call the callback function with the final updated content
// callback(updatedContent);
// Resolve the promise with the final content
// Step 3: Resolve 结果
resolve(updatedContent);
});
}
@@ -344,6 +350,21 @@ export default {
console.error("❌ 找不到 word/document.xml无法解析 Word 文件");
return;
}
const relsFile = zip.file("word/_rels/document.xml.rels");
let imageRelMap = {};
if (relsFile) {
const relsXml = await relsFile.async("string");
const relDoc = new DOMParser().parseFromString(relsXml, "application/xml");
const rels = relDoc.getElementsByTagName("Relationship");
for (let r = 0; r < rels.length; r++) {
const id = rels[r].getAttribute("Id");
const target = rels[r].getAttribute("Target");
if (target && target.includes("media/")) {
// 补全路径,通常 rels 里的路径是相对 word 文件夹的
imageRelMap[id] = `word/${target}`;
}
}
}
const documentXml = await documentFile.async("string");
const parser = new DOMParser();
const documentDoc = parser.parseFromString(documentXml, "application/xml");
@@ -452,14 +473,44 @@ export default {
const drawings = run.getElementsByTagName("w:drawing");
for (let d = 0; d < drawings.length; d++) {
const drawing = drawings[d];
// 使用命名空间提取 a:blip
// --- 1. 提取宽高 (EMUs 转 PX) ---
// Word 存储尺寸的地方通常在 wp:extent
const extent = drawing.getElementsByTagName("wp:extent")[0];
let styleStr = "";
if (extent) {
const cx = parseInt(extent.getAttribute("cx"));
const cy = parseInt(extent.getAttribute("cy"));
// 9525 是 EMU 到像素的换算比例
const widthPx = Math.round(cx / 9525);
const heightPx = Math.round(cy / 9525);
styleStr = `width="${widthPx}" height="${heightPx}"`;
}
const blips = drawing.getElementsByTagNameNS("http://schemas.openxmlformats.org/drawingml/2006/main", "blip");
for (let b = 0; b < blips.length; b++) {
const blip = blips[b];
const embedId = blip.getAttribute("r:embed");
if (embedId) {
textContent += `<img data-embed="${embedId}"/>`;
if (embedId && imageRelMap[embedId]) {
// const imagePath = imageRelMap[embedId];
// const imgFile = zip.file(imagePath);
// if (imgFile) {
// const fileSize = imgFile._data.uncompressedSize;
// // 依然保留 1MB 限制,保护浏览器不卡死
// if (fileSize <= 1048576) {
// const base64 = await imgFile.async("base64");
// const ext = imagePath.split('.').pop().toLowerCase();
// // <img src="data:image/${ext};base64,${base64}" ${styleStr} />
// // --- 2. 直接拼接带宽高的 img 标签 ---
// textContent += ``;
// } else {
// // console.warn(`跳过大图片: ${imagePath}, 大小: ${(fileSize / 1024 / 1024).toFixed(2)}MB`);
// textContent += ``;
// }
// }
}
}
}
@@ -843,7 +894,7 @@ export default {
// 使用正则表达式删除属性(保留 data-latex
let updatedAttributes = attributes.replace(/\s([a-zA-Z0-9-]+)(="[^"]*")?/g, function (attrMatch, attrName) {
if (attrName === "data-latex") {
if (attrName === "data-latex" || attrName === "data-id" || attrName === "data-wrap") {
return attrMatch;
}
if (type == 'table' && tag == 'img' && (attrName === "src" || attrName === "width" || attrName === "height")) {
@@ -856,9 +907,9 @@ export default {
});
// 2. 删除所有不需要的标签 (除 `strong`, `em`, `sub`, `sup`, `b`, `i` 外的所有标签)
if (type == 'table') {
inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue|wmath|img))[^>]+>/g, ''); // 删除不需要的标签
inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue|wmath|img|myfigure|mytable))[^>]+>/g, ''); // 删除不需要的标签
} else {
inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue|wmath))[^>]+>/g, ''); // 删除不需要的标签
inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue|wmath|myfigure|mytable))[^>]+>/g, ''); // 删除不需要的标签
}
@@ -2055,6 +2106,14 @@ export default {
onAction: () => {
// 插入自定义表格到编辑器中
ed.setContent('');
const customCallback = ed.getParam('clear_custom_action');
if (typeof customCallback === 'function') {
customCallback(ed, vueInstance);
} else {
// 3. 如果没有自定义逻辑,执行默认逻辑
vueInstance.$emit('onClear');
}
}
});
@@ -2065,11 +2124,11 @@ export default {
onAction: function () {
// 必须获取带 HTML 的内容,否则里面的 em/i 标签在拼接前就丢了
var selectedText = ed.selection.getContent({ format: 'html' });
if (selectedText) {
// 这就是你想要的:直接外层套一个 blue
var wrappedText = `<blue>${selectedText}</blue>`;
// 使用 setContent 强行回写
ed.selection.setContent(wrappedText);
}
@@ -2090,7 +2149,7 @@ export default {
const uid = 'wmath-' + Math.random().toString(36).substr(2, 9);
// 3. 创建一个 <wmath> 标签并插入到光标处
const wmathHtml = `<wmath contenteditable="false" data-id="${uid}" data-latex=""></wmath>`;
const wmathHtml = `<wmath contenteditable="false" data-id="${uid}" data-latex="" data-wrap="block"></wmath>`;
ed.insertContent(wmathHtml); // 在光标位置插入 wmath 标签
// 4. 打开公式编辑器窗口,并传递光标位置、编辑器 ID 和 wmathId
@@ -2152,29 +2211,29 @@ export default {
ed.ui.registry.addMenuButton('MoreSymbols', {
text: '···', // 按钮显示的三个点
tooltip: 'More Special Characters',
onAction: () => {}, // 菜单主按钮点击通常不执行操作,由子菜单执行
onAction: () => { }, // 菜单主按钮点击通常不执行操作,由子菜单执行
fetch: (callback) => {
const items = [
{
type: 'menuitem',
text: 'en-dash (短划线)',//短划线
onAction: () => ed.insertContent('')
},
{
type: 'menuitem',
text: 'minus sign (减号)',//减号
onAction: () => ed.insertContent('')
},
{
type: 'menuitem',
text: 'hyphen (连接符)',
onAction: () => ed.insertContent('-')
},
];
callback(items);
const items = [
{
type: 'menuitem',
text: 'en-dash (短划线)',//短划线
onAction: () => ed.insertContent('')
},
{
type: 'menuitem',
text: 'minus sign (减号)',//减号
onAction: () => ed.insertContent('')
},
{
type: 'menuitem',
text: 'hyphen (连接符)',
onAction: () => ed.insertContent('-')
},
];
callback(items);
}
});
});
ed.ui.registry.addButton('removeBlue', {
text: 'Blue', // 按钮文本

View File

@@ -34,20 +34,20 @@ function throttle(fn, delay = 1000, immediate = false) {
let args = arguments
if (immediate) {
console.log("立即执行参数 执行一次方法")
fn.apply(_this, args)
immediate = false
return
}
if (status) {
console.log("当前点击状态为正在重复点击,请稍等片刻后在点击执行")
return
}
console.log("执行节流:当前执行了一次点击方法")
fn.apply(_this, args)
status = true // 修改状态
timer = setTimeout(() => {
console.log("规定时间到,重置状态,可以重新调用")
status = false
}, delay)
}

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

@@ -2,19 +2,26 @@
//记得切换
//正式
// const mediaUrl = '/public/';
// const baseUrl = '/';
const mediaUrl = '/public/';
const baseUrl = '/';
//正式环境
// const mediaUrl = 'https://submission.tmrjournals.com/public/';
// // const mediaUrl = 'http://zmzm.tougao.dev.com/public/';
// const baseUrl = '/api'
const mediaUrl = 'http://tougaotest.tmrjournals.com/public/';
// const mediaUrl = 'http://zmzm.tougao.dev.com/public/';
const baseUrl = '/api';
//测试环境
// const mediaUrl = 'http://tougaotest.tmrjournals.com/public/';
// // const mediaUrl = 'http://zmzm.tougao.dev.com/public/';
// const baseUrl = '/api';
////新正式环境
// const mediaUrl = 'http://mytest.tmrjournals.com/public/';
// const baseUrl = '/api';
//本地(正式环境 )
// const mediaUrl = 'https://submission.tmrjournals.com/public/';
// const baseUrl = '/api';

View File

@@ -381,7 +381,6 @@ const en = {
reply: 'Reply',
execute: 'Execute',
revoke: 'Revoke',
solve: 'Solve',
cancelsolve: 'Cancel resolved',
Resolved: 'Resolved',
@@ -396,25 +395,37 @@ 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!',
Multicolumn: 'Multicolumn',
singleRow: "single-row",
Row: "Row",
addRow: "Add Row",
Uncheck: 'Uncheck the paragraph',
ManuscirptAIProofreading: 'Manuscript AI Proofreading',
AIProofreading: 'AI Proofreading',
Association: 'Association',
BatchAddcontent: 'Batch Add content',
MoveUp: 'Move Up',
MoveDown: 'Move Down',
jump: 'Locate',
editAssociation: 'Edit Association',
UnbindAssociation: 'Unbind Association',
},
pendingPayment: {
title: 'Title',
@@ -536,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,18 +389,29 @@ const zh = {
removeAnnotations: '确定要删除这条批注吗?',
removeProofread: '确定要删除这条建议吗?',
removeContent: '确定要删除这条内容吗?',
removeimg: '确定要删除这张图片吗?',
removetable: '确定要删除这个表格吗?',
reContent: '确定要恢复这条内容吗?',
uploadImageInfo: 'Figures 只能上传 JPG、JPEG 和 PNG 格式的文件',
selectComment: '请选择要添加批注的文本',
selectLinkText: '执行图表关联前,请先选定目标文本内容',
selectWord:'请只选中单个单词!',
selectOne:'请只勾选单个段落!',
alreadyCommented:'文本中已有批注内容请重新选择',
Multicolumn:'多列',
singleRow:"单列",
Row:"空行",
addRow:"新增空行",
Uncheck:'取消勾选段落',
ManuscirptAIProofreading:'稿件AI校对',
AIProofreading:'AI校对',
Association:'关联',
BatchAddcontent: '批量添加内容',
MoveUp: '上移',
MoveDown: '下移',
jump: '定位',
editAssociation: '编辑关联',
UnbindAssociation: '取消关联',
},
pendingPayment: {
title: 'Title',
@@ -518,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": "图"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,14 +2,66 @@
<div style="height: 100%">
<div
class="container"
style="
height: 100%;
min-width: calc(1000px);
width: calc(100%);
background-color: #fafafa;
padding: 0px 0 0 0;
box-sizing: border-box;
position: relative;
box-sizing: border-box;
"
>
<div
class="right-side"
style="
width: 285px;
float: left;
height: 100%;
background-color: #fff;
box-shadow: 0 1px 3px rgb(16 17 19 / 6%);
border-radius: 4px;
overflow-y: auto;
"
>
<!-- <p style="padding: 10px 10px; box-sizing: border-box; font-weight: bold"> -->
<!-- <b class="MaxBtn" style="right: 80px; top: 5px; padding: 0"><common-drag-word @tables="getTables"></common-drag-word></b> -->
<!-- <b class="MaxBtn" @click="MTxtPic()" style="background-color: #13bc20; right: 40px; top: 5px">
<svg
t="1684978324047"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="1967"
width="15"
height="15"
>
<path
d="M512 46.208a42.666667 42.666667 0 0 1 4.992 85.077333L512 131.541333H174.208a42.666667 42.666667 0 0 0-42.368 37.717334l-0.298667 4.949333v487.850667L307.2 501.12a88.874667 88.874667 0 0 1 112.042667-6.570667l5.845333 4.608 150.442667 128.896 101.973333-101.888a88.874667 88.874667 0 0 1 110.122667-12.373333l6.058666 4.138667 104.832 78.592V512a42.666667 42.666667 0 0 1 85.077334-4.992l0.298666 4.992v342.698667a128 128 0 0 1-120.490666 127.786666l-7.509334 0.213334H174.208a128 128 0 0 1-127.786667-120.490667l-0.213333-7.509333V174.208a128 128 0 0 1 120.490667-127.786667l7.509333-0.213333H512zM366.378667 563.2l-1.536 0.853333-233.301334 213.76v76.885334a42.666667 42.666667 0 0 0 37.717334 42.368l4.949333 0.298666H855.893333a42.666667 42.666667 0 0 0 42.368-37.717333l0.298667-4.949333v-151.808l-3.285333-2.090667-152.789334-114.602667a3.541333 3.541333 0 0 0-3.2-0.554666l-1.450666 0.853333-97.28 97.28 76.970666 66.048a42.666667 42.666667 0 0 1-51.2 68.010667l-4.309333-3.2-292.437333-250.666667a3.541333 3.541333 0 0 0-3.2-0.768z m415.829333-516.992a42.666667 42.666667 0 0 1 42.410667 37.717333l0.256 4.949334v96h96a42.666667 42.666667 0 0 1 4.992 85.077333l-4.992 0.256h-96v96a42.666667 42.666667 0 0 1-85.034667 4.992l-0.298667-4.992v-96h-96a42.666667 42.666667 0 0 1-4.949333-85.034667l4.949333-0.298666h96v-96a42.666667 42.666667 0 0 1 42.666667-42.666667z"
fill="#ffffff"
p-id="1968"
></path>
</svg> </b
><b class="MaxBtn" @click="MTxtTable()" style="background-color: #e07404; right: 0px; top: 5px">
<i class="el-icon-document-add"></i>
</b> -->
<!-- </p> -->
<div class="unfetteredBox" style="height: 100%">
<commonCatalogue
<catalogue
:content="Main_List"
:articleId="articleId"
ref="catalogue"
@getTables="getTables"
@goToListComment="goToListComment"
style="width: 100%; height: 100%; padding: 0 0px; box-sizing: border-box; background-color: #fff"
>
</catalogue>
<common-word-html-type-setting
:urlList="{
img: 'api/Preaccept/getMainImages',
table: 'api/Preaccept/getMainTables'
@@ -19,7 +71,6 @@
ref="commonWordHtmlTypeSetting"
@onDragStart="onDragStart"
@huifu="huifu"
:catalogueList="tableData"
@onAddComment="onAddComment"
@addImage="handleImageAdd"
@addTable="handleTableAdd"
@@ -30,19 +81,24 @@
@goToListComment="goToListComment"
style="width: 100%; height: 100%; padding: 0 0px; box-sizing: border-box; background-color: #fff"
>
</commonCatalogue>
</common-word-html-type-setting>
<input type="file" ref="fileInput" style="display: none" @change="handleFileChange" />
</div>
</div>
<div style="" class="right-content-box">
<common-reference
ref="commonReference1"
:tableData="tableData"
></common-reference>
<!-- <common-word
<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> -->
<common-word
v-if="htmlContent"
ref="commonWord"
:value="htmlContent"
@@ -55,6 +111,7 @@
@loaded="loadedWord"
@onEdit="onEdit"
@addContent="onAddContent"
@changeSort="changeSort"
@onDelete="onDelete"
@onDeletes="onDeletes"
@@ -78,7 +135,10 @@
<template slot="comment">
<div style="" class="commentList annotations"></div>
</template>
</common-word> -->
<template slot="refrences">
<!-- <reference ref="commonRef" :articleId="articleId" :p_article_id="p_article_id"></reference> -->
</template>
</common-word>
</div>
</div>
@@ -157,7 +217,8 @@
size="80vw"
>
<el-form ref="editMes" :model="lineStyle" label-width="80px">
<!-- <common-late-x></common-late-x> -->
<!-- :id="`editor-${new Date().getTime()}-${lineStyle.am_id}-${lineStyle.amt_id}-title`" -->
<el-form-item label="Title:">
<common-content
:id="`editor-${new Date().getTime()}-${lineStyle.am_id}-${lineStyle.amt_id}-title`"
@@ -186,7 +247,7 @@
></common-table>
</el-form-item>
<el-form-item label="Note:">
<!-- :id="`editor-${new Date().getTime()}-${lineStyle.am_id}-${lineStyle.amt_id}-note`" -->
<common-content
:id="`editor-${new Date().getTime()}-${lineStyle.am_id}-${lineStyle.amt_id}-note`"
:isAutomaticUpdate="true"
@@ -356,34 +417,25 @@
</el-button>
</span>
</el-dialog>
<common-late-x v-if="showLateX" @close="showLateX = false" @save="saveLateX" :LateXInfo="LateXInfo"></common-late-x>
</div>
</template>
<script>
import commonCatalogue from '@/components/page/components/OnlineProofreading/catalogue.vue';
import commonReference from '@/components/page/components/OnlineProofreading/reference.vue';
import bus from '@/components/common/bus';
import { del, isShallow } from 'vue';
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/OnlineProofreading/catalogue.vue';
import reference from '@/components/page/components/OnlineProofreading/reference.vue';
export default {
data() {
return {
tableData: [
// { title: 'Title ', key: '1' },
// { title: 'Author name', key: '2' },
// { title: 'Affialition', key: '3' },
// { title: 'Abstract & Keywords', key: '4' },
// { title: 'Main text', key: '5' },
// { title: 'Back Matter', key: '6' },
// { title: 'Editorial inforation', key: '7' },
// { title: 'Reference ', key: '8' },
],
p_article_id:3452,
zoomNum: (window.innerWidth * 0.38) / 850,
uploadWordTables: [],
tablesHtmlVisible: false,
@@ -507,12 +559,13 @@ export default {
components: {
Tinymce,
bottomTinymce,
commonCatalogue,
commonReference
catalogue,
reference,
},
computed: {
combinedValue() {
// 将两个值组合成一个新的值,可以是字符串、数组、对象等
// return `${this.isFirstComponentLoaded}-${this.isWordComponentLoaded}`;
}
},
watch: {
@@ -537,45 +590,49 @@ export default {
}
},
async created() {
localStorage.removeItem('scrollPosition');
this.isShowEditComment();
this.getDate();
this.getRefData();
this.getCommentList();
// this.loadDictionary().catch(console.error);
this.getReferenceList();
},
mounted() {},
async activated() {
this.isShowEditComment();
this.getDate();
this.getRefData();
this.getCommentList();
this.getReferenceList();
},
methods: {
// 获取引用文献信息
getRefData() {
getReferenceList() {
this.$api
.post('api/Preaccept/getArticleReferences', { article_id: this.$route.query.id })
.post('api/Production/getReferList', {
p_article_id: this.p_article_id
})
.then((res) => {
console.log(res);
if (res.code == 0 && res.data.refers.length > 0) {
this.tableData = res.data.refers;
}
this.referenceList = res.data.refers;
this.$nextTick(() => {
// 更新引用列表
// this.$refs.commonRef.init(this.referenceList);
});
})
.catch((err) => {
console.log(err);
});
},
async copyArray(data) {
try {
// 将数组内容转换为字符串,使用换行符分隔
const textToCopy = JSON.stringify(data);
// 使用 Clipboard API 复制文本
await navigator.clipboard.writeText(textToCopy);
alert('数组内容已复制到剪贴板!');
} catch (err) {
@@ -676,7 +733,7 @@ export default {
async saveContent(content, am_id) {
var that = this;
var str = content.replace(/^<p>\s*(.*?)\s*<\/p>$/, '$1').trim();
str = await that.$commonJS.decodeHtml(str);
await that.$api
@@ -814,14 +871,22 @@ export default {
// 编辑评论,显示文本框
goToListComment(id, type) {
this.goToComment(id);
var am_id;
if (type == 'img') {
am_id = this.Main_List.find((item) => item.ami_id == id).am_id;
}else if (type == 'content') {
am_id=id
}
else {
am_id = this.Main_List.find((item) => item.amt_id == id).am_id;
}
if (am_id) {
this.goToComment(am_id);
}
},
goToComment(mainId) {
this.$nextTick(() => {
this.$refs.commonReference1.goToComment(mainId);
this.$refs.commonWord.goToComment(mainId);
});
},
getTables(tables, html) {
@@ -1038,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 渲染
@@ -1166,19 +1231,21 @@ export default {
},
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.getDate();
this.getCommentList();
}else{
this.$message.error(res.msg);
}
})
.catch((err) => {
this.$message.error(err.msg);
@@ -1251,6 +1318,7 @@ export default {
},
deleteProofreading(data, index) {
console.log('comment at line 480:', data);
},
async cancelSolveComment(data) {
@@ -1372,7 +1440,7 @@ export default {
this.currentId = dataId;
},
async onDrop(event, dataId) {
if (event.dataTransfer.getData('image')) {
const draggedImage = JSON.parse(event.dataTransfer.getData('image'));
@@ -1446,10 +1514,13 @@ export default {
});
},
getWord() {
this.htmlContent = 'true';
},
// 获取数据
async getDate() {
this.imagesList = [];
let urlLInk = '';
let urlTask = {};
@@ -1469,7 +1540,7 @@ export default {
.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;
});
@@ -1477,13 +1548,14 @@ export default {
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();
loading.close();
});
// }, 1000);
@@ -1842,11 +1914,12 @@ export default {
},
handleAvatarError(res, file) {},
beforeAvatarUpload(file) {
// const isLt2M = file.size / 1024 / 1024 < 10;
// if (!isLt2M) {
// this.$message.error('Picture size cannot exceed 10M!');
// }
// return isLt2M;
const isLt2M = file.size / 1024 / 1024 < 20;
if (!isLt2M) {
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'));
@@ -1871,28 +1944,6 @@ export default {
</script>
<style scoped>
.container{
height: 100%;
min-width: calc(1000px);
width: calc(100%);
background-color: #fafafa;
padding: 0px 0 0 0;
box-sizing: border-box;
position: relative;
box-sizing: border-box;
}
.right-side{
width: 280px;
float: left;
height: 100%;
background-color: #fff;
box-shadow: 0 1px 3px rgb(16 17 19 / 6%);
border-radius: 4px;
overflow-y: auto;
}
.right-content-box{
width: 100%; width: calc(100% - 285px); float: right; height: calc(100% - 0px); background-color: #e4e9ed
}
.lineStyle {
border-top: 1px solid #0066994d;
padding: 20px 20px 40px 20px;

View File

@@ -2804,7 +2804,7 @@ export default {
that.$commonJS.extractWordTablesToArrays(File.raw, function (wordTables) {
that.addWordTablesList(wordTables);
loading.close();
});
},that.form.article_id);
};
reader.readAsArrayBuffer(File.raw);
}

View File

@@ -1298,7 +1298,7 @@
<span slot="label" class="title" :style="v.color ? `color:${v.color}` : ''">
{{ `${i + 1}. ${v.topic} : ` }}
</span>
<div style="">
<div style="" v-if="v.value!='hotspot'">
<div
style="color: #333; width: calc(100% - 180px); margin-left: 180px"
v-if="v.parameter && v.parameter.length > 0"
@@ -1349,6 +1349,58 @@
</template>
</div>
<div style="color: #888; line-height: 22px" v-if="v.explanationValue">
<p
:class="{ 'short-content': !v.showFullContent }"
@click="toggleContent1(i)"
style="margin-top: 0; cursor: pointer; color: #888"
>
<span>{{ $t('aiReview.Explain') }} : </span> {{ currentArticleData.ai_review[v.explanationValue] }}
</p>
</div>
</div>
<div style="" v-else>
<div
style="color: #333; width: calc(100% - 180px); margin-left: 180px"
v-if=" currentArticleData.ai_review[v.value] && currentArticleData.ai_review[v.value].length > 0"
>
<div label="``" v-for="(item, index) in currentArticleData.ai_review[v.value]">
<span class="title" style="" :style="item.color ? `color:${item.color}` : ''">
{{ `(${index + 1}) 领域 : ${item['领域']} ` }}
</span>
<div style="">
<div style="color: #333">
是否热点 : 【{{item['是否热点'] }}】
<div v-if="item['解释说明']">
<div>
<p
:class="{ 'short-content': !item.showFullContent }"
@click="toggleContent2(i, index)"
style="margin-top: 0; cursor: pointer; color: #888; line-height: 22px"
>
<span>{{ $t('aiReview.Explain') }} : </span>
{{ item['解释说明'] }}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div style="color: #333" v-else>
<template v-if="v.isShowSign == 1"> 【{{ currentArticleData.ai_review[v.value] }}】 </template>
<template v-else>
{{ currentArticleData.ai_review[v.value] }}
</template>
</div>
<div style="color: #888; line-height: 22px" v-if="v.explanationValue">
<p
:class="{ 'short-content': !v.showFullContent }"
@@ -2076,7 +2128,7 @@ export default {
ai_review: ''
};
if (res.data) {
this.currentArticleData.ai_review = res.data;
this.currentArticleData.ai_review = {...res.data, hotspot: res.data.hotspot?JSON.parse(res.data.hotspot):''};
var aiReview = {};
if (this.currentArticleData.ai_review.journal_scope_assessment == '') {
aiReview = {
@@ -2108,6 +2160,8 @@ export default {
};
}
this.aiReview[0] = aiReview;
}
})
.catch((err) => {

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,101 @@
<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: 14px;"
: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
<span><i class="el-icon-refresh" style="color: #006699; font-weight: bold;margin-left: 6px;cursor: pointer;" @click="refreshPdfList"></i></span>
<span v-if="detailMes.file_sub_table" style="color: #006699; font-size: 13px;font-weight: normal;float: right;">Supplementary Material :<a :href="'/public/articleSUBTAB/' +detailMes.file_sub_table" target="_blank" style="margin-left: 20px">
<i class="el-icon-download" style="color: #006699; font-weight: bold"></i>
</a></span>
</h5>
<p v-if="PDFfileList.length == 0" style="color: #666; font-size: 14px">No Manuscript</p>
<div v-for="(item, index) in PDFfileList" :key="index" class="load_pdf">
<img src="../../assets/img/icon_pdf.png" style="width: 26px; height: 26px;" />
<span style="color: #333">Typesetting {{ index + 1 }}</span>
<span style="margin-left: 40px; color: #888; font-size: 13px">Time : {{ modifDate(item.create_time * 1000) }}</span>
<a :href="'/public/' + 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 +829,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 +1029,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,
@@ -1256,6 +1293,7 @@ export default {
}
],
DLfileList: [],
PDFfileList: [],
UpTypeFile: {
pdf: ''
},
@@ -1450,8 +1488,34 @@ export default {
this.getAuthorJG();
this.getCount();
this.getWorldPdf();
this.getPdfList();
},
methods: {
async generatePDF() {
// 1. 验证逻辑(保持原样)
try {
this.isGenerating = true; // 开启加载
// 注意:这里要加 await确保拿到结果后再往下走
const res = await this.$api.post('api/Production/createArticlePdf', {
p_article_id: this.p_article_id
});
if (res.status == 1) {
this.$message.success("The PDF document is being processed. Please wait and refresh the page in one minute.");
} else {
this.$message.error("Generation failed, please try again.");
}
} catch (error) {
console.error(error);
this.$message.error("Network error, please try again.");
} finally {
// 只有请求真正完成后,才会执行这里
this.isGenerating = false;
}
},
getArticleFinal(id) {
// api/Finalreview/getRecord
this.$api
@@ -1721,6 +1785,40 @@ export default {
this.$message.error(err);
});
},
refreshPdfList(){ const loading = this.$loading({
lock: true,
text: 'Loading...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
try {
this.getPdfList();
loading.close();
} catch (error) {
this.$message.error(error);
}
},
getPdfList() {
// 可以下载的word列表
this.$api
.post('/api/Production/getProductionArticlePdf', {
p_article_id: this.p_article_id
})
.then((res) => {
if (res.status == 1) {
this.PDFfileList = res.data;
} else {
this.$message.error(res.msg);
}
})
.catch((err) => {
this.$message.error(err);
});
},
getCount() {
// 获取国家列表
@@ -2883,7 +2981,7 @@ export default {
this.preactive = 1;
// 获取文章信息
this.$api
.post('api/Production/getProductionMains', {
.post('api/Preaccept/getArticleMains', {
p_article_id: this.p_article_id
})
.then((res) => {
@@ -3071,6 +3169,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: 48%; /* 右侧固定宽度 */
}
.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 +3789,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

@@ -1,44 +1,17 @@
<template>
<!-- v-show="tables.length > 0 || images.length > 0" -->
<div
style=""
class="ManuscirptList"
>
<!-- 图片缩略图区域 -->
<div class="title">
Reference List
</div>
<div
style=""
class="arrlist"
>
<ul style="width: 100%; height: auto">
<li >
<div style="display: flex; flex-wrap: wrap; gap: 10px; justify-content: start">
<div @click="goToListComment(item.p_refer_id)"
v-for="(item, index) in catalogueList"
style="width: calc(100%); display: flex; align-items: center; justify-content: space-between; color: #606266"
>
<div class="doi_box"><span style="color: #888;margin-right: 5px;">{{ index+1 }}.</span> <span class="doi">{{ item.refer_doi }}</span></div>
<!-- <div
style="
width: calc(100% - 50px);
white-space: nowrap; /* 防止文本换行 */
overflow: hidden; /* 隐藏超出部分 */
text-overflow: ellipsis;
"
>
<p
v-html="index + 1 + '. ' + item.content"
style="font-size: 14px; width: 100%; overflow: hidden; line-height: 30px"
></p>
</div> -->
</div>
<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>
@@ -49,9 +22,18 @@
<script>
import { mediaUrl } from '@/common/js/commonJS.js'; // 引入通用逻辑
export default {
props: ['articleId', 'imgWidth', 'imgHeight', 'scale', 'isEdit', 'isShowEdit', 'urlList', 'content','catalogueList'],
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,
@@ -69,7 +51,7 @@ export default {
{ title: 'Typed', type: '1' },
{ title: 'Unfettered', type: '2' }
],
images: [],
tables: [],
imagesList: [],
@@ -91,6 +73,33 @@ export default {
}
},
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.catalogueList = res.data;
},
changeIsCollapse(e) {
localStorage.setItem('isCollapse', e);
},
@@ -254,6 +263,7 @@ export default {
}
},
goToListComment(id, type) {
console.log('id at line 399:', id);
this.$emit('goToListComment', id, type);
},
async refresh(type) {
@@ -356,7 +366,7 @@ export default {
});
},
isHeaderRow(rowIndex, table) {
var table =table;
var table = table;
var head = table[0];
@@ -406,8 +416,8 @@ export default {
>`;
if (table.table && table.table.length > 0) {
table.table.forEach((row,i) => {
modalContent += `<tr class="${this.isHeaderRow(i, table.table)? 'table-header-row':'' }">`;
table.table.forEach((row, i) => {
modalContent += `<tr class="${this.isHeaderRow(i, table.table) ? 'table-header-row' : ''}">`;
row.forEach((cell) => {
modalContent += `
<td
@@ -459,217 +469,153 @@ export default {
<style scoped>
.ManuscirptList {
background-color: none !important;
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0);
color: #333639;
background-color: transparent !important; margin-top: 0px !important; height: 100%; padding: 0 !important; overflow: hidden
}
.ManuscirptList div {
cursor: pointer;
background-color: none !important;
}
::v-deep .wordTableHtml table {
cursor: pointer;
background-color: #fff !important;
border-bottom: 1px solid #000 !important;
}
.el-menu-vertical-demo {
width: 85px;
}
.el-menu-vertical-demo li {
height: 100%;
padding: 0;
box-sizing: border-box;
background-color: #f5f5f5 !important; /* Word 导航栏通常是白色的 */
border-right: 1px solid #e1e1e1;
display: flex;
flex-direction: column;
padding: 10px 6px !important;
font-size: 14px !important;
text-align: center;
}
::v-deep .el-menu-vertical-demo.el-menu-item:focus,
.el-menu-item:hover {
background-color: #f8f8f9 !important;
/* 模拟 Word 导航标题 */
.word-navigation-header {
padding: 12px 16px 0 16px;
font-size: 14px;
color: #333;
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
}
::v-deep .el-menu-item.is-active {
background-color: #2196f32e !important;
.word-tabs {
display: flex;
margin-top: 10px;
border-bottom: 1px solid #e1e1e1;
}
.isSelectType {
background-color: #2196f32e !important;
color: #006699;
.word-tabs span {
padding: 4px 12px;
font-size: 12px;
color: #666;
cursor: pointer;
position: relative;
}
li {
.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;
}
.item_box {
background-color: #f2f3f5;
border: 1px solid rgba(0, 0, 0, 0);
border-radius: 6px;
/* 基础条目样式 - 模拟 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;
}
.go-transition .title {
background-color: #e5e6eb;
color: #767c82;
font-size: 13px;
padding: 2px 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
.go-transition .list-img {
padding: 6px 0;
background-color: #f2f3f5;
box-sizing: border-box;
}
.go-transition .list-img :hover {
transition: all 0.4s;
}
.go-content-charts-item-box {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 9px;
transition: all 0.7s linear;
}
.arraylist {
background-color: #f8f8f9;
}
.arrlist {
width: 280px; padding: 14px; height: calc(100% - 30px); box-sizing: border-box; overflow-y: auto;
border-top: 1px solid #ddd;
}
.arrlist li{
line-height: 30px
}
.arrlist::-webkit-scrollbar-thumb {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0);
min-height: 12px;
border: 4px solid transparent;
background-clip: content-box;
border-radius: 7px;
background-color: #c8d5e1;
}
.badge {
background-color: #f56c6c;
border-radius: 10px;
color: #fff;
display: inline-block;
font-size: 12px;
height: 18px;
line-height: 18px;
padding: 0 6px;
text-align: center;
/* 重点Word 常用排版字体 */
.title-content {
font-weight: bold !important;
font-style: normal !important;
font-size: 13px !important;
line-height: 1.3;
white-space: nowrap;
border: 1px solid #fff;
position: absolute;
}
.comments-section {
padding: 10px;
box-sizing: border-box;
border: 1px solid #cecfd3;
border-left: 2px solid #cecfd3;
/* border-radius: 5px; */
background-color: #f9f9f9;
box-shadow: rgba(16, 17, 19, 0.06) 0px 1px 3px;
width: 300px;
background-color: #fafafa;
float: right;
position: absolute;
right: -302px;
top: 0px;
z-index: 999;
padding: 4px 0px;
overflow: hidden;
text-overflow: ellipsis;
/* Times New Roman 增加学术感 */
font-family: 'Charis SIL' !important;
color: #333;
}
.comment-item {
padding: 5px 0;
border-bottom: 1px solid #ddd;
cursor: pointer;
box-sizing: border-box;
/* 保持 v-html 内部样式 */
::v-deep .title-content b {
font-weight: bold;
}
::v-deep .title-content i {
font-style: italic;
}
/* --- Word 风格的层级缩进 --- */
/* 1级标题 */
.level-1 {
padding-left: 16px;
}
.level-1 .title-content {
font-style: italic !important;
}
/* 2级标题 */
.level-2 {
padding-left: 36px;
}
.level-2 .title-content {
font-size: 14px;
}
.comment-item:last-child {
border-bottom: none;
/* 3级标题 */
.level-3 {
padding-left: 56px;
}
.level-3 .title-content {
font-size: 14px;
color: #666;
}
.comment-details {
margin-top: 20px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
background-color: #f9f9f9;
/* 隐藏滚动条 */
.arrlist::-webkit-scrollbar {
width: 5px;
}
.comment-details h3 {
margin-bottom: 10px;
}
.comment-details p {
margin-bottom: 10px;
}
.comment-details button {
padding: 5px 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.comment-details button:hover {
background-color: #0056b3;
}
.comments-section ul,
.comments-section li {
list-style: none;
margin: 0;
padding: 0;
}
::v-deep.pizhu.el-link--default:hover {
border-color: #fc625d !important;
}
.go-mt {
/* background-color: #f2f3f5;
height: 28px; */
width: 200px;
float: right;
}
.double .item_box {
width: 46% !important;
}
.double .item_box .previewbox {
display: none;
}
.double .item_box .imgbox {
height: 50px !important;
width: auto !important;
}
.double .item_box .imgbox img {
height: 60px !important;
width: auto !important;
}
.double .go-transition .title {
font-size: 12px !important;
padding: 0 2px !important;
line-height: 20px !important;
}
.single {
}
.title{
line-height: 28px;
padding: 10px 10px;
font-size: 18px;
font-weight: bold;
}
.doi_box{
width: 100%;
display: flex;align-items: center;
font-size: 14px;
line-height: 16px;
}
.doi{
width: calc(100% );
white-space: nowrap;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
.arrlist::-webkit-scrollbar-thumb {
background: #cdcdcd;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
<template>
<div class="tinymce-container editor-container">
<textarea class="tinymce-textarea" :id="tinymceId"></textarea>
</div>
</template>
@@ -10,139 +11,7 @@ import htmlDocx from 'html-docx-js/dist/html-docx.js';
import { Document, Packer, PageOrientation, Paragraph, TextRun } from 'docx'; // 引入 docx.js
import html2canvas from 'html2canvas';
import { extractHexImagesFromRTF, hexToBlob } from '@/utils/rtfParser';
const tableStyle = `
.word-img-placeholder {
background: #f0f0f0 ;
}
*{
font-family: 'Charis SIL';
}
b span{
font-weight: bold !important;
}
i span{
font-style: italic !important; ;
}
sub span{
vertical-align: sub;
}
sup span{
vertical-align: sup;
}
sub {
vertical-align: sub!important;
}
sup {
vertical-align: sup !important;
}
span[style*="vertical-align: super"] {
vertical-align: super !important;
}
span[style*="vertical-align: sub"] {
vertical-align: sub !important;
}
table {
border:0px !important;
border-collapse: collapse; /* 去除单元格间隙 */
width: auto;
margin : 0 auto !important;
table-layout: auto; /* 自动调整列宽 */
text-align:left;
font-family:'Charis SIL' !important;
font-size: 14px !important;
mso-font-kerning: 1.0000pt !important;
line-height: 20px !important;
mos-line-height: 20px !important;
}
table td, table th {
padding: 5px;
text-align:left !important;
white-space: pre-wrap; /* 保留换行符并换行 */
word-wrap: break-word; /* 长单词自动换行 */
word-break: break-word;
font-family:'Charis SIL' !important;
font-size: 14px !important;
mso-font-kerning: 1.0000pt !important;
line-height: 20px !important;
mos-line-height: 20px !important;
}
table tbody tr td{
text-align:left !important;
border-left:none !important;
mso-border-left-alt:none !important;
border-right:none !important;
mso-border-right-alt:none !important;
border-top:none;mso-border-top-alt:none !important;
border-bottom:none !important;
mso-border-bottom-alt:none !important;
border:1px dashed #dcdfe6 !important;
border-left:1px dashed #dcdfe6 !important;
border-right:1px dashed #dcdfe6 !important;
word-break: keep-all !important;
// text-align: justify !important; // 设置两端对齐
}
table tr td p{
text-align:left !important;
margin:0;
font-family:'Charis SIL' !important;
font-size: 14px !important;
mso-font-kerning: 1.0000pt !important;
line-height: 20px !important;
mos-line-height: 20px !important;
}
table span{
color:#000000;text-align:left !important;
font-family:'Charis SIL' !important;
font-size: 14px !important;
mso-font-kerning: 1.0000pt !important;
line-height: 20px !important;
mos-line-height: 20px !important;
}
table .color-highlight{
color:rgb(0,130,170) !important;
font-family:'Charis SIL' !important;
font-size: 14px !important;
mso-font-kerning: 1.0000pt !important;
line-height: 20px !important;
mos-line-height: 20px !important;
}
table tr:first-child td {
border-top:1.0000pt solid #000 !important;mso-border-top-alt:0.5000pt solid #000 !important;border-bottom:1.0000pt solid #000 !important;mso-border-bottom-alt:0.5000pt solid #000 !important;
}
table tr:last-of-type {
border-bottom:1.0000pt solid #000 !important;mso-border-bottom-alt:0.5000pt solid #000 !important;;
}
table span blue {
color: rgb(0, 130, 170) !important;
}
blue {
color: rgb(0, 130, 170) !important;
}
blue *{
color: rgb(0, 130, 170) !important;
}
.wordTableHtml table tr.table-header-row:nth-of-type(2) td {
border-bottom: 1px solid #000 !important;
}
mjx-container {
font-size: 14px !important;
;
}
wmath{
width: 100%;
display: block;
display: flex;
}
Info{
color:#ff8f25;
}
`;
import { tableStyle } from '@/utils/tinymceStyles';
export default {
name: 'tinymce',
components: {},
@@ -199,6 +68,10 @@ export default {
},
data() {
return {
uploadNotifications: {},
totalUploadImages: 0,
uploadedImageCount: 0,
uploadNotificationInstance: null, // 全局通知实例
baseUrl: this.Common.baseUrl,
mediaUrl: this.Common.mediaUrl,
typesettingType: 1,
@@ -268,6 +141,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;
@@ -277,9 +159,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();
@@ -335,43 +347,55 @@ export default {
}
},
// 按照你要求的 XMLHttpRequest 格式编写
uploadSingleImage(base64Data, index) {
const _this = this;
//转为 Blob
const blob = _this.dataURLtoBlob(base64Data);
const xhr = new XMLHttpRequest();
const formData = new FormData();
// 按照你截图中的参数名,这里假设是 'file'
formData.append('mainImage', 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.onload = function () {
if (xhr.status !== 200) {
console.error('HTTP Error: ' + xhr.status);
return;
}
try {
const json = JSON.parse(xhr.responseText);
if (json.code === 0) {
// 2. 拼接服务器返回的 URL
const finalUrl = _this.mediaUrl + 'articleImage/' + json.data.upurl;
// 3. 找到对应的加载中占位图并替换
const doc = tinymce.activeEditor.getDoc();
const placeholder = doc.querySelector(`img[data-idx="${index}"]`);
if (placeholder) {
placeholder.src = finalUrl;
placeholder.removeAttribute('data-idx'); // 任务完成,移除标记
}
}
} catch (e) {
console.error('解析响应失败', e);
}
};
// 直接接收已经转好的 blob
uploadSingleImage(blob, index) {
const _this = this;
const xhr = new XMLHttpRequest();
const formData = new FormData();
// 直接把传进来的 blob 丢进表单
formData.append('file_name', blob, `word_img_${index}.png`);
formData.append('article_id', this.articleId);
xhr.send(formData);
xhr.withCredentials = false;
xhr.open('POST', _this.baseUrl + 'api/Articlemain/uploadTableImage');
xhr.onload = function () {
if (xhr.status !== 200) {
_this.removePlaceholder(index); // 失败清理
return;
}
try {
const json = JSON.parse(xhr.responseText);
if (json.status == 1) {
const finalUrl = _this.mediaUrl + 'articleTableImage/' + json.data;
const doc = tinymce.activeEditor.getDoc();
const placeholder = doc.querySelector(`img[data-idx="${index}"]`);
if (placeholder) {
placeholder.src = finalUrl;
placeholder.removeAttribute('data-idx');
}
} else {
_this.removePlaceholder(index);
}
} catch (e) {
console.error('解析响应失败', e);
_this.removePlaceholder(index);
}
};
xhr.onerror = () => _this.removePlaceholder(index);
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(','),
@@ -393,7 +417,7 @@ export default {
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",
@@ -402,20 +426,50 @@ export default {
valid_elements:
this.type == 'table'
? '*[*]'
: `img[src|alt|width|height],strong,em,sub,sup,blue,table,b,i,wmath${this.valid_elements}`, // 允许的标签和属性
: `img[src|alt|width|height],strong,em,sub,sup,blue,table,b,i,myfigure,mytable,wmath${this.valid_elements}`, // 允许的标签和属性
// valid_elements: '*[*]', // 允许所有 HTML 标签
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' },
italic: { inline: 'i' },
italic: { inline: 'i' }
},
body_class: 'panel-body ',
object_resizing: false,
@@ -443,12 +497,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);
@@ -460,9 +514,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 {
@@ -487,41 +541,77 @@ export default {
_this.$commonJS.initEditorButton(_this, ed);
var currentWmathElement = null;
ed.on('click', function (e) {
const wmathElement = e.target.closest('wmath');
if (wmathElement) {
currentWmathElement = wmathElement; // 👈 保存当前点击的元素
const latexContentRaw = wmathElement.getAttribute('data-latex') || '';
console.log('at line 488: raw =', latexContentRaw);
// 去除所有 $ 符号
const latexContent = latexContentRaw.replace(/\$/g, '').trim();
const wmathElement = e.target.closest('wmath');
if (wmathElement) {
currentWmathElement = wmathElement; // 保存当前点击的元素
// 1. 提取内容:获取 LaTeX
const latexContentRaw = wmathElement.getAttribute('data-latex') || '';
const latexContent = latexContentRaw.replace(/\$/g, '').trim();
const encoded = encodeURIComponent(latexContent);
// 编码后用于传递到弹窗
const encoded = encodeURIComponent(latexContent);
// 给 wmath 添加唯一 data-id方便后续精准替换
let wmathId = wmathElement.getAttribute('data-id');
if (!wmathId) {
wmathId = 'wmath-' + Math.random().toString(36).substr(2, 9);
wmathElement.setAttribute('data-id', wmathId);
}
// 当前编辑器 ID 也保存下来(如果你有多个编辑器)
const editorId = ed.id;
// 打开编辑窗口并传参(传递 data-id + 内容)
window.open(
`/LateX?id=${encoded}&wmathId=${wmathId}&editorId=${editorId}`,
'_blank',
'width=1000,height=800,scrollbars=no,resizable=no'
);
}
});
// 修改 paste 事件中的逻辑
ed.on('paste', (event) => {
// 2. 提取状态:获取之前的 wrap 模式 👈 重要新增
const wrapMode = wmathElement.getAttribute('data-wrap') || 'block';
// 3. 处理唯一 ID
let wmathId = wmathElement.getAttribute('data-id');
if (!wmathId) {
wmathId = 'wmath-' + Math.random().toString(36).substr(2, 9);
wmathElement.setAttribute('data-id', wmathId);
}
const editorId = ed.id;
// 4. 打开窗口:在 URL 参数中增加 wrap 模式 👈 这样弹窗就知道该默认选哪个了
window.open(
`/LateX?id=${encoded}&wmathId=${wmathId}&editorId=${editorId}&wrap=${wrapMode}`,
'_blank',
'width=1000,height=800,scrollbars=no,resizable=no'
);
}
});
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 imageBlob = _this.hexToBlob(img.hex, img.mimeType);
_this.updateUploadProgressNotification(i, 'uploading');
// 2. 【核心优化】直接上传这个 Blob 文件
// uploadSingleImage 内部需要改用 FormData 发送
await _this.uploadSingleImage(imageBlob, 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 并关闭
}
});
@@ -632,8 +722,9 @@ export default {
});
let tempDiv = document.createElement('div');
tempDiv.innerHTML = content;
if (tempDiv.querySelector('table')) {
console.log('粘贴的内容包含表格');
if (_this.type == 'table') {
// 3. 在这里直接消费外部变量 currentPasteBase64Images
// content = content.replace(new RegExp(`src="${silentPlaceholder}"`, 'gi'), () => {
@@ -674,21 +765,37 @@ export default {
args.content = container.innerHTML; // 更新处理后的内容
});
} else {
// _this.$confirm('检测到粘贴内容包含表格,是否需要以表格形式添加?', '提示', {
// confirmButtonText: '添加表格',
// cancelButtonText: '纯文本添加',
// type: 'info'
// }).then(() => {
// _this.$emit('openAddTable', content);
// return false
// }).catch(() => {
// });
}
} else {
console.log('Original content:', content); // 输出原始粘贴内容
// 改进的正则表达式,匹配 $$...$$ 格式的 LaTeX 公式
const mathRegex = /\$\$([^$]+)\$\$/g;
// 1. 修改正则:同时匹配 $$...$$ (块级) 和 $...$ (行内)
// 注意:先匹配双美元符,再匹配单美元符,防止冲突
const mathRegex = /\$\$([\s\S]+?)\$\$|\$([\s\S]+?)\$/g;
// 如果粘贴的内容包含 $$...$$ 格式的公式,进行处理
content = content.replace(mathRegex, function (match, formula) {
console.log('Matched formula:', formula); // 输出每个匹配的公式
// 将公式包裹在 <wmath> 标签中,保留 $$...$$ 结构
return `<wmath data-latex="${match}">${match}</wmath>`;
});
content = content.replace(mathRegex, function (match, blockFormula, inlineFormula) {
// 判断是块级还是行内
const formula = blockFormula || inlineFormula;
const mode = blockFormula ? 'block' : 'inline';
console.log(`Matched ${mode} formula:`, formula);
console.log('Processed content:', content); // 输出处理后的内容
// 2. 统一改造:标签内只放纯 formula不带 $ 符号
// 属性中保存 data-latex 和 data-wrap
return `<wmath data-wrap="${mode}" data-latex="${formula.trim()}">${formula.trim()}</wmath>`;
});
console.log('Processed content:', content); // 输出处理后的内容
}
// 更新 args.content 为处理后的内容
// 阻止默认的粘贴行为,确保自定义处理优先执行
@@ -706,10 +813,13 @@ export default {
window.renderMathJax(_this.tinymceId);
}, 10);
},
clear_custom_action: (editor, vm) => {
vm.onClear();
},
init_instance_callback: (editor) => {
if (_this.value) {
editor.setContent('<p>'+_this.value+'</p>');
editor.setContent('<p>' + _this.value + '</p>');
setTimeout(() => {
window.renderMathJax(_this.tinymceId); // 初始化时渲染 MathJax
@@ -734,50 +844,73 @@ export default {
// 👂 message 监听器:处理编辑 + 新增两种情况
window.addEventListener('message', function (event) {
const data = event.data;
// console.log('data at line 648:', data);
// ✅ 编辑现有公式:替换或删除
if (data && data.type === 'update-wmath') {
const { editorId, wmathId, latex } = data;
const newLatex = latex ? latex.trim() : '';
const data = event.data;
if (data && (data.type === 'update-wmath' || data.type === 'insert-wmath')) {
const { editorId, wmathId, latex } = data;
const newLatex = latex ? latex.trim() : '';
if (!editorId) return;
if (!editorId || !wmathId) return;
const targetEditor = tinymce.get(editorId);
if (!targetEditor) return;
const targetEditor = tinymce.get(editorId);
if (!targetEditor) return;
const targetWmath = targetEditor.dom.select(`wmath[data-id="${wmathId}"]`, targetEditor.getBody())[0];
// 尝试寻找现有标签
const targetWmath = wmathId ? targetEditor.dom.select(`wmath[data-id="${wmathId}"]`, targetEditor.getBody())[0] : null;
if (targetWmath) {
if (!newLatex) {
// ❌ 删除公式
targetEditor.dom.remove(targetWmath);
} else {
// ✅ 更新公式
targetWmath.setAttribute('data-latex', newLatex);
targetWmath.innerHTML = newLatex;
if (targetWmath) {
// --- 原有的更新/删除逻辑 ---
if (!newLatex) {
targetEditor.dom.remove(targetWmath);
} else {
targetWmath.setAttribute('data-latex', newLatex);
if (data.wrapMode === 'inline') {
targetWmath.setAttribute('data-wrap', 'inline');
} else {
targetWmath.setAttribute('data-wrap', 'block');
}
targetWmath.innerHTML = newLatex;
}
} else if (newLatex) {
// --- ✨ 新增逻辑:如果找不到现有标签且有 latex 内容 ---
// 生成一个新的唯一 ID (如果你后端或插件没给的话)
const newId = wmathId || 'wmath_' + Date.now();
// 构建新的 wmath 标签字符串
// 这里你可以根据之前选的 wrapMode (块级/行内) 来决定样式
const htmlToInsert = `<wmath data-id="${newId}" data-latex="${newLatex}" data-wrap="${data.wrapMode === 'inline' ? 'inline' : 'block'}">${newLatex}</wmath>`;
// 在当前光标位置插入内容
targetEditor.insertContent(htmlToInsert);
}
setTimeout(() => {
if (typeof renderMathJax === 'function') {
this.window.renderMathJax(editorId);
}
}, 10);
}
}
}
});
// 统一渲染
setTimeout(() => {
if (typeof renderMathJax === 'function') {
renderMathJax(editorId);
}
}, 10);
}
});
// 🚩 标记为已注册,防止重复
window._wmath_listener_registered = true;
// 🧠 导出保存位置函数(你可以在按钮点击时调用它)
// 导出保存位置函数(你可以在按钮点击时调用它)
window._recordLatexInsertContext = function (editorInstance) {
latexEditorBookmark = editorInstance.selection.getBookmark(2);
activeEditorId = editorInstance.id;
};
}
},
// 提取 Word 文件中的表格
hexToBlob(hex, mimeType) {
const len = hex.length / 2;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
}
return new Blob([bytes], { type: mimeType });
},
updateTableStyles(container) {
var html = this.$commonJS.updateTableStyles(container, this.typesettingType);
var editor = window.tinymce.activeEditor; // 将外部 DOM 内容更新到编辑器
@@ -787,10 +920,11 @@ export default {
editor.focus(); // 聚焦到编辑器// 触发编辑器内容变化后,如果需要,可能还要设置编辑器的样式
},
//销毁富文本
destroyTinymce() {
destroyTinymce() {this.onClear();
if (window.tinymce.get(this.tinymceId)) {
window.tinymce.get(this.tinymceId).destroy();
}
},
//设置内容
setContent(value) {

View File

@@ -20,6 +20,7 @@
<el-form-item style="">
<div style="position: relative; top: 4px; margin-left: 14px">
<el-upload
ref="uploadFile"
class="upload-demo up_newstyle"
@@ -83,7 +84,7 @@ export default {
figure_copyright: this.value,
user_id: localStorage.getItem('U_id'),
type: 'figurecopyright',
url: this.value==1?this.fileList[0].url:'',
url: this.fileList[0].url,
// is_figure_copyright: this.value
}
var this_ = this;

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||processedItem.image)" 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,isNoBg) {
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: isNoBg ? [] : 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

@@ -1,151 +0,0 @@
<template>
<div style="position: fixed;top: 0;left: 0;background-color: #f8f8f8; width: 100%; height: 100%; overflow: hidden; background: linear-gradient(to top, #fbfffe, #ebf9ff);">
<div class="navbar-default">
<div class="nav commonWidth" style="height: 100%">
<div class="title">Digital Formula Editor</div>
</div>
</div>
<div class="commonWidth" style="padding:40px 20px; box-sizing: border-box;">
<!-- <div style="margin-bottom: 20px;font-size: 20px;">Numerical formula:</div> -->
<div style="display: flex;align-items: center;justify-content: space-between;">
<div ref="mathField" class="math-container" style="width: calc(100% - 100px);"></div>
<span @click="copyLatex" style="font-size: 20px;width: 80px;cursor: pointer;">📋 Copy </span>
</div>
<!-- 公式显示框 -->
<div class="formula-box" :style="{ opacity: showLatex ? 1 : 0 }">
<p>LaTeX 代码:</p>
<textarea ref="latexBox" class="latex-text" readonly>$${{ latex }}$$</textarea>
</div>
</div>
</div>
</template>
<script>
import { MathfieldElement } from 'mathlive';
export default {
data() {
return {
latex: '', // 默认公式
// latex: '\\frac{a}{b} + \\sqrt{x^2 + y^2} + e^{i\\pi}', // 默认公式
mathFieldInstance: null,
showLatex: false, // 控制 LaTeX 代码框的显示与隐藏
};
},
mounted() {
if (this.$refs.mathField) {
// 创建 MathfieldElement 组件
const mf = new MathfieldElement();
mf.style.width = '100%';
mf.virtualKeyboardMode = 'manual'; // 显示虚拟键盘
mf.value = this.latex; // 设置默认值
// 监听输入变化,更新 LaTeX 代码
mf.addEventListener('input', (event) => {
this.latex = event.target.value;
});
// 挂载到 DOM
this.$refs.mathField.appendChild(mf);
this.mathFieldInstance = mf;
// 强制显示虚拟键盘并保持显示
mf.executeCommand('showVirtualKeyboard');
// 保证虚拟键盘持续显示
this.keepKeyboardVisible(mf);
} else {
console.error('MathLive 未正确加载');
}
},
methods: {
keepKeyboardVisible(mf) {
// 每秒确保虚拟键盘显示
setInterval(() => {
mf.executeCommand('showVirtualKeyboard');
}, 20); // 每秒确保键盘显示
},
copyLatex() {
if (this.$refs.latexBox) {
this.$refs.latexBox.select(); // 选中文本
document.execCommand('copy'); // 复制到剪贴板
setTimeout(( )=>{
window.close();
},100)
}
},
},
beforeDestroy() {
this.mathFieldInstance = null; // 组件销毁时清除实例
}
};
</script>
<style scoped>
.commonWidth {
width: 100%;
margin: 0 auto;
}
.title {
font-size: 32px;
font-weight: bold;
line-height: 80px;
background-color: #0f4eb6;
color: #fff;
}
.navbar-default {
width: 100%;
height: 80px;
background-color: #0f4eb6;
border-color: #e7e7e7;
}
.math-container {
min-height: 40px;
border: 1px solid #ccc;
padding: 5px;
font-size: 18px;
/* margin-bottom: 20px; */
}
/* 公式弹出框样式 */
.formula-box {
margin-top: 10px;
padding: 10px;
border: 1px solid #ccc;
background-color: #f9f9f9;
display: flex;
flex-direction: column;
align-items: flex-start;
}
/* LaTeX 代码框 */
.latex-text {
width: 100%;
height: 50px;
font-size: 16px;
border: 1px solid #ccc;
padding: 5px;
margin-bottom: 10px;
resize: none;
}
/* 复制按钮 */
button {
padding: 5px 10px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
/* 防止虚拟键盘覆盖 */
::v-deep .ML__virtual-keyboard-toggle {
opacity: 1 !important; /* 确保虚拟键盘的按钮可见 */
}
</style>

View File

@@ -45,6 +45,13 @@
$${{ latex }}$$</textarea
>
</div>
<div style="margin-bottom: 15px;">
<p style="padding: 10px 0; color: #333;">Text Wrap</p>
<el-select v-model="wrapMode" placeholder="Select wrap mode" style="width: 100%">
<el-option label="Text above and below" value="block"></el-option>
<el-option label="Inline with text" value="inline"></el-option>
</el-select>
</div>
<div style="margin-top: 10px; overflow: hidden">
<el-button type="primary" @click="SaveyLatex" style="background-color: #1654f7 !important; float: right"
>Submit</el-button
@@ -94,7 +101,8 @@ export default {
list: formulaTemplates,
latex: '', // 默认公式
mathFieldInstance: null,
showLatex: false // 控制 LaTeX 代码框的显示与隐藏
showLatex: false, // 控制 LaTeX 代码框的显示与隐藏
wrapMode: 'block',
};
},
mounted() {
@@ -102,6 +110,9 @@ export default {
if (this.$route.query.id) {
this.latex = this.$route.query.id;
}
if (this.$route.query.wrap ) {
this.wrapMode = this.$route.query.wrap;
}
if (this.$refs.mathField) {
const mf = new MathfieldElement();
mf.style.width = '100%';
@@ -152,11 +163,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);
@@ -202,7 +213,8 @@ export default {
type: 'update-wmath',
latex: this.latex && this.latex != '' ? `$$${this.latex}$$` : '',
editorId: editorId,
wmathId: wmathId
wmathId: wmathId,
wrapMode: this.wrapMode
},
'*'
);

View File

@@ -0,0 +1,163 @@
<template>
<el-dialog
:close-on-click-modal="false"
:title="`Link ${currentTypeText}`"
:visible.sync="visible"
width="1000px"
custom-class="academic-link-dialog"
append-to-body
>
<p v-if="errorMessage" class="error-message"><i class="el-icon-warning"></i> &nbsp;{{ errorMessage }}</p>
<div class="list-container">
<el-radio-group v-model="selectedId" class="full-width-radio">
<div
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 || `${currentTypeText} ${index+1}`"
class="custom-radio-title"
@click.stop="handleClick(item)"
:title="stripHtml(item.title || `${currentTypeText} ${index+1}`)"
></div
></el-radio>
</div>
<el-empty
v-if="displayList.length === 0"
:description="`No ${currentTypeText.toLowerCase()}s found`"
:image-size="60"
></el-empty>
</el-radio-group>
</div>
<div slot="footer" class="dialog-footer">
<el-button size="small" @click="visible = false">Cancel</el-button>
<el-button size="small" type="primary" @click="confirm" :disabled="!selectedId">Confirm</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
props: ['content', 'urlList', 'articleId', 'displayList', 'currentTypeText'], // 全文内容
data() {
return {
errorMessage: '',
visible: false,
selectedId: null,
initialSelectedId: null,
selectLinkData: {}
};
},
methods: {
stripHtml(html) {
return html ? html.replace(/<[^>]+>/g, '') : '';
},
handleClick(item) {
this.selectedId = item.amt_id || item.ami_id;
this.$forceUpdate();
},
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);
// 3. 存在则赋值,不存在(或原本就是 null则设为 null
this.selectedId = exists ? inputId : null;
// 调试日志,方便排查回显失败的原因
if (inputId && !exists) {
this.errorMessage = `The associated resource no longer exists. Please re-select and bind.`;
}
this.visible = true;
},
confirm() {
const item = this.displayList.find((i) => i.amt_id === this.selectedId || i.ami_id === this.selectedId);
this.$emit('confirm', { select: item, linkData: this.selectLinkData, type: this.currentTypeText.toLowerCase() });
this.visible = false;
}
}
};
</script>
<style lang="less" scoped>
::v-deep .el-dialog__body {
padding: 10px 20px 20px !important;
}
::v-deep .el-radio {
width: 100% !important;
display: flex;
align-items: flex-start;
}
/* 保持原有好看的样式 */
.academic-link-dialog {
.dialog-header-tip {
background: #f0f7ff;
padding: 12px;
border-radius: 4px;
margin-bottom: 15px;
font-size: 13px;
color: #409eff;
i {
margin-right: 8px;
}
}
.list-container {
max-height: 400px;
overflow-y: auto;
border: 1px solid #f0f0f0;
border-radius: 4px;
padding: 10px;
}
.item-card {
padding: 10px;
margin-bottom: 6px;
border: 1px solid transparent;
border-radius: 4px;
&:hover {
background: #f5f7fa;
}
&.active {
border-color: #409eff;
background: #f0f7ff;
}
}
.full-width-radio {
width: 100%;
}
}
/* 核心:限制宽度并触发省略号 */
.custom-radio-title {
display: inline-block;
vertical-align: middle;
max-width: 100%; /* 必须设置一个宽度,或者 100% */
overflow: hidden;
text-overflow: ellipsis; /* 超出显示点点点 */
white-space: nowrap; /* 强制不换行 */
}
/* 兼容 Element UI让 radio 的 label 容器能够自适应宽度 */
::v-deep .el-radio__label {
display: inline-flex;
align-items: center;
width: calc(100% - 25px); /* 减去左侧 radio 圆圈的宽度 */
}
.error-message {
background-color: #fdf6ec;
border-color: #faecd8;
color: #e6a23c;
padding: 0 10px;
line-height: 30px;
font-size: 14px;
border-radius: 4px;
margin-bottom: 20px;
}
</style>

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 {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -35,7 +35,7 @@
<span
>Figure {{ index + 1 }}
<span
@click="openPreview(index, 'img')"
@click="openPreview(index, 'img',img)"
>
<!-- <el-tooltip class="item" effect="dark" :content="$t('commonTable.preview')" placement="top"> -->
@@ -149,7 +149,7 @@
<div class="title">
<span
>Table {{ index + 1 }}
<span @click="openPreview(index, 'table')">
<span @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>
</el-tooltip> </span
@@ -252,11 +252,17 @@
</ul>
</div>
<DynamicTable
ref="myTableModal"
/>
</div>
</template>
<script>
import { mediaUrl } from '@/common/js/commonJS.js'; // 引入通用逻辑
import DynamicTable from './DynamicTable.vue';
export default {
props: ['articleId', 'imgWidth', 'imgHeight', 'scale', 'isEdit', 'isShowEdit', 'urlList', 'content'],
data() {
@@ -298,6 +304,9 @@ export default {
}
}
},
components: {
DynamicTable
},
methods: {
@@ -423,10 +432,10 @@ export default {
});
}
},
openPreview(index, type) {
this.$nextTick(() => {
document.getElementById(type + '-modal-' + index).style.display = 'flex';
});
openPreview(index, type,data) {
console.log("🚀 ~ data:", data);
this.$refs.myTableModal.open(type,data,'no');
},
async getWordimgList() {
await this.$api
@@ -453,31 +462,7 @@ export default {
}
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%;background:#FFF;" >`;
this.$commonJS.createImageModal(index, modalContent, 'img');
// }
// if (extension === 'tif') {
// this.$nextTick(() => {
// this.$commonJS.renderTiffImage(
// this.mediaUrl + img.image,
// `tiff-canvas-modal-${index}`,
// function (url) {
// list[index].dataUrl = url;
// },
// index,
// true
// );
// });
// }
});
}
this.images = list;
// this.$emit('loaded');
});
@@ -512,36 +497,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;"
>`;
table.table.forEach((row) => {
modalContent += `<tr>`;
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', '');
});
}
});
}

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

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

View File

@@ -195,6 +195,7 @@ const components = {
'common-word-html': () => import('@/components/page/components/table/wordHtml.vue'),
'common-word-html-type-setting': () => import('@/components/page/components/table/wordHtmlTypesetting.vue'),
'common-drag-word': () => import('@/components/page/components/table/dragWord.vue'),
'common-media-link-dialog': () => import('@/components/page/components/table/MediaLinkDialog.vue'),
};
Object.keys(components).forEach(key => {

274
src/utils/tinymceStyles.js Normal file
View File

@@ -0,0 +1,274 @@
export const tableStyle = `
wmath[data-wrap="inline"] {
display: inline-block !important;
width: auto !important;
}
.word-img-placeholder {
background: #f0f0f0 ;
}
*{
font-family: 'Charis SIL';
}
b span{
font-weight: bold !important;
}
i span{
font-style: italic !important; ;
}
sub span{
vertical-align: sub;
}
sup span{
vertical-align: sup;
}
sub {
vertical-align: sub!important;
}
sup {
vertical-align: sup !important;
}
span[style*="vertical-align: super"] {
vertical-align: super !important;
}
span[style*="vertical-align: sub"] {
vertical-align: sub !important;
}
table {
border:0px !important;
border-collapse: collapse; /* 去除单元格间隙 */
width: auto;
margin : 0 auto !important;
table-layout: auto; /* 自动调整列宽 */
text-align:left;
font-family:'Charis SIL' !important;
font-size: 14px !important;
mso-font-kerning: 1.0000pt !important;
line-height: 20px !important;
mos-line-height: 20px !important;
}
table td, table th {
padding: 5px;
text-align:left !important;
white-space: pre-wrap; /* 保留换行符并换行 */
word-wrap: break-word; /* 长单词自动换行 */
word-break: break-word;
font-family:'Charis SIL' !important;
font-size: 14px !important;
mso-font-kerning: 1.0000pt !important;
line-height: 20px !important;
mos-line-height: 20px !important;
}
table tbody tr td{
text-align:left !important;
border-left:none !important;
mso-border-left-alt:none !important;
border-right:none !important;
mso-border-right-alt:none !important;
border-top:none;mso-border-top-alt:none !important;
border-bottom:none !important;
mso-border-bottom-alt:none !important;
border:1px dashed #dcdfe6 !important;
border-left:1px dashed #dcdfe6 !important;
border-right:1px dashed #dcdfe6 !important;
word-break: keep-all !important;
// text-align: justify !important; // 设置两端对齐
}
table tr td p{
text-align:left !important;
margin:0;
font-family:'Charis SIL' !important;
font-size: 14px !important;
mso-font-kerning: 1.0000pt !important;
line-height: 20px !important;
mos-line-height: 20px !important;
}
table span{
color:#000000;text-align:left !important;
font-family:'Charis SIL' !important;
font-size: 14px !important;
mso-font-kerning: 1.0000pt !important;
line-height: 20px !important;
mos-line-height: 20px !important;
}
table .color-highlight{
color:rgb(0,130,170) !important;
font-family:'Charis SIL' !important;
font-size: 14px !important;
mso-font-kerning: 1.0000pt !important;
line-height: 20px !important;
mos-line-height: 20px !important;
}
table tr:first-child td {
border-top:1.0000pt solid #000 !important;mso-border-top-alt:0.5000pt solid #000 !important;border-bottom:1.0000pt solid #000 !important;mso-border-bottom-alt:0.5000pt solid #000 !important;
}
table tr:last-of-type {
border-bottom:1.0000pt solid #000 !important;mso-border-bottom-alt:0.5000pt solid #000 !important;;
}
table span blue {
color: rgb(0, 130, 170) !important;
}blue {
color: rgb(0, 130, 170) !important;
}
blue sup {
color: rgb(0, 130, 170) !important;
}
blue sub {
color: rgb(0, 130, 170) !important;
}
.wordTableHtml table tr.table-header-row:nth-of-type(2) td {
border-bottom: 1px solid #000 !important;
}
mjx-container {
font-size: 14px !important;
;
}
wmath{
width: 100%;
display: block;
display: flex;
}
Info{
color:#ff8f25;
}
`;
/**
* 提取自编辑器 template 节点的公共样式
* 包含基础布局、H1-H3 标识、校对状态、图片/表格特定样式
*/
export const commonWordStyle = `
/* 1. 基础段落与容器 */
.pMain {
margin-top: 5px;
margin-bottom: 5px;
min-height: 22px;
font-size: 14px;
line-height: 22px;
font-family: 'Charis SIL', serif;
text-align: justify;
color: #606266;
position: relative;
}
/* H1 标题容器块 */
.pMainH1 {
background: rgb(222, 235, 247);
color: rgb(210, 90, 90) !important;
text-align: center !important;
border-radius: 10px;
font-size: 16px;
font-weight: bold;
margin-top: 15px !important;
margin-bottom: 10px !important;
box-shadow: rgba(222, 234, 247, 0.6) 0px 4px 4px;
}
.pMainH1 *{
color: rgb(210, 90, 90) !important;
font-weight: bold;
}
.pMainH1 *.pMain {
margin-top: 0 !important;
margin-bottom: 0 !important;
line-height: 30px !important;
text-align: center;
font-size: 16px;
font-weight: bold !important;
}
.pMainH1 * .pMain i {
font-style: normal !important;
}
/* 选中/激活时的发光边框 */
.glowing-border {
border: 3px solid #006699d1;
border-radius: 5px;
padding: 4px;
animation: glow-flash 1.5s infinite alternate;
}
@keyframes glow-flash {
0% { box-shadow: 0 0 5px #006699d1; }
100% { box-shadow: 0 0 20px #006699d1; }
}
/* 2. 状态标识样式 */
.proofreading-num, .Proofreadingstatus {
position: absolute;
right: 6px;
z-index: 10;
cursor: pointer;
}
.proofreading-num {
background: #e61a12;
color: #fff;
border-radius: 20px;
padding: 0px 6px;
font-size: 12px;
}
.Proofreadingstatus {
background-color: #fff;
border-radius: 16px;
font-size: 16px;
right: 2px;
}
.isRemark {
display: flex;
align-items: center;
z-index: 2;
cursor: pointer;
}
.isRemark span {
font-weight: bold;
background-color: #fef0f0;
color: #f56c6c;
padding: 0 5px;
font-size: 12px;
}
/* 3. 内容类型样式 (图片/表格) */
.MaxPicture { text-align: center !important; }
.myeditablediv img { width: 80% !important; max-width: 580px; margin-bottom: 10px; }
.imageTitle, .tableTitle {
color: rgb(210, 90, 90) !important;
font-weight: bold;
text-align: center;
margin-bottom: 10px;
display: block;
}
.table_Box { padding: 8px 15px; }
.table-box { width: 100%; border-collapse: collapse; text-align: center; }
.table-box td { border: 1px solid #000; }
.oddColor td { background: rgb(250, 231, 232); }
/* 4. 辅助层级标识 (H1/H2/H3 侧边圆圈) */
.Htitle {
color: #4d99f1;
background-color: #dbebfca6;
border-radius: 50%;
font-weight: bold;
position: absolute;
line-height: 24px;
font-size: 16px;
left: -30px;
top: 0px;
text-align: center;
width: 24px;
height: 24px;
}
.select-main-id { position: absolute !important; }
`;

View File

@@ -70,14 +70,10 @@ module.exports = {
},
proxy: {
'/api': {
// target: 'https://www.tmrjournals.cn',
// target: 'http://www.tougao.com/public/index.php/',
// target: 'http://www.tougao.com/',
// target: 'http://192.168.110.110/tougao/public/index.php/',
// target: 'http://api.tmrjournals.com/public/index.php/',//正式
// target: 'http://zmzm.tougao.dev.com/',//晓玲
// target: 'https://submission.tmrjournals.com/',//正式
target: 'http://tougaotest.tmrjournals.com/public/index.php/',//测试环境
// target: 'http://zmzm.tougao.dev.com/',//晓玲本地
target: 'https://submission.tmrjournals.com/',//正式
// target: 'http://tougaotest.tmrjournals.com/public/index.php/',//测试环境
// target: 'http://mytest.tmrjournals.com/public/index.php/',//新正式环境
changeOrigin: true,
pathRewrite: {
'^/api': ''