This commit is contained in:
zyronon
2023-10-31 01:34:44 +08:00
parent 32be9661bc
commit 10a4d70061
9 changed files with 177 additions and 284 deletions

1
components.d.ts vendored
View File

@@ -43,6 +43,7 @@ declare module 'vue' {
IconWrapper: typeof import('./src/components/IconWrapper.vue')['default']
Input: typeof import('./src/components/Input.vue')['default']
List: typeof import('./src/components/List.vue')['default']
ListItem: typeof import('./src/components/ListItem.vue')['default']
MiniModal: typeof import('./src/components/MiniModal.vue')['default']
Modal: typeof import('./src/components/Modal/Modal.vue')['default']
Options: typeof import('./src/components/Practice/Options.vue')['default']

View File

@@ -5,6 +5,7 @@ import Input from "@/components/Input.vue";
import {$computed, $ref} from "vue/macros";
import {cloneDeep, throttle} from "lodash-es";
import {Article} from "@/types.ts";
import ListItem from "@/components/ListItem.vue";
interface IProps {
list: Article[]
@@ -19,9 +20,7 @@ const emit = defineEmits<{
'update:list': [list: Article[]],
}>()
let dragItem: Article = $ref({id: ''} as any)
let searchKey = $ref('')
let draggable = $ref(false)
let localList = $computed({
get() {
@@ -41,47 +40,6 @@ let localList = $computed({
}
})
function dragstart(item: Article) {
dragItem = item;
}
const dragenter = throttle((e, item: Article) => {
// console.log('dragenter', 'item.id', item.id, 'dragItem.id', dragItem.id)
e.preventDefault();
// 避免源对象触发自身的dragenter事件
if (dragItem.id && dragItem.id !== item.id) {
let rIndex = props.list.findIndex(v => v.id === dragItem.id)
let rIndex2 = props.list.findIndex(v => v.id === item.id)
// console.log('dragenter', 'item-Index', rIndex2, 'dragItem.index', rIndex)
//这里不能直接用localList splice。不知道为什么会导致有筛选的情况下多动无法变换位置
let temp = cloneDeep(props.list)
temp.splice(rIndex, 1);
temp.splice(rIndex2, 0, cloneDeep(dragItem));
localList = temp;
}
}, 300)
function dragover(e) {
// console.log('dragover')
e.preventDefault();
}
function dragend() {
// console.log('dragend')
draggable = false
dragItem = {id: ''} as Article
}
function delItem(item: Article) {
if (item.id === props.selectItem.id) {
emit('delSelectItem')
}
let rIndex = props.list.findIndex(v => v.id === item.id)
if (rIndex > -1) {
localList.splice(rIndex, 1)
}
}
let el: HTMLDivElement = $ref()
function scrollBottom() {
@@ -103,64 +61,16 @@ defineExpose({scrollBottom})
<div class="search">
<Input v-model="searchKey"/>
</div>
<transition-group name="drag" class="list" tag="div">
<div class="item"
:class="[
(selectItem.id === item.id) && 'active',
draggable && 'draggable',
(dragItem.id === item.id) && 'active'
]"
@click="emit('selectItem',item)"
v-for="(item,index) in localList"
:key="item.id"
:draggable="draggable"
@dragstart="dragstart(item)"
@dragenter="dragenter($event, item)"
@dragover="dragover($event)"
@dragend="dragend()"
>
<div class="left">
<div class="name"> {{ `${index + 1}. ${item.title}` }}</div>
<div class="translate-name"> {{ ` ${item.titleTranslate}` }}</div>
</div>
<div class="right">
<BaseIcon
@click="delItem(item)"
title="删除" icon="ph:star"/>
<BaseIcon
@click="delItem(item)"
title="删除" icon="fluent:delete-24-regular"/>
<div
@mousedown="draggable = true"
@mouseup="draggable = false"
>
<BaseIcon icon="carbon:move"/>
</div>
</div>
</div>
</transition-group>
<ListItem @click="emit('selectItem',item)"
v-for="(item,index) in localList"
:key="item.id">
<div class="name"> {{ `${index + 1}. ${item.title}` }}</div>
<div class="translate-name"> {{ ` ${item.titleTranslate}` }}</div>
</ListItem>
</div>
</template>
<style scoped lang="scss">
.drag-move, /* 对移动中的元素应用的过渡 */
.drag-enter-active,
.drag-leave-active {
transition: all 0.5s ease;
}
.drag-enter-from,
.drag-leave-to {
opacity: 0;
transform: translateX(50rem);
}
/* 确保将离开的元素从布局流中删除
以便能够正确地计算移动的动画。 */
.drag-leave-active {
position: absolute;
}
.list-wrapper {
transition: all .3s;
flex: 1;
@@ -173,50 +83,7 @@ defineExpose({scrollBottom})
}
.list {
.item {
width: 100%;
box-sizing: border-box;
background: #e1e1e1;
border-radius: 8rem;
margin-bottom: 10rem;
padding: 10rem;
display: flex;
justify-content: space-between;
transition: all .3s;
color: black;
.left {
.name {
font-size: 18rem;
}
.translate-name {
font-size: 16rem;
}
}
.right {
display: flex;
flex-direction: column;
transition: all .3s;
opacity: 0;
}
&:hover {
.right {
opacity: 1;
}
}
&.active {
background: var(--color-item-active);
color: white;
}
&.draggable {
cursor: move;
}
}
}
}
</style>

View File

@@ -0,0 +1,80 @@
<script setup lang="ts">
import BaseIcon from "@/components/BaseIcon.vue";
import VolumeIcon from "@/components/VolumeIcon.vue";
defineProps<{
showVolume?: boolean,
active?: boolean
}>()
defineEmits<{
toggleDel: [val: any],
toggleCollect: [val: any],
play: [],
}>()
</script>
<template>
<div class="list-item"
:class="active"
>
<div class="left">
<slot></slot>
</div>
<div class="right">
<BaseIcon
@click="$emit('toggleDel')"
title="收藏" icon="ph:star"/>
<BaseIcon
@click="$emit('toggleCollect')"
title="删除" icon="fluent:delete-24-regular"/>
</div>
</div>
</template>
<style scoped lang="scss">
.list-item {
width: 100%;
box-sizing: border-box;
background: white;
color: black;
font-size: 18rem;
border-radius: 8rem;
margin-bottom: 10rem;
display: flex;
justify-content: space-between;
transition: all .3s;
padding: 10rem;
gap: 20rem;
.left {
display: flex;
gap: 10rem;
flex-direction: column;
word-break: break-word;
}
.right {
display: flex;
flex-direction: column;
gap: 5rem;
transition: all .3s;
opacity: 0;
}
&:hover {
background: rgb(226, 226, 226);
.right {
opacity: 1;
}
}
&.active {
background: var(--color-item-active);
color: white;
}
}
</style>

View File

@@ -86,11 +86,11 @@ onUnmounted(() => {
margin-bottom: 10rem;
transition: all .3s;
position: relative;
margin-top: 20rem;
margin-top: 15rem;
&.hide {
margin-bottom: -90rem;
margin-top: 65rem;
margin-top: 50rem;
.progress {
bottom: calc(100% + 20rem);

View File

@@ -147,33 +147,33 @@ defineExpose({del, showWord, hideWord})
<template>
<div class="typing-word">
<div class="translate"
:style="{
<div class="translate"
:style="{
fontSize: settingStore.fontSize.wordTranslateFontSize +'rem',
opacity: settingStore.translate ? 1 : 0
}"
>
<div v-for="i in word.trans">{{ i }}</div>
</div>
<div class="word-wrapper">
<div class="word"
:class="wrong && 'is-wrong'"
:style="{fontSize: settingStore.fontSize.wordForeignFontSize +'rem'}"
>
<span class="input" v-if="input">{{ input }}</span>
<span class="wrong" v-if="wrong">{{ wrong }}</span>
<template v-if="settingStore.dictation">
<div v-for="i in word.trans">{{ i }}</div>
</div>
<div class="word-wrapper">
<div class="word"
:class="wrong && 'is-wrong'"
:style="{fontSize: settingStore.fontSize.wordForeignFontSize +'rem'}"
>
<span class="input" v-if="input">{{ input }}</span>
<span class="wrong" v-if="wrong">{{ wrong }}</span>
<template v-if="settingStore.dictation">
<span class="letter" v-if="!showFullWord"
@mouseenter="settingStore.allowWordTip && (showFullWord = true)">{{
displayWord.split('').map(() => '_').join('')
}}</span>
<span class="letter" v-else @mouseleave="showFullWord = false">{{ displayWord }}</span>
</template>
<span class="letter" v-else>{{ displayWord }}</span>
<span class="letter" v-else @mouseleave="showFullWord = false">{{ displayWord }}</span>
</template>
<span class="letter" v-else>{{ displayWord }}</span>
</div>
<VolumeIcon ref="volumeIconRef" :simple="true" :cb="()=>playWordAudio(word.name)"/>
</div>
<VolumeIcon ref="volumeIconRef" :simple="true" :cb="()=>playWordAudio(word.name)"/>
</div>
<div class="phonetic">{{ settingStore.wordSoundType === 'us' ? word.usphone : word.ukphone }}</div>
<div class="phonetic">{{ settingStore.wordSoundType === 'us' ? word.usphone : word.ukphone }}</div>
</div>
</template>
@@ -182,9 +182,11 @@ defineExpose({del, showWord, hideWord})
.typing-word {
width: 100%;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.phonetic, .translate {
font-size: 20rem;
@@ -192,6 +194,12 @@ defineExpose({del, showWord, hideWord})
transition: all .3s;
}
.translate{
position: absolute;
transform: translateY(-50%);
margin-bottom: 90rem;
}
.word-wrapper {
display: flex;
align-items: center;

View File

@@ -104,6 +104,7 @@ function next(isTyping: boolean = true) {
emitter.emit(EventKey.openStatModal, stat)
}
} else {
2
data.index++
isTyping && practiceStore.inputWordNumber++
console.log('这个词完了')
@@ -205,11 +206,14 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
@wrong="wordWrong"
@next="next"
/>
<Options
@remove="remove"
@skip="skip"
@collect="collect"
/>
<div class="options-wrapper">
<Options
@remove="remove"
@skip="skip"
@collect="collect"
/>
</div>
<Teleport to="body">
<div class="word-panel-wrapper">
<Panel>
@@ -302,6 +306,11 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
}
}
.options-wrapper{
position: absolute;
//bottom: 0;
margin-top: 120rem;
}
}
$article-width: 50vw;

View File

@@ -1,12 +1,12 @@
<script setup lang="ts">
import VolumeIcon from "@/components/VolumeIcon.vue";
import {Icon} from "@iconify/vue";
import {usePlayWordAudio} from "@/hooks/sound.ts";
import {Word} from "@/types.ts";
import ListItem from "@/components/ListItem.vue";
import VolumeIcon from "@/components/VolumeIcon.vue";
const playWordAudio = usePlayWordAudio()
const props = defineProps<{
defineProps<{
word: Word,
active?: boolean
}>()
@@ -17,84 +17,53 @@ const emit = defineEmits<{
</script>
<template>
<div class="word-item"
:class="{active}">
<div class="left">
<div class="title">
<span class="word">{{ word.name }}</span>
<span class="phonetic">{{ word.usphone }}</span>
</div>
<div class="translate">{{ word.trans.join('') }}</div>
<ListItem
class="item"
:show-volume="true"
@play="playWordAudio(word.name)"
:active="active">
<div class="title">
<span class="word">{{ word.name }}</span>
<span class="phonetic">{{ word.usphone }}</span>
<VolumeIcon class="volume" @click="playWordAudio(word.name)"></VolumeIcon>
</div>
<div class="right">
<VolumeIcon @click="playWordAudio(word.name)"></VolumeIcon>
<div class="del"
@click.stop="emit('del')"
>
<Icon
icon="fluent:delete-28-regular"
width="20"
color="#929596"/>
</div>
</div>
</div>
<div class="translate" v-if="word.trans.length">{{ word.trans.join('') }}</div>
</ListItem>
</template>
<style scoped lang="scss">
@import "@/assets/css/variable.scss";
.word-item {
background: var(--color-header-bg);
border-radius: 6rem;
padding: 12rem;
display: flex;
justify-content: space-between;
transition: all .3s;
color: var(--color-font-1);
&.active {
background: $second;
color: white;
.phonetic {
color: white!important;
}
.item {
.volume {
opacity: 0;
}
&:hover {
//background: $dark-main-bg;
//background: $item-hover;
background: rgb(226, 226, 226);
}
.left {
.title {
display: flex;
align-items: center;
gap: 10rem;
.word {
font-size: 24rem;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
display: flex;
}
.phonetic {
font-size: 14rem;
color: gray;
}
}
}
.right {
display: flex;
align-items: center;
flex-direction: column;
gap: 5rem;
.del {
cursor: pointer;
.volume {
opacity: 1;
}
}
}
.title {
display: flex;
align-items: center;
gap: 8rem;
.word {
font-size: 18rem;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
display: flex;
}
.phonetic {
font-size: 14rem;
color: gray;
}
}
.translate {
font-size: 16rem;
}
</style>

View File

@@ -71,46 +71,5 @@ watch(() => props.list, () => {
padding: 0 $space;
overflow: auto;
.item {
background: var(--color-header-bg);
border-radius: 6rem;
padding: 12rem;
display: flex;
justify-content: space-between;
transition: all .3s;
color: var(--color-font-1);
&.active {
background: $second;
color: white;
}
&:hover {
//background: $dark-main-bg;
//background: $item-hover;
background: rgb(226, 226, 226);
}
.left {
.letter {
margin-bottom: 10rem;
font-size: 24rem;
line-height: 1;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
display: flex;
}
.info {
display: flex;
gap: 20rem
}
}
.right {
display: flex;
flex-direction: column;
justify-content: space-between;
}
}
}
</style>

View File

@@ -26,8 +26,8 @@ export const useBaseStore = defineStore('base', {
...cloneDeep(DefaultDict),
words: [
{
"name": "cancel",
"trans": [],
"name": "cancelcancelcancelcancelcancelcancelcancelcancel",
"trans": ['取消取消取消取消取消取消取消取消取消取消取消取消取消取消取消取消'],
"usphone": "",
"ukphone": ""
},
@@ -194,10 +194,10 @@ export const useBaseStore = defineStore('base', {
}
],
current: {
// dictType: DictType.word,
// index: 1,
dictType: DictType.article,
index: 0,
dictType: DictType.word,
index: 1,
// dictType: DictType.article,
// index: 0,
practiceType: DictType.word,
},
simpleWords: [