feat:修复主题
This commit is contained in:
@@ -453,17 +453,17 @@ function importData(e) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tabIndex === 5" class="about">
|
||||
<div v-if="tabIndex === 5" class="center flex-col">
|
||||
<h1>Type Words</h1>
|
||||
<p>
|
||||
本项目完全开源!好用请大家多多点Star!
|
||||
<p class="w-100 text-xl">
|
||||
感谢使用本项目!本项目是开源项目,如果觉得有帮助,请在 GitHub 点个 Star,您的支持是我持续改进的动力。
|
||||
</p>
|
||||
<p>
|
||||
GitHub地址:<a href="https://github.com/zyronon/typing-word">https://github.com/zyronon/typing-word</a>
|
||||
GitHub地址:<a href="https://github.com/zyronon/TypeWords" target="_blank">https://github.com/zyronon/TypeWords</a>
|
||||
</p>
|
||||
<p>
|
||||
反馈:<a
|
||||
href="https://github.com/zyronon/typing-word/issues">https://github.com/zyronon/typing-word/issues</a>
|
||||
href="https://github.com/zyronon/TypeWords/issues" target="_blank">https://github.com/zyronon/TypeWords/issues</a>
|
||||
</p>
|
||||
<div class="text-md color-gray">
|
||||
Build {{ gitLastCommitHash }}
|
||||
@@ -503,8 +503,8 @@ function importData(e) {
|
||||
gap: .6rem;
|
||||
|
||||
&.active {
|
||||
background: var(--color-main-active);
|
||||
color: var(--color-input-bg);
|
||||
background: var(--color-select-bg);
|
||||
color: var(--color-select-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -551,7 +551,7 @@ function importData(e) {
|
||||
border: 1px solid gray;
|
||||
border-radius: .2rem;
|
||||
padding: 0 .3rem;
|
||||
background: var(--color-second-bg);
|
||||
background: var(--color-second);
|
||||
color: var(--color-font-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ function startStudy() {
|
||||
</div>
|
||||
|
||||
<Dialog v-model="showAddChooseDialog" title="选项">
|
||||
<div class="color-black px-6 w-100">
|
||||
<div class="color-main px-6 w-100">
|
||||
<div class="cursor-pointer hover:bg-black/10 p-2 rounded"
|
||||
@click="showAddChooseDialog = false,showSearchDialog = true">选择一本书籍
|
||||
</div>
|
||||
@@ -131,7 +131,7 @@ function startStudy() {
|
||||
:show-close="false"
|
||||
@close="searchKey = ''"
|
||||
:header="false">
|
||||
<div class="color-black w-140">
|
||||
<div class="color-main w-140">
|
||||
<div class="p-4">
|
||||
<Input v-if="showSearchDialog" :autofocus="true" v-model="searchKey"/>
|
||||
</div>
|
||||
@@ -146,7 +146,7 @@ function startStudy() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="h-40 center flex-col text-xl color-black/60">
|
||||
<div v-else class="h-40 center flex-col text-xl color-main">
|
||||
<div> 请输入书籍名称搜索</div>
|
||||
<div>或直接在书籍列表选中</div>
|
||||
</div>
|
||||
|
||||
@@ -227,7 +227,7 @@ useWindowClick(() => showExport = false)
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
color: var(--color-font-1);
|
||||
background: var(--color-second-bg);
|
||||
background: var(--color-second);
|
||||
display: flex;
|
||||
|
||||
.close {
|
||||
|
||||
@@ -1,661 +0,0 @@
|
||||
<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 {MessageBox} from "@/utils/MessageBox.tsx";
|
||||
import {getSplitTranslateText, 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: () => getDefaultArticle(),
|
||||
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>(getDefaultArticle())
|
||||
|
||||
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 appendTranslate(str: string) {
|
||||
let selectionStart = textareaRef.selectionStart;
|
||||
let selectionEnd = textareaRef.selectionEnd;
|
||||
editArticle.textTranslate = editArticle.textTranslate.slice(0, selectionStart) + str + editArticle.textTranslate.slice(selectionEnd)
|
||||
}
|
||||
|
||||
function splitTranslateText() {
|
||||
if (editArticle.textTranslate.trim()){
|
||||
editArticle.textTranslate = getSplitTranslateText(editArticle.textTranslate.trim())
|
||||
renewSections()
|
||||
}
|
||||
}
|
||||
|
||||
function onPaste(event: ClipboardEvent) {
|
||||
event.preventDefault()
|
||||
// @ts-ignore
|
||||
let paste = (event.clipboardData || window.clipboardData).getData("text");
|
||||
return MessageBox.confirm(
|
||||
'是否需要自动分句',
|
||||
'提示',
|
||||
() => {
|
||||
let r = getSplitTranslateText(paste)
|
||||
if (r) {
|
||||
appendTranslate(r)
|
||||
renewSections()
|
||||
}
|
||||
},
|
||||
() => {
|
||||
appendTranslate(paste)
|
||||
renewSections()
|
||||
}, null,
|
||||
{
|
||||
confirmButtonText: '需要',
|
||||
cancelButtonText: '关闭',
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function onBlur() {
|
||||
document.removeEventListener('paste', onPaste);
|
||||
}
|
||||
|
||||
function onFocus() {
|
||||
document.addEventListener('paste', onPaste);
|
||||
}
|
||||
|
||||
//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-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>
|
||||
</ol>
|
||||
<el-button type="primary" @click="renewSections">应用</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)"
|
||||
@blur="onBlur"
|
||||
@focus="onFocus"
|
||||
type="textarea"
|
||||
class="base-textarea"
|
||||
placeholder="请填写翻译"
|
||||
ref="textareaRef"
|
||||
>
|
||||
</textarea>
|
||||
<div class="justify-between items-center gap-2 flex">
|
||||
<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> 按钮进行自动分句</li>
|
||||
<li><span class="color-red font-bold">或</span> 手动调整分句,一行一句,段落之间空一行</li>
|
||||
<li>修改完成后点击 <span class="color-red font-bold">应用</span> 按钮同步到左侧结果栏
|
||||
</li>
|
||||
</ol>
|
||||
<div class="flex flex-col gap-2 items-end">
|
||||
<div class="translate-item">
|
||||
{{ progress }}%
|
||||
<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>
|
||||
|
||||
</div>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="startNetworkTranslate"
|
||||
:loading="progress!==0 && progress !== 100"
|
||||
>翻译
|
||||
</el-button>
|
||||
<div>
|
||||
<el-button type="primary" @click="splitTranslateText">分句</el-button>
|
||||
<el-button type="primary" @click="renewSections">应用</el-button>
|
||||
</div>
|
||||
|
||||
</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);
|
||||
flex: 1;
|
||||
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;
|
||||
}
|
||||
|
||||
.translate-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: calc(var(--space) / 2);
|
||||
}
|
||||
|
||||
.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>
|
||||
@@ -306,7 +306,7 @@ function setStartTime(val: Sentence, i: number, j: number) {
|
||||
</template>
|
||||
</el-popover>
|
||||
<el-button type="primary" @click="splitText">分句</el-button>
|
||||
<el-button type="primary" @click="apply">应用</el-button>
|
||||
<el-button type="primary" @click="() => apply()">应用</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row flex flex-col gap-2">
|
||||
@@ -474,7 +474,7 @@ function setStartTime(val: Sentence, i: number, j: number) {
|
||||
@close="showEditAudioDialog = false"
|
||||
@ok="saveLrcPosition"
|
||||
>
|
||||
<div class="p-4 pt-0 color-black w-150 flex flex-col gap-2">
|
||||
<div class="p-4 pt-0 color-main w-150 flex flex-col gap-2">
|
||||
<div class="">
|
||||
教程:点击音频播放按钮,当播放到句子开始时,点击开始时间的 <span class="color-red">记录</span>
|
||||
按钮;当播放到句子结束时,点击结束时间的 <span class="color-red">记录</span> 按钮,最后再试听是否正确
|
||||
@@ -539,8 +539,6 @@ function setStartTime(val: Sentence, i: number, j: number) {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
|
||||
.content {
|
||||
color: var(--color-article);
|
||||
height: 100%;
|
||||
|
||||
@@ -234,7 +234,7 @@ useWindowClick(() => showExport = false)
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
color: var(--color-font-1);
|
||||
background: var(--color-second-bg);
|
||||
background: var(--color-second);
|
||||
display: flex;
|
||||
|
||||
.close {
|
||||
|
||||
@@ -45,6 +45,6 @@ useDisableEventListener(() => props.modelValue)
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
background: var(--color-main-bg);
|
||||
background: var(--color-primary);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,7 +6,6 @@ import {usePracticeStore} from "@/stores/practice.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio} from "@/hooks/sound.ts";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts";
|
||||
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'
|
||||
@@ -75,7 +74,6 @@ const store = useBaseStore()
|
||||
const statisticsStore = usePracticeStore()
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
window.$ = jq
|
||||
watch([() => sectionIndex, () => sentenceIndex, () => wordIndex, () => stringIndex], ([a, b, c,]) => {
|
||||
checkCursorPosition(a, b, c)
|
||||
})
|
||||
@@ -97,18 +95,20 @@ watch(() => settingStore.translate, () => {
|
||||
function checkCursorPosition(a = sectionIndex, b = sentenceIndex, c = wordIndex) {
|
||||
// console.log('checkCursorPosition')
|
||||
_nextTick(() => {
|
||||
let currentWord = jq(`.section:nth(${a}) .sentence:nth(${b}) .word:nth(${c})`)
|
||||
// console.log(a, b, c, currentWord)
|
||||
if (currentWord.length) {
|
||||
let end = currentWord.find('.word-end')
|
||||
// console.log(end)
|
||||
if (end.length) {
|
||||
let articleRect = articleWrapperRef.getBoundingClientRect()
|
||||
// 选中目标元素
|
||||
const currentWord = document.querySelector(`.section:nth-of-type(${a + 1}) .sentence:nth-of-type(${b + 1}) .word:nth-of-type(${c + 1})`);
|
||||
if (currentWord) {
|
||||
// 在 currentWord 内找 .word-end
|
||||
const end = currentWord.querySelector('.word-end');
|
||||
if (end) {
|
||||
// 获取 articleWrapper 的位置
|
||||
const articleRect = articleWrapperRef.getBoundingClientRect();
|
||||
const endRect = end.getBoundingClientRect();
|
||||
// 计算相对位置
|
||||
cursor = {
|
||||
top: end.offset().top - articleRect.top,
|
||||
left: end.offset().left - articleRect.left,
|
||||
}
|
||||
// console.log(cursor)
|
||||
top: endRect.top - articleRect.top,
|
||||
left: endRect.left - articleRect.left,
|
||||
};
|
||||
}
|
||||
}
|
||||
},)
|
||||
@@ -548,7 +548,7 @@ let showQuestions = $ref(false)
|
||||
}
|
||||
|
||||
.hover-show {
|
||||
background: var(--color-main-active);
|
||||
background: var(--color-select-bg);
|
||||
color: white !important;
|
||||
|
||||
.wrote {
|
||||
@@ -654,7 +654,7 @@ let showQuestions = $ref(false)
|
||||
}
|
||||
|
||||
.word-start {
|
||||
color: var(--color-main-active);
|
||||
color: var(--color-select-bg);
|
||||
}
|
||||
|
||||
.wrong {
|
||||
|
||||
@@ -53,6 +53,7 @@ function next() {
|
||||
}
|
||||
|
||||
function init() {
|
||||
//todo 这个页面,直接访问白屏
|
||||
if (!store.currentBook.articles.length) return
|
||||
articleData.articles = cloneDeep(store.currentBook.articles)
|
||||
getCurrentPractice()
|
||||
@@ -436,8 +437,6 @@ const {playSentenceAudio} = usePlaySentenceAudio()
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
//padding-right: var(--practice-wrapper-padding-right);
|
||||
transform: translateX(var(--practice-wrapper-translateX));
|
||||
}
|
||||
|
||||
.swiper-wrapper {
|
||||
@@ -501,7 +500,7 @@ const {playSentenceAudio} = usePlaySentenceAudio()
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: .6rem;
|
||||
background: var(--color-second-bg);
|
||||
background: var(--color-second);
|
||||
padding: .2rem var(--space) .4rem var(--space);
|
||||
z-index: 2;
|
||||
border: 1px solid var(--color-item-border);
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {Word} from "@/types.ts";
|
||||
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
|
||||
import {usePlayWordAudio} from "@/hooks/sound.ts";
|
||||
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
item: Word,
|
||||
showTranslate?: boolean
|
||||
showWord?: boolean
|
||||
border?: boolean
|
||||
}>(), {
|
||||
showTranslate: true,
|
||||
showWord: true,
|
||||
border: true
|
||||
})
|
||||
|
||||
const playWordAudio = usePlayWordAudio()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="word-item"
|
||||
:class="{
|
||||
border,
|
||||
}"
|
||||
>
|
||||
<div class="left">
|
||||
<slot name="prefix" :item="item"></slot>
|
||||
<div class="title-wrapper">
|
||||
<div class="item-title">
|
||||
<span class="word" :class="!showWord && 'word-shadow'">{{ item.word }}</span>
|
||||
<span class="phonetic">{{ item.phonetic0 }}</span>
|
||||
<VolumeIcon class="volume" @click="playWordAudio(item.word)"></VolumeIcon>
|
||||
</div>
|
||||
<div class="item-sub-title" v-if="item.trans.length && showTranslate">
|
||||
<div v-for="v in item.trans">{{ (v.pos ? v.pos + '.' : '') + (v.cn) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<slot name="suffix" :item="item"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
.word-item {
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background: var(--color-item-bg);
|
||||
color: var(--color-font-1);
|
||||
font-size: 1.1rem;
|
||||
border-radius: .5rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
transition: all .3s;
|
||||
padding: .6rem;
|
||||
gap: .6rem;
|
||||
border: 1px solid var(--color-item-border);
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
gap: .6rem;
|
||||
|
||||
.title-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .2rem;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .3rem;
|
||||
transition: all .3s;
|
||||
}
|
||||
|
||||
.volume, .collect, .easy {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--color-item-hover);
|
||||
|
||||
.volume, .collect, .easy {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: var(--color-item-active);
|
||||
$c: #E6A23C;
|
||||
|
||||
.phonetic, .item-sub-title {
|
||||
color: var(--color-gray) !important;
|
||||
}
|
||||
|
||||
.volume, .collect, .easy, .fill {
|
||||
color: $c;
|
||||
}
|
||||
}
|
||||
|
||||
&.border {
|
||||
&.active {
|
||||
.item-title {
|
||||
border-bottom: 2px solid gray !important;
|
||||
}
|
||||
}
|
||||
|
||||
.item-title {
|
||||
transition: all .3s;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.item-title {
|
||||
border-bottom: 2px solid gray !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
color: var(--color-font-1);
|
||||
|
||||
.word {
|
||||
font-size: 1.2rem;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.phonetic {
|
||||
font-size: .9rem;
|
||||
color: gray;
|
||||
}
|
||||
}
|
||||
|
||||
.item-sub-title {
|
||||
font-size: 1rem;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<template>
|
||||
<div class="flex justify-center">
|
||||
<div class="w-[70vw] 2xl:w-[50vw] page">
|
||||
<div class="page w-[70vw] 2xl:w-[50vw]">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -103,7 +103,7 @@ watch(() => settingStore.load, (n) => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: var(--color-second-bg);
|
||||
background: var(--color-notice-bg);
|
||||
padding: 1.8rem;
|
||||
border-radius: 0.7rem;
|
||||
width: 30rem;
|
||||
@@ -124,7 +124,7 @@ watch(() => settingStore.load, (n) => {
|
||||
}
|
||||
|
||||
.active {
|
||||
color: var(--color-main-active);
|
||||
color: var(--color-select-bg);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
@@ -148,7 +148,7 @@ watch(() => settingStore.load, (n) => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: var(--color-main-bg);
|
||||
background: var(--color-primary);
|
||||
|
||||
.href {
|
||||
font-size: 0.9rem;
|
||||
@@ -156,7 +156,7 @@ watch(() => settingStore.load, (n) => {
|
||||
}
|
||||
|
||||
.star {
|
||||
color: var(--color-main-active);
|
||||
color: var(--color-select-bg);
|
||||
}
|
||||
|
||||
.right {
|
||||
|
||||
@@ -18,7 +18,6 @@ $w: 1.4rem;
|
||||
border-radius: .3rem;
|
||||
background: transparent;
|
||||
transition: all .3s;
|
||||
//color: var(--color-main-active);
|
||||
|
||||
&:hover {
|
||||
background: var(--color-primary);
|
||||
@@ -30,4 +29,4 @@ $w: 1.4rem;
|
||||
height: $w;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -70,7 +70,7 @@ const vFocus = {
|
||||
}
|
||||
|
||||
&.focus {
|
||||
border: 1px solid var(--color-main-active);
|
||||
border: 1px solid var(--color-select-bg);
|
||||
|
||||
:deep(svg) {
|
||||
color: gray;
|
||||
|
||||
@@ -219,7 +219,7 @@ $header-height: 3rem;
|
||||
.panel {
|
||||
border-radius: .5rem;
|
||||
width: var(--panel-width);
|
||||
background: var(--color-second-bg);
|
||||
background: var(--color-second);
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -257,7 +257,7 @@ $header-height: 3rem;
|
||||
color: gray;
|
||||
|
||||
&.active {
|
||||
color: var(--color-main-active);
|
||||
color: var(--color-select-bg);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,7 +499,6 @@ function importData(e) {
|
||||
}
|
||||
|
||||
.content {
|
||||
background: var(--color-header-bg);
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
@@ -539,7 +538,7 @@ function importData(e) {
|
||||
border: 1px solid gray;
|
||||
border-radius: .2rem;
|
||||
padding: 0 .3rem;
|
||||
background: var(--color-second-bg);
|
||||
background: var(--color-second);
|
||||
color: var(--color-font-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +279,7 @@ $header-height: 4rem;
|
||||
|
||||
.modal {
|
||||
position: relative;
|
||||
background: var(--color-second-bg);
|
||||
background: var(--color-second);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -59,7 +59,7 @@ watch(() => props.modelValue, (n) => {
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
width: 12rem;
|
||||
background: var(--color-second-bg);
|
||||
background: var(--color-second);
|
||||
border-radius: .5rem;
|
||||
box-shadow: 0 0 8px 2px var(--color-item-border);
|
||||
padding: .6rem var(--space);
|
||||
|
||||
@@ -203,4 +203,4 @@ defineExpose({scrollBottom})
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -19,10 +19,10 @@ const {toggleTheme} = useTheme()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layout">
|
||||
<div class="layout anim">
|
||||
<!-- 第一个aside 占位用-->
|
||||
<div class="aside space" :class="{'expand':settingStore.sideExpand}"></div>
|
||||
<div class="aside fixed" :class="{'expand':settingStore.sideExpand}">
|
||||
<div class="aside anim fixed" :class="{'expand':settingStore.sideExpand}">
|
||||
<div class="top">
|
||||
<Logo v-if="settingStore.sideExpand"/>
|
||||
<div class="row" @click="router.push('/home')">
|
||||
@@ -78,11 +78,11 @@ const {toggleTheme} = useTheme()
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
background: var(--color-background);
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
.aside {
|
||||
background: var(--color-second-bg);
|
||||
background: var(--color-second);
|
||||
height: 100vh;
|
||||
padding: 1rem 1rem;
|
||||
box-sizing: border-box;
|
||||
@@ -91,7 +91,6 @@ const {toggleTheme} = useTheme()
|
||||
justify-content: space-between;
|
||||
box-shadow: rgb(0 0 0 / 3%) 0px 0px 12px 0px;
|
||||
width: 4.5rem;
|
||||
transition: all 0.3s;
|
||||
z-index: 2;
|
||||
|
||||
.row {
|
||||
|
||||
@@ -172,7 +172,7 @@ const progressTextRight = $computed(() => {
|
||||
<div class="card flex gap-10">
|
||||
<div class="flex-1 flex flex-col gap-2">
|
||||
<div class="flex">
|
||||
<div class="bg-slate-200 px-3 h-14 rounded-md flex items-center">
|
||||
<div class="bg-third px-3 h-14 rounded-md flex items-center">
|
||||
<span class="text-xl font-bold">{{ store.sdict.name || '请选择书籍开始学习' }}</span>
|
||||
<BaseIcon title="切换词典" :icon="store.sdict.name ? 'gg:arrows-exchange' : 'fluent:add-20-filled'"
|
||||
class="ml-4"
|
||||
@@ -214,11 +214,12 @@ const progressTextRight = $computed(() => {
|
||||
<div class="flex gap-1 items-center">
|
||||
每日目标
|
||||
<div style="color:#ac6ed1;" @click="setPerDayStudyNumber"
|
||||
class="bg-slate-200 px-2 h-10 flex center text-2xl rounded cursor-pointer">
|
||||
class="bg-third px-2 h-10 flex center text-2xl rounded cursor-pointer">
|
||||
{{ store.sdict.id ? store.sdict.perDayStudyNumber : 0 }}
|
||||
</div>
|
||||
个单词 <span class="color-blue cursor-pointer" @click="setPerDayStudyNumber">更改</span>
|
||||
</div>
|
||||
<div class="btn">开始学习</div>
|
||||
<div class="rounded-xl bg-slate-800 flex items-center gap-2 py-3 px-5 text-white cursor-pointer"
|
||||
:class="store.sdict.name || 'opacity-70 cursor-not-allowed'" @click="startStudy">
|
||||
<span>开始学习</span>
|
||||
|
||||
@@ -206,7 +206,7 @@ const progress = $computed(() => {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: .6rem;
|
||||
background: var(--color-second-bg);
|
||||
background: var(--color-second);
|
||||
padding: .2rem var(--space) .4rem var(--space);
|
||||
z-index: 2;
|
||||
border: 1px solid var(--color-item-border);
|
||||
|
||||
Reference in New Issue
Block a user