feat: 修改文章编辑器
This commit is contained in:
@@ -11,6 +11,8 @@ import Tooltip from "@/pages/pc/components/Tooltip.vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import IconWrapper from "@/pages/pc/components/IconWrapper.vue";
|
||||
import {useArticleOptions} from "@/hooks/dict.ts";
|
||||
import audio from '/public/sound/article/nce2-1/1.mp3'
|
||||
import {emitter} from "@/utils/eventBus.ts";
|
||||
|
||||
const statisticsStore = usePracticeStore()
|
||||
const settingStore = useSettingStore()
|
||||
@@ -55,86 +57,90 @@ onUnmounted(() => {
|
||||
<template>
|
||||
<div class="footer " :class="!settingStore.showToolbar && 'hide'">
|
||||
<div class="bottom">
|
||||
<div class="flex gap-2">
|
||||
<el-progress
|
||||
class="flex-1"
|
||||
:percentage="progress"
|
||||
:stroke-width="8"
|
||||
:show-text="false"/>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between">
|
||||
<div class="stat">
|
||||
<div class="row">
|
||||
<div class="num">{{ speedMinute }}分钟</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">时间</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="num">{{ statisticsStore.total }}</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">单词总数</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="num">{{ format(statisticsStore.inputWordNumber, '', 0) }}</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">输入数</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="num">{{ format(statisticsStore.wrong, '', 0) }}</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">错误数</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="num">{{ format(statisticsStore.correctRate, '%') }}</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">正确率</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-3 center">
|
||||
<Tooltip
|
||||
:title="`开关默写模式(${settingStore.shortcutKeyMap[ShortcutKey.ToggleDictation]})`"
|
||||
>
|
||||
<IconWrapper>
|
||||
<Icon icon="majesticons:eye-off-line"
|
||||
v-if="settingStore.dictation"
|
||||
@click="settingStore.dictation = false"/>
|
||||
<Icon icon="mdi:eye-outline"
|
||||
v-else
|
||||
@click="settingStore.dictation = true"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
<div>
|
||||
<el-progress
|
||||
class="flex-1"
|
||||
:percentage="progress"
|
||||
:stroke-width="8"
|
||||
:show-text="false"/>
|
||||
<div class="stat">
|
||||
<div class="row">
|
||||
<div class="num">{{ speedMinute }}分钟</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">时间</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="num">{{ statisticsStore.total }}</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">单词总数</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="num">{{ format(statisticsStore.inputWordNumber, '', 0) }}</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">输入数</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="num">{{ format(statisticsStore.wrong, '', 0) }}</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">错误数</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="num">{{ format(statisticsStore.correctRate, '%') }}</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">正确率</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<audio :src="audio" controls></audio>
|
||||
<div class="flex gap-3 center">
|
||||
<Tooltip
|
||||
:title="`开关默写模式(${settingStore.shortcutKeyMap[ShortcutKey.ToggleDictation]})`"
|
||||
>
|
||||
<IconWrapper>
|
||||
<Icon icon="majesticons:eye-off-line"
|
||||
v-if="settingStore.dictation"
|
||||
@click="settingStore.dictation = false"/>
|
||||
<Icon icon="mdi:eye-outline"
|
||||
v-else
|
||||
@click="settingStore.dictation = true"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
|
||||
<TranslateSetting/>
|
||||
<TranslateSetting/>
|
||||
|
||||
<VolumeSetting/>
|
||||
<VolumeSetting/>
|
||||
|
||||
<BaseIcon
|
||||
:title="`编辑(${settingStore.shortcutKeyMap[ShortcutKey.EditArticle]})`"
|
||||
icon="tabler:edit"
|
||||
@click="emit('edit',)"
|
||||
/>
|
||||
<BaseIcon
|
||||
:title="`编辑(${settingStore.shortcutKeyMap[ShortcutKey.EditArticle]})`"
|
||||
icon="tabler:edit"
|
||||
@click="emitter.emit(ShortcutKey.EditArticle)"
|
||||
/>
|
||||
|
||||
<BaseIcon
|
||||
v-if="!isArticleCollect()"
|
||||
class="collect"
|
||||
@click="toggleArticleCollect()"
|
||||
:title="`收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
|
||||
icon="ph:star"/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
class="fill"
|
||||
@click="toggleArticleCollect()"
|
||||
:title="`取消收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
|
||||
icon="ph:star-fill"/>
|
||||
<BaseIcon
|
||||
:title="`下一句(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
|
||||
icon="icon-park-outline:go-ahead"
|
||||
@click="emit('over')"/>
|
||||
<BaseIcon
|
||||
v-if="!isArticleCollect()"
|
||||
class="collect"
|
||||
@click="toggleArticleCollect()"
|
||||
:title="`收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
|
||||
icon="ph:star"/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
class="fill"
|
||||
@click="toggleArticleCollect()"
|
||||
:title="`取消收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
|
||||
icon="ph:star-fill"/>
|
||||
<BaseIcon
|
||||
:title="`下一句(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
|
||||
icon="icon-park-outline:go-ahead"
|
||||
@click="emit('over')"/>
|
||||
|
||||
<BaseIcon
|
||||
@click="settingStore.showPanel = !settingStore.showPanel"
|
||||
:title="`面板(${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"
|
||||
icon="tdesign:menu-unfold"/>
|
||||
<BaseIcon
|
||||
@click="settingStore.showPanel = !settingStore.showPanel"
|
||||
:title="`面板(${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"
|
||||
icon="tdesign:menu-unfold"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import DictListPanel from "@/pages/pc/components/DictListPanel.vue";
|
||||
import {Icon} from '@iconify/vue'
|
||||
import {ActivityCalendar} from "vue-activity-calendar";
|
||||
import "vue-activity-calendar/style.css";
|
||||
import {useRouter} from "vue-router";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import DictList from "@/pages/pc/components/list/DictList.vue";
|
||||
import {enArticle} from "@/assets/dictionary.ts";
|
||||
import {DictType} from "@/types.ts";
|
||||
import BasePage from "@/pages/pc/components/BasePage.vue";
|
||||
|
||||
const base = useBaseStore()
|
||||
|
||||
@@ -53,7 +53,6 @@ function toggle() {
|
||||
<div
|
||||
v-else
|
||||
class="text"
|
||||
:style="`font-size: 1rem;`"
|
||||
@click="toggle">
|
||||
{{ value }}
|
||||
</div>
|
||||
|
||||
@@ -5,15 +5,13 @@ import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {getAudioFileUrl, useChangeAllSound, usePlayAudio, useWatchAllSound} from "@/hooks/sound.ts";
|
||||
import {getShortcutKey, useDisableEventListener, useEventListener} from "@/hooks/event.ts";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
import {DefaultShortcutKeyMap, Dict, DictType, ShortcutKey} from "@/types.ts";
|
||||
import {DefaultShortcutKeyMap, ShortcutKey} from "@/types.ts";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import {APP_NAME, EXPORT_DATA_KEY, SAVE_DICT_KEY, SAVE_SETTING_KEY, SoundFileOptions} from "@/utils/const.ts";
|
||||
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
|
||||
import {BaseState, useBaseStore} from "@/stores/base.ts";
|
||||
import * as copy from "copy-to-clipboard";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {saveAs} from "file-saver";
|
||||
import {checkAndUpgradeSaveDict, checkAndUpgradeSaveSetting, shakeCommonDict} from "@/utils";
|
||||
import {dayjs} from "element-plus";
|
||||
import {GITHUB} from "@/config/ENV.ts";
|
||||
|
||||
|
||||
|
||||
@@ -11,13 +11,17 @@ import {
|
||||
renewSectionTexts,
|
||||
renewSectionTranslates
|
||||
} from "@/hooks/translate.ts";
|
||||
import * as copy from "copy-to-clipboard";
|
||||
|
||||
import {MessageBox} from "@/utils/MessageBox.tsx";
|
||||
import {getSplitTranslateText} from "@/hooks/article.ts";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
import {watch} from "vue";
|
||||
import {watch, ref} from "vue";
|
||||
import Empty from "@/components/Empty.vue";
|
||||
import {UploadProps, UploadUserFile} from "element-plus";
|
||||
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";
|
||||
|
||||
interface IProps {
|
||||
article?: Article,
|
||||
@@ -70,6 +74,12 @@ watch(() => props.article, val => {
|
||||
// console.log('ar', article)
|
||||
}, {immediate: true})
|
||||
|
||||
watch(() => editArticle.text, (s) => {
|
||||
if (!s.trim()) {
|
||||
editArticle.sections = []
|
||||
}
|
||||
})
|
||||
|
||||
function renewSections() {
|
||||
if (editArticle.text.trim()) {
|
||||
renewSectionTexts(editArticle)
|
||||
@@ -136,7 +146,6 @@ async function startNetworkTranslate() {
|
||||
return ElMessage.error('请填写正文!')
|
||||
}
|
||||
renewSectionTexts(editArticle)
|
||||
editArticle.textNetworkTranslate = ''
|
||||
//注意!!!
|
||||
//这里需要用异步,因为watch了article.networkTranslate,改变networkTranslate了之后,会重新设置article.sections
|
||||
//导致getNetworkTranslate里面拿到的article.sections是废弃的值
|
||||
@@ -145,8 +154,6 @@ async function startNetworkTranslate() {
|
||||
progress = v
|
||||
})
|
||||
failCount = 0
|
||||
|
||||
copy(JSON.stringify(editArticle.sections))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -230,61 +237,147 @@ function save(option: 'save' | 'saveAndNext') {
|
||||
//不知道为什么直接用editArticle,取到是空的默认值
|
||||
defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
|
||||
|
||||
const fileList = ref<UploadUserFile[]>([])
|
||||
|
||||
const handleExceed: UploadProps['onExceed'] = (files, uploadFiles) => {
|
||||
ElMessage.warning(
|
||||
`The limit is 3, you selected ${files.length} files this time, add up to ${
|
||||
files.length + uploadFiles.length
|
||||
} totally`
|
||||
)
|
||||
}
|
||||
|
||||
const handleChange: UploadProps['onChange'] = (uploadFile, uploadFiles) => {
|
||||
console.log(1)
|
||||
fileList.value.push({
|
||||
name: uploadFile.name,
|
||||
url: uploadFile.url,
|
||||
})
|
||||
}
|
||||
|
||||
function test() {
|
||||
let lrc = `[00:00.00]Lesson 1 A Private Conversation
|
||||
[00:04.35]First listen and then answer the question.
|
||||
[00:09.26]Why did the writer complain to the people behind him?
|
||||
[00:14.60]Last week I went to the theatre.
|
||||
[00:19.15]I had a very good seat.
|
||||
[00:22.03]The play was very interesting.
|
||||
[00:24.59]I did not enjoy it.
|
||||
[00:27.26]A young man and a young woman were sitting behind me.
|
||||
[00:31.65]They were talking loudly.
|
||||
[00:34.43]I got very angry.
|
||||
[00:36.98]I could not hear the actors.
|
||||
[00:40.36]I turned round.I looked at the man and the woman angrily.
|
||||
[00:46.59]They did not pay any attention.
|
||||
[00:50.65]In the end,I could not bear it.
|
||||
[00:54.57]I turned round again 'I can't hear a word!'I said angrily
|
||||
[01:02.98]'It's none of your business,'the young man said rudely.
|
||||
[01:08.85]'This is a private conversation!'
|
||||
`
|
||||
let lrcList = _parseLRC(lrc)
|
||||
console.log(lrcList)
|
||||
editArticle.sections.map((v, i) => {
|
||||
v.map((w, j) => {
|
||||
console.log(w)
|
||||
for (let k = 0; k < lrcList.length; k++) {
|
||||
let s = lrcList[k]
|
||||
let d = Comparison.default.cosine.similarity(w.text, s.text)
|
||||
d = Comparison.default.levenshtein.similarity(w.text, s.text)
|
||||
d = Comparison.default.longestCommonSubsequence.similarity(w.text, s.text)
|
||||
// d = Comparison.default.metricLcs.similarity(w.text, s.text)
|
||||
console.log(w.text, s.text, d)
|
||||
if (d >= 0.8) {
|
||||
w.audioPosition = [s.start, s.end ?? -1]
|
||||
w.test = s.text
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
console.log(editArticle.sections.flat())
|
||||
// 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() {
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="content">
|
||||
<div class="row">
|
||||
<div class="title">①原文</div>
|
||||
<div class="item">
|
||||
<div class="label">标题:</div>
|
||||
<textarea
|
||||
v-model="editArticle.title"
|
||||
type="textarea"
|
||||
class="base-textarea"
|
||||
placeholder="请填写原文标题"
|
||||
>
|
||||
</textarea>
|
||||
</div>
|
||||
<div class="item basic">
|
||||
<div class="label">正文:</div>
|
||||
<textarea
|
||||
v-model="editArticle.text"
|
||||
@input="renewSections"
|
||||
:readonly="![100,0].includes(progress)"
|
||||
type="textarea"
|
||||
class="base-textarea"
|
||||
placeholder="请填写原文正文"
|
||||
>
|
||||
<div class="row flex flex-col gap-2">
|
||||
<div class="title">原文</div>
|
||||
<div class="">标题:</div>
|
||||
<input
|
||||
v-model="editArticle.title"
|
||||
type="text"
|
||||
class="base-input"
|
||||
placeholder="请填写原文标题"
|
||||
/>
|
||||
<div class="">正文:</div>
|
||||
<textarea
|
||||
v-model="editArticle.text"
|
||||
:readonly="![100,0].includes(progress)"
|
||||
type="textarea"
|
||||
class="base-textarea"
|
||||
placeholder="请复制原文"
|
||||
>
|
||||
</textarea>
|
||||
<div class="justify-between items-center gap-2 flex">
|
||||
<div class="text-base mb-1 color-white/60">请复制原文,然后进行分句,一行一句,段落间空一行。修改完成后,点击 <span class="color-red font-bold">应用按钮</span> 同步到左侧结果
|
||||
</div>
|
||||
<el-button type="primary" @click="renewSections">应用</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="title">②译文</div>
|
||||
<div class="item">
|
||||
<div class="label">
|
||||
<span>标题:</span>
|
||||
<el-radio-group
|
||||
v-model="editArticle.useTranslateType"
|
||||
@change="renewSections"
|
||||
>
|
||||
<el-radio-button :value="TranslateType.custom">本地翻译</el-radio-button>
|
||||
<el-radio-button :value="TranslateType.network">网络翻译</el-radio-button>
|
||||
<el-radio-button :value="TranslateType.none">不需要翻译</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="editArticle.titleTranslate"
|
||||
type="textarea"
|
||||
class="base-textarea"
|
||||
placeholder="请填写翻译标题"
|
||||
>
|
||||
</textarea>
|
||||
<div class="row flex flex-col gap-2">
|
||||
<div class="title">译文</div>
|
||||
<div class="flex gap-2">
|
||||
标题
|
||||
</div>
|
||||
<div class="item basic">
|
||||
<div class="label">
|
||||
<span>正文:</span>
|
||||
<div class="translate-item" v-if="editArticle.useTranslateType === TranslateType.network">
|
||||
<input
|
||||
v-model="editArticle.titleTranslate"
|
||||
type="text"
|
||||
class="base-input"
|
||||
placeholder="请填写翻译标题"
|
||||
/>
|
||||
|
||||
<div class="flex">
|
||||
<span>正文:</span>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="editArticle.textCustomTranslate"
|
||||
:readonly="![100,0].includes(progress)"
|
||||
@blur="onBlur"
|
||||
@focus="onFocus"
|
||||
type="textarea"
|
||||
class="base-textarea"
|
||||
placeholder="请填写翻译"
|
||||
ref="textareaRef"
|
||||
>
|
||||
</textarea>
|
||||
<div class="justify-between items-center gap-2 flex">
|
||||
<div class="text-base mb-1 color-white/60">
|
||||
请复制译文,如果没有可点击“网络翻译”按钮进行翻译,然后进行分句,一行一句,段落间空一行。修改完成后,点击 <span class="color-red font-bold">应用按钮</span> 同步到左侧结果
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="translate-item">
|
||||
<el-progress :percentage="progress"
|
||||
:duration="30"
|
||||
:striped="progress !== 100"
|
||||
@@ -292,7 +385,6 @@ defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
|
||||
:stroke-width="8"
|
||||
:show-text="true"/>
|
||||
<el-select v-model="networkTranslateEngine"
|
||||
style="width: 80rem;"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in TranslateEngineOptions"
|
||||
@@ -305,55 +397,57 @@ defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
|
||||
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>
|
||||
</div>
|
||||
<textarea
|
||||
v-if="editArticle.useTranslateType === TranslateType.custom"
|
||||
v-model="editArticle.textCustomTranslate"
|
||||
@input="renewSections"
|
||||
@blur="onBlur"
|
||||
@focus="onFocus"
|
||||
type="textarea"
|
||||
class="base-textarea"
|
||||
placeholder="请填写翻译正文"
|
||||
ref="textareaRef"
|
||||
>
|
||||
</textarea>
|
||||
<textarea
|
||||
v-if="editArticle.useTranslateType === TranslateType.network"
|
||||
v-model="editArticle.textNetworkTranslate"
|
||||
:readonly="![100,0].includes(progress)"
|
||||
@input="renewSections"
|
||||
@blur="onBlur"
|
||||
@focus="onFocus"
|
||||
type="textarea"
|
||||
class="base-textarea"
|
||||
placeholder="等待网络翻译中..."
|
||||
ref="textareaRef"
|
||||
>
|
||||
</textarea>
|
||||
<Empty
|
||||
text="不需要翻译~"
|
||||
v-if="editArticle.useTranslateType === TranslateType.none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="title">③译文对照</div>
|
||||
<div class="title">结果</div>
|
||||
<div class="center">正文、译文与结果均可编辑,修改一处,另外两处会自动同步变动</div>
|
||||
<div class="flex gap-2">
|
||||
<BaseButton>导入音频</BaseButton>
|
||||
<BaseButton @click="test">导入音频LRC</BaseButton>
|
||||
<el-upload
|
||||
v-model:file-list="fileList"
|
||||
class="upload-demo"
|
||||
:limit="1"
|
||||
:on-change="handleChange"
|
||||
>
|
||||
<el-button type="primary">Click to upload</el-button>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
jpg/png files with a size less than 500KB.
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<audio :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" v-for="(sentence,indexJ) in item">
|
||||
<EditAbleText
|
||||
:value="sentence.text"
|
||||
@save="(e:string) => saveSentenceText(sentence,e)"
|
||||
/>
|
||||
<EditAbleText
|
||||
:value="sentence.translate"
|
||||
@save="(e:string) => saveSentenceTranslate(sentence,e)"
|
||||
/>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -384,7 +478,7 @@ defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
|
||||
@import "@/assets/css/style";
|
||||
|
||||
.content {
|
||||
color: var(--color-font-1);
|
||||
color: var(--color-article);
|
||||
flex: 1;
|
||||
display: flex;
|
||||
gap: var(--space);
|
||||
@@ -393,7 +487,7 @@ defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
|
||||
}
|
||||
|
||||
.row {
|
||||
flex: 10;
|
||||
flex: 7;
|
||||
width: 33%;
|
||||
//height: 100%;
|
||||
display: flex;
|
||||
@@ -404,28 +498,21 @@ defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
&:nth-child(1) {
|
||||
flex: 7;
|
||||
&:nth-child(3) {
|
||||
flex: 10;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
font-size: 1.4rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 100%;
|
||||
//margin-bottom: 10rem;
|
||||
|
||||
.label {
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.translate-item {
|
||||
@@ -458,7 +545,8 @@ defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
|
||||
}
|
||||
|
||||
.sentence {
|
||||
margin-bottom: 1.2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1.2;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
|
||||
@@ -21,6 +21,7 @@ import {useWindowClick} from "@/hooks/event.ts";
|
||||
import ArticleList from "@/pages/pc/components/list/ArticleList.vue";
|
||||
import * as copy from "copy-to-clipboard";
|
||||
import {getTranslateText} from "@/hooks/article.ts";
|
||||
import {_copy} from "@/utils";
|
||||
|
||||
const emit = defineEmits<{
|
||||
back: []
|
||||
@@ -210,7 +211,7 @@ function exportData(val: {
|
||||
type: string,
|
||||
data?: Article
|
||||
}) {
|
||||
copy(JSON.stringify(cloneDeep(runtimeStore.editDict.articles).map(v => {
|
||||
_copy(JSON.stringify(cloneDeep(runtimeStore.editDict.articles).map(v => {
|
||||
delete v.sections
|
||||
return v
|
||||
})))
|
||||
|
||||
@@ -1,7 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import {splitEnArticle} from "@/hooks/article.ts";
|
||||
|
||||
function test() {
|
||||
let {newText, sections} = splitEnArticle(
|
||||
`While it is yet to be seen what direction the second Trump administration will take globally in its China policy, VOA traveled to the main island of Mahe in Seychelles to look at how China and the U.S. have impacted the country, and how each is fairing in that competition for influence there.
|
||||
`)
|
||||
}
|
||||
|
||||
function test2() {
|
||||
let {newText, sections} = splitEnArticle(
|
||||
`
|
||||
Last week I went to the theatre. I had a very good seat. The play was very interesting. I did not enjoy it. A young man and a young woman were sitting behind me. They were talking loudly. I got very angry. I could not hear the actors. I turned round. I looked at the man and the woman angrily. They did not pay any attention. In the end, I could not bear it. I turned round again. I cant hear a word! I said angrily.
|
||||
Its none of your business, the young man said rudely. This is a private conversation!
|
||||
`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="word flex justify-center ">
|
||||
<div class="word flex center h-screen ">
|
||||
<El-Button @click="test">test</El-Button>
|
||||
<El-Button @click="test2">test2</El-Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -95,7 +95,7 @@ watch(() => settingStore.translate, () => {
|
||||
|
||||
|
||||
function checkCursorPosition(a = sectionIndex, b = sentenceIndex, c = wordIndex) {
|
||||
console.log('checkCursorPosition')
|
||||
// console.log('checkCursorPosition')
|
||||
_nextTick(() => {
|
||||
let currentWord = jq(`.section:nth(${a}) .sentence:nth(${b}) .word:nth(${c})`)
|
||||
// console.log(a, b, c, currentWord)
|
||||
@@ -115,7 +115,7 @@ function checkCursorPosition(a = sectionIndex, b = sentenceIndex, c = wordIndex)
|
||||
}
|
||||
|
||||
function checkTranslateLocation() {
|
||||
console.log('checkTranslateLocation')
|
||||
// console.log('checkTranslateLocation')
|
||||
return new Promise<void>(resolve => {
|
||||
_nextTick(() => {
|
||||
let articleRect = articleWrapperRef.getBoundingClientRect()
|
||||
@@ -129,7 +129,7 @@ function checkTranslateLocation() {
|
||||
let translate: HTMLDivElement = document.querySelector(translateClassName)
|
||||
|
||||
translate.style.opacity = '1'
|
||||
translate.style.top = wordRect.top - articleRect.top + 28 + 'px'
|
||||
translate.style.top = wordRect.top - articleRect.top + 24 + 'px'
|
||||
// @ts-ignore
|
||||
translate.firstChild.style.width = wordRect.left - articleRect.left + 'px'
|
||||
// console.log(word, wordRect.left - articleRect.left)
|
||||
@@ -469,8 +469,6 @@ defineExpose({showSentence, play, del, hideSentence, nextSentence})
|
||||
//color: rgb(22, 163, 74);
|
||||
}
|
||||
|
||||
|
||||
$article-width: 1000px;
|
||||
.typing-article {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
@@ -538,18 +536,15 @@ $article-width: 1000px;
|
||||
}
|
||||
|
||||
&.tall {
|
||||
line-height: 2.5;
|
||||
line-height: 2.2;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 1.2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
.sentence {
|
||||
transition: all .3s;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.word {
|
||||
@@ -580,8 +575,8 @@ $article-width: 1000px;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-size: 1.1rem;
|
||||
line-height: 3.5;
|
||||
font-size: 1.2rem;
|
||||
line-height: 3.0;
|
||||
letter-spacing: .2rem;
|
||||
font-family: var(--zh-article-family);
|
||||
font-weight: bold;
|
||||
|
||||
Reference in New Issue
Block a user