Merge branch 'master' into dev
# Conflicts: # src/pages/word/DictDetail.vue
This commit is contained in:
@@ -25,7 +25,6 @@ let isInitializing = true // 标记是否正在初始化
|
||||
watch(store.$state, (n: BaseState) => {
|
||||
// 如果正在初始化,不保存数据,避免覆盖
|
||||
if (isInitializing) return
|
||||
console.log('watch')
|
||||
let data = shakeCommonDict(n)
|
||||
set(SAVE_DICT_KEY.key, JSON.stringify({val: data, version: SAVE_DICT_KEY.version}))
|
||||
|
||||
@@ -132,4 +131,4 @@ onMounted(() => {
|
||||
v-model="showTransfer"
|
||||
@ok="init"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
6
src/apis/dict.ts
Normal file
6
src/apis/dict.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import http from '@/utils/http.ts'
|
||||
import { Dict } from '@/types/types.ts'
|
||||
|
||||
export function addDict(params?, data?) {
|
||||
return http<Dict>('dict/addDict', data, params, 'post')
|
||||
}
|
||||
6
src/apis/words.ts
Normal file
6
src/apis/words.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import http from '@/utils/http.ts'
|
||||
import { Dict } from '@/types/types.ts'
|
||||
|
||||
export function wordDelete(params?, data?) {
|
||||
return http<Dict>('word/delete', data, params, 'post')
|
||||
}
|
||||
@@ -1,45 +1,49 @@
|
||||
<script setup lang="tsx">
|
||||
|
||||
import { nextTick, onMounted, useSlots } from "vue";
|
||||
import { Sort } from "@/types/types.ts";
|
||||
import MiniDialog from "@/components/dialog/MiniDialog.vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import { debounce } from "@/utils";
|
||||
import PopConfirm from "@/components/PopConfirm.vue";
|
||||
import Empty from "@/components/Empty.vue";
|
||||
import { nextTick, onMounted, useSlots } from 'vue'
|
||||
import { Sort } from '@/types/types.ts'
|
||||
import MiniDialog from '@/components/dialog/MiniDialog.vue'
|
||||
import BaseIcon from '@/components/BaseIcon.vue'
|
||||
import BaseButton from '@/components/BaseButton.vue'
|
||||
import { debounce } from '@/utils'
|
||||
import PopConfirm from '@/components/PopConfirm.vue'
|
||||
import Empty from '@/components/Empty.vue'
|
||||
import Pagination from '@/components/base/Pagination.vue'
|
||||
import Checkbox from "@/components/base/checkbox/Checkbox.vue";
|
||||
import DeleteIcon from "@/components/icon/DeleteIcon.vue";
|
||||
import Dialog from "@/components/dialog/Dialog.vue";
|
||||
import BaseInput from "@/components/base/BaseInput.vue";
|
||||
import { Host } from "@/config/env.ts";
|
||||
import Checkbox from '@/components/base/checkbox/Checkbox.vue'
|
||||
import DeleteIcon from '@/components/icon/DeleteIcon.vue'
|
||||
import Dialog from '@/components/dialog/Dialog.vue'
|
||||
import BaseInput from '@/components/base/BaseInput.vue'
|
||||
import { Host } from '@/config/env.ts'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
loading?: boolean
|
||||
showToolbar?: boolean
|
||||
showPagination?: boolean
|
||||
exportLoading?: boolean
|
||||
importLoading?: boolean
|
||||
request?: Function
|
||||
list?: any[]
|
||||
}>(), {
|
||||
loading: true,
|
||||
showToolbar: true,
|
||||
showPagination: true,
|
||||
exportLoading: false,
|
||||
importLoading: false,
|
||||
})
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
loading?: boolean
|
||||
showToolbar?: boolean
|
||||
showPagination?: boolean
|
||||
exportLoading?: boolean
|
||||
importLoading?: boolean
|
||||
request?: Function
|
||||
list?: any[]
|
||||
}>(),
|
||||
{
|
||||
loading: true,
|
||||
showToolbar: true,
|
||||
showPagination: true,
|
||||
exportLoading: false,
|
||||
importLoading: false,
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
add: []
|
||||
click: [val: {
|
||||
item: any,
|
||||
index: number
|
||||
}],
|
||||
click: [
|
||||
val: {
|
||||
item: any
|
||||
index: number
|
||||
},
|
||||
]
|
||||
import: [e: Event]
|
||||
export: []
|
||||
del: [ids: number[]],
|
||||
del: [ids: number[]]
|
||||
sort: [type: Sort, pageNo: number, pageSize: number]
|
||||
}>()
|
||||
|
||||
@@ -89,7 +93,7 @@ let showSortDialog = $ref(false)
|
||||
let showSearchInput = $ref(false)
|
||||
let showImportDialog = $ref(false)
|
||||
|
||||
const closeImportDialog = () => showImportDialog = false
|
||||
const closeImportDialog = () => (showImportDialog = false)
|
||||
|
||||
function sort(type: Sort) {
|
||||
if ([Sort.reverse, Sort.random].includes(type)) {
|
||||
@@ -111,7 +115,7 @@ defineExpose({
|
||||
scrollToBottom,
|
||||
scrollToItem,
|
||||
closeImportDialog,
|
||||
getData
|
||||
getData,
|
||||
})
|
||||
|
||||
let loading2 = $ref(false)
|
||||
@@ -122,7 +126,7 @@ let params = $ref({
|
||||
total: 0,
|
||||
list: [],
|
||||
sortType: null,
|
||||
searchKey: ''
|
||||
searchKey: '',
|
||||
})
|
||||
|
||||
function search(key: string) {
|
||||
@@ -159,182 +163,181 @@ function handlePageNo(e) {
|
||||
|
||||
onMounted(async () => {
|
||||
getData()
|
||||
|
||||
})
|
||||
|
||||
defineRender(
|
||||
() => {
|
||||
const d = (item) => <Checkbox
|
||||
modelValue={selectIds.includes(item.id)}
|
||||
onChange={() => toggleSelect(item)}
|
||||
size="large"/>
|
||||
defineRender(() => {
|
||||
const d = item => (
|
||||
<Checkbox
|
||||
modelValue={selectIds.includes(item.id)}
|
||||
onChange={() => toggleSelect(item)}
|
||||
size="large"
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<div class="flex flex-col gap-3">
|
||||
{
|
||||
props.showToolbar && <div>
|
||||
{
|
||||
showSearchInput ? (
|
||||
<div class="flex gap-4">
|
||||
<BaseInput
|
||||
clearable
|
||||
modelValue={params.searchKey}
|
||||
onUpdate:modelValue={debounce(e => search(e), 500)}
|
||||
class="flex-1"
|
||||
autofocus>
|
||||
{{
|
||||
subfix: () => <IconFluentSearch24Regular
|
||||
class="text-lg text-gray"
|
||||
/>
|
||||
}}
|
||||
</BaseInput>
|
||||
<BaseButton onClick={cancelSearch}>取消</BaseButton>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex justify-between">
|
||||
<div class="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
disabled={!params.list.length}
|
||||
onChange={() => toggleSelectAll()}
|
||||
modelValue={selectAll}
|
||||
size="large"/>
|
||||
<span>{selectIds.length} / {params.total}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 relative">
|
||||
{
|
||||
selectIds.length ?
|
||||
<PopConfirm title="确认删除所有选中数据?"
|
||||
onConfirm={handleBatchDel}
|
||||
>
|
||||
<BaseIcon class="del" title="删除">
|
||||
<DeleteIcon/>
|
||||
</BaseIcon>
|
||||
</PopConfirm>
|
||||
: null
|
||||
}
|
||||
<BaseIcon
|
||||
onClick={() => showImportDialog = true}
|
||||
title="导入">
|
||||
<IconSystemUiconsImport/>
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
onClick={() => emit('export')}
|
||||
title="导出">
|
||||
{props.exportLoading ? <IconEosIconsLoading/> : <IconPhExportLight/>}
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
onClick={() => emit('add')}
|
||||
title="添加单词">
|
||||
<IconFluentAdd20Regular/>
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
disabled={!params.list.length}
|
||||
title="改变顺序"
|
||||
onClick={() => showSortDialog = !showSortDialog}
|
||||
>
|
||||
<IconFluentArrowSort20Regular/>
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
disabled={!params.list.length}
|
||||
onClick={() => showSearchInput = !showSearchInput}
|
||||
title="搜索">
|
||||
<IconFluentSearch20Regular/>
|
||||
</BaseIcon>
|
||||
<MiniDialog
|
||||
modelValue={showSortDialog}
|
||||
onUpdate:modelValue={e => showSortDialog = e}
|
||||
style="width: 8rem;"
|
||||
>
|
||||
<div class="mini-row-title">
|
||||
列表顺序设置
|
||||
</div>
|
||||
<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.random)}>随机当前页</BaseButton>
|
||||
<BaseButton onClick={() => sort(Sort.randomAll)}>随机所有</BaseButton>
|
||||
</div>
|
||||
</MiniDialog>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
loading2 ?
|
||||
<div class="h-full w-full center text-4xl">
|
||||
<IconEosIconsLoading color="gray"/>
|
||||
</div>
|
||||
: params.list.length ? (
|
||||
<>
|
||||
<div class="flex-1 overflow-auto"
|
||||
ref={e => listRef = e}>
|
||||
{params.list.map((item, index) => {
|
||||
return (
|
||||
<div class="list-item-wrapper"
|
||||
key={item.word}
|
||||
>
|
||||
{s.default({
|
||||
checkbox: d,
|
||||
item,
|
||||
index: (params.pageSize * (params.pageNo - 1)) + index + 1
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{
|
||||
props.showPagination && <div class="flex justify-end">
|
||||
<Pagination
|
||||
currentPage={params.pageNo}
|
||||
onUpdate:current-page={handlePageNo}
|
||||
pageSize={params.pageSize}
|
||||
onUpdate:page-size={(e) => params.pageSize = e}
|
||||
pageSizes={[20, 50, 100, 200]}
|
||||
layout="total,sizes"
|
||||
total={params.total}/>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
) : <Empty/>
|
||||
}
|
||||
|
||||
<Dialog modelValue={showImportDialog}
|
||||
onUpdate:modelValue={closeImportDialog}
|
||||
title="导入教程"
|
||||
>
|
||||
<div className="w-100 p-4 pt-0">
|
||||
<div>请按照模板的格式来填写数据</div>
|
||||
<div class="color-red">单词项为必填,其他项可不填</div>
|
||||
<div>翻译:一行一个翻译,前面词性,后面内容(如n.取消);多个翻译请换行</div>
|
||||
<div>例句:一行原文,一行译文;多个请换<span class="color-red">两</span>行</div>
|
||||
<div>短语:一行原文,一行译文;多个请换<span class="color-red">两</span>行</div>
|
||||
<div>同义词、同根词、词源:请前往官方词典,然后编辑其中某个单词,参考其格式</div>
|
||||
<div class="mt-6">
|
||||
模板下载地址:<a href={`https://${Host}/libs/单词导入模板.xlsx`}>单词导入模板</a>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<BaseButton
|
||||
onClick={() => {
|
||||
let d: HTMLDivElement = document.querySelector('#upload-trigger')
|
||||
d.click()
|
||||
}}
|
||||
loading={props.importLoading}>导入</BaseButton>
|
||||
<input
|
||||
id="upload-trigger"
|
||||
type="file"
|
||||
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
|
||||
onChange={e => emit('import', e)}
|
||||
class="w-0 h-0 opacity-0"/>
|
||||
</div>
|
||||
return (
|
||||
<div class="flex flex-col gap-3">
|
||||
{props.showToolbar && (
|
||||
<div>
|
||||
{showSearchInput ? (
|
||||
<div class="flex gap-4">
|
||||
<BaseInput
|
||||
clearable
|
||||
modelValue={params.searchKey}
|
||||
onUpdate:modelValue={debounce(e => search(e), 500)}
|
||||
class="flex-1"
|
||||
autofocus
|
||||
>
|
||||
{{
|
||||
subfix: () => <IconFluentSearch24Regular class="text-lg text-gray" />,
|
||||
}}
|
||||
</BaseInput>
|
||||
<BaseButton onClick={cancelSearch}>取消</BaseButton>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex justify-between">
|
||||
<div class="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
disabled={!params.list.length}
|
||||
onChange={() => toggleSelectAll()}
|
||||
modelValue={selectAll}
|
||||
size="large"
|
||||
/>
|
||||
<span>
|
||||
{selectIds.length} / {params.total}
|
||||
</span>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<div class="flex gap-2 relative">
|
||||
{selectIds.length ? (
|
||||
<PopConfirm title="确认删除所有选中数据?" onConfirm={handleBatchDel}>
|
||||
<BaseIcon class="del" title="删除">
|
||||
<DeleteIcon />
|
||||
</BaseIcon>
|
||||
</PopConfirm>
|
||||
) : null}
|
||||
<BaseIcon onClick={() => (showImportDialog = true)} title="导入">
|
||||
<IconSystemUiconsImport />
|
||||
</BaseIcon>
|
||||
<BaseIcon onClick={() => emit('export')} title="导出">
|
||||
{props.exportLoading ? <IconEosIconsLoading /> : <IconPhExportLight />}
|
||||
</BaseIcon>
|
||||
<BaseIcon onClick={() => emit('add')} title="添加单词">
|
||||
<IconFluentAdd20Regular />
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
disabled={!params.list.length}
|
||||
title="改变顺序"
|
||||
onClick={() => (showSortDialog = !showSortDialog)}
|
||||
>
|
||||
<IconFluentArrowSort20Regular />
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
disabled={!params.list.length}
|
||||
onClick={() => (showSearchInput = !showSearchInput)}
|
||||
title="搜索"
|
||||
>
|
||||
<IconFluentSearch20Regular />
|
||||
</BaseIcon>
|
||||
<MiniDialog
|
||||
modelValue={showSortDialog}
|
||||
onUpdate:modelValue={e => (showSortDialog = e)}
|
||||
style="width: 8rem;"
|
||||
>
|
||||
<div class="mini-row-title">列表顺序设置</div>
|
||||
<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.random)}>随机当前页</BaseButton>
|
||||
<BaseButton onClick={() => sort(Sort.randomAll)}>随机所有</BaseButton>
|
||||
</div>
|
||||
</MiniDialog>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="relative flex-1 overflow-hidden">
|
||||
{params.list.length ? (
|
||||
<div class="overflow-auto h-full" ref={e => (listRef = e)}>
|
||||
{params.list.map((item, index) => {
|
||||
return (
|
||||
<div class="list-item-wrapper" key={item.word}>
|
||||
{s.default({
|
||||
checkbox: d,
|
||||
item,
|
||||
index: params.pageSize * (params.pageNo - 1) + index + 1,
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
) : !loading2 ? (
|
||||
<Empty />
|
||||
) : null}
|
||||
{loading2 && (
|
||||
<div class="absolute top-0 left-0 bottom-0 right-0 bg-black bg-op-10 center text-4xl">
|
||||
<IconEosIconsLoading color="gray" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{props.showPagination && (
|
||||
<div class="flex justify-end">
|
||||
<Pagination
|
||||
currentPage={params.pageNo}
|
||||
onUpdate:current-page={handlePageNo}
|
||||
pageSize={params.pageSize}
|
||||
onUpdate:page-size={e => (params.pageSize = e)}
|
||||
pageSizes={[20, 50, 100, 200]}
|
||||
layout="total,sizes"
|
||||
total={params.total}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Dialog
|
||||
modelValue={showImportDialog}
|
||||
onUpdate:modelValue={closeImportDialog}
|
||||
title="导入教程"
|
||||
>
|
||||
<div className="w-100 p-4 pt-0">
|
||||
<div>请按照模板的格式来填写数据</div>
|
||||
<div class="color-red">单词项为必填,其他项可不填</div>
|
||||
<div>翻译:一行一个翻译,前面词性,后面内容(如n.取消);多个翻译请换行</div>
|
||||
<div>
|
||||
例句:一行原文,一行译文;多个请换<span class="color-red">两</span>行
|
||||
</div>
|
||||
<div>
|
||||
短语:一行原文,一行译文;多个请换<span class="color-red">两</span>行
|
||||
</div>
|
||||
<div>同义词、同根词、词源:请前往官方词典,然后编辑其中某个单词,参考其格式</div>
|
||||
<div class="mt-6">
|
||||
模板下载地址:<a href={`https://${Host}/libs/单词导入模板.xlsx`}>单词导入模板</a>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<BaseButton
|
||||
onClick={() => {
|
||||
let d: HTMLDivElement = document.querySelector('#upload-trigger')
|
||||
d.click()
|
||||
}}
|
||||
loading={props.importLoading}
|
||||
>
|
||||
导入
|
||||
</BaseButton>
|
||||
<input
|
||||
id="upload-trigger"
|
||||
type="file"
|
||||
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
|
||||
onChange={e => emit('import', e)}
|
||||
class="w-0 h-0 opacity-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { offset } from "@floating-ui/dom";
|
||||
import { offset } from '@floating-ui/dom'
|
||||
|
||||
export const GITHUB = 'https://github.com/zyronon/TypeWords'
|
||||
export const Host = 'typewords.cc'
|
||||
@@ -7,25 +7,27 @@ export const Origin = `https://${Host}`
|
||||
export const APP_NAME = 'Type Words'
|
||||
|
||||
const common = {
|
||||
word_dict_list_version: 1
|
||||
word_dict_list_version: 1,
|
||||
}
|
||||
const map = {
|
||||
DEV: {
|
||||
API: 'http://localhost/',
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export const ENV = Object.assign(map['DEV'], common)
|
||||
|
||||
export let AppEnv = {
|
||||
TOKEN: localStorage.getItem('token') ?? '',
|
||||
IS_OFFICIAL: false,
|
||||
IS_OFFICIAL: true,
|
||||
IS_LOGIN: false,
|
||||
CAN_REQUEST: false
|
||||
CAN_REQUEST: false,
|
||||
}
|
||||
|
||||
AppEnv.IS_LOGIN = !!AppEnv.TOKEN
|
||||
AppEnv.CAN_REQUEST = AppEnv.IS_LOGIN && AppEnv.IS_OFFICIAL
|
||||
// AppEnv.IS_OFFICIAL = true
|
||||
// AppEnv.CAN_REQUEST = true
|
||||
// console.log('AppEnv.CAN_REQUEST',AppEnv.CAN_REQUEST)
|
||||
|
||||
export const RESOURCE_PATH = ENV.API + 'static'
|
||||
@@ -38,41 +40,41 @@ export const DICT_LIST = {
|
||||
ARTICLE: {
|
||||
ALL: `/list/article.json`,
|
||||
RECOMMENDED: `/list/article.json`,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export const SoundFileOptions = [
|
||||
{value: '机械键盘', label: '机械键盘'},
|
||||
{value: '机械键盘1', label: '机械键盘1'},
|
||||
{value: '机械键盘2', label: '机械键盘2'},
|
||||
{value: '老式机械键盘', label: '老式机械键盘'},
|
||||
{value: '笔记本键盘', label: '笔记本键盘'},
|
||||
{ value: '机械键盘', label: '机械键盘' },
|
||||
{ value: '机械键盘1', label: '机械键盘1' },
|
||||
{ value: '机械键盘2', label: '机械键盘2' },
|
||||
{ value: '老式机械键盘', label: '老式机械键盘' },
|
||||
{ value: '笔记本键盘', label: '笔记本键盘' },
|
||||
]
|
||||
export const APP_VERSION = {
|
||||
key: 'type-words-app-version',
|
||||
version: 2
|
||||
version: 2,
|
||||
}
|
||||
export const SAVE_DICT_KEY = {
|
||||
key: 'typing-word-dict',
|
||||
version: 4
|
||||
version: 4,
|
||||
}
|
||||
export const SAVE_SETTING_KEY = {
|
||||
key: 'typing-word-setting',
|
||||
version: 17
|
||||
version: 17,
|
||||
}
|
||||
export const EXPORT_DATA_KEY = {
|
||||
key: 'typing-word-export',
|
||||
version: 4
|
||||
version: 4,
|
||||
}
|
||||
export const LOCAL_FILE_KEY = 'typing-word-files'
|
||||
|
||||
export const PracticeSaveWordKey = {
|
||||
key: 'PracticeSaveWord',
|
||||
version: 1
|
||||
version: 1,
|
||||
}
|
||||
export const PracticeSaveArticleKey = {
|
||||
key: 'PracticeSaveArticle',
|
||||
version: 1
|
||||
version: 1,
|
||||
}
|
||||
|
||||
export const TourConfig = {
|
||||
@@ -80,21 +82,22 @@ export const TourConfig = {
|
||||
defaultStepOptions: {
|
||||
canClickTarget: false,
|
||||
classes: 'shadow-md bg-purple-dark',
|
||||
cancelIcon: {enabled: true},
|
||||
cancelIcon: { enabled: true },
|
||||
modalOverlayOpeningPadding: 10,
|
||||
modalOverlayOpeningRadius: 6,
|
||||
floatingUIOptions: {
|
||||
middleware: [offset({mainAxis: 30})]
|
||||
middleware: [offset({ mainAxis: 30 })],
|
||||
},
|
||||
},
|
||||
total: 7
|
||||
total: 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',
|
||||
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`,
|
||||
JSZIP: `${Origin}/libs/jszip.min.js`,
|
||||
XLSX: `${Origin}/libs/xlsx.full.min.js`,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="tsx">
|
||||
import { DictId, Sort } from '@/types/types.ts'
|
||||
|
||||
import { detail } from '@/apis'
|
||||
import { add2MyDict, detail } from '@/apis'
|
||||
import BackIcon from '@/components/BackIcon.vue'
|
||||
import BaseButton from '@/components/BaseButton.vue'
|
||||
import BaseIcon from '@/components/BaseIcon.vue'
|
||||
@@ -35,8 +35,9 @@ import {
|
||||
} from '@/utils'
|
||||
import { MessageBox } from '@/utils/MessageBox.tsx'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { computed, onMounted, reactive, ref, shallowReactive, shallowRef, watch } from 'vue'
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { wordDelete } from '@/apis/words.ts'
|
||||
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const base = useBaseStore()
|
||||
@@ -131,18 +132,52 @@ async function onSubmitWord() {
|
||||
})
|
||||
}
|
||||
|
||||
function batchDel(ids: string[]) {
|
||||
ids.map(id => {
|
||||
let rIndex2 = allList.findIndex(v => v.id === id)
|
||||
if (rIndex2 > -1) {
|
||||
if (id === wordForm.id) {
|
||||
wordForm = getDefaultFormWord()
|
||||
async function batchDel(ids: string[]) {
|
||||
let localHandle = () => {
|
||||
ids.map(id => {
|
||||
let rIndex2 = allList.findIndex(v => v.id === id)
|
||||
if (rIndex2 > -1) {
|
||||
if (id === wordForm.id) {
|
||||
wordForm = getDefaultFormWord()
|
||||
}
|
||||
allList.splice(rIndex2, 1)
|
||||
}
|
||||
})
|
||||
tableRef.value.getData()
|
||||
syncDictInMyStudyList()
|
||||
}
|
||||
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
if (dict.custom) {
|
||||
if (dict.sync) {
|
||||
let res = await wordDelete(null, {
|
||||
wordIds: ids,
|
||||
userDictId: dict?.userDictId,
|
||||
dictId: dict.id,
|
||||
})
|
||||
if (res.success) {
|
||||
tableRef.value.getData()
|
||||
} else {
|
||||
return Toast.error(res.msg ?? '删除失败')
|
||||
}
|
||||
} else {
|
||||
localHandle()
|
||||
}
|
||||
} else {
|
||||
let r = await add2MyDict({
|
||||
id: dict.id,
|
||||
perDayStudyNumber: dict.perDayStudyNumber,
|
||||
lastLearnIndex: dict.lastLearnIndex,
|
||||
complete: dict.complete,
|
||||
})
|
||||
if (!r.success) return Toast.error(r.msg)
|
||||
else {
|
||||
dict.userDictId = r.data
|
||||
}
|
||||
allList.splice(rIndex2, 1)
|
||||
}
|
||||
})
|
||||
tableRef.value.getData()
|
||||
syncDictInMyStudyList()
|
||||
} else {
|
||||
localHandle()
|
||||
}
|
||||
}
|
||||
|
||||
//把word对象的字段全转成字符串
|
||||
@@ -220,12 +255,10 @@ onMounted(async () => {
|
||||
}
|
||||
if (base.word.bookList.find(book => book.id === runtimeStore.editDict.id)) {
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
//todo 优化:这里只返回详情
|
||||
let res = await detail({ id: runtimeStore.editDict.id })
|
||||
if (res.success) {
|
||||
runtimeStore.editDict.statistics = res.data.statistics
|
||||
if (res.data.words.length) {
|
||||
runtimeStore.editDict.words = res.data.words
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -362,12 +395,12 @@ function importData(e) {
|
||||
)
|
||||
} else {
|
||||
tableRef.value.closeImportDialog()
|
||||
e.target.value = ''
|
||||
importLoading = false
|
||||
allList = runtimeStore.editDict.words
|
||||
tableRef.value.getData()
|
||||
syncDictInMyStudyList()
|
||||
Toast.success('导入成功!')
|
||||
e.target.value = ''
|
||||
importLoading = false
|
||||
allList = runtimeStore.editDict.words
|
||||
tableRef.value.getData()
|
||||
syncDictInMyStudyList()
|
||||
Toast.success('导入成功!')
|
||||
}
|
||||
} else {
|
||||
Toast.warning('导入失败!原因:没有数据/未认别到数据')
|
||||
@@ -466,17 +499,52 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
const dict = $computed(() => runtimeStore.editDict)
|
||||
|
||||
//获取本地单词列表
|
||||
function getLocalList({ pageNo, pageSize, searchKey }) {
|
||||
let list = allList
|
||||
let total = allList.length
|
||||
if (searchKey.trim()) {
|
||||
list = allList.filter(v => v.word.toLowerCase().includes(searchKey.trim().toLowerCase()))
|
||||
total = list.length
|
||||
}
|
||||
list = list.slice((pageNo - 1) * pageSize, (pageNo - 1) * pageSize + pageSize)
|
||||
return { list, total }
|
||||
}
|
||||
|
||||
async function requestList({ pageNo, pageSize, searchKey }) {
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
} else {
|
||||
let list = allList
|
||||
let total = allList.length
|
||||
if (searchKey.trim()) {
|
||||
list = allList.filter(v => v.word.toLowerCase().includes(searchKey.trim().toLowerCase()))
|
||||
total = list.length
|
||||
if (
|
||||
!dict.custom &&
|
||||
![DictId.wordCollect, DictId.wordWrong, DictId.wordKnown].includes(dict.en_name || dict.id)
|
||||
) {
|
||||
// 非自定义词典,直接请求json
|
||||
|
||||
//如果没数据则请求
|
||||
if (!allList.length) {
|
||||
let r = await _getDictDataByUrl(dict)
|
||||
allList = r.words
|
||||
}
|
||||
list = list.slice((pageNo - 1) * pageSize, (pageNo - 1) * pageSize + pageSize)
|
||||
return { list, total }
|
||||
return getLocalList({ pageNo, pageSize, searchKey })
|
||||
} else {
|
||||
// 自定义词典
|
||||
|
||||
//如果登录了,则请求后端数据
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
//todo 加上sync标记
|
||||
if (dict.sync || true) {
|
||||
//todo 优化:这里应该只返回列表
|
||||
let res = await detail({ id: dict.id, pageNo, pageSize })
|
||||
if (res.success) {
|
||||
return { list: res.data.words, total: res.data.length }
|
||||
}
|
||||
return { list: [], total: 0 }
|
||||
}
|
||||
} else {
|
||||
//未登录则用本地保存的数据
|
||||
allList = dict.words
|
||||
}
|
||||
return getLocalList({ pageNo, pageSize, searchKey })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user