Merge branch 'dev'
This commit is contained in:
@@ -1,71 +0,0 @@
|
||||
## 目标
|
||||
- 在 `onTyping` 方法(291-382行)中判断当前/下一个单词是否为人名,并在练习时自动忽略(不要求输入、不提示错误、不停顿等待空格)。
|
||||
|
||||
## 数据来源
|
||||
- 使用 `props.article.nameList: string[]`(来自编辑页保存),作为要忽略的人名列表。
|
||||
|
||||
## 匹配策略
|
||||
- 构建一个人名集合 `nameSet`:
|
||||
- `trim()` 后的字符串;
|
||||
- 若开启 `ignoreCase` 则统一转小写匹配。
|
||||
- 判定函数 `isNameWord(word: ArticleWord)`:
|
||||
- 仅当 `word.type === PracticeArticleWordType.Word` 时参与匹配;
|
||||
- 对 `word.word` 进行同样的规范化后 `nameSet.has(...)`。
|
||||
|
||||
## 处理时机与行为
|
||||
- 在 `onTyping` 开始处、拿到 `currentWord` 后:
|
||||
- 若是“人名”,则直接跳过本词;若该词 `nextSpace` 为真,连带空格也跳过(避免进入 `isSpace` 状态)。
|
||||
- 跳过后继续处理当前按键:复用已有模式(如 `isSpace` 分支里)调用 `next()` 和递归 `onTyping(e)`。
|
||||
- 在 `next()` 内也追加“人名跳过”逻辑(与已有忽略符号/数字类似),保证连续多个需要忽略的词可以被连续跳过:
|
||||
- 当 `currentWord` 是人名:
|
||||
- 若 `currentWord.nextSpace` 为真:`isSpace = false`;
|
||||
- 递归调用 `next()` 继续到下一个词;
|
||||
- 否则正常 `emit('nextWord', currentWord)`。
|
||||
|
||||
## 代码改动点
|
||||
- 在组件顶部或方法内构建 `nameSet`(建议用 `$computed`):
|
||||
```ts
|
||||
const nameSet = $computed(() => {
|
||||
const list = props.article?.nameList ?? []
|
||||
return new Set(list.map(s => (settingStore.ignoreCase ? s.toLowerCase() : s).trim()).filter(Boolean))
|
||||
})
|
||||
function isNameWord(w: ArticleWord) {
|
||||
if (w.type !== PracticeArticleWordType.Word) return false
|
||||
const token = (settingStore.ignoreCase ? w.word.toLowerCase() : w.word).trim()
|
||||
return nameSet.has(token)
|
||||
}
|
||||
```
|
||||
- 在 `onTyping` 里,`let currentWord = currentSentence.words[wordIndex]` 之后:
|
||||
```ts
|
||||
if (isNameWord(currentWord)) {
|
||||
// 跳过当前人名,连带空格
|
||||
isSpace = false
|
||||
const savedTypingFlag = isTyping
|
||||
next()
|
||||
isTyping = false
|
||||
return onTyping(e)
|
||||
}
|
||||
```
|
||||
- 在 `next()` 内,设置 `currentWord` 后、`emit('nextWord', currentWord)` 之前:
|
||||
```ts
|
||||
if (isNameWord(currentWord)) {
|
||||
// 人名与后续空格都跳过
|
||||
isSpace = false
|
||||
return next()
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
- 保持与现有忽略规则一致(符号/数字已通过 `ignoreSymbol` 处理),人名跳过逻辑与其同层级。
|
||||
- 忽略人名时不触发 `wrong` 或提示音,不进入 `isSpace` 等待。
|
||||
- 若连续出现多个需要忽略的词(如人名+标点+人名),递归 `next()` 将逐个跳过。
|
||||
|
||||
## 验证
|
||||
- 在包含人名的文本中练习:
|
||||
- 人名处不需要输入,光标自动跳到下一非忽略词;
|
||||
- 不会卡在空格等待;
|
||||
- 大小写忽略效果符合 `settingStore.ignoreCase` 设置。
|
||||
|
||||
## 交付
|
||||
- 按上述方案在 `TypingArticle.vue` 中实现辅助函数与两处调用点的改动;
|
||||
- 仅修改该文件,不影响其他页面。
|
||||
@@ -1,69 +0,0 @@
|
||||
## 目标
|
||||
- 在“人物名称管理”弹框中使用临时变量编辑名称列表,只在点击“确定”时写回 `editArticle.nameList: string[]`
|
||||
|
||||
## 数据结构
|
||||
- `editArticle.nameList: string[]`
|
||||
- 临时变量:`let nameListRef = $ref<string[]>([])`
|
||||
|
||||
## 生命周期
|
||||
- 弹框打开时初始化:`nameListRef = cloneDeep(editArticle.nameList || [])`
|
||||
- 弹框关闭时不写回:丢弃修改
|
||||
|
||||
## 交互设计
|
||||
- 弹框 `v-model="showNameDialog"`、`:footer="true"`、`@close="showNameDialog = false"`、`@ok="saveNameList"`
|
||||
- 按钮:
|
||||
- “添加名称” → `nameListRef.push('')`
|
||||
- 每行名称使用 `BaseInput v-model="nameListRef[i]"`
|
||||
- “删除”名称 → `nameListRef.splice(i,1)`
|
||||
|
||||
## 保存逻辑
|
||||
- `saveNameList()`:
|
||||
- 清理:`trim()` + `filter(Boolean)`
|
||||
- 写回:`editArticle.nameList = cleaned`
|
||||
- 关闭弹框:`showNameDialog = false`
|
||||
|
||||
## 实现细节(EditArticle.vue 增加)
|
||||
- 脚本:
|
||||
```ts
|
||||
let showNameDialog = $ref(false)
|
||||
let nameListRef = $ref<string[]>([])
|
||||
|
||||
watch(() => showNameDialog, (v) => {
|
||||
if (v) nameListRef = cloneDeep(Array.isArray(editArticle.nameList) ? editArticle.nameList : [])
|
||||
})
|
||||
|
||||
function addName() { nameListRef.push('') }
|
||||
function removeName(i: number) { nameListRef.splice(i,1) }
|
||||
function saveNameList() {
|
||||
const cleaned = nameListRef.map(s => (s ?? '').trim()).filter(Boolean)
|
||||
editArticle.nameList = cleaned
|
||||
}
|
||||
```
|
||||
- 模板(620-628 区域):
|
||||
```vue
|
||||
<Dialog title="人物名称管理"
|
||||
v-model="showNameDialog"
|
||||
:footer="true"
|
||||
@close="showNameDialog = false"
|
||||
@ok="saveNameList">
|
||||
<div class="p-4 pt-0 color-main w-150 flex flex-col gap-3">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="text-base">配置需要忽略的人名,练习时自动忽略这些名称</div>
|
||||
<BaseButton size="small" type="info" @click="addName">添加名称</BaseButton>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center gap-2" v-for="(name,i) in nameListRef" :key="i">
|
||||
<BaseInput v-model="nameListRef[i]" placeholder="输入名称" size="large" />
|
||||
<BaseButton size="small" type="info" @click="removeName(i)">删除</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
```
|
||||
|
||||
## 验证
|
||||
- 打开弹框 → 编辑临时列表 → 点击“确定”后检查 `editArticle.nameList` 是否更新;点击关闭则不更新
|
||||
|
||||
## 注意
|
||||
- 若类型仍为旧版 `string[][]`,请同步调整为 `string[]` 以与当前实现一致
|
||||
@@ -1,109 +0,0 @@
|
||||
## 目标与范围
|
||||
|
||||
* 适配 `public/static-home.html` 在 768px 以下与 480px 以下的移动端展示与触控体验
|
||||
|
||||
* 不改变页面文案与结构,只进行样式与布局响应式改造
|
||||
|
||||
## 结构与布局
|
||||
|
||||
* 将固定宽度 `.w { width: 60vw; }` 改为容器 `.container { width: min(1200px, 92%); }` 并在模板中替换为 `.container`
|
||||
|
||||
* `.card-wrap` 改为自适应栅格:`grid-template-columns: repeat(auto-fit, minmax(240px, 1fr))`
|
||||
|
||||
* 移动端(<=768px)卡片单列展示;间距适当增大,避免拥挤
|
||||
|
||||
## 样式改造
|
||||
|
||||
* 标题缩放:
|
||||
|
||||
* `@media (max-width: 768px) h1 { font-size: 3rem; }`
|
||||
|
||||
* `@media (max-width: 480px) h1 { font-size: 2.4rem; }`
|
||||
|
||||
* 主按钮组:
|
||||
|
||||
* 移动端按钮全宽:`.base-button { width: 100%; margin: .5rem 0; height: 2.8rem; font-size: 1rem; }`
|
||||
|
||||
* 悬浮降低透明度改为轻微位移:`transform: translateY(-1px);`
|
||||
|
||||
* 内容区与间距:
|
||||
|
||||
* `@media (max-width: 768px) .content { margin-top: 4rem; gap: 1.4rem; }`
|
||||
|
||||
* `@media (max-width: 480px) .content { margin-top: 3.2rem; gap: 1.2rem; }`
|
||||
|
||||
* 赞助区 `.sky`:
|
||||
|
||||
* 图片强制全宽:`.sky a { width: 100% !important; } .sky-img { width: 100%; }`
|
||||
|
||||
* 移动端上下内边距缩小,减少跳动
|
||||
|
||||
* 卡片 `.card`:
|
||||
|
||||
* 移动端去掉固定 `width: 25%`,改为自适应栅格控制
|
||||
|
||||
* 增加触控阴影:`box-shadow: 0 6px 20px rgba(0,0,0,.08);`
|
||||
|
||||
* 底部链接 `.bottom`:
|
||||
|
||||
* 在 <=768px 改为纵向堆叠:`flex-direction: column; align-items: flex-start; gap: .6rem;`
|
||||
|
||||
## 示例变更片段(将添加到 <style> 内)
|
||||
|
||||
```css
|
||||
/* 容器与栅格 */
|
||||
.container { width: min(1200px, 92%); }
|
||||
.card-wrap { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; margin-bottom: 1.2rem; }
|
||||
.card { width: auto; box-shadow: 0 6px 20px rgba(0,0,0,.08); }
|
||||
|
||||
/* 标题与内容间距 */
|
||||
@media (max-width: 768px) {
|
||||
h1 { font-size: 3rem !important; }
|
||||
.content { margin-top: 4rem; gap: 1.4rem; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
h1 { font-size: 2.4rem !important; }
|
||||
.content { margin-top: 3.2rem; gap: 1.2rem; }
|
||||
}
|
||||
|
||||
/* 按钮移动端全宽 */
|
||||
@media (max-width: 768px) {
|
||||
.base-button { width: 100%; margin: .5rem 0; height: 2.8rem; font-size: 1rem; }
|
||||
}
|
||||
|
||||
/* 赞助区图片全宽 */
|
||||
.sky a { width: 100% !important; }
|
||||
.sky-img { width: 100%; }
|
||||
|
||||
/* 底部链接在移动端纵向排布 */
|
||||
@media (max-width: 768px) {
|
||||
.bottom { flex-direction: column; align-items: flex-start; gap: .6rem; }
|
||||
}
|
||||
```
|
||||
|
||||
## 交互与触控优化
|
||||
|
||||
* 增加触控可点击区域:`.icon { padding: .2rem; }`
|
||||
|
||||
* 禁用点击高亮:`html, body { -webkit-tap-highlight-color: transparent; }`
|
||||
|
||||
## 可访问性与表现
|
||||
|
||||
* 保持当前 `meta viewport` 设置,确保缩放正确
|
||||
|
||||
* 文本对比度在暗色模式下保持可读
|
||||
|
||||
## 验证方式
|
||||
|
||||
* 本地预览在 Chrome DevTools 切换 iPhone 14 / Pixel 7 视窗
|
||||
|
||||
* 检查首屏按钮是否折行、卡片是否单列、赞助图是否铺满
|
||||
|
||||
* 交互:滚动、点击弹窗与社交按钮触控区域确认
|
||||
|
||||
## 执行与交付
|
||||
|
||||
* 我将按上述片段更新 `<style>` 并把模板里的 `.w` 替换为 `.container`
|
||||
|
||||
* 修改完成后提供预览链接与截图确认
|
||||
|
||||
1
components.d.ts
vendored
1
components.d.ts
vendored
@@ -99,6 +99,7 @@ declare module 'vue' {
|
||||
IconFluentSlideTextTitleEdit20Regular: typeof import('~icons/fluent/slide-text-title-edit20-regular')['default']
|
||||
IconFluentSparkle20Regular: typeof import('~icons/fluent/sparkle20-regular')['default']
|
||||
IconFluentSpeakerEdit20Regular: typeof import('~icons/fluent/speaker-edit20-regular')['default']
|
||||
IconFluentSpeakerSettings20Regular: typeof import('~icons/fluent/speaker-settings20-regular')['default']
|
||||
IconFluentStar16Filled: typeof import('~icons/fluent/star16-filled')['default']
|
||||
IconFluentStar16Regular: typeof import('~icons/fluent/star16-regular')['default']
|
||||
IconFluentStar20Filled: typeof import('~icons/fluent/star20-filled')['default']
|
||||
|
||||
@@ -2,7 +2,7 @@ version: "2"
|
||||
services:
|
||||
typeword:
|
||||
image: "node:latest"
|
||||
#environment: #按需配置,主要为了科学上网解决依赖安装网络问题
|
||||
#environment: #按需配置, 主要为了科学上网解决依赖安装网络问题
|
||||
# - HTTP_PROXY=http://127.0.0.1:80
|
||||
# HTTPS_PROXY=http://127.0.0.1:80
|
||||
working_dir: /home/node/app
|
||||
|
||||
@@ -465,7 +465,7 @@
|
||||
"id": "wr-2ZE",
|
||||
"title": "The weekend",
|
||||
"titleTranslate": "周末",
|
||||
"text": "MRS. JOHNSON:Hello. Where you at the butcher's?\n\nMRS. WILLIAMS:Yes. I was.\n\nWere you at butcher's too?\n\nMRS. JOHNSON:No, I wasn't\n\nI was at the greengrocer's.\n\nHow's Jimmy today?\n\nMRS. WILLIAMS:He's very well, thank you.\n\nMRS. JOHNSON:Was he absent from school last week?\n\nMRS. WILLIAMS:Yes, he was.\n\nHe was absent on Monday,Tuesday,Wednesday and Tuesday.\n\nHow are you all keeping?\n\nMRS. JOHNSON:Very well, thank you.\n\nWe're going to spend three days in the country.\n\nWe're going to stay at my mother's for the weekend.\n\nMRS. WILLIAMS:Friday, Saturday and Sunday in the country!\n\nAren't you lucky!",
|
||||
"text": "MRS. JOHNSON:Hello. Where you at the butcher's?\n\nMRS. WILLIAMS:Yes. I was.\n\nWere you at butcher's too?\n\nMRS. JOHNSON:No, I wasn't\n\nI was at the greengrocer's.\n\nHow's Jimmy today?\n\nMRS. WILLIAMS:He's very well, thank you.\n\nMRS. JOHNSON:Was he absent from school last week?\n\nMRS. WILLIAMS:Yes, he was.\n\nHe was absent on Monday,Tuesday,Wednesday and Thursday.\n\nHow are you all keeping?\n\nMRS. JOHNSON:Very well, thank you.\n\nWe're going to spend three days in the country.\n\nWe're going to stay at my mother's for the weekend.\n\nMRS. WILLIAMS:Friday, Saturday and Sunday in the country!\n\nAren't you lucky!",
|
||||
"textTranslate": "约翰逊夫人:您好。刚才您在肉店里吗? \n\n威廉斯夫人:是的,我在肉店里。 \n\n您也在肉店里吗? \n\n约翰逊夫人:不, 我不是。 \n\n我在蔬菜水果店里。 \n\n吉米今天怎么样? \n\n威廉斯夫人:他很好,谢谢您。 \n\n约翰逊夫人:上星期他没上学吧? \n\n威廉斯夫人:是的,他没上学。 \n\n他星期一、 星期二、星期三和星期四没 去上学。 \n\n你们身体都好吗? \n\n约翰逊夫人:很好,谢谢您。 \n\n我们打算到乡下去三天, \n\n在我母亲家度周末。 \n\n威廉斯夫人:星期五、星期六和星期日在乡下 过! \n\n你们真幸运啊!",
|
||||
"newWords": [],
|
||||
"audioSrc": "/sound/article/nce1/067&068-The Weekend.mp3",
|
||||
|
||||
@@ -94,5 +94,7 @@ export const LIB_JS_URL = {
|
||||
SHEPHERD: import.meta.env.MODE === 'development' ?
|
||||
'https://cdn.jsdelivr.net/npm/shepherd.js@14.5.1/dist/esm/shepherd.mjs'
|
||||
: Origin + '/libs/Shepherd.14.5.1.mjs',
|
||||
SNAPDOM: `${Origin}/libs/snapdom.min.js`
|
||||
SNAPDOM: `${Origin}/libs/snapdom.min.js`,
|
||||
JSZIP: `${Origin}/libs/jszip.min.js`,
|
||||
XLSX: `${Origin}/libs/xlsx.full.min.js`,
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { onDeactivated, onMounted, onUnmounted, watch } from "vue";
|
||||
import { emitter, EventKey } from "@/utils/eventBus.ts";
|
||||
import { useRuntimeStore } from "@/stores/runtime.ts";
|
||||
import { useSettingStore } from "@/stores/setting.ts";
|
||||
import { isMobile } from "@/utils";
|
||||
import {onDeactivated, onMounted, onUnmounted, watch} from "vue";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {isMobile} from "@/utils";
|
||||
|
||||
export function useWindowClick(cb: (e: PointerEvent) => void) {
|
||||
onMounted(() => {
|
||||
@@ -92,9 +92,12 @@ export function useEventListener(type: string, listener: EventListenerOrEventLis
|
||||
repeat: false,
|
||||
isComposing: false,
|
||||
type,
|
||||
preventDefault() {},
|
||||
stopPropagation() {},
|
||||
stopImmediatePropagation() {},
|
||||
preventDefault() {
|
||||
},
|
||||
stopPropagation() {
|
||||
},
|
||||
stopImmediatePropagation() {
|
||||
},
|
||||
}
|
||||
return base as unknown as KeyboardEvent
|
||||
}
|
||||
@@ -131,7 +134,7 @@ export function useEventListener(type: string, listener: EventListenerOrEventLis
|
||||
const value = target?.value ?? ''
|
||||
|
||||
if (event.inputType === 'deleteContentBackward') {
|
||||
dispatchSyntheticKey({ key: 'Backspace', code: 'Backspace', keyCode: 8 })
|
||||
dispatchSyntheticKey({key: 'Backspace', code: 'Backspace', keyCode: 8})
|
||||
if (target) target.value = ''
|
||||
return
|
||||
}
|
||||
@@ -252,6 +255,8 @@ export function useStartKeyboardEventListener() {
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
useEventListener('keydown', (e: KeyboardEvent) => {
|
||||
//解决无法复制、全选的问题
|
||||
if ((e.ctrlKey || e.metaKey) && ['KeyC', 'KeyA'].includes(e.code)) return
|
||||
if (!runtimeStore.disableEventListener) {
|
||||
|
||||
// 检查当前单词是否包含空格,如果包含,则空格键应该被视为输入
|
||||
|
||||
@@ -2,7 +2,7 @@ import {loadJsLib, shakeCommonDict} from "@/utils";
|
||||
import {
|
||||
APP_NAME,
|
||||
APP_VERSION,
|
||||
EXPORT_DATA_KEY,
|
||||
EXPORT_DATA_KEY, LIB_JS_URL,
|
||||
LOCAL_FILE_KEY,
|
||||
Origin,
|
||||
PracticeSaveArticleKey,
|
||||
@@ -28,7 +28,7 @@ export function useExport() {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
const JSZip = await loadJsLib('JSZip', `${Origin}/libs/jszip.min.js`);
|
||||
const JSZip = await loadJsLib('JSZip', LIB_JS_URL.JSZIP);
|
||||
let data = {
|
||||
version: EXPORT_DATA_KEY.version,
|
||||
val: {
|
||||
|
||||
@@ -280,7 +280,7 @@ let isNewHost = $ref(window.location.host === Host)
|
||||
</div>
|
||||
<BaseButton size="large" class="w-full md:w-auto"
|
||||
@click="startStudy"
|
||||
:disabled="!base.currentBook.name">
|
||||
:disabled="!base.sbook.name">
|
||||
<div class="flex items-center gap-2 justify-center w-full">
|
||||
<span class="line-height-[2]">{{ isSaveData ? '继续学习' : '开始学习' }}</span>
|
||||
<IconFluentArrowCircleRight16Regular class="text-xl"/>
|
||||
|
||||
@@ -15,7 +15,7 @@ import {getDefaultArticle} from "@/types/func.ts";
|
||||
import BackIcon from "@/components/BackIcon.vue";
|
||||
import MiniDialog from "@/components/dialog/MiniDialog.vue";
|
||||
import {onMounted} from "vue";
|
||||
import {Origin} from "@/config/env.ts";
|
||||
import { LIB_JS_URL, Origin } from "@/config/env.ts";
|
||||
import {syncBookInMyStudyList} from "@/hooks/article.ts";
|
||||
|
||||
const base = useBaseStore()
|
||||
@@ -132,7 +132,7 @@ function importData(e: any) {
|
||||
let reader = new FileReader();
|
||||
reader.onload = async function (s) {
|
||||
importLoading = true
|
||||
const XLSX = await loadJsLib('XLSX', `${Origin}/libs/xlsx.full.min.js`);
|
||||
const XLSX = await loadJsLib('XLSX', LIB_JS_URL.XLSX);
|
||||
let data = s.target.result;
|
||||
let workbook = XLSX.read(data, {type: 'binary'});
|
||||
let res: any[] = XLSX.utils.sheet_to_json(workbook.Sheets['Sheet1']);
|
||||
@@ -198,7 +198,7 @@ function importData(e: any) {
|
||||
|
||||
async function exportData(val: { type: string, data?: Article }) {
|
||||
exportLoading = true
|
||||
const XLSX = await loadJsLib('XLSX', `${Origin}/libs/xlsx.full.min.js`);
|
||||
const XLSX = await loadJsLib('XLSX', LIB_JS_URL.XLSX);
|
||||
const {type, data} = val
|
||||
let list = []
|
||||
let filename = ''
|
||||
|
||||
@@ -739,7 +739,7 @@ const currentPractice = inject('currentPractice', [])
|
||||
@click="emit('replay')">重新练习
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="store.currentBook.lastLearnIndex < store.currentBook.articles.length - 1"
|
||||
v-if="store.sbook.lastLearnIndex < store.sbook.articles.length - 1"
|
||||
@click="emit('next')">下一篇
|
||||
</BaseButton>
|
||||
</div>
|
||||
|
||||
237
src/pages/setting/Log.vue
Normal file
237
src/pages/setting/Log.vue
Normal file
@@ -0,0 +1,237 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/12/5</div>
|
||||
<div>内容:解决练习界面无法复制、全选的问题</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/12/3</div>
|
||||
<div>内容:单词、文章设置修改为弹框,更方便</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/12/3</div>
|
||||
<div>内容:录入新概念(三、四)部分音频,优化文章相关功能</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/12/2</div>
|
||||
<div>内容:完成新概念(一)音频,优化文章管理页面</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/30</div>
|
||||
<div>内容:文章里的单词可点击播放</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/29</div>
|
||||
<div>内容:修改 Slider 组件显示bug,新增 IE 浏览器检测提示</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/28</div>
|
||||
<div>内容:新增引导框、 新增<a href="https://github.com/zyronon/TypeWords/pull/175" target="_blank">词典测试模式(由大佬
|
||||
hebeihang 开发)</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/25</div>
|
||||
<div>内容:文章练习新增人名忽略功能(新概念一已全部适配),上传了新概念(一)1-18 音频</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/23</div>
|
||||
<div>内容:优化练习完成结算界面,新增分享功能</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/22</div>
|
||||
<div>内容:适配移动端</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/16</div>
|
||||
<div>内容:自测单词时,不认识单词可以直接输入,自动标识为错误单词,无需按2</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/15</div>
|
||||
<div>内容:练习单词时,底部工具栏新增“跳到下一阶段”按钮</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/14</div>
|
||||
<div>内容:新增文章练习时可跳过空格:如果在单词的最后一位上,不按空格直接输入下一个字母的话,自动跳下一个单词,
|
||||
按空格也自动跳下一个单词
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/13</div>
|
||||
<div>内容:新增文章练习时“输入时忽略符号/数字”选项</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/6</div>
|
||||
<div>内容:新增随机复习功能</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/10/30</div>
|
||||
<div>内容:集成PWA基础配置,支持用户以类App形式打开项目</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/10/26</div>
|
||||
<div>内容:进一步完善单词练习,解决复习数量太多的问题</div>
|
||||
</div>
|
||||
<div class="text-base mt-1">
|
||||
<ol>
|
||||
<li>
|
||||
<div class="title"><b>智能模式优化</b></div>
|
||||
<div class="desc">练习时新增四种练习模式:学习、自测、听写、默写。</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title"><b>学习模式</b></div>
|
||||
<div class="desc">
|
||||
<ul>
|
||||
<li>仅在练习新词时出现。</li>
|
||||
<li>采用「跟写 / 拼写」方式进行学习。</li>
|
||||
<li>每 7 个单词会 <b>强制进行听写</b>,解决原来“一次练太多,听写时已忘记”的问题。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title"><b>自测模式(新增)</b></div>
|
||||
<div class="desc">
|
||||
<ul>
|
||||
<li>仅在复习已学单词时出现。</li>
|
||||
<li>不再强制拼写,提供「我认识」与「不认识」选项。</li>
|
||||
<li>选择「我认识」后,该单词在后续听写或默写中将不再出现,<b>显著减少复习数量</b>。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title"><b>听写模式</b></div>
|
||||
<div class="desc">原有逻辑保持不变。</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title"><b>默写模式(新增)</b></div>
|
||||
<div class="desc">
|
||||
<ul>
|
||||
<li>仅显示释义,不自动发音,不显示单词长度。</li>
|
||||
<li>适合强化拼写记忆的场景。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
<b>说明:</b>
|
||||
<div>本次更新重点解决了“复习单词数量过多、效率偏低”的问题。</div>
|
||||
<div>通过引入「复习」与「默写」两种模式,使复习流程更加灵活、高效。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/10/8</div>
|
||||
<div>内容:文章支持自动播放下一篇</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/9/14</div>
|
||||
<div>内容:完善文章编辑、导入、导出等功能</div>
|
||||
</div>
|
||||
<div class="text-base mt-1">
|
||||
<div>1、文章的音频管理功能,目前已可添加音频、设置句子与音频的对应位置</div>
|
||||
<div>2、文章可导入、导出</div>
|
||||
<div>3、单词可导入、导出</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/8/10</div>
|
||||
<div>内容:2.0版本发布,全新UI,全新逻辑,新增短语、例句、近义词等功能</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/7/19</div>
|
||||
<div>内容:1.0版本发布</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.log-item {
|
||||
border-bottom: 1px solid var(--color-input-border);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -18,7 +18,7 @@ import {useBaseStore} from "@/stores/base.ts";
|
||||
import {saveAs} from "file-saver";
|
||||
import {
|
||||
APP_NAME, APP_VERSION, EMAIL,
|
||||
EXPORT_DATA_KEY, GITHUB, Host,
|
||||
EXPORT_DATA_KEY, GITHUB, Host, LIB_JS_URL,
|
||||
LOCAL_FILE_KEY,
|
||||
Origin,
|
||||
PracticeSaveArticleKey,
|
||||
@@ -41,6 +41,7 @@ import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {useUserStore} from "@/stores/user.ts";
|
||||
import {useExport} from "@/hooks/export.ts";
|
||||
import MigrateDialog from "@/components/MigrateDialog.vue";
|
||||
import Log from "@/pages/setting/Log.vue";
|
||||
|
||||
const emit = defineEmits<{
|
||||
toggleDisabledDialogEscKey: [val: boolean]
|
||||
@@ -231,7 +232,7 @@ function importJson(str: string, notice: boolean = true) {
|
||||
notice && Toast.success('导入成功!')
|
||||
} catch (err) {
|
||||
return Toast.error('导入失败!')
|
||||
}finally {
|
||||
} finally {
|
||||
importLoading = false
|
||||
}
|
||||
}
|
||||
@@ -253,7 +254,7 @@ async function importData(e) {
|
||||
reader.readAsText(file);
|
||||
} else if (file.name.endsWith(".zip")) {
|
||||
try {
|
||||
const JSZip = await loadJsLib('JSZip', `${Origin}/libs/jszip.min.js`);
|
||||
const JSZip = await loadJsLib('JSZip', LIB_JS_URL.JSZIP);
|
||||
const zip = await JSZip.loadAsync(file);
|
||||
|
||||
const dataFile = zip.file("data.json");
|
||||
@@ -397,220 +398,7 @@ function transferOk() {
|
||||
</div>
|
||||
|
||||
<!-- 日志-->
|
||||
<div v-if="tabIndex === 5">
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/12/3</div>
|
||||
<div>内容:单词、文章设置修改为弹框,更方便</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/12/3</div>
|
||||
<div>内容:录入新概念(三、四)部分音频,优化文章相关功能</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/12/2</div>
|
||||
<div>内容:完成新概念(一)音频,优化文章管理页面</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/30</div>
|
||||
<div>内容:文章里的单词可点击播放</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/29</div>
|
||||
<div>内容:修改 Slider 组件显示bug,新增 IE 浏览器检测提示</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/28</div>
|
||||
<div>内容:新增引导框、 新增<a href="https://github.com/zyronon/TypeWords/pull/175" target="_blank">词典测试模式(由大佬
|
||||
hebeihang 开发)</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/25</div>
|
||||
<div>内容:文章练习新增人名忽略功能(新概念一已全部适配),上传了新概念(一)1-18 音频</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/23</div>
|
||||
<div>内容:优化练习完成结算界面,新增分享功能</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/22</div>
|
||||
<div>内容:适配移动端</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/16</div>
|
||||
<div>内容:自测单词时,不认识单词可以直接输入,自动标识为错误单词,无需按2</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/15</div>
|
||||
<div>内容:练习单词时,底部工具栏新增“跳到下一阶段”按钮</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/14</div>
|
||||
<div>内容:新增文章练习时可跳过空格:如果在单词的最后一位上,不按空格直接输入下一个字母的话,自动跳下一个单词,
|
||||
按空格也自动跳下一个单词
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/13</div>
|
||||
<div>内容:新增文章练习时“输入时忽略符号/数字”选项</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/6</div>
|
||||
<div>内容:新增随机复习功能</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/10/30</div>
|
||||
<div>内容:集成PWA基础配置,支持用户以类App形式打开项目</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/10/26</div>
|
||||
<div>内容:进一步完善单词练习,解决复习数量太多的问题</div>
|
||||
</div>
|
||||
<div class="text-base mt-1">
|
||||
<ol>
|
||||
<li>
|
||||
<div class="title"><b>智能模式优化</b></div>
|
||||
<div class="desc">练习时新增四种练习模式:学习、自测、听写、默写。</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title"><b>学习模式</b></div>
|
||||
<div class="desc">
|
||||
<ul>
|
||||
<li>仅在练习新词时出现。</li>
|
||||
<li>采用「跟写 / 拼写」方式进行学习。</li>
|
||||
<li>每 7 个单词会 <b>强制进行听写</b>,解决原来“一次练太多,听写时已忘记”的问题。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title"><b>自测模式(新增)</b></div>
|
||||
<div class="desc">
|
||||
<ul>
|
||||
<li>仅在复习已学单词时出现。</li>
|
||||
<li>不再强制拼写,提供「我认识」与「不认识」选项。</li>
|
||||
<li>选择「我认识」后,该单词在后续听写或默写中将不再出现,<b>显著减少复习数量</b>。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title"><b>听写模式</b></div>
|
||||
<div class="desc">原有逻辑保持不变。</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title"><b>默写模式(新增)</b></div>
|
||||
<div class="desc">
|
||||
<ul>
|
||||
<li>仅显示释义,不自动发音,不显示单词长度。</li>
|
||||
<li>适合强化拼写记忆的场景。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
<b>说明:</b>
|
||||
<div>本次更新重点解决了“复习单词数量过多、效率偏低”的问题。</div>
|
||||
<div>通过引入「复习」与「默写」两种模式,使复习流程更加灵活、高效。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/10/8</div>
|
||||
<div>内容:文章支持自动播放下一篇</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/9/14</div>
|
||||
<div>内容:完善文章编辑、导入、导出等功能</div>
|
||||
</div>
|
||||
<div class="text-base mt-1">
|
||||
<div>1、文章的音频管理功能,目前已可添加音频、设置句子与音频的对应位置</div>
|
||||
<div>2、文章可导入、导出</div>
|
||||
<div>3、单词可导入、导出</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/8/10</div>
|
||||
<div>内容:2.0版本发布,全新UI,全新逻辑,新增短语、例句、近义词等功能</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/7/19</div>
|
||||
<div>内容:1.0版本发布</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Log v-if="tabIndex === 5"/>
|
||||
|
||||
<div v-if="tabIndex === 6" class="center flex-col">
|
||||
<h1>Type Words</h1>
|
||||
@@ -640,11 +428,6 @@ function transferOk() {
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.log-item {
|
||||
border-bottom: 1px solid var(--color-input-border);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.col-line {
|
||||
border-right: 2px solid gainsboro;
|
||||
}
|
||||
|
||||
@@ -278,7 +278,7 @@ function importData(e) {
|
||||
reader.onload = async function (s) {
|
||||
let data = s.target.result;
|
||||
importLoading = true
|
||||
const XLSX = await loadJsLib('XLSX', `${Origin}/libs/xlsx.full.min.js`);
|
||||
const XLSX = await loadJsLib('XLSX', LIB_JS_URL.XLSX);
|
||||
let workbook = XLSX.read(data, {type: 'binary'});
|
||||
let res: any[] = XLSX.utils.sheet_to_json(workbook.Sheets['Sheet1']);
|
||||
if (res.length) {
|
||||
@@ -357,7 +357,7 @@ function importData(e) {
|
||||
|
||||
async function exportData() {
|
||||
exportLoading = true
|
||||
const XLSX = await loadJsLib('XLSX', `${Origin}/libs/xlsx.full.min.js`);
|
||||
const XLSX = await loadJsLib('XLSX', LIB_JS_URL.XLSX);
|
||||
let list = runtimeStore.editDict.words
|
||||
let filename = runtimeStore.editDict.name
|
||||
let wb = XLSX.utils.book_new()
|
||||
|
||||
@@ -145,7 +145,7 @@ watch(dict_list, (val) => {
|
||||
<div class="w-full" v-else>
|
||||
<DictGroup
|
||||
v-for="item in groupedByCategoryAndTag"
|
||||
:select-id="store.currentStudyWordDict.id"
|
||||
:select-id="store.sdict.id"
|
||||
@selectDict="selectDict"
|
||||
quantifier="个词"
|
||||
:groupByTag="item[1]"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, provide, ref, watch } from "vue";
|
||||
import {onMounted, onUnmounted, provide, ref, watch} from "vue";
|
||||
|
||||
import Statistics from "@/pages/word/Statistics.vue";
|
||||
import { emitter, EventKey, useEvents } from "@/utils/eventBus.ts";
|
||||
@@ -47,6 +47,8 @@ let showConflictNotice = $ref(false)
|
||||
let allWrongWords = new Set()
|
||||
let showStatDialog = $ref(false)
|
||||
let loading = $ref(false)
|
||||
let timer = $ref(0)
|
||||
let isFocus = true
|
||||
let taskWords = $ref<TaskWords>({
|
||||
new: [],
|
||||
review: [],
|
||||
@@ -110,6 +112,13 @@ onMounted(() => {
|
||||
} else {
|
||||
showConflictNotice = true
|
||||
}
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
isFocus = !document.hidden
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
timer && clearInterval(timer)
|
||||
})
|
||||
|
||||
watchOnce(() => data.words.length, (newVal, oldVal) => {
|
||||
@@ -220,8 +229,17 @@ function initData(initVal: TaskWords, init: boolean = false) {
|
||||
statStore.startDate = Date.now()
|
||||
statStore.inputWordNumber = 0
|
||||
statStore.wrong = 0
|
||||
statStore.spend = 0
|
||||
isTypingWrongWord.value = false
|
||||
}
|
||||
clearInterval(timer)
|
||||
timer = setInterval(() => {
|
||||
if (isFocus) {
|
||||
statStore.spend += 1000
|
||||
savePracticeData()
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
}
|
||||
|
||||
const word = $computed<Word>(() => {
|
||||
|
||||
@@ -109,15 +109,16 @@ const progress = $computed(() => {
|
||||
<div class="name">{{ status }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<!-- <div class="num">{{ statStore.spend }}分钟</div>-->
|
||||
<div class="num">{{ Math.floor(statStore.spend / 1000 / 60) }}分钟</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">时间</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="num">{{ statStore.total }}</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">单词总数</div>
|
||||
</div>
|
||||
<!-- <div class="row">-->
|
||||
<!-- <div class="num">{{ format(statStore.inputWordNumber, '', 0) }}</div>-->
|
||||
<!-- <div class="line"></div>-->
|
||||
<!-- <div class="name">总输入数</div>-->
|
||||
<!-- </div>-->
|
||||
<div class="row">
|
||||
<div class="num">{{ format(statStore.wrong, '', 0) }}</div>
|
||||
<div class="line"></div>
|
||||
|
||||
@@ -188,7 +188,7 @@ async function onTyping(e: KeyboardEvent) {
|
||||
}
|
||||
inputLock = true
|
||||
let letter = e.key
|
||||
console.log('letter',letter)
|
||||
// console.log('letter',letter)
|
||||
//默写特殊逻辑
|
||||
if (settingStore.wordPracticeType === WordPracticeType.Dictation) {
|
||||
if (e.code === 'Space') {
|
||||
@@ -659,35 +659,35 @@ useEvents([
|
||||
|
||||
.typing-word {
|
||||
padding: 0 0.5rem 12rem;
|
||||
|
||||
|
||||
.word {
|
||||
font-size: 2rem !important;
|
||||
letter-spacing: 0.1rem;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
|
||||
.phonetic, .translate {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.label {
|
||||
width: 4rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
|
||||
.cn {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
|
||||
.en {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.pos {
|
||||
font-size: 0.9rem;
|
||||
width: 3rem;
|
||||
}
|
||||
|
||||
|
||||
// 移动端按钮组调整
|
||||
.flex.gap-4 {
|
||||
flex-direction: column;
|
||||
@@ -695,7 +695,7 @@ useEvents([
|
||||
gap: 0.5rem;
|
||||
position: relative;
|
||||
z-index: 10; // 确保按钮不被其他元素遮挡
|
||||
|
||||
|
||||
.base-button {
|
||||
width: 100%;
|
||||
min-height: 48px;
|
||||
@@ -705,14 +705,14 @@ useEvents([
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 确保短语和例句区域保持默认层级
|
||||
.phrase-section,
|
||||
.sentence {
|
||||
position: relative;
|
||||
z-index: auto;
|
||||
}
|
||||
|
||||
|
||||
// 移动端例句和短语调整
|
||||
.sentence,
|
||||
.phrase {
|
||||
@@ -721,7 +721,7 @@ useEvents([
|
||||
margin-bottom: 0.5rem;
|
||||
pointer-events: auto; // 允许点击但不调起输入法
|
||||
}
|
||||
|
||||
|
||||
// 移动端短语调整
|
||||
.flex.items-center.gap-4 {
|
||||
flex-direction: column;
|
||||
@@ -735,35 +735,35 @@ useEvents([
|
||||
@media (max-width: 480px) {
|
||||
.typing-word {
|
||||
padding: 0 0.3rem 12rem;
|
||||
|
||||
|
||||
.word {
|
||||
font-size: 1.5rem !important;
|
||||
letter-spacing: 0.05rem;
|
||||
margin: 0.3rem 0;
|
||||
}
|
||||
|
||||
|
||||
.phonetic, .translate {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
|
||||
.label {
|
||||
width: 3rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
|
||||
.cn {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
|
||||
.en {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
|
||||
.pos {
|
||||
font-size: 0.8rem;
|
||||
width: 2.5rem;
|
||||
}
|
||||
|
||||
|
||||
.sentence {
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.3;
|
||||
|
||||
@@ -72,12 +72,6 @@ export const useBaseStore = defineStore('base', {
|
||||
allIgnoreWords() {
|
||||
return this.known.words.map((v: Word) => v.word.toLowerCase()).concat(this.simpleWords.map((v: string) => v.toLowerCase()))
|
||||
},
|
||||
currentStudyWordDict(): Dict {
|
||||
if (this.word.studyIndex >= 0) {
|
||||
return this.word.bookList[this.word.studyIndex] ?? getDefaultDict()
|
||||
}
|
||||
return getDefaultDict()
|
||||
},
|
||||
sdict(): Dict {
|
||||
if (this.word.studyIndex >= 0) {
|
||||
return this.word.bookList[this.word.studyIndex] ?? getDefaultDict()
|
||||
@@ -93,9 +87,6 @@ export const useBaseStore = defineStore('base', {
|
||||
if (!this.sdict.perDayStudyNumber) return 0
|
||||
return Math.ceil((this.sdict.length - this.sdict.lastLearnIndex) / this.sdict.perDayStudyNumber)
|
||||
},
|
||||
currentBook(): Dict {
|
||||
return this.article.bookList[this.article.studyIndex] ?? {}
|
||||
},
|
||||
sbook(): Dict {
|
||||
return this.article.bookList[this.article.studyIndex] ?? {}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user