feat:save

This commit is contained in:
zyronon
2025-07-09 04:11:48 +08:00
parent b7fa79d148
commit 50bea6d759
46 changed files with 2519 additions and 1108 deletions

View File

@@ -54,7 +54,6 @@ useEvent(EventKey.changeDict, () => {
</div>
</template>
<style scoped lang="scss">
@import "@/assets/css/variable";
$header-height: 50rem;
.panel {

View File

@@ -113,8 +113,7 @@ function $no() {
</template>
<style scoped lang="scss">
@import "../common";
@use '../common' as *;
.my {
font-size: 18rem;
@@ -210,4 +209,4 @@ function $no() {
}
}
}
</style>
</style>

View File

@@ -42,7 +42,7 @@ const gitLastCommitHash = ref(LATEST_COMMIT_HASH);
</template>
<style scoped lang="scss">
@import "../../common";
@use '../../common' as *;
.setting {
display: flex;
@@ -61,4 +61,4 @@ const gitLastCommitHash = ref(LATEST_COMMIT_HASH);
}
}
</style>
</style>

View File

@@ -235,7 +235,6 @@ watch(() => props.word, () => {
</template>
<style scoped lang="scss">
@import "@/assets/css/variable";
.typing-word {
width: 95%;
@@ -350,4 +349,4 @@ watch(() => props.word, () => {
}
}
}
</style>
</style>

View File

@@ -27,7 +27,7 @@ function clickEvent(e) {
<div class="absolute bottom-4 right-4">3</div>
</div>
</div>
<div class="grid flex-1 flex gap-5 mt-4">
<div class="grid flex-1 flex gap-5 mt-4" @click="router.push('edit-article')">
<div class="p-4 flex-1 rounded-md bg-slate-200 relative">
<span>添加</span>
<div class="absolute bottom-4 right-4">3</div>
@@ -82,4 +82,4 @@ function clickEvent(e) {
.title {
@apply text-lg font-medium;
}
</style>
</style>

View File

@@ -0,0 +1,15 @@
<script setup lang="ts">
import EditArticle2 from "@/pages/pc/components/article/EditArticle2.vue";
</script>
<template>
<div class="h-screen">
<EditArticle2 class="vue"></EditArticle2>
</div>
</template>
<style scoped lang="scss">
</style>

View File

@@ -155,7 +155,6 @@ onUnmounted(() => {
</template>
<style scoped lang="scss">
@import "@/assets/css/variable";
.footer {
width: var(--article-width);
@@ -220,4 +219,4 @@ onUnmounted(() => {
}
}
</style>
</style>

View File

@@ -116,7 +116,6 @@ function del(e) {
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
.dict-list-panel {
width: 50%;
@@ -183,4 +182,4 @@ function del(e) {
}
}
</style>
</style>

View File

@@ -40,7 +40,7 @@ useDisableEventListener(() => focus)
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
.base-input {
border: 1px solid var(--color-second-bg);
@@ -83,4 +83,4 @@ useDisableEventListener(() => focus)
}
}
}
</style>
</style>

View File

@@ -210,7 +210,7 @@ function changeCollect() {
</Transition>
</template>
<style scoped lang="scss">
@import "@/assets/css/variable";
$header-height: 3rem;
.slide-item {

View File

@@ -44,7 +44,7 @@ onMounted(() => {
})
</script>
<style scoped lang="scss">
@import "@/assets/css/variable";
$w: 6rem;
$w2: calc($w / 2);
@@ -75,4 +75,4 @@ $w2: calc($w / 2);
}
}
</style>
</style>

View File

@@ -463,7 +463,7 @@ function importData(e) {
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
.setting {
width: 40vw;
@@ -650,4 +650,4 @@ function importData(e) {
text-align: center;
}
</style>
</style>

View File

@@ -200,7 +200,7 @@ defineExpose({del, showWord, hideWord, play})
</template>
<style scoped lang="scss">
@import "@/assets/css/variable";
.typing-word {
width: 100%;
@@ -273,4 +273,4 @@ defineExpose({del, showWord, hideWord, play})
}
}
}
</style>
</style>

View File

@@ -325,7 +325,7 @@ const status = $computed(() => {
</template>
<style scoped lang="scss">
@import "@/assets/css/variable";
.practice-word {
height: 100%;
@@ -393,4 +393,4 @@ const status = $computed(() => {
height: calc(100% - 1.5rem);
}
</style>
</style>

View File

@@ -561,7 +561,7 @@ function setStartTime(val: Sentence, i: number, j: number) {
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
.content {
color: var(--color-article);
@@ -658,4 +658,4 @@ function setStartTime(val: Sentence, i: number, j: number) {
}
}
}
</style>
</style>

View File

@@ -0,0 +1,664 @@
<script setup lang="ts">
import {Article, DefaultArticle, Sentence, TranslateEngine} from "@/types.ts";
import BaseButton from "@/components/BaseButton.vue";
import EditAbleText from "@/pages/pc/components/EditAbleText.vue";
import {Icon} from "@iconify/vue";
import {
getNetworkTranslate,
getSentenceAllText,
getSentenceAllTranslateText,
renewSectionTexts,
renewSectionTranslates
} from "@/hooks/translate.ts";
import {genArticleSectionData, splitCNArticle2, splitEnArticle2, usePlaySentenceAudio} from "@/hooks/article.ts";
import {cloneDeep, last} from "lodash-es";
import {watch} from "vue";
import Empty from "@/components/Empty.vue";
import {UploadProps} from "element-plus";
import {_nextTick, _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,
type?: 'single' | 'batch'
}
const props = withDefaults(defineProps<IProps>(), {
article: () => cloneDeep(DefaultArticle),
type: 'single'
})
const emit = defineEmits<{
save: [val: Article],
saveAndNext: [val: Article]
}>()
let networkTranslateEngine = $ref('baidu')
let progress = $ref(0)
let failCount = $ref(0)
let textareaRef = $ref<HTMLTextAreaElement>()
const TranslateEngineOptions = [
{value: 'baidu', label: '百度'},
{value: 'youdao', label: '有道'},
]
let editArticle = $ref<Article>(cloneDeep(DefaultArticle))
watch(() => props.article, val => {
editArticle = cloneDeep(val)
progress = 0
failCount = 0
// let r = getSplitTranslateText(editArticle.textTranslate)
// if (r) {
// editArticle.textTranslate = r
// ElMessage({
// message: '检测到本地翻译未格式化,已自动格式化',
// type: 'success',
// duration: 3000
// })
// }
renewSections()
console.log('ar', editArticle)
}, {immediate: true})
watch(() => editArticle.text, (s) => {
if (!s.trim()) {
editArticle.sections = []
}
})
function renewSections() {
if (editArticle.text.trim()) {
renewSectionTexts(editArticle)
failCount = renewSectionTranslates(editArticle, editArticle.textTranslate)
} else {
editArticle.sections = []
}
}
function apply() {
if (editArticle.text.trim()) {
editArticle.sections = genArticleSectionData(editArticle.text)
let count = 0
if (editArticle.lrcPosition.length) {
editArticle.sections.map((v, i) => {
v.map((w, j) => {
w.audioPosition = editArticle.lrcPosition[count]
count++
})
})
}
failCount = renewSectionTranslates(editArticle, editArticle.textTranslate)
} else {
editArticle.sections = []
}
}
//分句原文
function splitText() {
editArticle.text = splitEnArticle2(editArticle.text.trim())
return
let text = editArticle.text.trim();
if (text) {
editArticle.text = splitEnArticle2(text)
}
}
//分句翻译
function splitTranslateText() {
editArticle.textTranslate = splitCNArticle2(editArticle.textTranslate.trim())
return
let text = editArticle.textTranslate.trim();
if (text) {
editArticle.textTranslate = splitCNArticle2(text)
}
}
//TODO
async function startNetworkTranslate() {
if (!editArticle.title.trim()) {
return ElMessage.error('请填写标题!')
}
if (!editArticle.text.trim()) {
return ElMessage.error('请填写正文!')
}
renewSectionTexts(editArticle)
//注意!!!
//这里需要用异步因为watch了article.networkTranslate改变networkTranslate了之后会重新设置article.sections
//导致getNetworkTranslate里面拿到的article.sections是废弃的值
setTimeout(async () => {
await getNetworkTranslate(editArticle, TranslateEngine.Baidu, true, (v: number) => {
progress = v
})
failCount = 0
})
}
function saveSentenceTranslate(sentence: Sentence, val: string) {
sentence.translate = val
editArticle.textTranslate = getSentenceAllTranslateText(editArticle)
renewSections()
}
function saveSentenceText(sentence: Sentence, val: string) {
sentence.text = val
editArticle.text = getSentenceAllText(editArticle)
renewSections()
}
function save(option: 'save' | 'saveAndNext') {
// return console.log(cloneDeep(editArticle))
return new Promise((resolve: Function) => {
// console.log('article', article)
// copy(JSON.stringify(article))
editArticle.title = editArticle.title.trim()
editArticle.titleTranslate = editArticle.titleTranslate.trim()
editArticle.text = editArticle.text.trim()
editArticle.textTranslate = editArticle.textTranslate.trim()
if (!editArticle.title) {
ElMessage.error('请填写标题!')
return resolve(false)
}
if (!editArticle.text) {
ElMessage.error('请填写正文!')
return resolve(false)
}
const saveTemp = () => {
emit(option as any, editArticle)
return resolve(true)
}
saveTemp()
})
}
//不知道为什么直接用editArticle取到是空的默认值
defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
const handleChange: UploadProps['onChange'] = (uploadFile, uploadFiles) => {
console.log(uploadFile)
let reader = new FileReader();
reader.readAsText(uploadFile.raw, 'UTF-8');
reader.onload = function (e) {
let lrc: string = e.target.result as string;
console.log(lrc)
if (lrc.trim()) {
let lrcList = _parseLRC(lrc)
console.log('lrcList', lrcList)
if (lrcList.length) {
editArticle.lrcPosition = editArticle.sections.map((v, i) => {
return v.map((w, j) => {
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]
break
}
}
return w.audioPosition ?? []
})
}).flat()
}
}
}
}
let currentSentence = $ref<Sentence>({} as any)
let editSentence = $ref<Sentence>({} as any)
let preSentence = $ref<Sentence>({} as any)
let showEditAudioDialog = $ref(false)
let sentenceAudioRef = $ref<HTMLAudioElement>()
let audioRef = $ref<HTMLAudioElement>()
function handleShowEditAudioDialog(val: Sentence, i: number, j: number) {
showEditAudioDialog = true
currentSentence = val
editSentence = cloneDeep(val)
preSentence = null
audioRef.pause()
if (j == 0) {
if (i != 0) {
preSentence = last(editArticle.sections[i - 1])
}
} else {
preSentence = editArticle.sections[i][j - 1]
}
if (!editSentence.audioPosition?.length) {
editSentence.audioPosition = [0, 0]
if (preSentence) {
editSentence.audioPosition = [preSentence.audioPosition[1] ?? 0, 0]
}
}
_nextTick(() => {
sentenceAudioRef.currentTime = editSentence.audioPosition[0]
})
}
function recordStart() {
if (sentenceAudioRef.paused) {
sentenceAudioRef.play()
}
editSentence.audioPosition[0] = Number(sentenceAudioRef.currentTime.toFixed(2))
}
function recordEnd() {
if (!sentenceAudioRef.paused) {
sentenceAudioRef.pause()
}
editSentence.audioPosition[1] = Number(sentenceAudioRef.currentTime.toFixed(2))
}
const {playSentenceAudio} = usePlaySentenceAudio()
function saveLrcPosition() {
// showEditAudioDialog = false
currentSentence.audioPosition = cloneDeep(editSentence.audioPosition)
editArticle.lrcPosition = editArticle.sections.map((v, i) => v.map((w, j) => (w.audioPosition ?? []))).flat()
}
function jumpAudio(time: number) {
sentenceAudioRef.currentTime = time
}
function setPreEndTimeToCurrentStartTime() {
if (preSentence) {
editSentence.audioPosition[0] = preSentence.audioPosition[1]
}
}
function setStartTime(val: Sentence, i: number, j: number) {
let preSentence = null
if (j == 0) {
if (i != 0) {
preSentence = last(editArticle.sections[i - 1])
}
} else {
preSentence = editArticle.sections[i][j - 1]
}
if (preSentence) {
val.audioPosition[0] = preSentence.audioPosition[1]
} else {
val.audioPosition[0] = Number(Number(audioRef.currentTime).toFixed(2))
}
}
</script>
<template>
<div class="content">
<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-end items-center flex">
<el-popover
class="box-item"
title="使用方法"
placement="top"
:width="400"
>
<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>分句规则一行一句段落间空一行</li>
<li>修改完成后点击 <span class="color-red font-bold">应用</span> 按钮同步到左侧结果栏
</li>
</ol>
<template #reference>
<Icon icon="ri:question-line" class="mr-3" width="20"/>
</template>
</el-popover>
<el-button type="primary" @click="splitText">分句</el-button>
<el-button type="primary" @click="apply">应用</el-button>
</div>
</div>
<div class="row flex flex-col gap-2">
<div class="title">译文</div>
<div class="flex gap-2">
标题
</div>
<input
v-model="editArticle.titleTranslate"
type="text"
class="base-input"
placeholder="请填写翻译标题"
/>
<div class="flex">
<span>正文</span>
</div>
<textarea
v-model="editArticle.textTranslate"
:readonly="![100,0].includes(progress)"
type="textarea"
class="base-textarea"
placeholder="请填写翻译"
ref="textareaRef"
>
</textarea>
<div class="justify-between items-center flex">
<div class="flex gap-2 items-center ">
<el-button
type="primary"
@click="startNetworkTranslate"
:loading="progress!==0 && progress !== 100"
>翻译
</el-button>
<el-select v-model="networkTranslateEngine"
class="w-20"
>
<el-option
v-for="item in TranslateEngineOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
{{ progress }}%
</div>
<div class="flex items-center">
<el-popover
class="box-item"
title="使用方法"
placement="top"
:width="400"
>
<ol class="py-0 pl-5 my-0 text-base color-black/60">
<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>分句规则一行一句段落间空一行</li>
<li>修改完成后点击 <span class="color-red font-bold">应用</span> 按钮同步到左侧结果栏
</li>
</ol>
<template #reference>
<Icon icon="ri:question-line" class="mr-3" width="20"/>
</template>
</el-popover>
<el-button type="primary" @click="splitTranslateText">分句</el-button>
<el-button type="primary" @click="apply">应用</el-button>
</div>
</div>
</div>
<div class="row flex flex-col gap-2">
<div class="title">结果</div>
<div class="center">正文译文与结果均可编辑修改一处另外两处会自动同步变动</div>
<div class="flex gap-2">
<BaseButton>添加音频</BaseButton>
<el-upload
class="upload-demo"
:limit="1"
:on-change="handleChange"
:auto-upload="false"
>
<el-button type="primary">添加音频LRC文件</el-button>
</el-upload>
<audio ref="audioRef" :src="editArticle.audioSrc" controls></audio>
</div>
<template v-if="editArticle.sections.length">
<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-[2] flex justify-end gap-1 items-center">
<div class="flex justify-end gap-2">
<div class="flex flex-col items-center justify-center">
<div>{{ sentence.audioPosition?.[0] ?? 0 }}s</div>
<BaseIcon
@click="setStartTime(sentence,indexI,indexJ)"
:icon="indexI === 0 && indexJ === 0 ?'ic:sharp-my-location':'twemoji:end-arrow'"
:title="indexI === 0 && indexJ === 0 ?'设置开始时间':'使用前一句的结束时间'"
/>
</div>
<div>-</div>
<div class="flex flex-col items-center justify-center">
<div v-if="sentence.audioPosition?.[1] !== -1">{{ sentence.audioPosition?.[1] ?? 0 }}s</div>
<div v-else> 结束</div>
<BaseIcon
@click="sentence.audioPosition[1] = Number(Number(audioRef.currentTime).toFixed(2))"
title="设置结束时间"
icon="ic:sharp-my-location"
/>
</div>
</div>
<div class="flex flex-col">
<BaseIcon :icon="sentence.audioPosition?.length ? 'basil:edit-outline' : 'basil:add-outline'"
@click="handleShowEditAudioDialog(sentence,indexI,indexJ)"/>
<BaseIcon v-if="sentence.audioPosition?.length" icon="hugeicons:play"
@click="playSentenceAudio(sentence,audioRef,editArticle)"/>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="options" v-if="editArticle.text.trim()">
<div class="status">
<span>状态</span>
<div class="warning" v-if="failCount">
<Icon icon="typcn:warning-outline"/>
共有{{ failCount }}句没有翻译
</div>
<div class="success" v-else>
<Icon icon="mdi:success-circle-outline"/>
翻译完成
</div>
</div>
<div class="left">
<BaseButton @click="save('save')">保存</BaseButton>
<BaseButton v-if="type === 'batch'" @click="save('saveAndNext')">保存并添加下一篇</BaseButton>
</div>
</div>
</template>
<Empty v-else text="没有译文对照~"/>
</div>
<Dialog title="设置音频与句子的对应位置(LRC)"
v-model="showEditAudioDialog"
:footer="true"
@close="showEditAudioDialog = false"
@ok="saveLrcPosition"
>
<div class="p-4 pt-0 color-black w-150 flex flex-col gap-2">
<div class="">
教程点击音频播放按钮当播放到句子开始时点击开始时间的 <span class="color-red">记录</span>
按钮当播放到句子结束时点击结束时间的 <span class="color-red">记录</span> 按钮最后再试听是否正确
</div>
<audio ref="sentenceAudioRef" :src="editArticle.audioSrc" controls class="w-full"></audio>
<div class="flex items-center gap-2 space-between mb-2" v-if="editSentence.audioPosition?.length">
<div>{{ editSentence.text }}</div>
<div class="flex items-center gap-2 shrink-0">
<div>
<span>{{ editSentence.audioPosition?.[0] }}s</span>
<span v-if="editSentence.audioPosition?.[1] !== -1"> - {{ editSentence.audioPosition?.[1] }}s</span>
<span v-else> - 结束</span>
</div>
<BaseIcon icon="hugeicons:play"
title="试听"
@click="playSentenceAudio(editSentence,sentenceAudioRef,editArticle)"/>
</div>
</div>
<div class="flex flex-col gap-2">
<div class="flex gap-2 items-center">
<div>开始时间</div>
<div class="flex space-between flex-1">
<div class="flex items-center gap-2">
<el-input-number v-model="editSentence.audioPosition[0]" :precision="2" :step="0.1">
<template #suffix>
<span>s</span>
</template>
</el-input-number>
<BaseIcon
@click="jumpAudio(editSentence.audioPosition[0])"
title="跳转"
icon="ic:sharp-my-location"
/>
<BaseIcon
@click="setPreEndTimeToCurrentStartTime"
title="使用前一句的结束时间"
icon="twemoji:end-arrow"
/>
</div>
<BaseButton @click="recordStart">记录</BaseButton>
</div>
</div>
<div class="flex gap-2 items-center">
<div>结束时间</div>
<div class="flex space-between flex-1">
<div class="flex items-center gap-2">
<el-input-number v-model="editSentence.audioPosition[1]" :precision="2" :step="0.1">
<template #suffix>
<span>s</span>
</template>
</el-input-number>
<span></span>
<BaseButton size="small" @click="editSentence.audioPosition[1] = -1">结束</BaseButton>
</div>
<BaseButton @click="recordEnd">记录</BaseButton>
</div>
</div>
</div>
</div>
</Dialog>
</div>
</template>
<style scoped lang="scss">
.content {
color: var(--color-article);
height: 100%;
box-sizing: border-box;
display: flex;
gap: var(--space);
padding: var(--space);
padding-top: .6rem;
}
.row {
flex: 7;
width: 33%;
//height: 100%;
display: flex;
flex-direction: column;
//opacity: 0;
&:nth-child(3) {
flex: 10;
}
.title {
font-weight: bold;
font-size: 1.4rem;
text-align: center;
}
.article-translate {
flex: 1;
overflow-y: overlay;
.section {
background: var(--color-textarea-bg);
margin-bottom: 1.2rem;
.section-title {
padding: 0.5rem;
border-bottom: 1px solid var(--color-item-border);
}
&:last-child {
margin-bottom: 0;
}
.sentence {
display: flex;
padding: 0.5rem 1.5rem;
line-height: 1.2;
border-bottom: 1px solid var(--color-item-border);
&:last-child {
border-bottom: none;
}
}
}
}
.options {
display: flex;
align-items: center;
justify-content: space-between;
.status {
display: flex;
align-items: center;
}
.warning {
display: flex;
align-items: center;
font-size: 1.2rem;
color: red;
}
.success {
display: flex;
align-items: center;
font-size: 1.2rem;
color: #67C23A;
}
.left {
gap: var(--space);
display: flex;
}
}
}
</style>

View File

@@ -204,7 +204,7 @@ useWindowClick(() => showExport = false)
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
.add-article {
//position: fixed;
@@ -289,4 +289,4 @@ useWindowClick(() => showExport = false)
}
}
}
</style>
</style>

View File

@@ -41,7 +41,6 @@ useDisableEventListener(() => props.modelValue)
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
.wrapper {
width: 100%;
@@ -49,4 +48,4 @@ useDisableEventListener(() => props.modelValue)
display: flex;
background: var(--color-main-bg);
}
</style>
</style>

View File

@@ -186,7 +186,7 @@ async function cancel() {
</template>
<style scoped lang="scss">
@import "@/assets/css/variable";
$modal-mask-bg: rgba(#000, .45);
$radius: .5rem;
@@ -365,4 +365,4 @@ $header-height: 4rem;
}
}
}
</style>
</style>

View File

@@ -35,7 +35,7 @@ watch(() => props.modelValue, (n) => {
</template>
<style lang="scss">
@import "@/assets/css/style";
.mini-row-title {
min-height: 2rem;
@@ -68,4 +68,4 @@ watch(() => props.modelValue, (n) => {
transform: translate3d(-50%, 0, 0);
//margin-top: 10rem;
}
</style>
</style>

View File

@@ -17,7 +17,7 @@ let disabledDialogEscKey = $ref(true)
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
.setting-modal {
width: 40vw;
@@ -151,4 +151,4 @@ let disabledDialogEscKey = $ref(true)
}
}
</style>
</style>

View File

@@ -48,7 +48,7 @@ onUnmounted(() => {
</template>
<style lang="scss" scoped>
@import "@/assets/css/style";
.all-word {
padding-bottom: var(--space);

View File

@@ -169,7 +169,7 @@ defineExpose({scrollToBottom, scrollToItem})
</template>
<style lang="scss" scoped>
@import "@/assets/css/style";
.scroller {
flex: 1;

View File

@@ -26,7 +26,7 @@ function toggle() {
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
.wrapper {
position: relative;
@@ -36,4 +36,4 @@ function toggle() {
position: relative;
}
</style>
</style>

View File

@@ -71,7 +71,7 @@ onMounted(() => {
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
.setting {
position: relative;
@@ -86,4 +86,4 @@ onMounted(() => {
align-items: flex-start;
}
}
</style>
</style>

View File

@@ -107,7 +107,7 @@ function save() {
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
.setting {
position: relative;
@@ -119,4 +119,4 @@ function save() {
justify-content: flex-end;
gap: .6rem;
}
</style>
</style>

View File

@@ -152,7 +152,7 @@ function toggle2() {
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
.wrapper {
width: 100rem;
@@ -174,4 +174,4 @@ function toggle2() {
transform: translateX(10rem);
}
}
</style>
</style>

View File

@@ -112,7 +112,7 @@ const {nav} = useNav()
}
</style>
<style scoped lang="scss">
@import "@/assets/css/style";
header {
width: var(--toolbar-width);
@@ -165,4 +165,4 @@ header {
}
}
}
</style>
</style>

View File

@@ -117,7 +117,7 @@ function formatLangType(val) {
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
.dict-list-panel {
width: 100%;
@@ -186,4 +186,4 @@ function formatLangType(val) {
}
}
</style>
</style>

View File

@@ -11,8 +11,6 @@ import jq from 'jquery'
import {_nextTick} from "@/utils";
import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css'
import ContextMenu from '@imengyu/vue3-context-menu'
import {useToast} from 'vue-toast-notification';
import 'vue-toast-notification/dist/theme-sugar.css';
import {getTranslateText} from "@/hooks/article.ts";
import BaseButton from "@/components/BaseButton.vue";
import QuestionForm from "@/pages/pc/components/QuestionForm.vue";
@@ -69,7 +67,6 @@ const currentIndex = computed(() => {
return `${sectionIndex}${sentenceIndex}${wordIndex}`
})
const $toast = useToast();
const playBeep = usePlayBeep()
const playCorrect = usePlayCorrect()
const playKeyboardAudio = usePlayKeyboardAudio()
@@ -322,7 +319,10 @@ function onContextMenu(e: MouseEvent, sentence: Sentence, i, j) {
label: "复制",
onClick: () => {
navigator.clipboard.writeText(sentence.text).then(r => {
$toast.success('已复制!', {position: 'top'});
ElMessage({
message: '已复制',
type: 'success',
})
})
}
},
@@ -330,10 +330,11 @@ function onContextMenu(e: MouseEvent, sentence: Sentence, i, j) {
label: "语法分析",
onClick: () => {
navigator.clipboard.writeText(sentence.text).then(r => {
$toast.success('已复制!随后将打开语法分析网站!', {
position: 'top',
duration: 3000,
});
ElMessage({
message: '已复制!随后将打开语法分析网站!',
type: 'success',
duration: 3000
})
setTimeout(() => {
window.open('https://enpuz.com/')
}, 1000)
@@ -483,7 +484,6 @@ let showQuestions = $ref(false)
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
.wrote {
color: grey;
@@ -692,4 +692,4 @@ let showQuestions = $ref(false)
}
}
</style>
</style>

View File

@@ -471,7 +471,7 @@ const {playSentenceAudio} = usePlaySentenceAudio()
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
.practice-wrapper {
font-size: 0.9rem;
@@ -591,4 +591,4 @@ const {playSentenceAudio} = usePlaySentenceAudio()
}
</style>
</style>

View File

@@ -75,7 +75,7 @@ onUnmounted(() => {
</template>
<style scoped lang="scss">
@import "@/assets/css/variable";
.footer {
width: var(--toolbar-width);
@@ -140,4 +140,4 @@ onUnmounted(() => {
}
}
</style>
</style>

View File

@@ -125,7 +125,7 @@ const isEnd = $computed(() => {
<Fireworks v-if="open"/>
</template>
<style scoped lang="scss">
@import "@/assets/css/variable";
$card-radius: .5rem;
$dark-second-bg: rgb(60, 63, 65);
@@ -169,4 +169,4 @@ $item-hover: rgb(75, 75, 75);
}
}
</style>
</style>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import origin from './data.json'
// import origin from './data.json'
import BaseButton from "@/components/BaseButton.vue";
import {checkAndUpgradeSaveDict, shakeCommonDict} from "@/utils";
import localforage from "localforage";
@@ -36,7 +36,7 @@ async function look() {
}
function set() {
localforage.setItem(SAVE_DICT_KEY.key, JSON.stringify({val: shakeCommonDict(origin.val as any), version: 3}))
// localforage.setItem(SAVE_DICT_KEY.key, JSON.stringify({val: shakeCommonDict(origin.val as any), version: 3}))
}
async function check() {
@@ -113,4 +113,4 @@ const data1 = generateData(columns, 1000)
width: 30rem;
}
}
</style>
</style>