feat: add audio-setting icon

This commit is contained in:
Zyronon
2025-12-30 01:44:54 +08:00
parent ca2596b323
commit 8d778a8b44
13 changed files with 296 additions and 328 deletions

View File

@@ -109,7 +109,7 @@ html.dark {
--color-label-bg: rgb(10, 10, 10);
--color-card-bg: rgb(30, 31, 34);
--color-card-bg: var(--color-second);
--bg-card-primary: rgb(30, 31, 34);
--bg-card-secend: rgb(43, 45, 48);

View File

@@ -1,8 +1,8 @@
<template>
<div class="flex gap-5 w-full h-4">
<div class="flex gap-5 w-full h-3">
<template v-for="i of props.stages">
<template v-if="i?.children?.length && i.active">
<div class="flex gap-1 h-4" :style="{ width: i.ratio + '%' }">
<div class="flex gap-1" :style="{ width: i.ratio + '%' }">
<template v-for="j of i.children">
<Tooltip :title="j.name">
<Progress

View File

@@ -1,16 +1,5 @@
<script setup lang="ts">
import {
computed,
nextTick,
onBeforeUnmount,
onMounted,
provide,
ref,
useAttrs,
useSlots,
VNode,
watch,
} from 'vue'
import { computed, nextTick, onBeforeUnmount, onMounted, provide, ref, useSlots, VNode, watch } from 'vue'
import { useWindowClick } from '@/hooks/event.ts'
interface Option {
@@ -26,8 +15,7 @@ const props = defineProps<{
options?: Option[]
}>()
const emit = defineEmits(['update:modelValue'])
const attrs = useAttrs()
const emit = defineEmits(['update:modelValue', 'toggle'])
const isOpen = ref(false)
const isReverse = ref(false)
@@ -69,6 +57,7 @@ const toggleDropdown = async () => {
if (props.disabled) return
isOpen.value = !isOpen.value
emit('toggle', isOpen.value)
if (isOpen.value) {
await nextTick()
@@ -81,6 +70,7 @@ const selectOption = (value: any, label: string) => {
selectedOption.value = { value, label }
emit('update:modelValue', value)
isOpen.value = false
emit('toggle', isOpen.value)
}
let selectValue = ref(props.modelValue)
@@ -97,6 +87,7 @@ useWindowClick((e: PointerEvent) => {
!dropdownRef.value.contains(e.target as Node)
) {
isOpen.value = false
emit('toggle', isOpen.value)
}
})
@@ -158,26 +149,15 @@ onBeforeUnmount(() => {
<template>
<div class="select" ref="selectRef">
<div
class="select__wrapper"
:class="{ disabled: disabled, active: isOpen }"
@click="toggleDropdown"
>
<div class="select__wrapper" :class="{ disabled: disabled, active: isOpen }" @click="toggleDropdown">
<div class="select__label" :class="{ 'is-placeholder': !selectedOption }">
{{ displayValue }}
</div>
<IconFluentChevronLeft20Filled
class="select__arrow"
:class="{ 'is-reverse': isOpen }"
width="16"
/>
<IconFluentChevronLeft20Filled class="select__arrow" :class="{ 'is-reverse': isOpen }" width="16" />
</div>
<teleport to="body">
<transition
:name="isReverse ? 'zoom-in-bottom' : 'zoom-in-top'"
:key="isReverse ? 'bottom' : 'top'"
>
<transition :name="isReverse ? 'zoom-in-bottom' : 'zoom-in-top'" :key="isReverse ? 'bottom' : 'top'">
<div class="select__dropdown" v-if="isOpen" ref="dropdownRef" :style="dropdownStyle">
<slot></slot>
</div>
@@ -237,9 +217,8 @@ onBeforeUnmount(() => {
.select__dropdown {
max-height: 200px;
overflow-y: auto;
background-color: var(--color-third);
border-radius: 0.25rem;
@apply shadow-lg;
background-color: var(--color-card-bg);
@apply shadow-xl rounded-lg border border-gray-300 border-solid;
}
/* 往下展开的动画 */

View File

@@ -35,37 +35,19 @@ watch(() => props.modelValue, (n) => {
</template>
<style lang="scss">
.mini-row-title {
min-height: 2rem;
text-align: center;
font-size: 1rem;
font-weight: bold;
@apply text-center text-base font-bold mb-2;
color: var(--color-font-1);
}
.mini-row {
min-height: 2rem;
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--space);
@apply min-h-10 flex justify-between items-center gap-space text-base text-font-1 word-break-keep-all;
color: var(--color-font-1);
word-break: keep-all;
}
.mini-modal {
position: absolute;
z-index: 9;
width: 12rem;
background: var(--color-second);
border-radius: .5rem;
box-shadow: 0 0 8px 2px var(--color-item-border);
padding: .6rem var(--space);
//top: 2.4rem;
left: 50%;
transform: translate3d(-50%, 0, 0);
//margin-top: 10rem;
background: var(--color-card-bg);
padding: var(--space) 1rem;
@apply z-9 absolute left-1/2 transform -translate-x-1/2 shadow-lg rounded-xl w-50;
}
</style>

View File

@@ -13,6 +13,7 @@ export function useSound(audioSrcList?: string[], audioFileLength?: number) {
if (audioSrcList) setAudio(audioSrcList, audioFileLength)
})
//这里同一个音频弄好几份是为了快速打字是,可同时发音
function setAudio(audioSrcList2: string[], audioFileLength2?: number) {
if (audioFileLength2) audioLength = audioFileLength2
audioList = []
@@ -23,6 +24,7 @@ export function useSound(audioSrcList?: string[], audioFileLength?: number) {
}
function play(volume: number = 100) {
console.log('play',audioList)
index++
if (audioList.length > 1 && audioList.length !== audioLength) {
audioList[index % audioList.length].volume = volume / 100
@@ -46,7 +48,7 @@ export function usePlayKeyboardAudio() {
settingStore.keyboardSoundFile = '机械键盘2'
}
let urlList = getAudioFileUrl(settingStore.keyboardSoundFile)
setAudio(urlList, urlList.length === 1 ? 3 : 1)
setAudio(urlList, urlList.length === 1 ? 4 : 1)
})
function playAudio() {
@@ -150,4 +152,4 @@ export function getAudioFileUrl(name: string) {
} else {
return [`/sound/key-sounds/${name}.mp3`]
}
}
}

View File

@@ -1,274 +1,136 @@
<script setup lang="ts">
let logList = [
{
date: '2025/12/30',
content: '单词练习界面,底部工具栏新增音频设置按钮',
},
{
date: '2025/12/27',
content: '优化进度条展示,现可展示当前阶段、所有阶段',
},
{
date: '2025/12/23',
content: '新增复习、自测、默写、听写模式',
},
{
date: '2025/12/20',
content: '新增资源分享页面',
},
{
date: '2025/12/17',
content: '新增帮助页面',
},
{
date: '2025/12/16',
content: '修复弹框内边距太小;单词、文章、通用设置在设置页面、练习界面均可进行设置',
},
{
date: '2025/12/15',
content: '修复在黑暗模式下,翻译颜色不正确;支持中文符号输入',
},
{
date: '2025/12/11',
content: '修复音标显示错误问题,优化反馈页面',
},
{
date: '2025/12/10',
content: '新增选项:复习比(单词练习时,复习词与新词的比例)',
},
{
date: '2025/12/5',
content: '解决练习界面无法复制、全选的问题',
},
{
date: '2025/12/3',
content: '单词、文章设置修改为弹框,更方便',
},
{
date: '2025/12/3',
content: '录入新概念(三、四)部分音频,优化文章相关功能',
},
{
date: '2025/12/2',
content: '完成新概念(一)音频,优化文章管理页面',
},
{
date: '2025/11/30',
content: '文章里的单词可点击播放',
},
{
date: '2025/11/29',
content: '修改 Slider 组件显示bug新增 IE 浏览器检测提示',
},
{
date: '2025/11/28',
content: '新增引导框、 新增词典测试模式由大佬hebeihang 开发)',
},
{
date: '2025/11/25',
content: '文章练习新增人名忽略功能新概念一已全部适配上传了新概念1-18 音频',
},
{
date: '2025/11/23',
content: '优化练习完成结算界面,新增分享功能',
},
{
date: '2025/11/22',
content: '适配移动端',
},
{
date: '2025/11/16',
content: '自测单词时不认识单词可以直接输入自动标识为错误单词无需按2',
},
{
date: '2025/11/15',
content: '练习单词时,底部工具栏新增“跳到下一阶段”按钮',
},
{
date: '2025/11/14',
content:
'新增文章练习时可跳过空格:如果在单词的最后一位上,不按空格直接输入下一个字母的话,自动跳下一个单词,按空格也自动跳下一个单词',
},
{
date: '2025/11/13',
content: '新增文章练习时“输入时忽略符号/数字”选项',
},
{
date: '2025/11/6',
content: '新增随机复习功能',
},
{
date: '2025/10/30',
content: '集成PWA基础配置支持用户以类App形式打开项目',
},
{
date: '2025/10/26',
content: '进一步完善单词练习,解决复习数量太多的问题',
},
{
date: '2025/10/8',
content: '文章支持自动播放下一篇',
},
{
date: '2025/9/14',
content: '完善文章编辑、导入、导出等功能',
},
{
date: '2025/8/10',
content: '2.0版本发布全新UI全新逻辑新增短语、例句、近义词等功能',
},
{
date: '2025/7/19',
content: '1.0版本发布',
},
]
</script>
<template>
<div>
<div class="log-item">
<div class="log-item" v-for="item in logList" :key="item.date">
<div class="mb-2">
<div>
<div>日期2025/12/20</div>
<div>内容新增资源分享页面</div>
</div>
</div>
</div>
<div class="log-item">
<div class="mb-2">
<div>
<div>日期2025/12/17</div>
<div>内容新增帮助页面</div>
</div>
</div>
</div>
<div class="log-item">
<div class="mb-2">
<div>
<div>日期2025/12/16</div>
<div>内容修复弹框内边距太小单词文章通用设置在设置页面练习界面均可进行设置</div>
</div>
</div>
</div>
<div class="log-item">
<div class="mb-2">
<div>
<div>日期2025/12/15</div>
<div>内容修复在黑暗模式下翻译颜色不正确支持中文符号输入</div>
</div>
</div>
</div>
<div class="log-item">
<div class="mb-2">
<div>
<div>日期2025/12/11</div>
<div>内容修复音标显示错误问题优化反馈页面</div>
</div>
</div>
</div>
<div class="log-item">
<div class="mb-2">
<div>
<div>日期2025/12/10</div>
<div>内容新增选项复习比(单词练习时复习词与新词的比例)</div>
</div>
</div>
</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>日期{{ item.date }}</div>
<div>内容{{ item.content }}</div>
</div>
</div>
</div>
@@ -276,10 +138,8 @@
</template>
<style scoped lang="scss">
.log-item {
border-bottom: 1px solid var(--color-input-border);
margin-bottom: 1rem;
}
</style>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { onMounted, onUnmounted, provide, ref, watch } from 'vue'
import Statistics from '@/pages/word/Statistics.vue'
import Statistics from '@/pages/word/components/Statistics.vue'
import { emitter, EventKey, useEvents } from '@/utils/eventBus.ts'
import { useSettingStore } from '@/stores/setting.ts'
import { useRuntimeStore } from '@/stores/runtime.ts'

View File

@@ -18,6 +18,8 @@ import Progress from '@/components/base/Progress.vue'
import SettingDialog from '@/components/setting/SettingDialog.vue'
import BaseButton from '@/components/BaseButton.vue'
import { useBaseStore } from '@/stores/base.ts'
import VolumeSettingMiniDialog from '@/pages/word/components/VolumeSettingMiniDialog.vue'
import StageProgress from '@/components/StageProgress.vue'
const statStore = usePracticeStore()
const store = useBaseStore()
@@ -238,6 +240,9 @@ const stages = $computed(() => {
</div>
<div class="flex gap-2 justify-center items-center" id="toolbar-icons">
<SettingDialog type="word" />
<VolumeSettingMiniDialog/>
<BaseIcon
v-if="settingStore.wordPracticeMode !== WordPracticeMode.Free"
@click="emit('skipStep')"

View File

@@ -0,0 +1,128 @@
<script setup lang="ts">
import BaseIcon from '@/components/BaseIcon.vue'
import Switch from '@/components/base/Switch.vue'
import { Option, Select } from '@/components/base/select'
import MiniDialog from '@/components/dialog/MiniDialog.vue'
import VolumeIcon from '@/components/icon/VolumeIcon.vue'
import { SoundFileOptions } from '@/config/env.ts'
import { useWindowClick } from '@/hooks/event.ts'
import { getAudioFileUrl, usePlayAudio } from '@/hooks/sound.ts'
import { useSettingStore } from '@/stores/setting.ts'
import { emitter, EventKey } from '@/utils/eventBus.ts'
const settingStore = useSettingStore()
let timer = 0
//停止切换事件因为hover到select时会跳出mini-dialog
let selectIsOpen = false
let show = $ref(false)
useWindowClick(() => {
if (selectIsOpen) {
selectIsOpen = false
} else {
show = false
}
})
function toggle(val: boolean) {
if (selectIsOpen) return
clearTimeout(timer)
if (val) {
emitter.emit(EventKey.closeOther)
show = val
} else {
timer = setTimeout(() => {
show = val
}, 100)
}
}
function selectToggle(e: boolean) {
//这里要延时设置因为关闭的时候如果太早设置了false了useWindowClick的事件就会把弹框关闭
setTimeout(() => (selectIsOpen = e))
}
function eventCheck(e) {
const isSelfOrChild = e.currentTarget.contains(e.target)
if (isSelfOrChild) {
//如果下拉框打开的情况就不拦截
if (selectIsOpen) return
e.stopPropagation()
}
}
</script>
<template>
<div class="setting" @click="eventCheck">
<BaseIcon @mouseenter="toggle(true)" @mouseleave="toggle(false)">
<IconClarityVolumeUpLine />
</BaseIcon>
<MiniDialog width="18rem" @mouseenter="toggle(true)" @mouseleave="toggle(false)" v-model="show">
<div class="mini-row-title">音效设置</div>
<div class="mini-row">
<label class="item-title">单词自动发音</label>
<div class="wrapper">
<Switch v-model="settingStore.wordSound" inline-prompt active-text="开" inactive-text="关" />
</div>
</div>
<div class="mini-row">
<label class="item-title">单词发音口音</label>
<div class="wrapper">
<Select v-model="settingStore.soundType" @toggle="selectToggle" placeholder="请选择" size="small">
<Option label="美音" value="us" />
<Option label="英音" value="uk" />
</Select>
</div>
</div>
<div class="mini-row">
<label class="item-title">按键音</label>
<div class="wrapper">
<Switch v-model="settingStore.keyboardSound" inline-prompt active-text="开" inactive-text="关" />
</div>
</div>
<div class="mini-row">
<label class="item-title">按键音效</label>
<div class="wrapper">
<Select v-model="settingStore.keyboardSoundFile" @toggle="selectToggle" placeholder="请选择" size="small">
<Option v-for="item in SoundFileOptions" :key="item.value" :label="item.label" :value="item.value">
<div class="el-option-row">
<span>{{ item.label }}</span>
<VolumeIcon :time="100" @click="usePlayAudio(getAudioFileUrl(item.value)[0])" />
</div>
</Option>
</Select>
</div>
</div>
<div class="mini-row">
<label class="item-title">效果音</label>
<div class="wrapper">
<Switch v-model="settingStore.effectSound" inline-prompt active-text="开" inactive-text="关" />
</div>
</div>
</MiniDialog>
</div>
</template>
<style scoped lang="scss">
.wrapper {
width: 50%;
position: relative;
text-align: right;
}
.setting {
position: relative;
}
.el-option-row {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.icon-wrapper {
transform: translateX(10rem);
}
}
</style>