wip
This commit is contained in:
@@ -199,7 +199,8 @@ html.dark {
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
html, body {
|
||||
html,
|
||||
body {
|
||||
//font-size: 1px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
@@ -244,7 +245,7 @@ html, body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > .page-content {
|
||||
&>.page-content {
|
||||
padding: 10rem;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
@@ -275,10 +276,12 @@ a {
|
||||
width: .5rem;
|
||||
height: .6rem;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
border-radius: .1rem;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--color-scrollbar);
|
||||
border-radius: .6rem;
|
||||
@@ -439,6 +442,7 @@ a {
|
||||
.center {
|
||||
@apply flex justify-center items-center;
|
||||
}
|
||||
|
||||
.center-col {
|
||||
@extend .center;
|
||||
@apply flex-col;
|
||||
@@ -499,9 +503,12 @@ a {
|
||||
}
|
||||
|
||||
@keyframes underline {
|
||||
0%, 100% {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
border-left: .1rem solid var(--color-article);
|
||||
}
|
||||
|
||||
50% {
|
||||
border-left: .1rem solid transparent;
|
||||
}
|
||||
@@ -521,4 +528,10 @@ a {
|
||||
background: transparent;
|
||||
font-size: 16px; // 防止iOS缩放
|
||||
color: transparent; // 文字透明
|
||||
}
|
||||
|
||||
.btn-no-margin {
|
||||
.base-button + .base-button {
|
||||
margin-left: 0!important;
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ const emit = defineEmits<{
|
||||
}],
|
||||
importData: [e: Event]
|
||||
exportData: []
|
||||
sort: [type: Sort,pageNo: number,pageSize: number]
|
||||
}>()
|
||||
|
||||
let listRef: any = $ref()
|
||||
@@ -85,11 +86,6 @@ let selectAll = $computed(() => {
|
||||
return !!selectIds.length
|
||||
})
|
||||
|
||||
let list2 = $ref([])
|
||||
onMounted(async () => {
|
||||
list2 = await props.request({ pageNo, pageSize })
|
||||
console.log('list2',list2)
|
||||
})
|
||||
|
||||
function toggleSelect(item) {
|
||||
let rIndex = selectIds.findIndex(v => v === item.id)
|
||||
@@ -117,12 +113,16 @@ const closeImportDialog = () => showImportDialog = false
|
||||
|
||||
function sort(type: Sort) {
|
||||
if (type === Sort.reverse) {
|
||||
Toast.success('已翻转排序')
|
||||
list.value = reverse(cloneDeep(list.value))
|
||||
emit('sort', type,pageNo,pageSize)
|
||||
}
|
||||
if (type === Sort.random) {
|
||||
Toast.success('已随机排序')
|
||||
list.value = shuffle(cloneDeep(list.value))
|
||||
emit('sort', type,pageNo,pageSize)
|
||||
}
|
||||
if (type === Sort.reverseAll) {
|
||||
emit('sort', type,1,total2)
|
||||
}
|
||||
if (type === Sort.randomAll) {
|
||||
emit('sort', type,1,total2)
|
||||
}
|
||||
showSortDialog = false
|
||||
}
|
||||
@@ -134,6 +134,7 @@ function handleBatchDel() {
|
||||
|
||||
function handlePageNo(e) {
|
||||
pageNo = e
|
||||
getData()
|
||||
scrollToTop()
|
||||
}
|
||||
|
||||
@@ -142,9 +143,29 @@ const s = useSlots()
|
||||
defineExpose({
|
||||
scrollToBottom,
|
||||
scrollToItem,
|
||||
closeImportDialog
|
||||
closeImportDialog,
|
||||
getData
|
||||
})
|
||||
|
||||
|
||||
let list2 = $ref([])
|
||||
let loading2 = $ref(false)
|
||||
let total2 = $ref(0)
|
||||
|
||||
async function getData() {
|
||||
loading2 = true
|
||||
let {list, total} = await props.request({ pageNo, pageSize })
|
||||
console.log('list2',list2)
|
||||
list2 = list
|
||||
total2 = total
|
||||
loading2 = false
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
getData()
|
||||
})
|
||||
|
||||
|
||||
defineRender(
|
||||
() => {
|
||||
const d = (item) => <Checkbox
|
||||
@@ -234,10 +255,12 @@ defineRender(
|
||||
<div class="mini-row-title">
|
||||
列表顺序设置
|
||||
</div>
|
||||
<div class="mini-row">
|
||||
<BaseButton size="small" onClick={() => sort(Sort.reverse)}>翻转
|
||||
</BaseButton>
|
||||
<BaseButton size="small" onClick={() => sort(Sort.random)}>随机</BaseButton>
|
||||
<div class="flex flex-col gap2 btn-no-margin">
|
||||
<BaseButton onClick={() => sort(Sort.reverse)}>翻转当前页</BaseButton>
|
||||
<BaseButton onClick={() => sort(Sort.reverseAll)}>翻转所有</BaseButton>
|
||||
<div class="line"></div>
|
||||
<BaseButton onClick={() => sort(Sort.reverse)}>随机当前页</BaseButton>
|
||||
<BaseButton onClick={() => sort(Sort.randomAll)}>随机所有</BaseButton>
|
||||
</div>
|
||||
</MiniDialog>
|
||||
</div>
|
||||
@@ -247,15 +270,15 @@ defineRender(
|
||||
</div>
|
||||
}
|
||||
{
|
||||
props.loading ?
|
||||
loading2 ?
|
||||
<div class="h-full w-full center text-4xl">
|
||||
<IconEosIconsLoading color="gray"/>
|
||||
</div>
|
||||
: currentList.length ? (
|
||||
: list2.length ? (
|
||||
<>
|
||||
<div class="flex-1 overflow-auto"
|
||||
ref={e => listRef = e}>
|
||||
{currentList.map((item, index) => {
|
||||
{list2.map((item, index) => {
|
||||
return (
|
||||
<div class="list-item-wrapper"
|
||||
key={item.word}
|
||||
@@ -274,7 +297,7 @@ defineRender(
|
||||
onUpdate:page-size={(e) => pageSize = e}
|
||||
pageSizes={[20, 50, 100, 200]}
|
||||
layout="prev, pager, next"
|
||||
total={props.total}/>
|
||||
total={total2}/>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
@@ -316,6 +339,4 @@ defineRender(
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script setup lang="tsx">
|
||||
import { DictId } from "@/types/types.ts";
|
||||
import { DictId, Sort } from "@/types/types.ts";
|
||||
|
||||
import BasePage from "@/components/BasePage.vue";
|
||||
import { computed, onMounted, reactive, ref, shallowReactive, watch } from "vue";
|
||||
import { useRuntimeStore } from "@/stores/runtime.ts";
|
||||
import { _getDictDataByUrl, _nextTick, convertToWord, isMobile, loadJsLib, useNav } from "@/utils";
|
||||
import { _getDictDataByUrl, _nextTick, cloneDeep, convertToWord, isMobile, loadJsLib, reverse, shuffle, useNav } from "@/utils";
|
||||
import { nanoid } from "nanoid";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import BaseTable from "@/components/BaseTable.vue";
|
||||
@@ -152,7 +152,7 @@ function word2Str(word) {
|
||||
res.phrases = word.phrases.map(v => (v.c + "\n" + v.cn).replaceAll('"', '')).join('\n\n')
|
||||
res.synos = word.synos.map(v => (v.pos + v.cn + "\n" + v.ws.join('/')).replaceAll('"', '')).join('\n\n')
|
||||
res.relWords = word.relWords.root ? ('词根:' + word.relWords.root + '\n\n' +
|
||||
word.relWords.rels.map(v => (v.pos + "\n" + v.words.map(v => (v.c + ':' + v.cn)).join('\n')).replaceAll('"', '')).join('\n\n')) : ''
|
||||
word.relWords.rels.map(v => (v.pos + "\n" + v.words.map(v => (v.c + ':' + v.cn)).join('\n')).replaceAll('"', '')).join('\n\n')) : ''
|
||||
res.etymology = word.etymology.map(v => (v.t + '\n' + v.d).replaceAll('"', '')).join('\n\n')
|
||||
return res
|
||||
}
|
||||
@@ -193,8 +193,8 @@ onMounted(async () => {
|
||||
router.push("/word")
|
||||
} else {
|
||||
if (!runtimeStore.editDict.words.length
|
||||
&& !runtimeStore.editDict.custom
|
||||
&& ![DictId.wordCollect, DictId.wordWrong, DictId.wordKnown].includes(runtimeStore.editDict.en_name || runtimeStore.editDict.id)
|
||||
&& !runtimeStore.editDict.custom
|
||||
&& ![DictId.wordCollect, DictId.wordWrong, DictId.wordKnown].includes(runtimeStore.editDict.en_name || runtimeStore.editDict.id)
|
||||
) {
|
||||
loading = true
|
||||
let r = await _getDictDataByUrl(runtimeStore.editDict)
|
||||
@@ -213,6 +213,7 @@ onMounted(async () => {
|
||||
}
|
||||
list2 = runtimeStore.editDict.words
|
||||
loading = false
|
||||
tableRef.value.getData()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -319,22 +320,22 @@ function importData(e) {
|
||||
|
||||
if (repeat.length) {
|
||||
MessageBox.confirm(
|
||||
'单词"' + repeat.map(v => v.word).join(', ') + '" 已存在,是否覆盖原单词?',
|
||||
'检测到重复单词',
|
||||
() => {
|
||||
repeat.map(v => {
|
||||
runtimeStore.editDict.words[v.index] = v
|
||||
delete runtimeStore.editDict.words[v.index]["index"]
|
||||
})
|
||||
},
|
||||
null,
|
||||
() => {
|
||||
tableRef.value.closeImportDialog()
|
||||
e.target.value = ''
|
||||
importLoading = false
|
||||
syncDictInMyStudyList()
|
||||
Toast.success('导入成功!')
|
||||
}
|
||||
'单词"' + repeat.map(v => v.word).join(', ') + '" 已存在,是否覆盖原单词?',
|
||||
'检测到重复单词',
|
||||
() => {
|
||||
repeat.map(v => {
|
||||
runtimeStore.editDict.words[v.index] = v
|
||||
delete runtimeStore.editDict.words[v.index]["index"]
|
||||
})
|
||||
},
|
||||
null,
|
||||
() => {
|
||||
tableRef.value.closeImportDialog()
|
||||
e.target.value = ''
|
||||
importLoading = false
|
||||
syncDictInMyStudyList()
|
||||
Toast.success('导入成功!')
|
||||
}
|
||||
)
|
||||
} else {
|
||||
tableRef.value.closeImportDialog()
|
||||
@@ -447,216 +448,238 @@ async function requestList({ pageNo, pageSize }) {
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
|
||||
} else {
|
||||
return list2.slice((pageNo - 1) * pageSize, (pageNo - 1) * pageSize + pageSize)
|
||||
return {
|
||||
list: list2.slice((pageNo - 1) * pageSize, (pageNo - 1) * pageSize + pageSize),
|
||||
total: list2.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sort(type: Sort, pageNo: number, pageSize: number) {
|
||||
debugger
|
||||
|
||||
console.log('sort', type)
|
||||
if ([Sort.reverse, Sort.random].includes(type)) {
|
||||
list2 = list2.slice(0, pageSize * (pageNo - 1))
|
||||
.concat(reverse(list2.slice(pageSize * (pageNo - 1), pageSize * (pageNo - 1) + pageSize)))
|
||||
.concat(list2.slice(pageSize * (pageNo - 1) + pageSize))
|
||||
} else if ([Sort.reverseAll, Sort.randomAll].includes(type)) {
|
||||
list2 = list2.slice(0, pageSize * (pageNo - 1))
|
||||
.concat(shuffle(list2.slice(pageSize * (pageNo - 1), pageSize * (pageNo - 1) + pageSize)))
|
||||
.concat(list2.slice(pageSize * (pageNo - 1) + pageSize))
|
||||
}
|
||||
runtimeStore.editDict.words = list2
|
||||
Toast.success('已排序成功')
|
||||
tableRef.value.getData()
|
||||
}
|
||||
|
||||
|
||||
defineRender(() => {
|
||||
return (
|
||||
<BasePage>
|
||||
{
|
||||
showBookDetail.value ? <div className="card mb-0 dict-detail-card flex flex-col">
|
||||
<div class="dict-header flex justify-between items-center relative">
|
||||
<BackIcon class="dict-back z-2"/>
|
||||
<div class="dict-title absolute page-title text-align-center w-full">{runtimeStore.editDict.name}</div>
|
||||
<div class="dict-actions flex">
|
||||
<BaseButton loading={studyLoading || loading} type="info"
|
||||
onClick={() => isEdit = true}>编辑</BaseButton>
|
||||
<BaseButton id="study" loading={studyLoading || loading} onClick={addMyStudyList}>学习</BaseButton>
|
||||
<BaseButton loading={studyLoading || loading} onClick={startTest}>测试</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-lg mt-2">介绍:{runtimeStore.editDict.description}</div>
|
||||
<div class="line my-3"></div>
|
||||
<BasePage>
|
||||
{
|
||||
showBookDetail.value ? <div className="card mb-0 dict-detail-card flex flex-col">
|
||||
<div class="dict-header flex justify-between items-center relative">
|
||||
<BackIcon class="dict-back z-2" />
|
||||
<div class="dict-title absolute page-title text-align-center w-full">{runtimeStore.editDict.name}</div>
|
||||
<div class="dict-actions flex">
|
||||
<BaseButton loading={studyLoading || loading} type="info"
|
||||
onClick={() => isEdit = true}>编辑</BaseButton>
|
||||
<BaseButton id="study" loading={studyLoading || loading} onClick={addMyStudyList}>学习</BaseButton>
|
||||
<BaseButton loading={studyLoading || loading} onClick={startTest}>测试</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-lg mt-2">介绍:{runtimeStore.editDict.description}</div>
|
||||
<div class="line my-3"></div>
|
||||
|
||||
{/* 移动端标签页导航 */}
|
||||
{isMob && isOperate && (
|
||||
<div class="tab-navigation mb-3">
|
||||
<div
|
||||
class={`tab-item ${activeTab === 'list' ? 'active' : ''}`}
|
||||
onClick={() => activeTab = 'list'}
|
||||
>
|
||||
单词列表
|
||||
</div>
|
||||
<div
|
||||
class={`tab-item ${activeTab === 'edit' ? 'active' : ''}`}
|
||||
onClick={() => activeTab = 'edit'}
|
||||
>
|
||||
{wordForm.id ? '编辑' : '添加'}单词
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="flex flex-1 overflow-hidden content-area">
|
||||
<div class={`word-list-section ${isMob && isOperate && activeTab !== 'list' ? 'mobile-hidden' : ''}`}>
|
||||
<BaseTable
|
||||
ref={tableRef}
|
||||
class="h-full"
|
||||
request={requestList}
|
||||
list={list}
|
||||
total={runtimeStore.editDict.length}
|
||||
loading={loading}
|
||||
onUpdate:list={e => list = e}
|
||||
del={delWord}
|
||||
batchDel={batchDel}
|
||||
add={addWord}
|
||||
onImportData={importData}
|
||||
onExportData={exportData}
|
||||
exportLoading={exportLoading}
|
||||
importLoading={importLoading}
|
||||
>
|
||||
{
|
||||
(val) =>
|
||||
<WordItem
|
||||
showTransPop={false}
|
||||
item={val.item}>
|
||||
{{
|
||||
prefix: () => val.checkbox(val.item),
|
||||
suffix: () => (
|
||||
<div class='flex flex-col'>
|
||||
<BaseIcon
|
||||
class="option-icon"
|
||||
onClick={() => editWord(val.item)}
|
||||
title="编辑">
|
||||
<IconFluentTextEditStyle20Regular/>
|
||||
</BaseIcon>
|
||||
<PopConfirm title="确认删除?"
|
||||
onConfirm={() => delWord(val.item.id)}
|
||||
>
|
||||
<BaseIcon
|
||||
class="option-icon"
|
||||
title="删除">
|
||||
<DeleteIcon/>
|
||||
</BaseIcon>
|
||||
</PopConfirm>
|
||||
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</WordItem>
|
||||
}
|
||||
</BaseTable>
|
||||
</div>
|
||||
{
|
||||
isOperate ? (
|
||||
<div
|
||||
class={`edit-section flex-1 flex flex-col ${isMob && activeTab !== 'edit' ? 'mobile-hidden' : ''}`}>
|
||||
<div class="common-title">
|
||||
{wordForm.id ? '修改' : '添加'}单词
|
||||
</div>
|
||||
<Form
|
||||
class="flex-1 overflow-auto pr-2"
|
||||
ref={e => wordFormRef = e}
|
||||
rules={wordRules}
|
||||
model={wordForm}
|
||||
label-width="7rem">
|
||||
<FormItem label="单词" prop="word">
|
||||
<BaseInput
|
||||
modelValue={wordForm.word}
|
||||
onUpdate:modelValue={e => wordForm.word = e}
|
||||
>
|
||||
|
||||
</BaseInput>
|
||||
</FormItem>
|
||||
<FormItem label="英音音标">
|
||||
<BaseInput
|
||||
modelValue={wordForm.phonetic0}
|
||||
onUpdate:modelValue={e => wordForm.phonetic0 = e}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="美音音标">
|
||||
<BaseInput
|
||||
modelValue={wordForm.phonetic1}
|
||||
onUpdate:modelValue={e => wordForm.phonetic1 = e}/>
|
||||
</FormItem>
|
||||
<FormItem label="翻译">
|
||||
<Textarea
|
||||
modelValue={wordForm.trans}
|
||||
onUpdate:modelValue={e => wordForm.trans = e}
|
||||
placeholder="一行一个翻译,前面词性,后面内容(如n.取消);多个翻译请换行"
|
||||
autosize={{ minRows: 6, maxRows: 10 }}/>
|
||||
</FormItem>
|
||||
<FormItem label="例句">
|
||||
<Textarea
|
||||
modelValue={wordForm.sentences}
|
||||
onUpdate:modelValue={e => wordForm.sentences = e}
|
||||
placeholder="一行原文,一行译文;多个请换两行"
|
||||
autosize={{ minRows: 6, maxRows: 10 }}/>
|
||||
</FormItem>
|
||||
<FormItem label="短语">
|
||||
<Textarea
|
||||
modelValue={wordForm.phrases}
|
||||
onUpdate:modelValue={e => wordForm.phrases = e}
|
||||
placeholder="一行原文,一行译文;多个请换两行"
|
||||
autosize={{ minRows: 6, maxRows: 10 }}/>
|
||||
</FormItem>
|
||||
<FormItem label="同义词">
|
||||
<Textarea
|
||||
modelValue={wordForm.synos}
|
||||
onUpdate:modelValue={e => wordForm.synos = e}
|
||||
placeholder="请参考已有单词格式"
|
||||
autosize={{ minRows: 6, maxRows: 20 }}/>
|
||||
</FormItem>
|
||||
<FormItem label="同根词">
|
||||
<Textarea
|
||||
modelValue={wordForm.relWords}
|
||||
onUpdate:modelValue={e => wordForm.relWords = e}
|
||||
placeholder="请参考已有单词格式"
|
||||
autosize={{ minRows: 6, maxRows: 20 }}/>
|
||||
</FormItem>
|
||||
<FormItem label="词源">
|
||||
<Textarea
|
||||
modelValue={wordForm.etymology}
|
||||
onUpdate:modelValue={e => wordForm.etymology = e}
|
||||
placeholder="请参考已有单词格式"
|
||||
autosize={{ minRows: 6, maxRows: 10 }}/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
<div class="center">
|
||||
<BaseButton
|
||||
type="info"
|
||||
onClick={closeWordForm}>关闭
|
||||
</BaseButton>
|
||||
<BaseButton type="primary"
|
||||
onClick={onSubmitWord}>保存
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
</div>
|
||||
</div> :
|
||||
<div class="card mb-0 dict-detail-card">
|
||||
<div class="dict-header flex justify-between items-center relative">
|
||||
<BackIcon class="dict-back z-2" onClick={() => {
|
||||
if (isAdd) {
|
||||
router.back()
|
||||
} else {
|
||||
isEdit = false
|
||||
}
|
||||
}}/>
|
||||
<div class="dict-title absolute page-title text-align-center w-full">
|
||||
{runtimeStore.editDict.id ? '修改' : '创建'}词典
|
||||
</div>
|
||||
</div>
|
||||
<div class="center">
|
||||
<EditBook
|
||||
isAdd={isAdd}
|
||||
isBook={false}
|
||||
onClose={formClose}
|
||||
onSubmit={() => isEdit = isAdd = false}
|
||||
/>
|
||||
</div>
|
||||
{/* 移动端标签页导航 */}
|
||||
{isMob && isOperate && (
|
||||
<div class="tab-navigation mb-3">
|
||||
<div
|
||||
class={`tab-item ${activeTab === 'list' ? 'active' : ''}`}
|
||||
onClick={() => activeTab = 'list'}
|
||||
>
|
||||
单词列表
|
||||
</div>
|
||||
}
|
||||
<div
|
||||
class={`tab-item ${activeTab === 'edit' ? 'active' : ''}`}
|
||||
onClick={() => activeTab = 'edit'}
|
||||
>
|
||||
{wordForm.id ? '编辑' : '添加'}单词
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<PracticeSettingDialog
|
||||
showLeftOption
|
||||
modelValue={showPracticeSettingDialog}
|
||||
onUpdate:modelValue={val => (showPracticeSettingDialog = val)}
|
||||
onOk={startPractice}/>
|
||||
</BasePage>
|
||||
<div class="flex flex-1 overflow-hidden content-area">
|
||||
<div class={`word-list-section ${isMob && isOperate && activeTab !== 'list' ? 'mobile-hidden' : ''}`}>
|
||||
<BaseTable
|
||||
sort={sort}
|
||||
ref={tableRef}
|
||||
class="h-full"
|
||||
request={requestList}
|
||||
list={list}
|
||||
total={runtimeStore.editDict.length}
|
||||
loading={loading}
|
||||
onUpdate:list={e => list = e}
|
||||
del={delWord}
|
||||
batchDel={batchDel}
|
||||
add={addWord}
|
||||
onImportData={importData}
|
||||
onExportData={exportData}
|
||||
exportLoading={exportLoading}
|
||||
importLoading={importLoading}
|
||||
>
|
||||
{
|
||||
(val) =>
|
||||
<WordItem
|
||||
showTransPop={false}
|
||||
item={val.item}>
|
||||
{{
|
||||
prefix: () => val.checkbox(val.item),
|
||||
suffix: () => (
|
||||
<div class='flex flex-col'>
|
||||
<BaseIcon
|
||||
class="option-icon"
|
||||
onClick={() => editWord(val.item)}
|
||||
title="编辑">
|
||||
<IconFluentTextEditStyle20Regular />
|
||||
</BaseIcon>
|
||||
<PopConfirm title="确认删除?"
|
||||
onConfirm={() => delWord(val.item.id)}
|
||||
>
|
||||
<BaseIcon
|
||||
class="option-icon"
|
||||
title="删除">
|
||||
<DeleteIcon />
|
||||
</BaseIcon>
|
||||
</PopConfirm>
|
||||
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</WordItem>
|
||||
}
|
||||
</BaseTable>
|
||||
</div>
|
||||
{
|
||||
isOperate ? (
|
||||
<div
|
||||
class={`edit-section flex-1 flex flex-col ${isMob && activeTab !== 'edit' ? 'mobile-hidden' : ''}`}>
|
||||
<div class="common-title">
|
||||
{wordForm.id ? '修改' : '添加'}单词
|
||||
</div>
|
||||
<Form
|
||||
class="flex-1 overflow-auto pr-2"
|
||||
ref={e => wordFormRef = e}
|
||||
rules={wordRules}
|
||||
model={wordForm}
|
||||
label-width="7rem">
|
||||
<FormItem label="单词" prop="word">
|
||||
<BaseInput
|
||||
modelValue={wordForm.word}
|
||||
onUpdate:modelValue={e => wordForm.word = e}
|
||||
>
|
||||
|
||||
</BaseInput>
|
||||
</FormItem>
|
||||
<FormItem label="英音音标">
|
||||
<BaseInput
|
||||
modelValue={wordForm.phonetic0}
|
||||
onUpdate:modelValue={e => wordForm.phonetic0 = e}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="美音音标">
|
||||
<BaseInput
|
||||
modelValue={wordForm.phonetic1}
|
||||
onUpdate:modelValue={e => wordForm.phonetic1 = e} />
|
||||
</FormItem>
|
||||
<FormItem label="翻译">
|
||||
<Textarea
|
||||
modelValue={wordForm.trans}
|
||||
onUpdate:modelValue={e => wordForm.trans = e}
|
||||
placeholder="一行一个翻译,前面词性,后面内容(如n.取消);多个翻译请换行"
|
||||
autosize={{ minRows: 6, maxRows: 10 }} />
|
||||
</FormItem>
|
||||
<FormItem label="例句">
|
||||
<Textarea
|
||||
modelValue={wordForm.sentences}
|
||||
onUpdate:modelValue={e => wordForm.sentences = e}
|
||||
placeholder="一行原文,一行译文;多个请换两行"
|
||||
autosize={{ minRows: 6, maxRows: 10 }} />
|
||||
</FormItem>
|
||||
<FormItem label="短语">
|
||||
<Textarea
|
||||
modelValue={wordForm.phrases}
|
||||
onUpdate:modelValue={e => wordForm.phrases = e}
|
||||
placeholder="一行原文,一行译文;多个请换两行"
|
||||
autosize={{ minRows: 6, maxRows: 10 }} />
|
||||
</FormItem>
|
||||
<FormItem label="同义词">
|
||||
<Textarea
|
||||
modelValue={wordForm.synos}
|
||||
onUpdate:modelValue={e => wordForm.synos = e}
|
||||
placeholder="请参考已有单词格式"
|
||||
autosize={{ minRows: 6, maxRows: 20 }} />
|
||||
</FormItem>
|
||||
<FormItem label="同根词">
|
||||
<Textarea
|
||||
modelValue={wordForm.relWords}
|
||||
onUpdate:modelValue={e => wordForm.relWords = e}
|
||||
placeholder="请参考已有单词格式"
|
||||
autosize={{ minRows: 6, maxRows: 20 }} />
|
||||
</FormItem>
|
||||
<FormItem label="词源">
|
||||
<Textarea
|
||||
modelValue={wordForm.etymology}
|
||||
onUpdate:modelValue={e => wordForm.etymology = e}
|
||||
placeholder="请参考已有单词格式"
|
||||
autosize={{ minRows: 6, maxRows: 10 }} />
|
||||
</FormItem>
|
||||
</Form>
|
||||
<div class="center">
|
||||
<BaseButton
|
||||
type="info"
|
||||
onClick={closeWordForm}>关闭
|
||||
</BaseButton>
|
||||
<BaseButton type="primary"
|
||||
onClick={onSubmitWord}>保存
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
</div>
|
||||
</div> :
|
||||
<div class="card mb-0 dict-detail-card">
|
||||
<div class="dict-header flex justify-between items-center relative">
|
||||
<BackIcon class="dict-back z-2" onClick={() => {
|
||||
if (isAdd) {
|
||||
router.back()
|
||||
} else {
|
||||
isEdit = false
|
||||
}
|
||||
}} />
|
||||
<div class="dict-title absolute page-title text-align-center w-full">
|
||||
{runtimeStore.editDict.id ? '修改' : '创建'}词典
|
||||
</div>
|
||||
</div>
|
||||
<div class="center">
|
||||
<EditBook
|
||||
isAdd={isAdd}
|
||||
isBook={false}
|
||||
onClose={formClose}
|
||||
onSubmit={() => isEdit = isAdd = false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<PracticeSettingDialog
|
||||
showLeftOption
|
||||
modelValue={showPracticeSettingDialog}
|
||||
onUpdate:modelValue={val => (showPracticeSettingDialog = val)}
|
||||
onOk={startPractice} />
|
||||
</BasePage>
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -9,210 +9,206 @@ import { add2MyDict, dictListVersion, myDictList } from "@/apis";
|
||||
import Toast from "@/components/base/toast/Toast.ts";
|
||||
|
||||
export interface BaseState {
|
||||
simpleWords: string[],
|
||||
load: boolean
|
||||
word: {
|
||||
studyIndex: number,
|
||||
bookList: Dict[],
|
||||
},
|
||||
article: {
|
||||
bookList: Dict[],
|
||||
studyIndex: number,
|
||||
},
|
||||
dictListVersion: number
|
||||
simpleWords: string[],
|
||||
load: boolean
|
||||
word: {
|
||||
studyIndex: number,
|
||||
bookList: Dict[],
|
||||
},
|
||||
article: {
|
||||
bookList: Dict[],
|
||||
studyIndex: number,
|
||||
},
|
||||
dictListVersion: number
|
||||
}
|
||||
|
||||
export const getDefaultBaseState = (): BaseState => ({
|
||||
simpleWords: [
|
||||
'a', 'an',
|
||||
'i', 'my', 'me', 'you', 'your', 'he', 'his', 'she', 'her', 'it',
|
||||
'what', 'who', 'where', 'how', 'when', 'which',
|
||||
'be', 'am', 'is', 'was', 'are', 'were', 'do', 'did', 'can', 'could', 'will', 'would',
|
||||
'the', 'that', 'this', 'and', 'not', 'no', 'yes',
|
||||
'to', 'of', 'for', 'at', 'in'
|
||||
simpleWords: [
|
||||
'a', 'an',
|
||||
'i', 'my', 'me', 'you', 'your', 'he', 'his', 'she', 'her', 'it',
|
||||
'what', 'who', 'where', 'how', 'when', 'which',
|
||||
'be', 'am', 'is', 'was', 'are', 'were', 'do', 'did', 'can', 'could', 'will', 'would',
|
||||
'the', 'that', 'this', 'and', 'not', 'no', 'yes',
|
||||
'to', 'of', 'for', 'at', 'in'
|
||||
],
|
||||
load: false,
|
||||
word: {
|
||||
bookList: [
|
||||
getDefaultDict({ id: DictId.wordCollect, en_name: DictId.wordCollect, name: '收藏' }),
|
||||
getDefaultDict({ id: DictId.wordWrong, en_name: DictId.wordCollect, name: '错词' }),
|
||||
getDefaultDict({
|
||||
id: DictId.wordKnown,
|
||||
en_name: DictId.wordCollect,
|
||||
name: '已掌握',
|
||||
description: '已掌握后的单词不会出现在练习中'
|
||||
}),
|
||||
],
|
||||
load: false,
|
||||
word: {
|
||||
bookList: [
|
||||
getDefaultDict({id: DictId.wordCollect, en_name: DictId.wordCollect, name: '收藏'}),
|
||||
getDefaultDict({id: DictId.wordWrong, en_name: DictId.wordCollect, name: '错词'}),
|
||||
getDefaultDict({
|
||||
id: DictId.wordKnown,
|
||||
en_name: DictId.wordCollect,
|
||||
name: '已掌握',
|
||||
description: '已掌握后的单词不会出现在练习中'
|
||||
}),
|
||||
],
|
||||
studyIndex: -1,
|
||||
},
|
||||
article: {
|
||||
bookList: [
|
||||
getDefaultDict({id: DictId.articleCollect, en_name: DictId.articleCollect, name: '收藏'})
|
||||
],
|
||||
studyIndex: -1,
|
||||
},
|
||||
dictListVersion: 1
|
||||
studyIndex: -1,
|
||||
},
|
||||
article: {
|
||||
bookList: [
|
||||
getDefaultDict({ id: DictId.articleCollect, en_name: DictId.articleCollect, name: '收藏' })
|
||||
],
|
||||
studyIndex: -1,
|
||||
},
|
||||
dictListVersion: 1
|
||||
})
|
||||
|
||||
export const useBaseStore = defineStore('base', {
|
||||
state: (): BaseState => {
|
||||
return getDefaultBaseState()
|
||||
state: (): BaseState => {
|
||||
return getDefaultBaseState()
|
||||
},
|
||||
getters: {
|
||||
collectWord(): Dict {
|
||||
return this.word.bookList[0]
|
||||
},
|
||||
getters: {
|
||||
collectWord(): Dict {
|
||||
return this.word.bookList[0]
|
||||
},
|
||||
collectArticle(): Dict {
|
||||
return this.article.bookList[0]
|
||||
},
|
||||
wrong(): Dict {
|
||||
return this.word.bookList[1]
|
||||
},
|
||||
known(): Dict {
|
||||
return this.word.bookList[2]
|
||||
},
|
||||
knownWords(): string[] {
|
||||
return this.known.words.map((v: Word) => v.word.toLowerCase())
|
||||
},
|
||||
allIgnoreWords() {
|
||||
return this.known.words.map((v: Word) => v.word.toLowerCase()).concat(this.simpleWords.map((v: string) => v.toLowerCase()))
|
||||
},
|
||||
sdict(): Dict {
|
||||
if (this.word.studyIndex >= 0) {
|
||||
return this.word.bookList[this.word.studyIndex] ?? getDefaultDict()
|
||||
}
|
||||
return getDefaultDict()
|
||||
},
|
||||
currentStudyProgress(): number {
|
||||
if (!this.sdict.length) return 0
|
||||
return _getStudyProgress(this.sdict.lastLearnIndex, this.sdict.length)
|
||||
},
|
||||
getDictCompleteDate(): number {
|
||||
if (!this.sdict.length) return 0
|
||||
if (!this.sdict.perDayStudyNumber) return 0
|
||||
return Math.ceil((this.sdict.length - this.sdict.lastLearnIndex) / this.sdict.perDayStudyNumber)
|
||||
},
|
||||
sbook(): Dict {
|
||||
return this.article.bookList[this.article.studyIndex] ?? {}
|
||||
},
|
||||
currentBookProgress(): number {
|
||||
if (!this.sbook.length) return 0
|
||||
if (this.sbook.complete) return 100
|
||||
return _getStudyProgress(this.sbook.lastLearnIndex, this.sbook.length)
|
||||
},
|
||||
collectArticle(): Dict {
|
||||
return this.article.bookList[0]
|
||||
},
|
||||
actions: {
|
||||
setState(obj: BaseState) {
|
||||
obj.word.bookList.map(book => {
|
||||
book.words = shallowReactive(book.words)
|
||||
book.articles = shallowReactive(book.articles)
|
||||
book.statistics = shallowReactive(book.statistics)
|
||||
})
|
||||
obj.article.bookList.map(book => {
|
||||
book.words = shallowReactive(book.words)
|
||||
book.articles = shallowReactive(book.articles)
|
||||
book.statistics = shallowReactive(book.statistics)
|
||||
})
|
||||
this.$patch(obj)
|
||||
},
|
||||
async init() {
|
||||
return new Promise(async resolve => {
|
||||
try {
|
||||
let configStr: string = await get(SAVE_DICT_KEY.key)
|
||||
let data = checkAndUpgradeSaveDict(configStr)
|
||||
if (AppEnv.IS_OFFICIAL) {
|
||||
let r = await dictListVersion()
|
||||
if (r.success) {
|
||||
data.dictListVersion = r.data
|
||||
}
|
||||
}
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
let res = await myDictList()
|
||||
if (res.success) {
|
||||
//只保留未同步的
|
||||
data.word.bookList = data.word.bookList.filter(v => !v.sync)
|
||||
data.article.bookList = data.article.bookList.filter(v => !v.sync)
|
||||
//这里看看是否要 shallowReactive
|
||||
Object.assign(data, res.data)
|
||||
}
|
||||
}
|
||||
console.log('data', data)
|
||||
this.setState(data)
|
||||
set(SAVE_DICT_KEY.key, JSON.stringify({
|
||||
val: shakeCommonDict(this.$state),
|
||||
version: SAVE_DICT_KEY.version
|
||||
}))
|
||||
} catch (e) {
|
||||
console.error('读取本地dict数据失败', e)
|
||||
}
|
||||
resolve(true)
|
||||
})
|
||||
},
|
||||
//改变词典
|
||||
async changeDict(val: Dict) {
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
let r = await add2MyDict({
|
||||
id: val.id,
|
||||
perDayStudyNumber: val.perDayStudyNumber,
|
||||
lastLearnIndex: val.lastLearnIndex,
|
||||
complete: val.complete,
|
||||
})
|
||||
if (!r.success) return Toast.error(r.msg)
|
||||
else val.userDictId = r.data
|
||||
}
|
||||
//把其他的词典的单词数据都删掉,全保存在内存里太卡了
|
||||
this.word.bookList.slice(3).map(v => {
|
||||
if (!v.custom) {
|
||||
v.words = shallowReactive([])
|
||||
}
|
||||
})
|
||||
let rIndex = this.word.bookList.findIndex((v: Dict) => v.id === val.id)
|
||||
if (val.words.length < val.perDayStudyNumber) {
|
||||
val.perDayStudyNumber = val.words.length
|
||||
}
|
||||
if (rIndex > -1) {
|
||||
this.word.studyIndex = rIndex
|
||||
this.word.bookList[this.word.studyIndex].words = shallowReactive(val.words)
|
||||
this.word.bookList[this.word.studyIndex].perDayStudyNumber = val.perDayStudyNumber
|
||||
this.word.bookList[this.word.studyIndex].lastLearnIndex = val.lastLearnIndex
|
||||
this.word.bookList[this.word.studyIndex].userDictId = val.userDictId
|
||||
} else {
|
||||
this.word.bookList.push(getDefaultDict(val))
|
||||
this.word.studyIndex = this.word.bookList.length - 1
|
||||
}
|
||||
},
|
||||
//改变书籍
|
||||
async changeBook(val: Dict) {
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
let r = await add2MyDict({
|
||||
id: val.id,
|
||||
perDayStudyNumber: val.perDayStudyNumber,
|
||||
lastLearnIndex: val.lastLearnIndex,
|
||||
complete: val.complete,
|
||||
})
|
||||
if (!r.success) {
|
||||
return Toast.error(r.msg)
|
||||
}
|
||||
}
|
||||
//把其他的书籍里面的文章数据都删掉,全保存在内存里太卡了
|
||||
this.article.bookList.slice(1).map(v => {
|
||||
if (!v.custom) {
|
||||
v.articles = shallowReactive([])
|
||||
}
|
||||
})
|
||||
let rIndex = this.article.bookList.findIndex((v: Dict) => v.id === val.id)
|
||||
if (rIndex > -1) {
|
||||
this.article.studyIndex = rIndex
|
||||
//不要整个等于,不然统计没了
|
||||
// this.article.bookList[this.article.studyIndex] = getDefaultDict(val)
|
||||
this.article.bookList[this.article.studyIndex].articles = shallowReactive(val.articles)
|
||||
this.article.bookList[this.article.studyIndex].cover = val.cover
|
||||
this.article.bookList[this.article.studyIndex].name = val.name
|
||||
this.article.bookList[this.article.studyIndex].description = val.description
|
||||
} else {
|
||||
this.article.bookList.push(getDefaultDict(val))
|
||||
this.article.studyIndex = this.article.bookList.length - 1
|
||||
}
|
||||
},
|
||||
wrong(): Dict {
|
||||
return this.word.bookList[1]
|
||||
},
|
||||
known(): Dict {
|
||||
return this.word.bookList[2]
|
||||
},
|
||||
knownWords(): string[] {
|
||||
return this.known.words.map((v: Word) => v.word.toLowerCase())
|
||||
},
|
||||
allIgnoreWords() {
|
||||
return this.known.words.map((v: Word) => v.word.toLowerCase()).concat(this.simpleWords.map((v: string) => v.toLowerCase()))
|
||||
},
|
||||
sdict(): Dict {
|
||||
if (this.word.studyIndex >= 0) {
|
||||
return this.word.bookList[this.word.studyIndex] ?? getDefaultDict()
|
||||
}
|
||||
return getDefaultDict()
|
||||
},
|
||||
currentStudyProgress(): number {
|
||||
if (!this.sdict.length) return 0
|
||||
return _getStudyProgress(this.sdict.lastLearnIndex, this.sdict.length)
|
||||
},
|
||||
getDictCompleteDate(): number {
|
||||
if (!this.sdict.length) return 0
|
||||
if (!this.sdict.perDayStudyNumber) return 0
|
||||
return Math.ceil((this.sdict.length - this.sdict.lastLearnIndex) / this.sdict.perDayStudyNumber)
|
||||
},
|
||||
sbook(): Dict {
|
||||
return this.article.bookList[this.article.studyIndex] ?? {}
|
||||
},
|
||||
currentBookProgress(): number {
|
||||
if (!this.sbook.length) return 0
|
||||
if (this.sbook.complete) return 100
|
||||
return _getStudyProgress(this.sbook.lastLearnIndex, this.sbook.length)
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setState(obj: BaseState) {
|
||||
obj.word.bookList.map(book => {
|
||||
book.words = shallowReactive(book.words)
|
||||
book.articles = shallowReactive(book.articles)
|
||||
book.statistics = shallowReactive(book.statistics)
|
||||
})
|
||||
obj.article.bookList.map(book => {
|
||||
book.words = shallowReactive(book.words)
|
||||
book.articles = shallowReactive(book.articles)
|
||||
book.statistics = shallowReactive(book.statistics)
|
||||
})
|
||||
this.$patch(obj)
|
||||
},
|
||||
async init() {
|
||||
return new Promise(async resolve => {
|
||||
try {
|
||||
let configStr: string = await get(SAVE_DICT_KEY.key)
|
||||
let data = checkAndUpgradeSaveDict(configStr)
|
||||
if (AppEnv.IS_OFFICIAL) {
|
||||
let r = await dictListVersion()
|
||||
if (r.success) {
|
||||
data.dictListVersion = r.data
|
||||
}
|
||||
}
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
let res = await myDictList()
|
||||
if (res.success) {
|
||||
//只保留未同步的
|
||||
data.word.bookList = data.word.bookList.filter(v => !v.sync)
|
||||
data.article.bookList = data.article.bookList.filter(v => !v.sync)
|
||||
//这里看看是否要 shallowReactive
|
||||
Object.assign(data, res.data)
|
||||
}
|
||||
}
|
||||
console.log('data', data)
|
||||
this.setState(data)
|
||||
} catch (e) {
|
||||
console.error('读取本地dict数据失败', e)
|
||||
}
|
||||
resolve(true)
|
||||
})
|
||||
},
|
||||
//改变词典
|
||||
async changeDict(val: Dict) {
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
let r = await add2MyDict({
|
||||
id: val.id,
|
||||
perDayStudyNumber: val.perDayStudyNumber,
|
||||
lastLearnIndex: val.lastLearnIndex,
|
||||
complete: val.complete,
|
||||
})
|
||||
if (!r.success) return Toast.error(r.msg)
|
||||
else val.userDictId = r.data
|
||||
}
|
||||
//把其他的词典的单词数据都删掉,全保存在内存里太卡了
|
||||
this.word.bookList.slice(3).map(v => {
|
||||
if (!v.custom) {
|
||||
v.words = shallowReactive([])
|
||||
}
|
||||
})
|
||||
let rIndex = this.word.bookList.findIndex((v: Dict) => v.id === val.id)
|
||||
if (val.words.length < val.perDayStudyNumber) {
|
||||
val.perDayStudyNumber = val.words.length
|
||||
}
|
||||
if (rIndex > -1) {
|
||||
this.word.studyIndex = rIndex
|
||||
this.word.bookList[this.word.studyIndex].words = shallowReactive(val.words)
|
||||
this.word.bookList[this.word.studyIndex].perDayStudyNumber = val.perDayStudyNumber
|
||||
this.word.bookList[this.word.studyIndex].lastLearnIndex = val.lastLearnIndex
|
||||
this.word.bookList[this.word.studyIndex].userDictId = val.userDictId
|
||||
} else {
|
||||
this.word.bookList.push(getDefaultDict(val))
|
||||
this.word.studyIndex = this.word.bookList.length - 1
|
||||
}
|
||||
},
|
||||
//改变书籍
|
||||
async changeBook(val: Dict) {
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
let r = await add2MyDict({
|
||||
id: val.id,
|
||||
perDayStudyNumber: val.perDayStudyNumber,
|
||||
lastLearnIndex: val.lastLearnIndex,
|
||||
complete: val.complete,
|
||||
})
|
||||
if (!r.success) {
|
||||
return Toast.error(r.msg)
|
||||
}
|
||||
}
|
||||
//把其他的书籍里面的文章数据都删掉,全保存在内存里太卡了
|
||||
this.article.bookList.slice(1).map(v => {
|
||||
if (!v.custom) {
|
||||
v.articles = shallowReactive([])
|
||||
}
|
||||
})
|
||||
let rIndex = this.article.bookList.findIndex((v: Dict) => v.id === val.id)
|
||||
if (rIndex > -1) {
|
||||
this.article.studyIndex = rIndex
|
||||
//不要整个等于,不然统计没了
|
||||
// this.article.bookList[this.article.studyIndex] = getDefaultDict(val)
|
||||
this.article.bookList[this.article.studyIndex].articles = shallowReactive(val.articles)
|
||||
this.article.bookList[this.article.studyIndex].cover = val.cover
|
||||
this.article.bookList[this.article.studyIndex].name = val.name
|
||||
this.article.bookList[this.article.studyIndex].description = val.description
|
||||
} else {
|
||||
this.article.bookList.push(getDefaultDict(val))
|
||||
this.article.studyIndex = this.article.bookList.length - 1
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -97,7 +97,9 @@ export interface Statistics {
|
||||
export enum Sort {
|
||||
normal = 0,
|
||||
random = 1,
|
||||
reverse = 2
|
||||
reverse = 2,
|
||||
reverseAll = 3,
|
||||
randomAll = 4,
|
||||
}
|
||||
|
||||
export enum ShortcutKey {
|
||||
|
||||
Reference in New Issue
Block a user