Develop dictionary management function
This commit is contained in:
@@ -2,52 +2,32 @@
|
||||
import {dictionaryResources} from '@/assets/dictionary.ts'
|
||||
import {useBaseStore} from "@/stores/base.ts"
|
||||
import {onMounted, reactive, watch} from "vue"
|
||||
import {DefaultDict, Dict, DictResource, DictType, languageCategoryOptions, Sort, Word} from "@/types.ts"
|
||||
import {assign, chunk, cloneDeep, groupBy, reverse, shuffle} from "lodash-es";
|
||||
import {DefaultDict, Dict, DictResource, DictType, Sort} from "@/types.ts"
|
||||
import {cloneDeep, reverse, shuffle} from "lodash-es";
|
||||
import {$computed, $ref} from "vue/macros";
|
||||
import {Icon} from '@iconify/vue';
|
||||
import DictGroup from "@/components/toolbar/DictGroup.vue";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import "vue-activity-calendar/style.css";
|
||||
import WordListDialog from "@/components/dialog/WordListDialog.vue";
|
||||
import {isArticle} from "@/hooks/article.ts";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts";
|
||||
import Slide from "@/components/Slide.vue";
|
||||
import DictList from "@/components/list/DictList.vue";
|
||||
import {FormInstance, FormRules} from "element-plus";
|
||||
import Empty from "@/components/Empty.vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import EditBatchArticleModal from "@/components/article/EditBatchArticleModal.vue";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import Dialog from "@/components/dialog/Dialog.vue";
|
||||
import {nanoid} from "nanoid";
|
||||
import {no} from "@/utils";
|
||||
import ChapterWordList from "@/pages/dict/components/ChapterWordList.vue";
|
||||
import {MessageBox} from "@/utils/MessageBox.tsx";
|
||||
import * as XLSX from 'xlsx'
|
||||
import ArticleDetail from "@/pages/dict/components/ArticleDetail.vue";
|
||||
import ArticleDictDetail from "@/pages/dict/components/ArticleDictDetail.vue";
|
||||
import WordDictDetail from "@/pages/dict/components/WordDictDetail.vue";
|
||||
import DictListPanel from "@/pages/dict/components/DictListPanel.vue";
|
||||
|
||||
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
let currentLanguage = $ref('my')
|
||||
let currentTranslateLanguage = $ref('common')
|
||||
let groupByLanguage = groupBy(dictionaryResources, 'language')
|
||||
let translateLanguageList = $ref([])
|
||||
|
||||
let step = $ref(1)
|
||||
let loading = $ref(false)
|
||||
let chapterList2 = $ref([])
|
||||
let chapterWordNumber = $ref(settingStore.chapterWordNumber)
|
||||
let chapterWordListRef: any = $ref()
|
||||
let residueWordListRef: any = $ref()
|
||||
let chapterListRef: any = $ref()
|
||||
|
||||
let chapterIndex = $ref(1)
|
||||
let residueWordList = $ref([])
|
||||
|
||||
async function selectDict(val: {
|
||||
dict: DictResource | Dict,
|
||||
@@ -57,8 +37,8 @@ async function selectDict(val: {
|
||||
console.log('item', item)
|
||||
step = 1
|
||||
isAddDict = false
|
||||
wordFormData.type = FormMode.None
|
||||
loading = true
|
||||
// wordFormData.type = FormMode.None
|
||||
// loading = true
|
||||
let find: Dict = store.myDictList.find((v: Dict) => v.id === item.id)
|
||||
if (find) {
|
||||
runtimeStore.editDict = cloneDeep(find)
|
||||
@@ -101,57 +81,14 @@ async function selectDict(val: {
|
||||
}
|
||||
}
|
||||
|
||||
chapterList2 = Array.from({length: runtimeStore.editDict.chapterWords.length}).map((v, i) => ({id: i}))
|
||||
loading = false
|
||||
// chapterList2 = Array.from({length: runtimeStore.editDict.chapterWords.length}).map((v, i) => ({id: i}))
|
||||
// loading = false
|
||||
}
|
||||
|
||||
function changeDict() {
|
||||
store.changeDict(runtimeStore.editDict)
|
||||
}
|
||||
|
||||
function groupByDictTags(dictList: DictResource[]) {
|
||||
return dictList.reduce<Record<string, DictResource[]>>((result, dict) => {
|
||||
dict.tags.forEach((tag) => {
|
||||
if (Object.prototype.hasOwnProperty.call(result, tag)) {
|
||||
result[tag].push(dict)
|
||||
} else {
|
||||
result[tag] = [dict]
|
||||
}
|
||||
})
|
||||
return result
|
||||
}, {})
|
||||
}
|
||||
|
||||
const groupByTranslateLanguage = $computed(() => {
|
||||
let data: any
|
||||
if (currentLanguage === 'article') {
|
||||
let articleList = dictionaryResources.filter(v => v.type === 'article')
|
||||
data = groupBy(articleList, 'translateLanguage')
|
||||
} else if (currentLanguage === 'my') {
|
||||
data = {
|
||||
common: store.myDictList.concat([{name: '',} as any])
|
||||
}
|
||||
} else {
|
||||
data = groupBy(groupByLanguage[currentLanguage], 'translateLanguage')
|
||||
}
|
||||
// console.log('groupByTranslateLanguage', data)
|
||||
translateLanguageList = Object.keys(data)
|
||||
currentTranslateLanguage = translateLanguageList[0]
|
||||
return data
|
||||
})
|
||||
|
||||
const groupedByCategoryAndTag = $computed(() => {
|
||||
const currentTranslateLanguageDictList = groupByTranslateLanguage[currentTranslateLanguage]
|
||||
const groupByCategory = groupBy(currentTranslateLanguageDictList, 'category')
|
||||
|
||||
let data = []
|
||||
for (const [key, value] of Object.entries(groupByCategory)) {
|
||||
data.push([key, groupByDictTags(value)])
|
||||
}
|
||||
// console.log('groupedByCategoryAndTag', data)
|
||||
return data
|
||||
})
|
||||
|
||||
const dictIsArticle = $computed(() => {
|
||||
return isArticle(runtimeStore.editDict.type)
|
||||
})
|
||||
@@ -164,7 +101,7 @@ function changeSort(v: Sort) {
|
||||
} else {
|
||||
runtimeStore.editDict.words = reverse(runtimeStore.editDict.originWords)
|
||||
}
|
||||
resetChapterList()
|
||||
// resetChapterList()
|
||||
}
|
||||
|
||||
|
||||
@@ -259,142 +196,11 @@ async function onSubmit() {
|
||||
/*词典相关*/
|
||||
/**/
|
||||
|
||||
|
||||
/**/
|
||||
/* 单词修改相关*/
|
||||
/**/
|
||||
|
||||
let wordFormData = $ref({
|
||||
where: '',
|
||||
type: '',
|
||||
name: '',
|
||||
id: '',
|
||||
index: 0
|
||||
})
|
||||
|
||||
enum FormMode {
|
||||
None = '',
|
||||
Add = 'Add',
|
||||
Edit = 'Edit',
|
||||
}
|
||||
|
||||
const DefaultFormWord = {
|
||||
name: '',
|
||||
usphone: '',
|
||||
ukphone: '',
|
||||
trans: '',
|
||||
}
|
||||
|
||||
let wordForm = $ref(cloneDeep(DefaultFormWord))
|
||||
const wordFormRef = $ref<FormInstance>()
|
||||
const wordRules = reactive<FormRules>({
|
||||
name: [
|
||||
{required: true, message: '请输入单词', trigger: 'blur'},
|
||||
{max: 30, message: '名称不能超过30个字符', trigger: 'blur'},
|
||||
],
|
||||
})
|
||||
|
||||
//同步到我的词典列表
|
||||
function syncMyDictList() {
|
||||
//任意修改,都将其变为自定义词典
|
||||
runtimeStore.editDict.isCustom = true
|
||||
|
||||
let rIndex = store.myDictList.findIndex(v => v.id === runtimeStore.editDict.id)
|
||||
if (rIndex > -1) {
|
||||
store.myDictList[rIndex] = cloneDeep(runtimeStore.editDict)
|
||||
} else {
|
||||
store.myDictList.push(cloneDeep(runtimeStore.editDict))
|
||||
}
|
||||
}
|
||||
|
||||
async function onSubmitWord() {
|
||||
await wordFormRef.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
let data: any = cloneDeep(wordForm)
|
||||
if (data.trans) {
|
||||
data.trans = data.trans.split('\n');
|
||||
} else {
|
||||
data.trans = []
|
||||
}
|
||||
//直接使用引用修改
|
||||
let index = wordFormData.where === 'chapter' ? 0 : 1;
|
||||
let list = [chapterWordList, residueWordList][index]
|
||||
let listRef = [chapterWordListRef, residueWordListRef][index]
|
||||
if (wordFormData.type === FormMode.Add) {
|
||||
data.id = nanoid(6)
|
||||
data.checked = false
|
||||
let r = list.find(v => v.name === wordForm.name)
|
||||
if (r) return ElMessage.warning('已有相同名称单词!')
|
||||
else list.push(data)
|
||||
runtimeStore.editDict.originWords.push(data)
|
||||
runtimeStore.editDict.words.push(data)
|
||||
ElMessage.success('添加成功')
|
||||
wordForm = cloneDeep(DefaultFormWord)
|
||||
setTimeout(listRef?.scrollToBottom, 100)
|
||||
} else {
|
||||
let r = list.find(v => v.id === wordFormData.id)
|
||||
if (r) assign(r, data)
|
||||
//同步修改到列表
|
||||
r = runtimeStore.editDict.originWords.find(v => v.id === wordFormData.id)
|
||||
if (r) assign(r, data)
|
||||
r = runtimeStore.editDict.words.find(v => v.id === wordFormData.id)
|
||||
if (r) assign(r, data)
|
||||
ElMessage.success('修改成功')
|
||||
}
|
||||
syncMyDictList()
|
||||
} else {
|
||||
ElMessage.warning('请填写完整')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function addWord(where: string) {
|
||||
// setTimeout(wordListRef?.scrollToBottom, 100)
|
||||
wordFormData.type = FormMode.Add
|
||||
wordFormData.where = where
|
||||
wordForm = cloneDeep(DefaultFormWord)
|
||||
}
|
||||
|
||||
function delWord(val: { word: Word }) {
|
||||
let rIndex = runtimeStore.editDict.originWords.findIndex(v => v.id === val.word.id)
|
||||
if (rIndex > -1) {
|
||||
runtimeStore.editDict.originWords.splice(rIndex, 1)
|
||||
}
|
||||
let rIndex2 = runtimeStore.editDict.words.findIndex(v => v.id === val.word.id)
|
||||
if (rIndex2 > -1) {
|
||||
runtimeStore.editDict.words.splice(rIndex2, 1)
|
||||
}
|
||||
|
||||
if (wordFormData.type === FormMode.Edit && wordForm.name === val.word.name) {
|
||||
closeWordForm()
|
||||
}
|
||||
syncMyDictList()
|
||||
}
|
||||
|
||||
function editWord(word: Word, index: number, where: string) {
|
||||
wordFormData.type = FormMode.Edit
|
||||
wordFormData.id = word.id
|
||||
wordFormData.where = where
|
||||
wordForm.name = word.name
|
||||
wordForm.ukphone = word.ukphone
|
||||
wordForm.usphone = word.usphone
|
||||
wordForm.trans = word.trans.join('\n')
|
||||
}
|
||||
|
||||
function closeWordForm() {
|
||||
wordFormData.type = FormMode.None
|
||||
wordForm = cloneDeep(DefaultFormWord)
|
||||
}
|
||||
|
||||
/**/
|
||||
/* 单词修改相关*/
|
||||
/**/
|
||||
|
||||
watch(() => step, v => {
|
||||
if (v === 0) {
|
||||
closeWordForm()
|
||||
// closeWordForm()
|
||||
closeDictForm()
|
||||
chapterWordNumber = settingStore.chapterWordNumber
|
||||
// chapterWordNumber = settingStore.chapterWordNumber
|
||||
}
|
||||
})
|
||||
|
||||
@@ -404,8 +210,6 @@ watch(() => store.load, v => {
|
||||
}
|
||||
})
|
||||
|
||||
let showAllocationChapterDialog = $ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
dictionaryResources.map(v => {
|
||||
if (categoryList[v.language]) {
|
||||
@@ -438,11 +242,11 @@ onMounted(() => {
|
||||
}
|
||||
if (type === "collect") {
|
||||
selectDict({dict: store.collect, index: 0})
|
||||
addWord('residue')
|
||||
// addWord('residue')
|
||||
}
|
||||
if (type === "simple") {
|
||||
selectDict({dict: store.simple, index: 0})
|
||||
addWord('residue')
|
||||
// addWord('residue')
|
||||
}
|
||||
|
||||
})
|
||||
@@ -450,307 +254,19 @@ onMounted(() => {
|
||||
// console.log('tagList', tagList)
|
||||
})
|
||||
|
||||
let chapterWordList: Word[] = $computed({
|
||||
get() {
|
||||
return runtimeStore.editDict.chapterWords[chapterIndex] ?? []
|
||||
},
|
||||
set(newValue) {
|
||||
runtimeStore.editDict.chapterWords[chapterIndex] = newValue
|
||||
}
|
||||
})
|
||||
|
||||
let chapterWordListCheckedTotal = $computed(() => {
|
||||
return chapterWordList.filter(v => v.checked).length
|
||||
})
|
||||
|
||||
let residueWordListCheckedTotal = $computed(() => {
|
||||
return residueWordList.filter(v => v.checked).length
|
||||
})
|
||||
|
||||
function handleChangeCurrentChapter(index: number) {
|
||||
chapterWordList.map(v => v.checked = false)
|
||||
chapterIndex = index
|
||||
closeWordForm()
|
||||
}
|
||||
|
||||
function toResidueWordList() {
|
||||
let list = cloneDeep(chapterWordList.filter(v => v.checked))
|
||||
list.map(v => v.checked = false)
|
||||
checkRepeatWord(list, residueWordList,
|
||||
noRepeatWords => {
|
||||
if (wordFormData.type === FormMode.Edit && wordFormData.where === 'chapter') {
|
||||
if (noRepeatWords.find(v => v.name === wordForm.name)) {
|
||||
wordFormData.where = 'residue'
|
||||
}
|
||||
}
|
||||
noRepeatWords.map(v => {
|
||||
let rIndex = chapterWordList.findIndex(s => s.id === v.id)
|
||||
if (rIndex > -1) chapterWordList.splice(rIndex, 1)
|
||||
})
|
||||
residueWordList = residueWordList.concat(noRepeatWords)
|
||||
setTimeout(residueWordListRef?.scrollToBottom, 100)
|
||||
},
|
||||
repeatWords => {
|
||||
if (wordFormData.type === FormMode.Edit && wordFormData.where === 'chapter') {
|
||||
if (repeatWords.find(v => v.name === wordForm.name)) {
|
||||
wordFormData.where = 'residue'
|
||||
}
|
||||
}
|
||||
repeatWords.map(v => {
|
||||
residueWordList[v.index] = v
|
||||
delete residueWordList[v.index].index
|
||||
|
||||
let rIndex = chapterWordList.findIndex(s => s.id === v.id)
|
||||
if (rIndex > -1) chapterWordList.splice(rIndex, 1)
|
||||
})
|
||||
setTimeout(residueWordListRef?.scrollToBottom, 100)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function toChapterWordList() {
|
||||
if (chapterIndex == -1) return ElMessage.warning('请选择章节')
|
||||
let list = cloneDeep(residueWordList.filter(v => v.checked))
|
||||
list.map(v => v.checked = false)
|
||||
|
||||
checkRepeatWord(list, chapterWordList,
|
||||
noRepeatWords => {
|
||||
if (wordFormData.type === FormMode.Edit && wordFormData.where !== 'chapter') {
|
||||
if (noRepeatWords.find(v => v.name === wordForm.name)) {
|
||||
wordFormData.where = 'chapter'
|
||||
}
|
||||
}
|
||||
noRepeatWords.map(v => {
|
||||
let rIndex = residueWordList.findIndex(s => s.id === v.id)
|
||||
if (rIndex > -1) residueWordList.splice(rIndex, 1)
|
||||
})
|
||||
chapterWordList = chapterWordList.concat(noRepeatWords)
|
||||
setTimeout(chapterWordListRef?.scrollToBottom, 100)
|
||||
},
|
||||
repeatWords => {
|
||||
if (wordFormData.type === FormMode.Edit && wordFormData.where !== 'chapter') {
|
||||
if (repeatWords.find(v => v.name === wordForm.name)) {
|
||||
wordFormData.where = 'chapter'
|
||||
}
|
||||
}
|
||||
repeatWords.map(v => {
|
||||
chapterWordList[v.index] = v
|
||||
delete chapterWordList[v.index].index
|
||||
|
||||
let rIndex = residueWordList.findIndex(s => s.id === v.id)
|
||||
if (rIndex > -1) residueWordList.splice(rIndex, 1)
|
||||
})
|
||||
setTimeout(chapterWordListRef?.scrollToBottom, 100)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function addNewChapter() {
|
||||
runtimeStore.editDict.chapterWords.push([])
|
||||
chapterList2 = Array.from({length: runtimeStore.editDict.chapterWords.length}).map((v, i) => ({id: i}))
|
||||
chapterListRef?.scrollToItem(chapterList2.length - 1)
|
||||
ElMessage.success('新增章节成功')
|
||||
console.log('scrollToBottom', chapterListRef)
|
||||
}
|
||||
|
||||
function delWordChapter(index: number) {
|
||||
let list = runtimeStore.editDict.chapterWords[index]
|
||||
list.map(v => v.checked = false)
|
||||
residueWordList = residueWordList.concat(list)
|
||||
runtimeStore.editDict.chapterWords.splice(index, 1)
|
||||
chapterList2 = Array.from({length: runtimeStore.editDict.chapterWords.length}).map((v, i) => ({id: i}))
|
||||
|
||||
if (chapterIndex >= index) chapterIndex--
|
||||
if (chapterIndex < 0) chapterIndex = 0
|
||||
|
||||
syncMyDictList()
|
||||
}
|
||||
|
||||
function resetChapterList(num?: number) {
|
||||
if (num !== undefined) {
|
||||
runtimeStore.editDict.chapterWordNumber = num
|
||||
}
|
||||
residueWordList = []
|
||||
chapterIndex = -1
|
||||
runtimeStore.editDict.words.map(v => v.checked = false)
|
||||
runtimeStore.editDict.chapterWords = chunk(runtimeStore.editDict.words, runtimeStore.editDict.chapterWordNumber)
|
||||
chapterList2 = Array.from({length: runtimeStore.editDict.chapterWords.length}).map((v, i) => ({id: i}))
|
||||
// console.log('runtimeStore.editDict.chapterWords',runtimeStore.editDict.chapterWords)
|
||||
// console.log('chapterList2',chapterList2)
|
||||
}
|
||||
|
||||
const isPinDict = $computed(() => {
|
||||
return [DictType.collect, DictType.wrong, DictType.simple].includes(runtimeStore.editDict.type)
|
||||
})
|
||||
|
||||
function exportData() {
|
||||
let wb = XLSX.utils.book_new()
|
||||
// 一个简单的sheet
|
||||
let sheetData = chapterWordList.map(v => {
|
||||
return {
|
||||
单词: v.name,
|
||||
'音标①': v.usphone,
|
||||
'音标②': v.ukphone,
|
||||
'释义(一行一个释义)': v.trans.join('\n')
|
||||
}
|
||||
})
|
||||
let sheet = XLSX.utils.json_to_sheet(sheetData)
|
||||
wb.Sheets['Sheet1'] = sheet
|
||||
wb.SheetNames = ['Sheet1']
|
||||
XLSX.writeFile(wb, `${runtimeStore.editDict.name}-zh-CN.xlsx`);
|
||||
ElMessage.success('导出成功!')
|
||||
}
|
||||
|
||||
function checkRepeatWord(
|
||||
words: Word[],
|
||||
targetList: Word[],
|
||||
concatNoRepeat: Function,
|
||||
concatRepeat: Function,
|
||||
notice?: Function
|
||||
) {
|
||||
let repeatWords = []
|
||||
let noRepeatWords = []
|
||||
words.map(v => {
|
||||
let rIndex = targetList.findIndex(s => s.name === v.name)
|
||||
if (rIndex > -1) {
|
||||
v.index = rIndex
|
||||
repeatWords.push(v)
|
||||
} else {
|
||||
noRepeatWords.push(v)
|
||||
}
|
||||
})
|
||||
concatNoRepeat(noRepeatWords)
|
||||
if (repeatWords.length) {
|
||||
MessageBox.confirm(
|
||||
'单词"' + repeatWords.map(v => v.name).join(', ') + '" 已存在,继续将会覆盖原有单词,是否继续?',
|
||||
'检测到重复单词',
|
||||
() => concatRepeat(repeatWords),
|
||||
null,
|
||||
() => notice?.()
|
||||
)
|
||||
} else {
|
||||
notice?.()
|
||||
}
|
||||
}
|
||||
|
||||
function importData(e: any) {
|
||||
let file = e.target.files[0]
|
||||
if (!file) return
|
||||
// no()
|
||||
let reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
let data = e.target.result;
|
||||
// 读取二进制的excel
|
||||
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 => {
|
||||
if (v['单词']) {
|
||||
let word: Word = {
|
||||
id: nanoid(6),
|
||||
checked: false,
|
||||
name: String(v['单词']),
|
||||
usphone: String(v['音标①'] ?? ''),
|
||||
ukphone: String(v['音标②'] ?? ''),
|
||||
trans: String(v['释义(一行一个释义)'] ?? '').split('\n')
|
||||
}
|
||||
return word
|
||||
}
|
||||
}).filter(v => v)
|
||||
|
||||
checkRepeatWord(words, residueWordList, noRepeatWords => {
|
||||
residueWordList = residueWordList.concat(noRepeatWords)
|
||||
setTimeout(residueWordListRef?.scrollToBottom, 100)
|
||||
},
|
||||
repeatWords => {
|
||||
repeatWords.map(v => {
|
||||
residueWordList[v.index] = v
|
||||
delete residueWordList[v.index].index
|
||||
})
|
||||
setTimeout(residueWordListRef?.scrollToBottom, 100)
|
||||
},
|
||||
() => ElMessage.success('导入成功!'))
|
||||
} else {
|
||||
ElMessage.warning('导入失败!原因:没有数据')
|
||||
}
|
||||
};
|
||||
reader.readAsBinaryString(file);
|
||||
}
|
||||
|
||||
async function resetDict() {
|
||||
MessageBox.confirm(
|
||||
'删除所有自定义内容: 章节、排序、单词,并恢复至默认状态,确认恢复?',
|
||||
'提示',
|
||||
async () => {
|
||||
isAddDict = false
|
||||
dictForm = cloneDeep(DefaultDictForm)
|
||||
closeWordForm()
|
||||
chapterIndex = -1
|
||||
if (runtimeStore.editDict.url) {
|
||||
runtimeStore.editDict.sort = Sort.normal
|
||||
runtimeStore.editDict.isCustom = false
|
||||
runtimeStore.editDict.chapterWordNumber = settingStore.chapterWordNumber
|
||||
let url = `./dicts/${runtimeStore.editDict.language}/${runtimeStore.editDict.type}/${runtimeStore.editDict.translateLanguage}/${runtimeStore.editDict.url}`;
|
||||
let r = await fetch(url)
|
||||
let v = await r.json()
|
||||
v.map(s => {
|
||||
s.id = nanoid(6)
|
||||
})
|
||||
runtimeStore.editDict.originWords = cloneDeep(v)
|
||||
changeSort(runtimeStore.editDict.sort)
|
||||
ElMessage.success('恢复成功')
|
||||
} else {
|
||||
ElMessage.success('恢复失败')
|
||||
}
|
||||
}
|
||||
)
|
||||
// runtimeStore.editDict
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="DictDialog">
|
||||
<Slide :slide-count="2" :step="step">
|
||||
<div class="dict-page">
|
||||
<header>
|
||||
<div class="tabs">
|
||||
<div class="tab"
|
||||
:class="currentLanguage === item.id && 'active'"
|
||||
@click="currentLanguage = item.id"
|
||||
v-for="item in languageCategoryOptions">
|
||||
<img :src='item.flag'/>
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="page-content">
|
||||
<div class="dict-list-wrapper">
|
||||
<template v-if="currentLanguage === 'my'">
|
||||
<DictList
|
||||
@add="step = 1;isAddDict = true"
|
||||
@selectDict="selectDict"
|
||||
:list="groupByTranslateLanguage['common']"/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="translate">
|
||||
<span>翻译:</span>
|
||||
<el-radio-group v-model="currentTranslateLanguage">
|
||||
<el-radio-button border v-for="i in translateLanguageList" :label="i">{{ i }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<DictGroup
|
||||
v-for="item in groupedByCategoryAndTag"
|
||||
:select-dict-name="runtimeStore.editDict.resourceId"
|
||||
@selectDict="selectDict"
|
||||
@detail="step = 1"
|
||||
:groupByTag="item[1]"
|
||||
:category="item[0]"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DictListPanel
|
||||
@add="step = 1"
|
||||
@select-dict="selectDict"
|
||||
/>
|
||||
<div class="dict-detail-page">
|
||||
<header>
|
||||
<Icon icon="octicon:arrow-left-24"
|
||||
@@ -779,123 +295,8 @@ async function resetDict() {
|
||||
</div>
|
||||
</header>
|
||||
<div class="detail" v-if="!isAddDict">
|
||||
<ArticleDetail v-if="dictIsArticle"/>
|
||||
<div class="page-content" v-else>
|
||||
<div class="left-column">
|
||||
<div class="header">
|
||||
<div class="common-title">
|
||||
<span>章节管理</span>
|
||||
<div class="options">
|
||||
<BaseIcon
|
||||
@click="addNewChapter"
|
||||
icon="fluent:add-20-filled"
|
||||
title="新增章节"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="select">
|
||||
<BaseButton size="small" @click="showAllocationChapterDialog = true">智能分配</BaseButton>
|
||||
<span>{{ runtimeStore.editDict.chapterWords.length }}章</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<RecycleScroller
|
||||
v-if="chapterList2.length"
|
||||
ref="chapterListRef"
|
||||
style="height: 100%;"
|
||||
:items="chapterList2"
|
||||
:item-size="63"
|
||||
key-field="id"
|
||||
v-slot="{ item,index }"
|
||||
>
|
||||
<div style="padding: 0 15rem;">
|
||||
<div class="common-list-item"
|
||||
:class="chapterIndex === item.id && 'active'"
|
||||
@click="handleChangeCurrentChapter(item.id)">
|
||||
<div class="flex gap10 flex1 ">
|
||||
<input type="radio" :checked="chapterIndex === item.id">
|
||||
<div class="item-title flex flex1 space-between">
|
||||
<span>第{{ item.id + 1 }}章</span>
|
||||
<span>{{ runtimeStore.editDict.chapterWords[item.id]?.length }}词</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<BaseIcon
|
||||
class-name="del"
|
||||
@click="delWordChapter(item.id)"
|
||||
title="移除"
|
||||
icon="solar:trash-bin-minimalistic-linear"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</RecycleScroller>
|
||||
<Empty v-else/>
|
||||
</div>
|
||||
</div>
|
||||
<ChapterWordList
|
||||
ref="chapterWordListRef"
|
||||
:title="`${chapterIndex > -1 ? `第${chapterIndex + 1}章` : ''} 单词列表`"
|
||||
:show-add="chapterIndex > -1"
|
||||
@add="addWord('chapter')"
|
||||
@del="delWord"
|
||||
:empty-title="chapterIndex === -1?'请选择章节':null"
|
||||
@edit="val => editWord(val.word,val.index,'chapter')"
|
||||
v-model:list="chapterWordList"/>
|
||||
<div class="options-column">
|
||||
<BaseButton @click="toChapterWordList"
|
||||
:disabled="residueWordListCheckedTotal === 0">
|
||||
<
|
||||
</BaseButton>
|
||||
<BaseButton @click="toResidueWordList"
|
||||
:disabled="chapterWordListCheckedTotal === 0">
|
||||
>
|
||||
</BaseButton>
|
||||
</div>
|
||||
<ChapterWordList
|
||||
ref="residueWordListRef"
|
||||
title="未分配单词列表"
|
||||
:empty-title="null"
|
||||
:show-add="true"
|
||||
@add="addWord('residue')"
|
||||
@del="delWord"
|
||||
@edit="val => editWord(val.word,val.index,'residue')"
|
||||
v-model:list="residueWordList"/>
|
||||
<div class="right-column">
|
||||
<div class="add" v-if="wordFormData.type">
|
||||
<div class="common-title">
|
||||
{{ wordFormData.type === FormMode.Add ? '添加' : '修改' }}单词
|
||||
{{
|
||||
wordFormData.type === FormMode.Add ? (wordFormData.where === 'chapter' ? `> 第${chapterIndex + 1}章` : '> 未分配') : ''
|
||||
}}
|
||||
</div>
|
||||
<el-form
|
||||
class="form"
|
||||
ref="wordFormRef"
|
||||
:rules="wordRules"
|
||||
:model="wordForm"
|
||||
label-width="100rem">
|
||||
<el-form-item label="单词" prop="name">
|
||||
<el-input v-model="wordForm.name"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="翻译">
|
||||
<el-input v-model="wordForm.trans"
|
||||
placeholder="多个翻译请换行"
|
||||
:autosize="{ minRows: 2, maxRows: 6 }"
|
||||
type="textarea"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="音标/发音①">
|
||||
<el-input v-model="wordForm.usphone"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="音标/发音②">
|
||||
<el-input v-model="wordForm.ukphone"/>
|
||||
</el-form-item>
|
||||
<div class="flex-center">
|
||||
<el-button @click="closeWordForm">关闭</el-button>
|
||||
<el-button type="primary" @click="onSubmitWord">保存</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ArticleDictDetail v-if="dictIsArticle"/>
|
||||
<WordDictDetail v-else/>
|
||||
</div>
|
||||
<div class="edit-dict" v-else>
|
||||
<div class="wrapper">
|
||||
@@ -956,53 +357,6 @@ async function resetDict() {
|
||||
</div>
|
||||
</Slide>
|
||||
</div>
|
||||
|
||||
<Dialog
|
||||
title="智能分配单词"
|
||||
:footer="true"
|
||||
@ok="resetChapterList(chapterWordNumber)"
|
||||
@cancel="chapterWordNumber = settingStore.chapterWordNumber"
|
||||
v-model="showAllocationChapterDialog">
|
||||
<div class="allocation-chapter">
|
||||
<div class="desc">
|
||||
<div>为您自动创建章节以及分配单词</div>
|
||||
<div>注意:已存在的章节将被删除!</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="label">每章单词数</div>
|
||||
<span class="text">最小:10</span>
|
||||
<el-slider :min="10"
|
||||
:step="10"
|
||||
:max="runtimeStore.editDict.words.length ?? 10"
|
||||
v-model="chapterWordNumber"
|
||||
/>
|
||||
<span class="text">最大:{{ runtimeStore.editDict.words.length ?? 10 }}</span>
|
||||
</div>
|
||||
|
||||
<div class="notice">鼠标按住滑块,按键盘左右箭头可进行微调</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="label">将会创建</div>
|
||||
<div class="option">
|
||||
<span>{{ Math.ceil(runtimeStore.editDict.words.length / chapterWordNumber) }}章</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="label">每章</div>
|
||||
<div class="option">
|
||||
<span>{{ chapterWordNumber }}个单词</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" :style="{opacity:runtimeStore.editDict.words.length % chapterWordNumber}">
|
||||
<div class="label">最后一章</div>
|
||||
<div class="option">
|
||||
<span>{{ runtimeStore.editDict.words.length % chapterWordNumber }}个单词</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
<WordListDialog/>
|
||||
<EditBatchArticleModal/>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -1024,70 +378,6 @@ $header-height: 60rem;
|
||||
height: 75vh;
|
||||
}
|
||||
|
||||
.dict-page {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
$header-height: 60rem;
|
||||
padding: var(--space);
|
||||
padding-top: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: $header-height;
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 20rem;
|
||||
|
||||
.tab {
|
||||
color: var(--color-font-1);
|
||||
cursor: pointer;
|
||||
padding: 10rem;
|
||||
padding-bottom: 5rem;
|
||||
transition: all .5s;
|
||||
border-bottom: 2px solid transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rem;
|
||||
|
||||
&.active {
|
||||
border-bottom: 2px solid $main;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 30rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-content {
|
||||
display: flex;
|
||||
height: calc(100% - $header-height);
|
||||
|
||||
.dict-list-wrapper {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
padding-right: var(--space);
|
||||
|
||||
.translate {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-font-1);
|
||||
margin-bottom: 30rem;
|
||||
|
||||
& > span {
|
||||
font-size: 22rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dict-detail-page {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
@@ -1143,83 +433,6 @@ $header-height: 60rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.page-content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
position: relative;
|
||||
gap: var(--space);
|
||||
|
||||
.header {
|
||||
padding: 0 var(--space);
|
||||
|
||||
.common-title {
|
||||
margin-bottom: 0;
|
||||
position: relative;
|
||||
|
||||
.options {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
display: flex;
|
||||
gap: 10rem;
|
||||
}
|
||||
}
|
||||
|
||||
.select {
|
||||
height: 45rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
gap: 5rem;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.column {
|
||||
flex: 1;
|
||||
background: white;
|
||||
border-radius: 10rem;
|
||||
background: var(--color-second-bg);
|
||||
color: var(--color-font-1);
|
||||
padding-bottom: var(--space);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.left-column {
|
||||
max-width: 250rem;
|
||||
width: 16vw;
|
||||
@extend .column;
|
||||
}
|
||||
|
||||
.options-column {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10rem;
|
||||
}
|
||||
|
||||
.right-column {
|
||||
//@extend .column;
|
||||
flex: 1;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
|
||||
.add {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.edit-dict {
|
||||
@@ -1237,38 +450,5 @@ $header-height: 60rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.allocation-chapter {
|
||||
width: 500rem;
|
||||
padding: var(--space);
|
||||
padding-top: 0;
|
||||
|
||||
.desc {
|
||||
margin-top: 10rem;
|
||||
margin-bottom: 35rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rem;
|
||||
margin-bottom: 15rem;
|
||||
word-break: keep-all;
|
||||
|
||||
.label {
|
||||
width: 90rem;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 12rem;
|
||||
}
|
||||
}
|
||||
|
||||
.notice {
|
||||
transform: translate3d(110rem, -20rem, 0);
|
||||
font-size: 11rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import VirtualWordList2 from "@/components/list/VirtualWordList2.vue";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
import {Article, DefaultArticle, TranslateType} from "@/types.ts";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts";
|
||||
import EditBatchArticleModal from "@/components/article/EditBatchArticleModal.vue";
|
||||
|
||||
const runtimeStore = useRuntimeStore()
|
||||
let chapterIndex = $ref(-1)
|
||||
@@ -96,6 +97,7 @@ function delArticle(index: number) {
|
||||
<Empty v-else/>
|
||||
</div>
|
||||
</div>
|
||||
<EditBatchArticleModal/>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
179
src/pages/dict/components/DictListPanel.vue
Normal file
179
src/pages/dict/components/DictListPanel.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {DictResource, languageCategoryOptions} from "@/types.ts";
|
||||
import {$computed, $ref} from "vue/macros";
|
||||
import {dictionaryResources} from "@/assets/dictionary.ts";
|
||||
import {groupBy} from "lodash-es";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import DictList from "@/components/list/DictList.vue";
|
||||
import DictGroup from "@/components/toolbar/DictGroup.vue";
|
||||
|
||||
const emit = defineEmits<{
|
||||
add: [],
|
||||
selectDict: [val: { dict: any, index: number }]
|
||||
}>()
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
|
||||
let currentLanguage = $ref('my')
|
||||
let currentTranslateLanguage = $ref('common')
|
||||
let groupByLanguage = groupBy(dictionaryResources, 'language')
|
||||
let translateLanguageList = $ref([])
|
||||
|
||||
function groupByDictTags(dictList: DictResource[]) {
|
||||
return dictList.reduce<Record<string, DictResource[]>>((result, dict) => {
|
||||
dict.tags.forEach((tag) => {
|
||||
if (Object.prototype.hasOwnProperty.call(result, tag)) {
|
||||
result[tag].push(dict)
|
||||
} else {
|
||||
result[tag] = [dict]
|
||||
}
|
||||
})
|
||||
return result
|
||||
}, {})
|
||||
}
|
||||
|
||||
const groupByTranslateLanguage = $computed(() => {
|
||||
let data: any
|
||||
if (currentLanguage === 'article') {
|
||||
let articleList = dictionaryResources.filter(v => v.type === 'article')
|
||||
data = groupBy(articleList, 'translateLanguage')
|
||||
} else if (currentLanguage === 'my') {
|
||||
data = {
|
||||
common: store.myDictList.concat([{name: '',} as any])
|
||||
}
|
||||
} else {
|
||||
data = groupBy(groupByLanguage[currentLanguage], 'translateLanguage')
|
||||
}
|
||||
// console.log('groupByTranslateLanguage', data)
|
||||
translateLanguageList = Object.keys(data)
|
||||
currentTranslateLanguage = translateLanguageList[0]
|
||||
return data
|
||||
})
|
||||
|
||||
const groupedByCategoryAndTag = $computed(() => {
|
||||
const currentTranslateLanguageDictList = groupByTranslateLanguage[currentTranslateLanguage]
|
||||
const groupByCategory = groupBy(currentTranslateLanguageDictList, 'category')
|
||||
|
||||
let data = []
|
||||
for (const [key, value] of Object.entries(groupByCategory)) {
|
||||
data.push([key, groupByDictTags(value)])
|
||||
}
|
||||
// console.log('groupedByCategoryAndTag', data)
|
||||
return data
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dict-list-panel">
|
||||
<header>
|
||||
<div class="tabs">
|
||||
<div class="tab"
|
||||
:class="currentLanguage === item.id && 'active'"
|
||||
@click="currentLanguage = item.id"
|
||||
v-for="item in languageCategoryOptions">
|
||||
<img :src='item.flag' alt=""/>
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="page-content">
|
||||
<div class="dict-list-wrapper">
|
||||
<template v-if="currentLanguage === 'my'">
|
||||
<DictList
|
||||
@add="emit('add')"
|
||||
@selectDict="e => emit('selectDict',e)"
|
||||
:list="groupByTranslateLanguage['common']"/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="translate">
|
||||
<span>翻译:</span>
|
||||
<el-radio-group v-model="currentTranslateLanguage">
|
||||
<el-radio-button border v-for="i in translateLanguageList" :label="i">{{ i }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<DictGroup
|
||||
v-for="item in groupedByCategoryAndTag"
|
||||
:select-dict-name="runtimeStore.editDict.resourceId"
|
||||
@selectDict="e => emit('selectDict',e)"
|
||||
:groupByTag="item[1]"
|
||||
:category="item[0]"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/assets/css/variable";
|
||||
|
||||
.dict-list-panel {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
$header-height: 60rem;
|
||||
padding: var(--space);
|
||||
padding-top: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: $header-height;
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 20rem;
|
||||
|
||||
.tab {
|
||||
color: var(--color-font-1);
|
||||
cursor: pointer;
|
||||
padding: 10rem;
|
||||
padding-bottom: 5rem;
|
||||
transition: all .5s;
|
||||
border-bottom: 2px solid transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rem;
|
||||
|
||||
&.active {
|
||||
border-bottom: 2px solid $main;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 30rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-content {
|
||||
display: flex;
|
||||
height: calc(100% - $header-height);
|
||||
|
||||
.dict-list-wrapper {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
padding-right: var(--space);
|
||||
|
||||
.translate {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-font-1);
|
||||
margin-bottom: 30rem;
|
||||
|
||||
& > span {
|
||||
font-size: 22rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,11 +1,713 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import Empty from "@/components/Empty.vue";
|
||||
import ChapterWordList from "@/pages/dict/components/ChapterWordList.vue";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import {$computed, $ref} from "vue/macros";
|
||||
import {assign, chunk, cloneDeep, reverse, shuffle} from "lodash-es";
|
||||
import {Sort, Word} from "@/types.ts";
|
||||
import {nanoid} from "nanoid";
|
||||
import {FormInstance, FormRules} from "element-plus";
|
||||
import {reactive} from "vue";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import Dialog from "@/components/dialog/Dialog.vue";
|
||||
import {MessageBox} from "@/utils/MessageBox.tsx";
|
||||
import * as XLSX from "xlsx";
|
||||
import WordListDialog from "@/components/dialog/WordListDialog.vue";
|
||||
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
|
||||
let loading = $ref(false)
|
||||
let chapterList2 = $ref([])
|
||||
let chapterWordListRef: any = $ref()
|
||||
let residueWordListRef: any = $ref()
|
||||
let chapterListRef: any = $ref()
|
||||
let chapterIndex = $ref(1)
|
||||
let residueWordList = $ref([])
|
||||
|
||||
let chapterWordList: Word[] = $computed({
|
||||
get() {
|
||||
return runtimeStore.editDict.chapterWords[chapterIndex] ?? []
|
||||
},
|
||||
set(newValue) {
|
||||
runtimeStore.editDict.chapterWords[chapterIndex] = newValue
|
||||
}
|
||||
})
|
||||
|
||||
function addNewChapter() {
|
||||
runtimeStore.editDict.chapterWords.push([])
|
||||
chapterList2 = Array.from({length: runtimeStore.editDict.chapterWords.length}).map((v, i) => ({id: i}))
|
||||
chapterListRef?.scrollToItem(chapterList2.length - 1)
|
||||
ElMessage.success('新增章节成功')
|
||||
console.log('scrollToBottom', chapterListRef)
|
||||
}
|
||||
|
||||
|
||||
function delWordChapter(index: number) {
|
||||
let list = runtimeStore.editDict.chapterWords[index]
|
||||
list.map(v => v.checked = false)
|
||||
residueWordList = residueWordList.concat(list)
|
||||
runtimeStore.editDict.chapterWords.splice(index, 1)
|
||||
chapterList2 = Array.from({length: runtimeStore.editDict.chapterWords.length}).map((v, i) => ({id: i}))
|
||||
|
||||
if (chapterIndex >= index) chapterIndex--
|
||||
if (chapterIndex < 0) chapterIndex = 0
|
||||
|
||||
syncMyDictList()
|
||||
}
|
||||
|
||||
|
||||
let chapterWordListCheckedTotal = $computed(() => {
|
||||
return chapterWordList.filter(v => v.checked).length
|
||||
})
|
||||
|
||||
let residueWordListCheckedTotal = $computed(() => {
|
||||
return residueWordList.filter(v => v.checked).length
|
||||
})
|
||||
|
||||
function handleChangeCurrentChapter(index: number) {
|
||||
chapterWordList.map(v => v.checked = false)
|
||||
chapterIndex = index
|
||||
closeWordForm()
|
||||
}
|
||||
|
||||
|
||||
function checkRepeatWord(
|
||||
words: Word[],
|
||||
targetList: Word[],
|
||||
concatNoRepeat: Function,
|
||||
concatRepeat: Function,
|
||||
notice?: Function
|
||||
) {
|
||||
let repeatWords = []
|
||||
let noRepeatWords = []
|
||||
words.map((v: any) => {
|
||||
let rIndex = targetList.findIndex(s => s.name === v.name)
|
||||
if (rIndex > -1) {
|
||||
v.index = rIndex
|
||||
repeatWords.push(v)
|
||||
} else {
|
||||
noRepeatWords.push(v)
|
||||
}
|
||||
})
|
||||
concatNoRepeat(noRepeatWords)
|
||||
if (repeatWords.length) {
|
||||
MessageBox.confirm(
|
||||
'单词"' + repeatWords.map(v => v.name).join(', ') + '" 已存在,继续将会覆盖原有单词,是否继续?',
|
||||
'检测到重复单词',
|
||||
() => concatRepeat(repeatWords),
|
||||
null,
|
||||
() => notice?.()
|
||||
)
|
||||
} else {
|
||||
notice?.()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function toResidueWordList() {
|
||||
let list = cloneDeep(chapterWordList.filter(v => v.checked))
|
||||
list.map(v => v.checked = false)
|
||||
checkRepeatWord(list, residueWordList,
|
||||
noRepeatWords => {
|
||||
if (wordFormData.type === FormMode.Edit && wordFormData.where === 'chapter') {
|
||||
if (noRepeatWords.find(v => v.name === wordForm.name)) {
|
||||
wordFormData.where = 'residue'
|
||||
}
|
||||
}
|
||||
noRepeatWords.map(v => {
|
||||
let rIndex = chapterWordList.findIndex(s => s.id === v.id)
|
||||
if (rIndex > -1) chapterWordList.splice(rIndex, 1)
|
||||
})
|
||||
residueWordList = residueWordList.concat(noRepeatWords)
|
||||
setTimeout(residueWordListRef?.scrollToBottom, 100)
|
||||
},
|
||||
repeatWords => {
|
||||
if (wordFormData.type === FormMode.Edit && wordFormData.where === 'chapter') {
|
||||
if (repeatWords.find(v => v.name === wordForm.name)) {
|
||||
wordFormData.where = 'residue'
|
||||
}
|
||||
}
|
||||
repeatWords.map(v => {
|
||||
residueWordList[v.index] = v
|
||||
delete residueWordList[v.index].index
|
||||
|
||||
let rIndex = chapterWordList.findIndex(s => s.id === v.id)
|
||||
if (rIndex > -1) chapterWordList.splice(rIndex, 1)
|
||||
})
|
||||
setTimeout(residueWordListRef?.scrollToBottom, 100)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function toChapterWordList() {
|
||||
if (chapterIndex == -1) return ElMessage.warning('请选择章节')
|
||||
let list = cloneDeep(residueWordList.filter(v => v.checked))
|
||||
list.map(v => v.checked = false)
|
||||
|
||||
checkRepeatWord(list, chapterWordList,
|
||||
noRepeatWords => {
|
||||
if (wordFormData.type === FormMode.Edit && wordFormData.where !== 'chapter') {
|
||||
if (noRepeatWords.find(v => v.name === wordForm.name)) {
|
||||
wordFormData.where = 'chapter'
|
||||
}
|
||||
}
|
||||
noRepeatWords.map(v => {
|
||||
let rIndex = residueWordList.findIndex(s => s.id === v.id)
|
||||
if (rIndex > -1) residueWordList.splice(rIndex, 1)
|
||||
})
|
||||
chapterWordList = chapterWordList.concat(noRepeatWords)
|
||||
setTimeout(chapterWordListRef?.scrollToBottom, 100)
|
||||
},
|
||||
repeatWords => {
|
||||
if (wordFormData.type === FormMode.Edit && wordFormData.where !== 'chapter') {
|
||||
if (repeatWords.find(v => v.name === wordForm.name)) {
|
||||
wordFormData.where = 'chapter'
|
||||
}
|
||||
}
|
||||
|
||||
repeatWords.map((v) => {
|
||||
chapterWordList[v.index] = v
|
||||
delete chapterWordList[v.index].index
|
||||
|
||||
let rIndex = residueWordList.findIndex(s => s.id === v.id)
|
||||
if (rIndex > -1) residueWordList.splice(rIndex, 1)
|
||||
})
|
||||
setTimeout(chapterWordListRef?.scrollToBottom, 100)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**/
|
||||
/* 单词修改相关*/
|
||||
/**/
|
||||
|
||||
let wordFormData = $ref({
|
||||
where: '',
|
||||
type: '',
|
||||
name: '',
|
||||
id: '',
|
||||
index: 0
|
||||
})
|
||||
|
||||
enum FormMode {
|
||||
None = '',
|
||||
Add = 'Add',
|
||||
Edit = 'Edit',
|
||||
}
|
||||
|
||||
const DefaultFormWord = {
|
||||
name: '',
|
||||
usphone: '',
|
||||
ukphone: '',
|
||||
trans: '',
|
||||
}
|
||||
let wordForm = $ref(cloneDeep(DefaultFormWord))
|
||||
const wordFormRef = $ref<FormInstance>()
|
||||
const wordRules = reactive<FormRules>({
|
||||
name: [
|
||||
{required: true, message: '请输入单词', trigger: 'blur'},
|
||||
{max: 30, message: '名称不能超过30个字符', trigger: 'blur'},
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
async function onSubmitWord() {
|
||||
await wordFormRef.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
let data: any = cloneDeep(wordForm)
|
||||
if (data.trans) {
|
||||
data.trans = data.trans.split('\n');
|
||||
} else {
|
||||
data.trans = []
|
||||
}
|
||||
//直接使用引用修改
|
||||
let index = wordFormData.where === 'chapter' ? 0 : 1;
|
||||
let list = [chapterWordList, residueWordList][index]
|
||||
let listRef = [chapterWordListRef, residueWordListRef][index]
|
||||
if (wordFormData.type === FormMode.Add) {
|
||||
data.id = nanoid(6)
|
||||
data.checked = false
|
||||
let r = list.find(v => v.name === wordForm.name)
|
||||
if (r) return ElMessage.warning('已有相同名称单词!')
|
||||
else list.push(data)
|
||||
runtimeStore.editDict.originWords.push(data)
|
||||
runtimeStore.editDict.words.push(data)
|
||||
ElMessage.success('添加成功')
|
||||
wordForm = cloneDeep(DefaultFormWord)
|
||||
setTimeout(listRef?.scrollToBottom, 100)
|
||||
} else {
|
||||
let r = list.find(v => v.id === wordFormData.id)
|
||||
if (r) assign(r, data)
|
||||
//同步修改到列表
|
||||
r = runtimeStore.editDict.originWords.find(v => v.id === wordFormData.id)
|
||||
if (r) assign(r, data)
|
||||
r = runtimeStore.editDict.words.find(v => v.id === wordFormData.id)
|
||||
if (r) assign(r, data)
|
||||
ElMessage.success('修改成功')
|
||||
}
|
||||
syncMyDictList()
|
||||
} else {
|
||||
ElMessage.warning('请填写完整')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//同步到我的词典列表
|
||||
function syncMyDictList() {
|
||||
//任意修改,都将其变为自定义词典
|
||||
runtimeStore.editDict.isCustom = true
|
||||
|
||||
let rIndex = store.myDictList.findIndex(v => v.id === runtimeStore.editDict.id)
|
||||
if (rIndex > -1) {
|
||||
store.myDictList[rIndex] = cloneDeep(runtimeStore.editDict)
|
||||
} else {
|
||||
store.myDictList.push(cloneDeep(runtimeStore.editDict))
|
||||
}
|
||||
}
|
||||
|
||||
function delWord(val: { word: Word }) {
|
||||
let rIndex = runtimeStore.editDict.originWords.findIndex(v => v.id === val.word.id)
|
||||
if (rIndex > -1) {
|
||||
runtimeStore.editDict.originWords.splice(rIndex, 1)
|
||||
}
|
||||
let rIndex2 = runtimeStore.editDict.words.findIndex(v => v.id === val.word.id)
|
||||
if (rIndex2 > -1) {
|
||||
runtimeStore.editDict.words.splice(rIndex2, 1)
|
||||
}
|
||||
|
||||
if (wordFormData.type === FormMode.Edit && wordForm.name === val.word.name) {
|
||||
closeWordForm()
|
||||
}
|
||||
syncMyDictList()
|
||||
}
|
||||
|
||||
function editWord(word: Word, index: number, where: string) {
|
||||
wordFormData.type = FormMode.Edit
|
||||
wordFormData.id = word.id
|
||||
wordFormData.where = where
|
||||
wordForm.name = word.name
|
||||
wordForm.ukphone = word.ukphone
|
||||
wordForm.usphone = word.usphone
|
||||
wordForm.trans = word.trans.join('\n')
|
||||
}
|
||||
|
||||
|
||||
function addWord(where: string) {
|
||||
// setTimeout(wordListRef?.scrollToBottom, 100)
|
||||
wordFormData.type = FormMode.Add
|
||||
wordFormData.where = where
|
||||
wordForm = cloneDeep(DefaultFormWord)
|
||||
}
|
||||
|
||||
function closeWordForm() {
|
||||
wordFormData.type = FormMode.None
|
||||
wordForm = cloneDeep(DefaultFormWord)
|
||||
}
|
||||
|
||||
/**/
|
||||
/* 单词修改相关*/
|
||||
/**/
|
||||
|
||||
let showAllocationChapterDialog = $ref(false)
|
||||
let chapterWordNumber = $ref(settingStore.chapterWordNumber)
|
||||
|
||||
function resetChapterList(num?: number) {
|
||||
if (num !== undefined) {
|
||||
runtimeStore.editDict.chapterWordNumber = num
|
||||
}
|
||||
residueWordList = []
|
||||
chapterIndex = -1
|
||||
runtimeStore.editDict.words.map(v => v.checked = false)
|
||||
runtimeStore.editDict.chapterWords = chunk(runtimeStore.editDict.words, runtimeStore.editDict.chapterWordNumber)
|
||||
chapterList2 = Array.from({length: runtimeStore.editDict.chapterWords.length}).map((v, i) => ({id: i}))
|
||||
// console.log('runtimeStore.editDict.chapterWords',runtimeStore.editDict.chapterWords)
|
||||
// console.log('chapterList2',chapterList2)
|
||||
}
|
||||
|
||||
function changeSort(v: Sort) {
|
||||
if (v === Sort.normal) {
|
||||
runtimeStore.editDict.words = cloneDeep(runtimeStore.editDict.originWords)
|
||||
} else if (v === Sort.random) {
|
||||
runtimeStore.editDict.words = shuffle(runtimeStore.editDict.originWords)
|
||||
} else {
|
||||
runtimeStore.editDict.words = reverse(runtimeStore.editDict.originWords)
|
||||
}
|
||||
resetChapterList()
|
||||
}
|
||||
|
||||
async function resetDict() {
|
||||
MessageBox.confirm(
|
||||
'删除所有自定义内容: 章节、排序、单词,并恢复至默认状态,确认恢复?',
|
||||
'提示',
|
||||
async () => {
|
||||
closeWordForm()
|
||||
chapterIndex = -1
|
||||
if (runtimeStore.editDict.url) {
|
||||
runtimeStore.editDict.sort = Sort.normal
|
||||
runtimeStore.editDict.isCustom = false
|
||||
runtimeStore.editDict.chapterWordNumber = settingStore.chapterWordNumber
|
||||
let url = `./dicts/${runtimeStore.editDict.language}/${runtimeStore.editDict.type}/${runtimeStore.editDict.translateLanguage}/${runtimeStore.editDict.url}`;
|
||||
let r = await fetch(url)
|
||||
let v = await r.json()
|
||||
v.map(s => {
|
||||
s.id = nanoid(6)
|
||||
})
|
||||
runtimeStore.editDict.originWords = cloneDeep(v)
|
||||
changeSort(runtimeStore.editDict.sort)
|
||||
ElMessage.success('恢复成功')
|
||||
} else {
|
||||
ElMessage.success('恢复失败')
|
||||
}
|
||||
}
|
||||
)
|
||||
// runtimeStore.editDict
|
||||
}
|
||||
|
||||
function exportData() {
|
||||
let wb = XLSX.utils.book_new()
|
||||
// 一个简单的sheet
|
||||
let sheetData = chapterWordList.map(v => {
|
||||
return {
|
||||
单词: v.name,
|
||||
'音标①': v.usphone,
|
||||
'音标②': v.ukphone,
|
||||
'释义(一行一个释义)': v.trans.join('\n')
|
||||
}
|
||||
})
|
||||
let sheet = XLSX.utils.json_to_sheet(sheetData)
|
||||
wb.Sheets['Sheet1'] = sheet
|
||||
wb.SheetNames = ['Sheet1']
|
||||
XLSX.writeFile(wb, `${runtimeStore.editDict.name}-zh-CN.xlsx`);
|
||||
ElMessage.success('导出成功!')
|
||||
}
|
||||
|
||||
function importData(e: any) {
|
||||
let file = e.target.files[0]
|
||||
if (!file) return
|
||||
// no()
|
||||
let reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
let data = e.target.result;
|
||||
// 读取二进制的excel
|
||||
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 => {
|
||||
if (v['单词']) {
|
||||
let word: Word = {
|
||||
id: nanoid(6),
|
||||
checked: false,
|
||||
name: String(v['单词']),
|
||||
usphone: String(v['音标①'] ?? ''),
|
||||
ukphone: String(v['音标②'] ?? ''),
|
||||
trans: String(v['释义(一行一个释义)'] ?? '').split('\n')
|
||||
}
|
||||
return word
|
||||
}
|
||||
}).filter(v => v)
|
||||
|
||||
checkRepeatWord(words, residueWordList, noRepeatWords => {
|
||||
residueWordList = residueWordList.concat(noRepeatWords)
|
||||
setTimeout(residueWordListRef?.scrollToBottom, 100)
|
||||
},
|
||||
repeatWords => {
|
||||
repeatWords.map(v => {
|
||||
residueWordList[v.index] = v
|
||||
delete residueWordList[v.index].index
|
||||
})
|
||||
setTimeout(residueWordListRef?.scrollToBottom, 100)
|
||||
},
|
||||
() => ElMessage.success('导入成功!'))
|
||||
} else {
|
||||
ElMessage.warning('导入失败!原因:没有数据')
|
||||
}
|
||||
};
|
||||
reader.readAsBinaryString(file);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div class="left-column">
|
||||
<div class="header">
|
||||
<div class="common-title">
|
||||
<span>章节管理</span>
|
||||
<div class="options">
|
||||
<BaseIcon
|
||||
@click="addNewChapter"
|
||||
icon="fluent:add-20-filled"
|
||||
title="新增章节"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="select">
|
||||
<BaseButton size="small" @click="showAllocationChapterDialog = true">智能分配</BaseButton>
|
||||
<span>{{ runtimeStore.editDict.chapterWords.length }}章</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<RecycleScroller
|
||||
v-if="chapterList2.length"
|
||||
ref="chapterListRef"
|
||||
style="height: 100%;"
|
||||
:items="chapterList2"
|
||||
:item-size="63"
|
||||
key-field="id"
|
||||
v-slot="{ item,index }"
|
||||
>
|
||||
<div style="padding: 0 15rem;">
|
||||
<div class="common-list-item"
|
||||
:class="chapterIndex === item.id && 'active'"
|
||||
@click="handleChangeCurrentChapter(item.id)">
|
||||
<div class="flex gap10 flex1 ">
|
||||
<input type="radio" :checked="chapterIndex === item.id">
|
||||
<div class="item-title flex flex1 space-between">
|
||||
<span>第{{ item.id + 1 }}章</span>
|
||||
<span>{{ runtimeStore.editDict.chapterWords[item.id]?.length }}词</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<BaseIcon
|
||||
class-name="del"
|
||||
@click="delWordChapter(item.id)"
|
||||
title="移除"
|
||||
icon="solar:trash-bin-minimalistic-linear"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</RecycleScroller>
|
||||
<Empty v-else/>
|
||||
</div>
|
||||
</div>
|
||||
<ChapterWordList
|
||||
ref="chapterWordListRef"
|
||||
:title="`${chapterIndex > -1 ? `第${chapterIndex + 1}章` : ''} 单词列表`"
|
||||
:show-add="chapterIndex > -1"
|
||||
@add="addWord('chapter')"
|
||||
@del="delWord"
|
||||
:empty-title="chapterIndex === -1?'请选择章节':null"
|
||||
@edit="val => editWord(val.word,val.index,'chapter')"
|
||||
v-model:list="chapterWordList"/>
|
||||
<div class="options-column">
|
||||
<BaseButton @click="toChapterWordList"
|
||||
:disabled="residueWordListCheckedTotal === 0">
|
||||
<
|
||||
</BaseButton>
|
||||
<BaseButton @click="toResidueWordList"
|
||||
:disabled="chapterWordListCheckedTotal === 0">
|
||||
>
|
||||
</BaseButton>
|
||||
</div>
|
||||
<ChapterWordList
|
||||
ref="residueWordListRef"
|
||||
title="未分配单词列表"
|
||||
:empty-title="null"
|
||||
:show-add="true"
|
||||
@add="addWord('residue')"
|
||||
@del="delWord"
|
||||
@edit="val => editWord(val.word,val.index,'residue')"
|
||||
v-model:list="residueWordList"/>
|
||||
<div class="right-column">
|
||||
<div class="add" v-if="wordFormData.type">
|
||||
<div class="common-title">
|
||||
{{ wordFormData.type === FormMode.Add ? '添加' : '修改' }}单词
|
||||
{{
|
||||
wordFormData.type === FormMode.Add ? (wordFormData.where === 'chapter' ? `> 第${chapterIndex + 1}章` : '> 未分配') : ''
|
||||
}}
|
||||
</div>
|
||||
<el-form
|
||||
class="form"
|
||||
ref="wordFormRef"
|
||||
:rules="wordRules"
|
||||
:model="wordForm"
|
||||
label-width="100rem">
|
||||
<el-form-item label="单词" prop="name">
|
||||
<el-input v-model="wordForm.name"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="翻译">
|
||||
<el-input v-model="wordForm.trans"
|
||||
placeholder="多个翻译请换行"
|
||||
:autosize="{ minRows: 2, maxRows: 6 }"
|
||||
type="textarea"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="音标/发音①">
|
||||
<el-input v-model="wordForm.usphone"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="音标/发音②">
|
||||
<el-input v-model="wordForm.ukphone"/>
|
||||
</el-form-item>
|
||||
<div class="flex-center">
|
||||
<el-button @click="closeWordForm">关闭</el-button>
|
||||
<el-button type="primary" @click="onSubmitWord">保存</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Dialog
|
||||
title="智能分配单词"
|
||||
:footer="true"
|
||||
@ok="resetChapterList(chapterWordNumber)"
|
||||
@cancel="chapterWordNumber = settingStore.chapterWordNumber"
|
||||
v-model="showAllocationChapterDialog">
|
||||
<div class="allocation-chapter">
|
||||
<div class="desc">
|
||||
<div>为您自动创建章节以及分配单词</div>
|
||||
<div>注意:已存在的章节将被删除!</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="label">每章单词数</div>
|
||||
<span class="text">最小:10</span>
|
||||
<el-slider :min="10"
|
||||
:step="10"
|
||||
:max="runtimeStore.editDict.words.length ?? 10"
|
||||
v-model="chapterWordNumber"
|
||||
/>
|
||||
<span class="text">最大:{{ runtimeStore.editDict.words.length ?? 10 }}</span>
|
||||
</div>
|
||||
|
||||
<div class="notice">鼠标按住滑块,按键盘左右箭头可进行微调</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="label">将会创建</div>
|
||||
<div class="option">
|
||||
<span>{{ Math.ceil(runtimeStore.editDict.words.length / chapterWordNumber) }}章</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="label">每章</div>
|
||||
<div class="option">
|
||||
<span>{{ chapterWordNumber }}个单词</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" :style="{opacity:runtimeStore.editDict.words.length % chapterWordNumber}">
|
||||
<div class="label">最后一章</div>
|
||||
<div class="option">
|
||||
<span>{{ runtimeStore.editDict.words.length % chapterWordNumber }}个单词</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
<WordListDialog/>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.page-content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
position: relative;
|
||||
gap: var(--space);
|
||||
|
||||
.header {
|
||||
padding: 0 var(--space);
|
||||
|
||||
.common-title {
|
||||
margin-bottom: 0;
|
||||
position: relative;
|
||||
|
||||
.options {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
display: flex;
|
||||
gap: 10rem;
|
||||
}
|
||||
}
|
||||
|
||||
.select {
|
||||
height: 45rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
gap: 5rem;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.column {
|
||||
flex: 1;
|
||||
background: white;
|
||||
border-radius: 10rem;
|
||||
background: var(--color-second-bg);
|
||||
color: var(--color-font-1);
|
||||
padding-bottom: var(--space);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.left-column {
|
||||
max-width: 250rem;
|
||||
width: 16vw;
|
||||
@extend .column;
|
||||
}
|
||||
|
||||
.options-column {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10rem;
|
||||
}
|
||||
|
||||
.right-column {
|
||||
//@extend .column;
|
||||
flex: 1;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
|
||||
.add {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.allocation-chapter {
|
||||
width: 500rem;
|
||||
padding: var(--space);
|
||||
padding-top: 0;
|
||||
|
||||
.desc {
|
||||
margin-top: 10rem;
|
||||
margin-bottom: 35rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rem;
|
||||
margin-bottom: 15rem;
|
||||
word-break: keep-all;
|
||||
|
||||
.label {
|
||||
width: 90rem;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 12rem;
|
||||
}
|
||||
}
|
||||
|
||||
.notice {
|
||||
transform: translate3d(110rem, -20rem, 0);
|
||||
font-size: 11rem;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user