diff --git a/.gitignore b/.gitignore index ee56584..9824385 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ pnpm-debug.log* *.njsproj *.sln *.sw? + +auto-imports.d.ts +component.d.ts diff --git a/components.d.ts b/components.d.ts new file mode 100644 index 0000000..25fe594 --- /dev/null +++ b/components.d.ts @@ -0,0 +1,36 @@ +/* eslint-disable */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +// biome-ignore lint: disable +export {} + +/* prettier-ignore */ +declare module 'vue' { + export interface GlobalComponents { + CDialog: typeof import('./src/components/UI/CDialog.vue')['default'] + ElAside: typeof import('element-plus/es')['ElAside'] + ElButton: typeof import('element-plus/es')['ElButton'] + ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] + ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] + ElContainer: typeof import('element-plus/es')['ElContainer'] + ElDialog: typeof import('element-plus/es')['ElDialog'] + ElDropdown: typeof import('element-plus/es')['ElDropdown'] + ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] + ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] + ElHeader: typeof import('element-plus/es')['ElHeader'] + ElInput: typeof import('element-plus/es')['ElInput'] + ElLink: typeof import('element-plus/es')['ElLink'] + ElMain: typeof import('element-plus/es')['ElMain'] + ElOption: typeof import('element-plus/es')['ElOption'] + ElPageHeader: typeof import('element-plus/es')['ElPageHeader'] + ElSelect: typeof import('element-plus/es')['ElSelect'] + ElStep: typeof import('element-plus/es')['ElStep'] + ElSteps: typeof import('element-plus/es')['ElSteps'] + ElSwitch: typeof import('element-plus/es')['ElSwitch'] + ElTabPane: typeof import('element-plus/es')['ElTabPane'] + ElTabs: typeof import('element-plus/es')['ElTabs'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + } +} diff --git a/public/index.html b/index.html similarity index 71% rename from public/index.html rename to index.html index ed04218..91420a0 100644 --- a/public/index.html +++ b/index.html @@ -1,13 +1,12 @@ - - + - + - <%= htmlWebpackPlugin.options.title %> + Wordin + \ No newline at end of file diff --git a/src/components/recite/recite.vue b/src/components/recite/recite.vue deleted file mode 100644 index 5fa5d47..0000000 --- a/src/components/recite/recite.vue +++ /dev/null @@ -1,422 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/composables/useDarkMode.ts b/src/composables/useDarkMode.ts new file mode 100644 index 0000000..aadbe83 --- /dev/null +++ b/src/composables/useDarkMode.ts @@ -0,0 +1,21 @@ +import { useMainStore } from '../store'; + +export function useDarkMode() { + const store = useMainStore(); + const HTMLnode = document.documentElement; + + const setDarkMode = () => { + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + if (prefersDark) { + HTMLnode.classList.add('dark'); + store.setDarkMode(true); + } else { + HTMLnode.classList.remove('dark'); + store.setDarkMode(false); + } + }; + + setDarkMode(); + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + mediaQuery.addEventListener('change', setDarkMode); +} \ No newline at end of file diff --git a/src/composables/useVuetifyDarkmode.ts b/src/composables/useVuetifyDarkmode.ts new file mode 100644 index 0000000..a400fe8 --- /dev/null +++ b/src/composables/useVuetifyDarkmode.ts @@ -0,0 +1,21 @@ +import { watch } from 'vue'; +import { useTheme } from 'vuetify'; +import { useMainStore } from '@/store'; + +export function useVuetifyDarkmode() { + const theme = useTheme(); + const store = useMainStore(); + + const setVuetifyDarkmode = (isDarkMode: boolean) => { + theme.global.name.value = isDarkMode ? 'dark' : 'light'; + }; + + setVuetifyDarkmode(store.isDarkMode); + + watch( + () => store.isDarkMode, + (isDarkMode) => { + setVuetifyDarkmode(isDarkMode); + } + ); +} \ No newline at end of file diff --git a/src/loader/el-loader.js b/src/loader/el-loader.js deleted file mode 100644 index c2c674d..0000000 --- a/src/loader/el-loader.js +++ /dev/null @@ -1,53 +0,0 @@ -import { - ElForm, - ElButton, - ElInput, - ElCheckbox, - ElDialog, - ElTable, - ElStep, - ElLink, - ElCard, - ElCheckboxGroup, - ElDropdown, - ElFooter, - ElMain, - ElContainer, - ElHeader, - ElPageHeader, - ElTabs, - ElTabPane, - ElSteps, - ElSwitch, - ElSelect, - ElOption, -} from 'element-plus' - -const element = { - install: function(app) { - app.use(ElForm) - app.use(ElButton) - app.use(ElInput) - app.use(ElCheckbox) - app.use(ElDialog) - app.use(ElTable) - app.use(ElStep) - app.use(ElLink) - app.use(ElCard) - app.use(ElCheckboxGroup) - app.use(ElDropdown) - app.use(ElFooter) - app.use(ElMain) - app.use(ElContainer) - app.use(ElHeader) - app.use(ElPageHeader) - app.use(ElTabPane) - app.use(ElSteps) - app.use(ElTabs) - app.use(ElSwitch) - app.use(ElSelect) - app.use(ElOption) - } -} - -export default element \ No newline at end of file diff --git a/src/loader/md-loader.js b/src/loader/md-loader.js deleted file mode 100644 index f563aad..0000000 --- a/src/loader/md-loader.js +++ /dev/null @@ -1,15 +0,0 @@ -const md = require('marked') - -module.exports = function (source) { - this.cacheable() - // source 是原始文件内容,html 是用 markdown-it 编译后的 html 内容 - const html = md.parse(source); - const template = ( - `` - ) - return template -} \ No newline at end of file diff --git a/src/main.js b/src/main.js deleted file mode 100644 index 3cfb319..0000000 --- a/src/main.js +++ /dev/null @@ -1,21 +0,0 @@ -import {createApp} from 'vue' -import App from './App.vue' -import { createStore } from 'vuex' -// import ElementPlus from 'element-plus' -import element from './loader/el-loader' -import 'element-plus/dist/index.css' -import 'element-plus/theme-chalk/dark/css-vars.css' -import 'boxicons' -import router from './router.js' -import axios from './js/request.js' - -const app = createApp(App); -const store = createStore({ - state(){ - } -}); -app.use(store); -app.use(element); -app.use(router); -app.config.globalProperties.$axios = axios; -app.mount('#app'); \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..574bbd8 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,32 @@ +import { createApp } from "vue"; +import { createPinia } from "pinia"; +import App from "./App.vue"; +import "element-plus/dist/index.css"; +import "element-plus/theme-chalk/dark/css-vars.css"; +import { useDarkMode } from "./composables/useDarkMode"; +import router from "./router"; + +// Vuetify +import "vuetify/styles"; +import { createVuetify } from "vuetify"; +import { aliases, mdi } from "vuetify/iconsets/mdi"; +import "@mdi/font/css/materialdesignicons.css"; // Ensure you are using + +const vuetify = createVuetify({ + icons: { + defaultSet: "mdi", + aliases, + sets: { + mdi, + }, + }, +}); + +const app = createApp(App); +app.use(vuetify); +app.use(router); +app.use(createPinia()); + +useDarkMode(); + +app.mount("#app"); diff --git a/src/router.js b/src/router/index.ts similarity index 50% rename from src/router.js rename to src/router/index.ts index 1778155..73ad414 100644 --- a/src/router.js +++ b/src/router/index.ts @@ -1,14 +1,14 @@ import {createRouter,createWebHashHistory} from 'vue-router' -const manage = ()=>import('./components/Manage.vue'); -const editor = ()=>import('./components/manage/Editor.vue'); -const home = ()=>import("./components/Home.vue"); -const recite = ()=>import('./components/recite/recite.vue'); -const setlist = ()=>import('./components/manage/SetList.vue'); -const newset = ()=>import('./components/manage/NewSet.vue'); -const display = ()=>import('./components/manage/Display.vue'); -const select = ()=>import('./components/recite/select.vue'); -const manual = ()=>import('./components/post/manual.vue'); -const about = ()=>import('./components/post/about.vue'); +const manage = ()=>import('@/views/ManageView.vue'); +const editor = ()=>import('@/views/manage/EditorView.vue'); +const home = ()=>import("@/views/HomeView.vue"); +const recite = ()=>import('@/views/recite/ReciteView.vue'); +const setlist = ()=>import('@/views/manage/SetListView.vue'); +const newset = ()=>import('@/views/manage/NewSetView.vue'); +const display = ()=>import('@/views/manage/DisplayView.vue'); +const select = ()=>import('@/views/recite/SelectView.vue'); +const manual = ()=>import('@/views/post/ManualView.vue'); +const about = ()=>import('@/views/post/AboutView.vue'); const routes = [ { path: '/', component: home }, diff --git a/src/js/history.js b/src/scripts/history.ts similarity index 55% rename from src/js/history.js rename to src/scripts/history.ts index 674e43e..bd77c1c 100644 --- a/src/js/history.js +++ b/src/scripts/history.ts @@ -1,6 +1,11 @@ -import axios from './request.js' +import { HistoryItem, WordItem } from '@/types'; +import axios from './request.ts'; + const limit = 10; -export default class _history { + +export default class IHistory { + private histories: HistoryItem[]; + constructor() { this.histories = []; let histories = localStorage.getItem("histories"); @@ -8,113 +13,136 @@ export default class _history { this.histories = JSON.parse(histories); } } - save() { + + save(): void { localStorage.setItem('histories', JSON.stringify(this.histories)); } - get length() { + + get length(): number { return this.histories.length; } - get top() { + + get top(): HistoryItem | undefined { return this.histories[0]; } - get _content(){ + + get _content(): HistoryItem[] { return this.histories; } - use(index) { + + use(index: number): boolean { if (this.histories.length > index) { console.log(this.histories); - let history = this.histories.splice(index, 1); + let history = this.histories.splice(index, 1)[0]; history.modified = (new Date()).getTime(); - this.histories = history.concat(this.histories); + this.histories = [history].concat(this.histories); console.log(this.histories); this.save(); return true; } else return false; } - del(index) { + + del(index: number): void { this.histories.splice(index, 1); this.save(); } - add(localsets, onlinesets, settings, callback) { + + add(localsets: string[], onlinesets: any[], settings: any, callback?: () => void): void { let time = (new Date()).getTime(); - let data = { - localsets, onlinesets, settings, + let data: HistoryItem = { + localsets, + onlinesets, + settings, current: 0, modified: time }; + if (settings.shuffle) { data.settings.seed = time; } + this.histories = [data].concat(this.histories); - if(this.histories.length > limit){ - this.histories = this.histories.slice(0,limit); + if (this.histories.length > limit) { + this.histories = this.histories.slice(0, limit); } this.save(); + if (typeof callback === 'function') { callback(); } } - count(cnt) { + + count(cnt: number): void { if (this.histories.length > 0) { this.histories[0].current = cnt; this.save(); } } - async init_recite(callback, err) { + + async init_recite( + callback?: (words: WordItem[], current: number, arr: number[], settings: any) => void, + err?: (msg: string) => void + ): Promise { let history = this.histories[0]; - let words = []; + let words: WordItem[] = []; + for (let i of history.localsets) { let content = localStorage.getItem(i); - if (content){ + if (content) { let temp = JSON.parse(content); - if(history.settings.ignore_phrases){ - for(let j of temp){ + if (history.settings.ignore_phrases) { + for (let j of temp) { console.log(j); - if(j.type != 'phr.'){ + if (j.type != 'phr.') { words.push(j); } } } - else words = words.concat(...temp); + else words = words.concat(temp); } - else return this.handle_err("单词本不存在",err); + else return this.handle_err("单词本不存在", err); } + for (let i of history.onlinesets) { let res = await axios.get("wordset/detail", { params: i - }) + }); if (res.status != 200) { - return this.handle_err("获取单词本时出现错误",err); + return this.handle_err("获取单词本时出现错误", err); } let temp = res.data; - if(history.settings.ignore_phrases){ - for(let j of temp){ - if(j.type != 'phr.'){ + if (history.settings.ignore_phrases) { + for (let j of temp) { + if (j.type != 'phr.') { words.push(j); } } } - else words = words.concat(...temp); + else words = words.concat(temp); } + history.total = words.length; - if(words.length <= 0){ - return this.handle_err("单词本为空",err); + if (words.length <= 0) { + return this.handle_err("单词本为空", err); } + let arr = Array.from(new Array(words.length).keys()); - if(history.settings.shuffle){ - let seed = history.settings.seed; + if (history.settings.shuffle) { + let seed = history.settings.seed as number; for (let i = words.length - 1; i > 0; i--) { [arr[i], arr[seed % i]] = [arr[seed % i], arr[i]]; } } + this.save(); if (typeof callback === 'function') { - callback(words,history.current,arr,history.settings); + callback(words, history.current, arr, history.settings); } } - handle_err(msg,err){ - this.histories.splice(0,1); + + handle_err(msg: string, err?: (msg: string) => void): void { + this.histories.splice(0, 1); this.save(); if (typeof err === 'function') { err(msg); diff --git a/src/js/request.js b/src/scripts/request.ts similarity index 100% rename from src/js/request.js rename to src/scripts/request.ts diff --git a/src/js/wordsets.js b/src/scripts/wordsets.ts similarity index 54% rename from src/js/wordsets.js rename to src/scripts/wordsets.ts index facbc34..884ca3e 100644 --- a/src/js/wordsets.js +++ b/src/scripts/wordsets.ts @@ -1,7 +1,41 @@ -import uuid from 'node-uuid'; +import { WordItem, WordSetInfo } from '@/types'; +import { v1 as uuidv1 } from 'uuid'; +// Add type declarations for the File System Access API +declare global { + interface FileSystemWritableFileStream { + write(data: any): Promise; + seek(position: number): Promise; + truncate(size: number): Promise; + close(): Promise; + } + + interface FileSystemFileHandle { + getFile(): Promise; + createWritable(): Promise; + } + + interface Window { + showSaveFilePicker(options?: { + types?: Array<{ + description: string; + accept: Record; + }>; + }): Promise; + + showOpenFilePicker(options?: { + multiple?: boolean; + types?: Array<{ + description: string; + accept: Record; + }>; + }): Promise; + } +} + +export class Wordset { + private sets: {[className: string]: WordSetInfo[]}; -export default class _wordset { constructor() { this.sets = {}; let wordsets = localStorage.getItem("wordsets"); @@ -9,54 +43,65 @@ export default class _wordset { this.sets = JSON.parse(wordsets); } } - save() { + + save(): void { localStorage.setItem("wordsets", JSON.stringify(this.sets)); } - get _inner() { + + get _inner(): {[className: string]: WordSetInfo[]} { return this.sets; } - get _firstClass(){ + + get _firstClass(): string | null { return Object.keys(this.sets)[0] || null; } - get _allclass(){ + + get _allclass(): string[] { return Object.keys(this.sets); } - get _allsets(){ + + get _allsets(): WordSetInfo[][] { return Object.values(this.sets); } - getClass(class_name) { + + getClass(class_name: string): WordSetInfo[] | null { if (this.sets[class_name]) { return this.sets[class_name]; } else return null; } - getSetStatus(class_name,id){ + + getSetStatus(class_name: string, id: string): WordSetInfo | null { let set_class = this.getClass(class_name); - if(set_class){ - for(let i of set_class){ - if(i.id === id){ + if(set_class) { + for(let i of set_class) { + if(i.id === id) { return i; } } } return null; } - getId(class_name,id){ + + getId(class_name: string, id: string): number | undefined { let set_class = this.sets[class_name]; - for(let index in set_class){ - if(set_class[index].id === id){ - return index; + for(let index in set_class) { + if(set_class[index].id === id) { + return Number(index); } } + return undefined; } - getSet(id) { - let sets = JSON.parse(localStorage.getItem(id)); + + getSet(id: string): WordItem[] { + let sets = localStorage.getItem(id); if (sets) { - return sets; + return JSON.parse(sets); } else return []; } - delSet(class_name, index) { + + delSet(class_name: string, index: number): boolean { let set_class = this.sets[class_name]; if (set_class) { if (set_class[index]) { @@ -71,11 +116,12 @@ export default class _wordset { } return false; } - addSet(class_name,name) { + + addSet(class_name: string, name: string): string { if (!this.sets[class_name]) { this.sets[class_name] = []; } - let id = uuid.v1(); + let id = uuidv1(); let time = (new Date()).getTime() this.sets[class_name].push({ name: name, @@ -86,14 +132,16 @@ export default class _wordset { this.save(); return id; } - saveSet(id,data){ + + saveSet(id: string, data: WordItem[]): void { localStorage.setItem(id, JSON.stringify(data)); } - renameSet(class_name,id,new_name){ + + renameSet(class_name: string, id: string, new_name: string): void { let set_class = this.getClass(class_name); - if(set_class){ - for(let i of set_class){ - if(i.id === id){ + if(set_class) { + for(let i of set_class) { + if(i.id === id) { i.name = new_name; this.save(); return; @@ -101,25 +149,28 @@ export default class _wordset { } } } - addSetTo(origin_id,dest_id){ + + addSetTo(origin_id: string, dest_id: string): number { let origin_set = this.getSet(origin_id); let dest_set = this.getSet(dest_id); - let set={}; + let set: {[word: string]: WordItem} = {}; origin_set.forEach(element => { set[element.word] = element; }); dest_set.forEach(element => { set[element.word] = element; }); - set = Object.values(set); - this.saveSet(dest_id,set); - return origin_set.length + dest_set.length - set.length; + let mergedSet = Object.values(set); + this.saveSet(dest_id, mergedSet); + return origin_set.length + dest_set.length - mergedSet.length; } - fromArray(arr,class_name,name){ - let id = this.addSet(class_name,name); - this.saveSet(id,arr); + + fromArray(arr: WordItem[], class_name: string, name: string): void { + let id = this.addSet(class_name, name); + this.saveSet(id, arr); } - async import_set(callback){ + + async import_set(callback?: (count: number) => void): Promise { let [fileHandle] = await window.showOpenFilePicker({ types: [ { @@ -144,11 +195,12 @@ export default class _wordset { } } this.save(); - if(typeof callback === "function"){ + if(typeof callback === "function") { callback(cnt); } } - async export_set(callback) { + + async export_set(callback?: (count: number) => void): Promise { let fileHandle = await window.showSaveFilePicker({ types: [ { @@ -162,19 +214,21 @@ export default class _wordset { let writestream = await fileHandle.createWritable(); let data = { wordsets: this.sets, - words: {} + words: {} as {[id: string]: string} }; let cnt = 0; for (let i of Object.values(this.sets)) { for (let j of i) { let temp = localStorage.getItem(j.id); - data.words[j.id] = temp; - cnt++; + if (temp) { + data.words[j.id] = temp; + cnt++; + } } } - writestream.write(JSON.stringify(data)); - writestream.close(); - if(typeof callback === "function"){ + await writestream.write(JSON.stringify(data)); + await writestream.close(); + if(typeof callback === "function") { callback(cnt); } } diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..e5294f6 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,17 @@ +import IHistory from '@/scripts/history'; +import { Wordset } from '@/scripts/wordsets'; +import { defineStore} from 'pinia' +import { ref } from 'vue' + +export const useMainStore = defineStore('main', () => { + const sets = ref({}); + const history = ref(new IHistory()) + const wordsets = ref(new Wordset()) + const isDarkMode = ref(false) + function setDarkMode(value: boolean) { + isDarkMode.value = value; + } + return { + sets,history,wordsets,setDarkMode,isDarkMode + } +}) \ No newline at end of file diff --git a/src/types/index.d.ts b/src/types/index.d.ts new file mode 100644 index 0000000..5a5c24a --- /dev/null +++ b/src/types/index.d.ts @@ -0,0 +1,25 @@ +export interface HistoryItem { + localsets: string[]; + onlinesets: any[]; + settings: { + shuffle: boolean; + seed?: number; + ignore_phrases?: boolean; + [key: string]: any; + }; + current: number; + modified: number; + total?: number; +} + +export interface WordItem { + type: string; + word: string; + trans: string +} + +export interface WordSetInfo { + name: string; + id: string; + created: number; +} \ No newline at end of file diff --git a/src/components/Home.vue b/src/views/HomeView.vue similarity index 80% rename from src/components/Home.vue rename to src/views/HomeView.vue index 0c2fc15..6582ccf 100644 --- a/src/components/Home.vue +++ b/src/views/HomeView.vue @@ -17,7 +17,7 @@
历史记录
-
+
{{ index + 1 }}
背诵进度: {{ history.current }}/{{ history.total || "Unknown" }}
{{ (new Date(history.modified)) }}
@@ -34,50 +34,34 @@ 确定
-
黑暗模式
- - \ No newline at end of file diff --git a/src/components/recite/select.vue b/src/views/recite/SelectView.vue similarity index 61% rename from src/components/recite/select.vue rename to src/views/recite/SelectView.vue index 28526ed..0ed2347 100644 --- a/src/components/recite/select.vue +++ b/src/views/recite/SelectView.vue @@ -1,6 +1,7 @@ -