feat: 修改文章编辑器

This commit is contained in:
zyronon
2025-03-24 02:00:11 +08:00
parent c44c8c4ed6
commit ba3ccedb08
5 changed files with 174 additions and 93 deletions

View File

@@ -22,7 +22,7 @@
"compromise": "^14.10.0",
"copy-to-clipboard": "^3.3.3",
"dayjs": "^1.11.11",
"element-plus": "^2.3.9",
"element-plus": "^2.9.7",
"file-saver": "^2.0.5",
"git-last-commit": "^1.0.1",
"hover.css": "^2.3.2",

17
pnpm-lock.yaml generated
View File

@@ -30,8 +30,8 @@ importers:
specifier: ^1.11.11
version: 1.11.11
element-plus:
specifier: ^2.3.9
version: 2.7.3(vue@3.4.27(typescript@5.4.5))
specifier: ^2.9.7
version: 2.9.7(vue@3.4.27(typescript@5.4.5))
file-saver:
specifier: ^2.0.5
version: 2.0.5
@@ -1481,6 +1481,9 @@ packages:
dayjs@1.11.11:
resolution: {integrity: sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==}
dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
de-indent@1.0.2:
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
@@ -1589,8 +1592,8 @@ packages:
electron-to-chromium@1.4.787:
resolution: {integrity: sha512-d0EFmtLPjctczO3LogReyM2pbBiiZbnsKnGF+cdZhsYzHm/A0GV7W94kqzLD8SN4O3f3iHlgLUChqghgyznvCQ==}
element-plus@2.7.3:
resolution: {integrity: sha512-OaqY1kQ2xzNyRFyge3fzM7jqMwux+464RBEqd+ybRV9xPiGxtgnj/sVK4iEbnKnzQIa9XK03DOIFzoToUhu1DA==}
element-plus@2.9.7:
resolution: {integrity: sha512-6vjZh5SXBncLhUwJGTVKS5oDljfgGMh6J4zVTeAZK3YdMUN76FgpvHkwwFXocpJpMbii6rDYU3sgie64FyPerQ==}
peerDependencies:
vue: ^3.2.0
@@ -5057,6 +5060,8 @@ snapshots:
dayjs@1.11.11: {}
dayjs@1.11.13: {}
de-indent@1.0.2: {}
debug@2.6.9:
@@ -5142,7 +5147,7 @@ snapshots:
electron-to-chromium@1.4.787: {}
element-plus@2.7.3(vue@3.4.27(typescript@5.4.5)):
element-plus@2.9.7(vue@3.4.27(typescript@5.4.5)):
dependencies:
'@ctrl/tinycolor': 3.6.1
'@element-plus/icons-vue': 2.3.1(vue@3.4.27(typescript@5.4.5))
@@ -5152,7 +5157,7 @@ snapshots:
'@types/lodash-es': 4.17.12
'@vueuse/core': 9.13.0(vue@3.4.27(typescript@5.4.5))
async-validator: 4.2.5
dayjs: 1.11.11
dayjs: 1.11.13
escape-html: 1.0.3
lodash: 4.17.21
lodash-es: 4.17.21

View File

@@ -27,7 +27,7 @@ defineEmits<{
justify-content: center;
align-items: center;
flex-direction: column;
font-size: .7rem;
font-size: .9rem;
gap: 1.3rem;
span {

View File

@@ -73,6 +73,7 @@ function toggle() {
.text {
color: var(--color-font-1);
font-size: 1.2rem;
min-height: 1.1rem;
}
</style>

View File

@@ -22,6 +22,7 @@ import {_copy, _parseLRC} from "@/utils";
import * as Comparison from "string-comparison"
import audio from '/public/sound/article/nce2-1/1.mp3'
import BaseIcon from "@/components/BaseIcon.vue";
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
interface IProps {
article?: Article,
@@ -298,26 +299,61 @@ function test() {
// console.log(cosine.similarity('Thanos', 'Rival'))
}
const a = new Audio(audio)
function play(sentence: Sentence) {
if (sentence.audioPosition?.length) {
let start = sentence.audioPosition[0];
a.currentTime = start
a.play()
let end = sentence.audioPosition?.[1]
if (end && end !== -1) {
setTimeout(() => {
a.pause()
}, (end - start) * 1000)
}
}
}
function s() {
}
function confirm() {
}
let currentSentence = $ref<Sentence>({} as any)
let showEditAudioDialog = $ref(false)
let sentenceAudioRef = $ref<HTMLAudioElement>()
let audioRef = $ref<HTMLAudioElement>()
function handleShowEditAudioDialog(val: Sentence) {
showEditAudioDialog = true
currentSentence = val
if (!currentSentence.audioPosition?.length) {
currentSentence.audioPosition = [0, 0]
}
}
function recordStart() {
if (sentenceAudioRef.paused) {
sentenceAudioRef.play()
}
currentSentence.audioPosition[0] = Number(sentenceAudioRef.currentTime.toFixed(2))
}
function recordEnd() {
if (!sentenceAudioRef.paused) {
sentenceAudioRef.pause()
}
currentSentence.audioPosition[1] = Number(sentenceAudioRef.currentTime.toFixed(2))
}
let timer = -1
function playSentenceAudio(sentence: Sentence, ref?: HTMLAudioElement) {
clearTimeout(timer)
if (sentence.audioPosition?.length) {
if (ref.played) {
ref.pause()
}
let start = sentence.audioPosition[0];
ref.currentTime = start
ref.play()
let end = sentence.audioPosition?.[1]
if (end && end !== -1) {
timer = setTimeout(() => {
ref.pause()
}, (end - start) * 1000)
}
}
}
</script>
<template>
@@ -343,9 +379,10 @@ function s() {
<div class="justify-between items-center gap-2 flex">
<ol class="py-0 pl-5 my-0 text-base color-black/60">
<li>复制原文</li>
<li>点击 <span class="color-red font-bold">应用按钮</span> 进行分句并同步到左侧结果</li>
<li>点击 <span class="color-red font-bold">应用</span> 按钮进行分句并同步到左侧结果</li>
<li>手动调整分句一行一句段落之间空一行<span class="color-red font-bold">可选</span></li>
<li>调整完后点击 <span class="color-red font-bold">应用按钮</span> 同步到左侧结果<span class="color-red font-bold">可选</span>
<li>修改完成后点击 <span class="color-red font-bold">应用</span> 按钮同步到左侧结果<span
class="color-red font-bold">可选</span>
</li>
</ol>
<el-button type="primary" @click="renewSections">应用</el-button>
@@ -380,21 +417,19 @@ function s() {
<div class="justify-between items-center gap-2 flex">
<ol class="py-0 pl-5 my-0 text-base color-black/60">
<li>复制译文</li>
<li>如果没有译文点击 <span class="color-red font-bold">翻译</span> 按钮<span class="color-red font-bold">可选</span></li>
<li>点击 <span class="color-red font-bold">应用按钮</span> 进行分句并同步到左侧结果</li>
<li>如果没有译文点击 <span class="color-red font-bold">翻译</span> 按钮<span
class="color-red font-bold">可选</span></li>
<li>点击 <span class="color-red font-bold">应用</span> 按钮进行分句并同步到左侧结果</li>
<li>手动调整分句一行一句段落之间空一行<span class="color-red font-bold">可选</span></li>
<li>调整完后点击 <span class="color-red font-bold">应用按钮</span> 同步到左侧结果<span class="color-red font-bold">可选</span>
<li>修改完成后点击 <span class="color-red font-bold">应用</span> 按钮同步到左侧结果<span
class="color-red font-bold">可选</span>
</li>
</ol>
<div class="flex flex-col gap-2">
<div class="flex flex-col gap-2 items-end">
<div class="translate-item">
<el-progress :percentage="progress"
:duration="30"
:striped="progress !== 100"
:striped-flow="progress !== 100"
:stroke-width="8"
:show-text="true"/>
{{ progress }}%
<el-select v-model="networkTranslateEngine"
class="w-20"
>
<el-option
v-for="item in TranslateEngineOptions"
@@ -403,25 +438,24 @@ function s() {
:value="item.value"
/>
</el-select>
<BaseButton
size="small"
@click="startNetworkTranslate"
:loading="progress!==0 && progress !== 100"
>翻译
</BaseButton>
</div>
<div class="flex justify-end">
<el-button type="primary" @click="renewSections">应用</el-button>
</div>
<el-button
type="primary"
@click="startNetworkTranslate"
:loading="progress!==0 && progress !== 100"
>翻译
</el-button>
<el-button type="primary" @click="renewSections">应用</el-button>
</div>
</div>
</div>
<div class="row">
<div class="row flex flex-col gap-2">
<div class="title">结果</div>
<div class="center">正文译文与结果均可编辑修改一处另外两处会自动同步变动</div>
<div class="flex gap-2">
<BaseButton>导入音频</BaseButton>
<BaseButton @click="test">导入音频LRC</BaseButton>
<BaseButton>添加音频</BaseButton>
<BaseButton @click="test">添加音频LRC</BaseButton>
<el-upload
v-model:file-list="fileList"
class="upload-demo"
@@ -435,27 +469,44 @@ function s() {
</div>
</template>
</el-upload>
<audio :src="audio" controls></audio>
<audio ref="audioRef" :src="audio" controls></audio>
</div>
<template v-if="editArticle.sections.length">
<div class="article-translate">
<div class="section" v-for="(item,indexI) in editArticle.sections">
<div class="sentence flex justify-between" v-for="(sentence,indexJ) in item">
<div>
<EditAbleText
:value="sentence.text"
@save="(e:string) => saveSentenceText(sentence,e)"
/>
<EditAbleText
v-if="sentence.translate"
:value="sentence.translate"
@save="(e:string) => saveSentenceTranslate(sentence,e)"
/>
</div>
<div class="flex items-center gap-2">
<div class="text-base"> {{ sentence.audioPosition?.[0] }} - {{ sentence.audioPosition?.[1] }}</div>
<div>
<BaseIcon icon="hugeicons:play" @click="play(sentence)"/>
<div class="flex-1 overflow-auto flex flex-col">
<div class="flex justify-between bg-black/10 py-2">
<div class="center flex-[7]">内容</div>
<div>|</div>
<div class="center flex-[3]">音频</div>
</div>
<div class="article-translate">
<div class="section " v-for="(item,indexI) in editArticle.sections">
<div class="section-title">{{ indexI + 1 }}</div>
<div class="sentence" v-for="(sentence,indexJ) in item">
<div class="flex-[7]">
<EditAbleText
:value="sentence.text"
@save="(e:string) => saveSentenceText(sentence,e)"
/>
<EditAbleText
class="text-lg!"
v-if="sentence.translate"
:value="sentence.translate"
@save="(e:string) => saveSentenceTranslate(sentence,e)"
/>
</div>
<div class="flex-[3]">
<div class="flex items-center justify-end gap-2">
<div class="text-base" v-if="sentence.audioPosition?.length">
<span>{{ sentence.audioPosition?.[0] }}s</span>
<span v-if="sentence.audioPosition?.[1] !== -1"> - {{ sentence.audioPosition?.[1] }}s</span>
</div>
<div>
<BaseIcon :icon="sentence.audioPosition?.length ? 'basil:edit-outline' : 'basil:add-outline'"
@click="handleShowEditAudioDialog(sentence)"/>
<BaseIcon v-if="sentence.audioPosition?.length" icon="hugeicons:play"
@click="playSentenceAudio(sentence,audioRef)"/>
</div>
</div>
</div>
</div>
</div>
@@ -481,6 +532,47 @@ function s() {
</template>
<Empty v-else text="没有译文对照~"/>
</div>
<Dialog title="音频"
v-model="showEditAudioDialog"
:footer="true"
@close="showEditAudioDialog = false"
>
<div class="p-4 pt-0 color-black w-150 flex flex-col gap-2">
<div>
句子{{ currentSentence.text }}
</div>
<audio ref="sentenceAudioRef" :src="audio" controls class="w-full"></audio>
<div class="">
使用方法点击上方播放按钮音频播放到句子开始时点击 记录开始时间 按钮播放到句子结束时点击 记录开始时间 按钮
</div>
<div class="flex items-center gap-4">
<div class="flex flex-col gap-2">
<div class="flex gap-2 items-center">
<BaseButton @click="recordStart">记录开始时间</BaseButton>
<div class="flex items-center gap-1">
<el-input-number v-model="currentSentence.audioPosition[0]" :precision="2" :step="0.1">
<template #suffix>
<span>s</span>
</template>
</el-input-number>
</div>
</div>
<div class="flex gap-2 items-center">
<BaseButton @click="recordEnd">记录结束时间</BaseButton>
<div class="flex items-center gap-1">
<el-input-number v-model="currentSentence.audioPosition[1]" :precision="2" :step="0.1">
<template #suffix>
<span>s</span>
</template>
</el-input-number>
</div>
</div>
</div>
<BaseButton @click="playSentenceAudio(currentSentence,audioRef)">播放记录时间</BaseButton>
</div>
</div>
</Dialog>
</div>
</template>
@@ -504,13 +596,6 @@ function s() {
flex-direction: column;
//opacity: 0;
.basic {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.8rem;
}
&:nth-child(3) {
flex: 10;
}
@@ -521,10 +606,6 @@ function s() {
text-align: center;
}
.item {
width: 100%;
}
.translate-item {
flex: 1;
display: flex;
@@ -533,37 +614,31 @@ function s() {
gap: calc(var(--space) / 2);
}
.el-progress {
flex: 1;
}
.article-translate {
margin-top: .6rem;
margin-bottom: 1.2rem;
flex: 1;
overflow: auto;
border-radius: .5rem;
overflow-y: overlay;
.section {
background: var(--color-textarea-bg);
margin-bottom: 1.2rem;
padding: var(--space);
border-radius: .5rem;
.section-title {
padding: 0.5rem;
border-bottom: 1px solid var(--color-item-border);
}
&:last-child {
margin-bottom: 0;
}
.sentence {
margin-bottom: 0.5rem;
display: flex;
padding: 0.5rem 1.5rem;
line-height: 1.2;
border-bottom: 1px solid var(--color-item-border);
&:last-child {
margin-bottom: 0;
}
.text {
font-size: 1.1rem;
border-bottom: none;
}
}
}