wip
This commit is contained in:
1
components.d.ts
vendored
1
components.d.ts
vendored
@@ -18,6 +18,7 @@ declare module 'vue' {
|
||||
BaseList: typeof import('./src/components/list/BaseList.vue')['default']
|
||||
BasePage: typeof import('./src/components/BasePage.vue')['default']
|
||||
BaseTable: typeof import('./src/components/BaseTable.vue')['default']
|
||||
BaseTable2: typeof import('./src/components/BaseTable2.vue')['default']
|
||||
Book: typeof import('./src/components/Book.vue')['default']
|
||||
ChannelIcons: typeof import('./src/components/ChannelIcons/ChannelIcons.vue')['default']
|
||||
Checkbox: typeof import('./src/components/base/checkbox/Checkbox.vue')['default']
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup lang="tsx">
|
||||
|
||||
import {nextTick, useSlots} from "vue";
|
||||
import {Sort} from "@/types/types.ts";
|
||||
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 {cloneDeep, debounce, reverse, shuffle} from "@/utils";
|
||||
import { cloneDeep, debounce, reverse, shuffle } from "@/utils";
|
||||
import PopConfirm from "@/components/PopConfirm.vue";
|
||||
import Empty from "@/components/Empty.vue";
|
||||
import Pagination from '@/components/base/Pagination.vue'
|
||||
@@ -14,7 +14,7 @@ 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 { Host } from "@/config/env.ts";
|
||||
|
||||
let list = defineModel('list')
|
||||
|
||||
@@ -27,6 +27,8 @@ const props = withDefaults(defineProps<{
|
||||
del?: Function
|
||||
batchDel?: Function
|
||||
add?: Function
|
||||
request?: Function
|
||||
total: number
|
||||
}>(), {
|
||||
loading: true,
|
||||
showToolbar: true,
|
||||
@@ -35,6 +37,7 @@ const props = withDefaults(defineProps<{
|
||||
importLoading: false,
|
||||
del: () => void 0,
|
||||
add: () => void 0,
|
||||
request: () => void 0,
|
||||
batchDel: () => void 0
|
||||
})
|
||||
|
||||
@@ -63,11 +66,10 @@ function scrollToTop() {
|
||||
|
||||
function scrollToItem(index: number) {
|
||||
nextTick(() => {
|
||||
listRef?.children[index]?.scrollIntoView({block: 'center', behavior: 'smooth'})
|
||||
listRef?.children[index]?.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
let pageNo = $ref(1)
|
||||
let pageSize = $ref(50)
|
||||
let currentList = $computed(() => {
|
||||
@@ -83,6 +85,12 @@ 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)
|
||||
if (rIndex > -1) {
|
||||
@@ -136,6 +144,7 @@ defineExpose({
|
||||
scrollToItem,
|
||||
closeImportDialog
|
||||
})
|
||||
|
||||
defineRender(
|
||||
() => {
|
||||
const d = (item) => <Checkbox
|
||||
@@ -175,136 +184,136 @@ defineRender(
|
||||
<span>{selectIds.length} / {list.value.length}</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('exportData')}
|
||||
title="导出">
|
||||
{props.exportLoading ? <IconEosIconsLoading/> : <IconPhExportLight/>}
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
onClick={props.add}
|
||||
title="添加单词">
|
||||
<IconFluentAdd20Regular/>
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
disabled={!currentList.length}
|
||||
title="改变顺序"
|
||||
onClick={() => showSortDialog = !showSortDialog}
|
||||
>
|
||||
<IconFluentArrowSort20Regular/>
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
disabled={!currentList.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="mini-row">
|
||||
<BaseButton size="small" onClick={() => sort(Sort.reverse)}>翻转
|
||||
</BaseButton>
|
||||
<BaseButton size="small" onClick={() => sort(Sort.random)}>随机</BaseButton>
|
||||
</div>
|
||||
</MiniDialog>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
props.loading ?
|
||||
<div class="h-full w-full center text-4xl">
|
||||
<IconEosIconsLoading color="gray"/>
|
||||
</div>
|
||||
: currentList.length ? (
|
||||
<>
|
||||
<div class="flex-1 overflow-auto"
|
||||
ref={e => listRef = e}>
|
||||
{currentList.map((item, index) => {
|
||||
return (
|
||||
<div class="list-item-wrapper"
|
||||
key={item.word}
|
||||
>
|
||||
{s.default({checkbox: d, item, index: (pageSize * (pageNo - 1)) + index + 1})}
|
||||
</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('exportData')}
|
||||
title="导出">
|
||||
{props.exportLoading ? <IconEosIconsLoading/> : <IconPhExportLight/>}
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
onClick={props.add}
|
||||
title="添加单词">
|
||||
<IconFluentAdd20Regular/>
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
disabled={!currentList.length}
|
||||
title="改变顺序"
|
||||
onClick={() => showSortDialog = !showSortDialog}
|
||||
>
|
||||
<IconFluentArrowSort20Regular/>
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
disabled={!currentList.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="mini-row">
|
||||
<BaseButton size="small" onClick={() => sort(Sort.reverse)}>翻转
|
||||
</BaseButton>
|
||||
<BaseButton size="small" onClick={() => sort(Sort.random)}>随机</BaseButton>
|
||||
</div>
|
||||
</MiniDialog>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
}
|
||||
</div>
|
||||
{
|
||||
props.showPagination && <div class="flex justify-end">
|
||||
<Pagination
|
||||
currentPage={pageNo}
|
||||
onUpdate:current-page={handlePageNo}
|
||||
pageSize={pageSize}
|
||||
onUpdate:page-size={(e) => pageSize = e}
|
||||
pageSizes={[20, 50, 100, 200]}
|
||||
layout="prev, pager, next"
|
||||
total={list.value.length}/>
|
||||
}
|
||||
{
|
||||
props.loading ?
|
||||
<div class="h-full w-full center text-4xl">
|
||||
<IconEosIconsLoading color="gray"/>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
) : <Empty/>
|
||||
}
|
||||
: currentList.length ? (
|
||||
<>
|
||||
<div class="flex-1 overflow-auto"
|
||||
ref={e => listRef = e}>
|
||||
{currentList.map((item, index) => {
|
||||
return (
|
||||
<div class="list-item-wrapper"
|
||||
key={item.word}
|
||||
>
|
||||
{s.default({ checkbox: d, item, index: (pageSize * (pageNo - 1)) + index + 1 })}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{
|
||||
props.showPagination && <div class="flex justify-end">
|
||||
<Pagination
|
||||
currentPage={pageNo}
|
||||
onUpdate:current-page={handlePageNo}
|
||||
pageSize={pageSize}
|
||||
onUpdate:page-size={(e) => pageSize = e}
|
||||
pageSizes={[20, 50, 100, 200]}
|
||||
layout="prev, pager, next"
|
||||
total={props.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('importData', e)}
|
||||
class="w-0 h-0 opacity-0"/>
|
||||
</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('importData', e)}
|
||||
class="w-0 h-0 opacity-0"/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
||||
313
src/components/BaseTable2.vue
Normal file
313
src/components/BaseTable2.vue
Normal file
@@ -0,0 +1,313 @@
|
||||
<script setup lang="tsx">
|
||||
|
||||
import { nextTick, 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 { cloneDeep, debounce, reverse, shuffle } from "@/utils";
|
||||
import PopConfirm from "@/components/PopConfirm.vue";
|
||||
import Empty from "@/components/Empty.vue";
|
||||
import Pagination from '@/components/base/Pagination.vue'
|
||||
import Toast from '@/components/base/toast/Toast.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";
|
||||
|
||||
let list = defineModel('list')
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
loading?: boolean
|
||||
showToolbar?: boolean
|
||||
showPagination?: boolean
|
||||
exportLoading?: boolean
|
||||
importLoading?: boolean
|
||||
del?: Function
|
||||
batchDel?: Function
|
||||
add?: Function
|
||||
total: number
|
||||
}>(), {
|
||||
loading: true,
|
||||
showToolbar: true,
|
||||
showPagination: true,
|
||||
exportLoading: false,
|
||||
importLoading: false,
|
||||
del: () => void 0,
|
||||
add: () => void 0,
|
||||
batchDel: () => void 0
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: [val: {
|
||||
item: any,
|
||||
index: number
|
||||
}],
|
||||
importData: [e: Event]
|
||||
exportData: []
|
||||
}>()
|
||||
|
||||
let listRef: any = $ref()
|
||||
|
||||
function scrollToBottom() {
|
||||
nextTick(() => {
|
||||
listRef?.scrollTo(0, listRef.scrollHeight)
|
||||
})
|
||||
}
|
||||
|
||||
function scrollToTop() {
|
||||
nextTick(() => {
|
||||
listRef?.scrollTo(0, 0)
|
||||
})
|
||||
}
|
||||
|
||||
function scrollToItem(index: number) {
|
||||
nextTick(() => {
|
||||
listRef?.children[index]?.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
let pageNo = $ref(1)
|
||||
let pageSize = $ref(50)
|
||||
let currentList = $computed(() => {
|
||||
if (searchKey) {
|
||||
return list.value.filter(v => v.word.includes(searchKey))
|
||||
}
|
||||
if (!props.showPagination) return list.value
|
||||
return list.value.slice((pageNo - 1) * pageSize, (pageNo - 1) * pageSize + pageSize)
|
||||
})
|
||||
|
||||
let selectIds = $ref([])
|
||||
let selectAll = $computed(() => {
|
||||
return !!selectIds.length
|
||||
})
|
||||
|
||||
function toggleSelect(item) {
|
||||
let rIndex = selectIds.findIndex(v => v === item.id)
|
||||
if (rIndex > -1) {
|
||||
selectIds.splice(rIndex, 1)
|
||||
} else {
|
||||
selectIds.push(item.id)
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSelectAll() {
|
||||
if (selectAll) {
|
||||
selectIds = []
|
||||
} else {
|
||||
selectIds = currentList.map(v => v.id)
|
||||
}
|
||||
}
|
||||
|
||||
let searchKey = $ref('')
|
||||
let showSortDialog = $ref(false)
|
||||
let showSearchInput = $ref(false)
|
||||
let showImportDialog = $ref(false)
|
||||
|
||||
const closeImportDialog = () => showImportDialog = false
|
||||
|
||||
function sort(type: Sort) {
|
||||
if (type === Sort.reverse) {
|
||||
Toast.success('已翻转排序')
|
||||
list.value = reverse(cloneDeep(list.value))
|
||||
}
|
||||
if (type === Sort.random) {
|
||||
Toast.success('已随机排序')
|
||||
list.value = shuffle(cloneDeep(list.value))
|
||||
}
|
||||
showSortDialog = false
|
||||
}
|
||||
|
||||
function handleBatchDel() {
|
||||
props.batchDel(selectIds)
|
||||
selectIds = []
|
||||
}
|
||||
|
||||
function handlePageNo(e) {
|
||||
pageNo = e
|
||||
scrollToTop()
|
||||
}
|
||||
|
||||
const s = useSlots()
|
||||
|
||||
defineExpose({
|
||||
scrollToBottom,
|
||||
scrollToItem,
|
||||
closeImportDialog
|
||||
})
|
||||
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={searchKey}
|
||||
onUpdate:modelValue={debounce(e => searchKey = e)}
|
||||
class="flex-1"
|
||||
autofocus>
|
||||
{{
|
||||
subfix: () => <IconFluentSearch24Regular
|
||||
class="text-lg text-gray"
|
||||
/>
|
||||
}}
|
||||
</BaseInput>
|
||||
<BaseButton onClick={() => (showSearchInput = false, searchKey = '')}>取消</BaseButton>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex justify-between">
|
||||
<div class="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
disabled={!currentList.length}
|
||||
onChange={() => toggleSelectAll()}
|
||||
modelValue={selectAll}
|
||||
size="large"/>
|
||||
<span>{selectIds.length} / {list.value.length}</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('exportData')}
|
||||
title="导出">
|
||||
{props.exportLoading ? <IconEosIconsLoading/> : <IconPhExportLight/>}
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
onClick={props.add}
|
||||
title="添加单词">
|
||||
<IconFluentAdd20Regular/>
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
disabled={!currentList.length}
|
||||
title="改变顺序"
|
||||
onClick={() => showSortDialog = !showSortDialog}
|
||||
>
|
||||
<IconFluentArrowSort20Regular/>
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
disabled={!currentList.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="mini-row">
|
||||
<BaseButton size="small" onClick={() => sort(Sort.reverse)}>翻转
|
||||
</BaseButton>
|
||||
<BaseButton size="small" onClick={() => sort(Sort.random)}>随机</BaseButton>
|
||||
</div>
|
||||
</MiniDialog>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
props.loading ?
|
||||
<div class="h-full w-full center text-4xl">
|
||||
<IconEosIconsLoading color="gray"/>
|
||||
</div>
|
||||
: currentList.length ? (
|
||||
<>
|
||||
<div class="flex-1 overflow-auto"
|
||||
ref={e => listRef = e}>
|
||||
{currentList.map((item, index) => {
|
||||
return (
|
||||
<div class="list-item-wrapper"
|
||||
key={item.word}
|
||||
>
|
||||
{s.default({ checkbox: d, item, index: (pageSize * (pageNo - 1)) + index + 1 })}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{
|
||||
props.showPagination && <div class="flex justify-end">
|
||||
<Pagination
|
||||
currentPage={pageNo}
|
||||
onUpdate:current-page={handlePageNo}
|
||||
pageSize={pageSize}
|
||||
onUpdate:page-size={(e) => pageSize = e}
|
||||
pageSizes={[20, 50, 100, 200]}
|
||||
layout="prev, pager, next"
|
||||
total={props.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('importData', e)}
|
||||
class="w-0 h-0 opacity-0"/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -19,7 +19,7 @@ export const ENV = Object.assign(map['DEV'], common)
|
||||
|
||||
export let AppEnv = {
|
||||
TOKEN: localStorage.getItem('token') ?? '',
|
||||
IS_OFFICIAL: true,
|
||||
IS_OFFICIAL: false,
|
||||
IS_LOGIN: false,
|
||||
CAN_REQUEST: false
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ const base = useBaseStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const isMob = isMobile()
|
||||
|
||||
let loading = $ref(false)
|
||||
|
||||
let list = $computed({
|
||||
@@ -65,8 +64,8 @@ let wordForm = $ref(getDefaultFormWord())
|
||||
let wordFormRef = $ref()
|
||||
const wordRules = reactive({
|
||||
word: [
|
||||
{required: true, message: '请输入单词', trigger: 'blur'},
|
||||
{max: 100, message: '名称不能超过100个字符', trigger: 'blur'},
|
||||
{ required: true, message: '请输入单词', trigger: 'blur' },
|
||||
{ max: 100, message: '名称不能超过100个字符', trigger: 'blur' },
|
||||
],
|
||||
})
|
||||
let studyLoading = $ref(false)
|
||||
@@ -201,10 +200,9 @@ onMounted(async () => {
|
||||
let r = await _getDictDataByUrl(runtimeStore.editDict)
|
||||
runtimeStore.editDict = r
|
||||
}
|
||||
|
||||
if (base.word.bookList.find(book => book.id === runtimeStore.editDict.id)) {
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
let res = await detail({id: runtimeStore.editDict.id})
|
||||
let res = await detail({ id: runtimeStore.editDict.id })
|
||||
if (res.success) {
|
||||
runtimeStore.editDict.statistics = res.data.statistics
|
||||
if (res.data.words.length) {
|
||||
@@ -213,6 +211,7 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
list2 = runtimeStore.editDict.words
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
@@ -227,7 +226,7 @@ let showPracticeSettingDialog = $ref(false)
|
||||
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
const {nav} = useNav()
|
||||
const { nav } = useNav()
|
||||
|
||||
//todo 可以和首页合并
|
||||
async function startPractice(query = {}) {
|
||||
@@ -244,7 +243,7 @@ async function startPractice(query = {}) {
|
||||
wordPracticeMode: settingStore.wordPracticeMode
|
||||
})
|
||||
let currentStudy = getCurrentStudyWord()
|
||||
nav('practice-words/' + store.sdict.id, query, {taskWords: currentStudy})
|
||||
nav('practice-words/' + store.sdict.id, query, { taskWords: currentStudy })
|
||||
}
|
||||
|
||||
async function addMyStudyList() {
|
||||
@@ -263,7 +262,6 @@ async function startTest() {
|
||||
await base.changeDict(runtimeStore.editDict)
|
||||
loading = false
|
||||
nav('word-test/' + store.sdict.id)
|
||||
|
||||
}
|
||||
|
||||
let exportLoading = $ref(false)
|
||||
@@ -279,7 +277,7 @@ function importData(e) {
|
||||
let data = s.target.result;
|
||||
importLoading = true
|
||||
const XLSX = await loadJsLib('XLSX', LIB_JS_URL.XLSX);
|
||||
let workbook = XLSX.read(data, {type: 'binary'});
|
||||
let workbook = XLSX.read(data, { type: 'binary' });
|
||||
let res: any[] = XLSX.utils.sheet_to_json(workbook.Sheets['Sheet1']);
|
||||
if (res.length) {
|
||||
let words = res.map(v => {
|
||||
@@ -386,7 +384,6 @@ function searchWord() {
|
||||
console.log('wordForm.word', wordForm.word)
|
||||
}
|
||||
|
||||
|
||||
watch(() => loading, (val) => {
|
||||
if (!val) return
|
||||
_nextTick(async () => {
|
||||
@@ -398,7 +395,7 @@ watch(() => loading, (val) => {
|
||||
tour.addStep({
|
||||
id: 'step3',
|
||||
text: '点击这里开始学习',
|
||||
attachTo: {element: '#study', on: 'bottom'},
|
||||
attachTo: { element: '#study', on: 'bottom' },
|
||||
buttons: [
|
||||
{
|
||||
text: `下一步(3/${TourConfig.total})`,
|
||||
@@ -413,7 +410,7 @@ watch(() => loading, (val) => {
|
||||
tour.addStep({
|
||||
id: 'step4',
|
||||
text: '这里可以选择学习模式、设置学习数量、修改学习进度',
|
||||
attachTo: {element: '#mode', on: 'bottom'},
|
||||
attachTo: { element: '#mode', on: 'bottom' },
|
||||
beforeShowPromise() {
|
||||
return new Promise((resolve) => {
|
||||
const timer = setInterval(() => {
|
||||
@@ -429,7 +426,7 @@ watch(() => loading, (val) => {
|
||||
text: `下一步(4/${TourConfig.total})`,
|
||||
action() {
|
||||
tour.next()
|
||||
startPractice({guide: 1})
|
||||
startPractice({ guide: 1 })
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -442,6 +439,18 @@ watch(() => loading, (val) => {
|
||||
}, 500)
|
||||
})
|
||||
|
||||
let pageNo = $ref(1)
|
||||
let pageSize = $ref(50)
|
||||
let list2 = $ref([])
|
||||
|
||||
async function requestList({ pageNo, pageSize }) {
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
|
||||
} else {
|
||||
return list2.slice((pageNo - 1) * pageSize, (pageNo - 1) * pageSize + pageSize)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
defineRender(() => {
|
||||
return (
|
||||
@@ -484,7 +493,9 @@ defineRender(() => {
|
||||
<BaseTable
|
||||
ref={tableRef}
|
||||
class="h-full"
|
||||
request={requestList}
|
||||
list={list}
|
||||
total={runtimeStore.editDict.length}
|
||||
loading={loading}
|
||||
onUpdate:list={e => list = e}
|
||||
del={delWord}
|
||||
@@ -564,42 +575,42 @@ defineRender(() => {
|
||||
modelValue={wordForm.trans}
|
||||
onUpdate:modelValue={e => wordForm.trans = e}
|
||||
placeholder="一行一个翻译,前面词性,后面内容(如n.取消);多个翻译请换行"
|
||||
autosize={{minRows: 6, maxRows: 10}}/>
|
||||
autosize={{ minRows: 6, maxRows: 10 }}/>
|
||||
</FormItem>
|
||||
<FormItem label="例句">
|
||||
<Textarea
|
||||
modelValue={wordForm.sentences}
|
||||
onUpdate:modelValue={e => wordForm.sentences = e}
|
||||
placeholder="一行原文,一行译文;多个请换两行"
|
||||
autosize={{minRows: 6, maxRows: 10}}/>
|
||||
autosize={{ minRows: 6, maxRows: 10 }}/>
|
||||
</FormItem>
|
||||
<FormItem label="短语">
|
||||
<Textarea
|
||||
modelValue={wordForm.phrases}
|
||||
onUpdate:modelValue={e => wordForm.phrases = e}
|
||||
placeholder="一行原文,一行译文;多个请换两行"
|
||||
autosize={{minRows: 6, maxRows: 10}}/>
|
||||
autosize={{ minRows: 6, maxRows: 10 }}/>
|
||||
</FormItem>
|
||||
<FormItem label="同义词">
|
||||
<Textarea
|
||||
modelValue={wordForm.synos}
|
||||
onUpdate:modelValue={e => wordForm.synos = e}
|
||||
placeholder="请参考已有单词格式"
|
||||
autosize={{minRows: 6, maxRows: 20}}/>
|
||||
autosize={{ minRows: 6, maxRows: 20 }}/>
|
||||
</FormItem>
|
||||
<FormItem label="同根词">
|
||||
<Textarea
|
||||
modelValue={wordForm.relWords}
|
||||
onUpdate:modelValue={e => wordForm.relWords = e}
|
||||
placeholder="请参考已有单词格式"
|
||||
autosize={{minRows: 6, maxRows: 20}}/>
|
||||
autosize={{ minRows: 6, maxRows: 20 }}/>
|
||||
</FormItem>
|
||||
<FormItem label="词源">
|
||||
<Textarea
|
||||
modelValue={wordForm.etymology}
|
||||
onUpdate:modelValue={e => wordForm.etymology = e}
|
||||
placeholder="请参考已有单词格式"
|
||||
autosize={{minRows: 6, maxRows: 10}}/>
|
||||
autosize={{ minRows: 6, maxRows: 10 }}/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
<div class="center">
|
||||
|
||||
@@ -245,8 +245,7 @@ let isNewHost = $ref(window.location.host === Host)
|
||||
2study.top 域名将在不久后停止使用
|
||||
</div>
|
||||
|
||||
|
||||
<div class="card flex flex-col md:flex-row gap-8">
|
||||
<div class="card flex flex-col md:flex-row gap-4">
|
||||
<div class="flex-1 w-full flex flex-col justify-between">
|
||||
<div class="flex gap-3">
|
||||
<div class="p-1 center rounded-full bg-white">
|
||||
|
||||
Reference in New Issue
Block a user