This commit is contained in:
zyronon
2023-09-25 18:43:22 +08:00
parent 3d72b9de08
commit 5c1994f0db
12 changed files with 719 additions and 439 deletions

View File

@@ -18,7 +18,9 @@ import {useDisableEventListener} from "@/hooks/event.ts";
import {MessageBox} from "@/utils/MessageBox.tsx";
import BaseIcon from "@/components/BaseIcon.vue";
import {useBaseStore} from "@/stores/base.ts";
import {$ref} from "vue/macros";
import {$computed, $ref} from "vue/macros";
import Input from "@/components/Input.vue";
import List from "@/components/List.vue";
interface IProps {
selectIndex?: number
@@ -31,7 +33,9 @@ const props = withDefaults(defineProps<IProps>(), {
const base = useBaseStore()
let article = $ref<Article>(cloneDeep(DefaultArticle))
let selectIndex = $ref<number>(props.selectIndex)
let selectItem = $ref<number>(props.selectIndex)
let networkTranslateEngine = $ref('baidu')
let searchKey = $ref('')
let progress = $ref(0)
const TranslateEngineOptions = [
{value: 'baidu', label: '百度'},
@@ -264,26 +268,43 @@ watch(() => article.useTranslateType, () => {
}
})
const list = $computed(() => {
if (searchKey) {
return base.currentEditDict.articles.filter((item: Article) => {
//把搜索内容,分词之后,判断是否有这个词,比单纯遍历包含体验更好
return searchKey.toLowerCase().split(' ').filter(v => v).some(value => {
return item.title.toLowerCase().includes(value) || item.titleTranslate.toLowerCase().includes(value)
})
})
} else {
return base.currentEditDict.articles
}
})
function selectArticle(index: number) {
article = cloneDeep(base.currentEditDict.articles[index])
selectIndex = index
}
function delArticle(index: number) {
if (index < selectIndex) {
selectIndex--
} else if (index === selectIndex) {
if (selectIndex === base.currentEditDict.articles.length - 1) {
function delArticle(item: Article) {
let rIndex = base.currentEditDict.articles.findIndex((v: Article) => v.id === item.id)
if (rIndex > -1) {
if (index < selectIndex) {
selectIndex--
} else if (index === selectIndex) {
if (selectIndex === base.currentEditDict.articles.length - 1) {
selectIndex--
}
}
base.currentEditDict.articles.splice(index, 1)
if (selectIndex < 0) {
article = cloneDeep(DefaultArticle)
} else {
article = cloneDeep(base.currentEditDict.articles[selectIndex])
}
}
base.currentEditDict.articles.splice(index, 1)
if (selectIndex < 0) {
article = cloneDeep(DefaultArticle)
} else {
article = cloneDeep(base.currentEditDict.articles[selectIndex])
}
}
</script>
<template>
@@ -293,27 +314,19 @@ function delArticle(index: number) {
<div class="dict-name">{{ base.currentEditDict.name }}</div>
<BaseIcon title="选择其他词典/文章" icon="carbon:change-catalog"/>
</header>
<div class="article-list">
<div class="item"
:class="[
(selectIndex === index) && 'active'
]"
@click="selectArticle(index)"
v-for="(item,index) in base.currentEditDict.articles">
<div class="left">
<div class="name"> {{ `${index + 1}. ${item.title}` }}</div>
<div class="translate-name"> {{ ` ${item.titleTranslate}` }}</div>
</div>
<div class="right">
<BaseIcon
@click="delArticle(index)"
title="删除" icon="fluent:delete-24-regular"/>
<BaseIcon
@click="delArticle(index)"
title="删除" icon="carbon:move"/>
</div>
</div>
</div>
<List
v-model:list="list"
v-model:searchKey="searchKey"
:select-index="selectIndex"
:row-key="(item:Article) => item.title"
@del-article="delArticle"
@select-article="selectArticle"
>
<template v-slot="{item,index}">
<div class="name"> {{ `${index + 1}. ${item.title}` }}</div>
<div class="translate-name"> {{ ` ${item.titleTranslate}` }}</div>
</template>
</List>
<div class="footer">
<BaseButton>导入</BaseButton>
<BaseButton>导出</BaseButton>
@@ -471,6 +484,7 @@ function delArticle(index: number) {
display: flex;
justify-content: space-between;
align-items: center;
//opacity: 0;
.dict-name {
font-size: 30rem;
@@ -478,46 +492,12 @@ function delArticle(index: number) {
}
}
.article-list {
flex: 1;
width: 300rem;
overflow: auto;
.name {
font-size: 18rem;
}
.item {
background: #e1e1e1;
border-radius: 8rem;
margin-bottom: 10rem;
padding: 10rem;
display: flex;
justify-content: space-between;
transition: all .3s;
.right {
display: flex;
flex-direction: column;
transition: all .3s;
opacity: 0;
}
&:hover {
.right {
opacity: 1;
}
}
&.active {
background: var(--color-item-active);
color: white;
}
.name {
font-size: 18rem;
}
.translate-name {
font-size: 16rem;
}
}
.translate-name {
font-size: 16rem;
}
.footer {
@@ -534,6 +514,7 @@ function delArticle(index: number) {
display: flex;
gap: $space;
padding: $space;
//opacity: 0;
}
.row {

View File

@@ -5,7 +5,7 @@ import IconWrapper from "@/components/IconWrapper.vue";
import {Icon} from "@iconify/vue";
defineProps<{
title: string,
title?: string,
icon: string,
}>()

22
src/components/Close.vue Normal file
View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
import {Icon} from "@iconify/vue";
defineEmits(['click'])
</script>
<template>
<div class="close"
@click="$emit('click')"
>
<Icon icon="ic:round-close"
width="20"
/>
</div>
</template>
<style scoped lang="scss">
.close {
cursor: pointer;
}
</style>

77
src/components/Input.vue Normal file
View File

@@ -0,0 +1,77 @@
<script setup lang="ts">
import {$ref} from "vue/macros";
import {Icon} from "@iconify/vue";
import Close from "@/components/Close.vue";
import {useWindowClick} from "@/hooks/event.ts";
defineProps<{
modelValue: string
}>()
defineEmits(['update:modelValue'])
let focus = $ref(false)
let inputEl = $ref<HTMLDivElement>()
useWindowClick((e: PointerEvent) => {
focus = inputEl.contains(e.target as any);
})
</script>
<template>
<div class="base-input"
:class="{focus}"
ref="inputEl"
>
<Icon icon="fluent:search-24-regular"
width="20"/>
<input type="text"
:value="modelValue"
@input="e=>$emit('update:modelValue',e.target.value)"
>
<Close @click="$emit('update:modelValue','')"/>
</div>
</template>
<style scoped lang="scss">
@import "@/assets/css/style.scss";
.base-input {
border: 1px solid var(--color-main-bg);
border-radius: 4rem;
padding: 3rem 5rem;
transition: all .3s;
display: flex;
align-items: center;
transition: all .3s;
:deep(svg) {
transition: all .3s;
color: var(--color-main-bg);
}
&.focus {
border: 1px solid var(--color-main-active);
:deep(svg) {
color: gray;
}
}
input {
font-family: $font-family;
font-size: 18rem;
outline: none;
min-height: 20rem;
flex: 1;
box-sizing: border-box;
outline: none;
border: none;
&[readonly] {
cursor: not-allowed;
opacity: .7;
}
}
}
</style>

171
src/components/List.vue Normal file
View File

@@ -0,0 +1,171 @@
<script setup lang="ts" generic="T">
import BaseIcon from "@/components/BaseIcon.vue";
import Input from "@/components/Input.vue";
import {$ref} from "vue/macros";
import {cloneDeep, throttle} from "lodash-es";
let dragIndex = $ref(-1)
interface IProps {
list: T[]
searchKey: string,
selectIndex: number,
rowKey: (item: T) => string
}
const props = defineProps<IProps>()
const emit = defineEmits<{
selectArticle: [index: number],
delArticle: [item: T],
'update:searchKey': [val: string],
'update:list': [list: T[]],
}>()
let draggable = $ref(false)
function dragstart(index: number) {
dragIndex = index;
}
const dragenter = throttle((e, index) => {
// console.log('dragenter', 'dragIndex', dragIndex, 'index', index)
e.preventDefault();
// 避免源对象触发自身的dragenter事件
if (dragIndex !== index && dragIndex !== -1) {
const source = props.list[dragIndex];
let temp = cloneDeep(props.list)
temp.splice(dragIndex, 1);
temp.splice(index, 0, source);
emit('update:list', temp)
// props.list = temp
// 排序变化后目标对象的索引变成源对象的索引
dragIndex = index;
}
}, 200)
function dragover(e, index) {
// console.log('dragover')
e.preventDefault();
}
function dragend() {
// console.log('dragend')
draggable = false
dragIndex = -1
}
</script>
<template>
<div class="list-wrapper">
<div class="search">
<Input :model-value="searchKey"
@update:model-value="(e:string) => emit('update:searchKey',e)"
/>
</div>
<transition-group
name="drag"
class="list"
tag="div"
>
<div class="item"
:class="[
(props.selectIndex === index) && 'active',
draggable && 'draggable',
(dragIndex === index) && 'active'
]"
@click="emit('selectArticle',index)"
v-for="(item,index) in props.list"
:key="rowKey(item)"
:draggable="draggable"
@dragstart="dragstart(index)"
@dragenter="dragenter($event, index)"
@dragover="dragover($event, index)"
@dragend="dragend()"
>
<div class="left">
<slot :item="item" :index="index"></slot>
</div>
<div class="right">
<BaseIcon
@click="emit('delArticle',item)"
title="删除" icon="fluent:delete-24-regular"/>
<div
@mousedown="draggable = true"
@mouseup="draggable = false"
>
<BaseIcon
icon="carbon:move"/>
</div>
</div>
</div>
</transition-group>
</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(30px);
}
/* 确保将离开的元素从布局流中删除
以便能够正确地计算移动的动画。 */
.drag-leave-active {
position: absolute;
}
.list-wrapper {
flex: 1;
overflow: auto;
padding-right: 5rem;
.search {
margin: 10rem 0;
width: 260rem;
}
.list {
.item {
width: 260rem;
box-sizing: border-box;
background: #e1e1e1;
border-radius: 8rem;
margin-bottom: 10rem;
padding: 10rem;
display: flex;
justify-content: space-between;
transition: all .3s;
.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

@@ -25,6 +25,7 @@ export default {
methods: {
showPop(e) {
if (this.disabled) return
if (!this.title) return
e.stopPropagation()
let rect = e.target.getBoundingClientRect()
this.show = true
@@ -49,7 +50,7 @@ export default {
<Teleport to="body">
<Transition name="fade">
{
this.show && (
this.show && this.title && (
<div ref="tip" class="tip">
{this.title}
</div>