Release 1.0.1

master 1.0.1
cast1e 2024-01-13 14:56:57 +08:00
parent eb4010bef0
commit 3cf483c422
17 changed files with 538 additions and 358 deletions

View File

@ -1,5 +1,6 @@
module.exports = { module.exports = {
presets: [ presets: [
'@vue/cli-plugin-babel/preset' '@vue/cli-plugin-babel/preset'
] ],
plugins: ["@babel/plugin-transform-private-methods"]
} }

55
package-lock.json generated
View File

@ -26,7 +26,8 @@
"@vue/cli-service": "~5.0.0", "@vue/cli-service": "~5.0.0",
"element-plus": "^2.3.14", "element-plus": "^2.3.14",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3" "eslint-plugin-vue": "^8.0.3",
"raw-loader": "^4.0.2"
} }
}, },
"node_modules/@aashutoshrathi/word-wrap": { "node_modules/@aashutoshrathi/word-wrap": {
@ -9330,6 +9331,58 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/raw-loader": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz",
"integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==",
"dev": true,
"dependencies": {
"loader-utils": "^2.0.0",
"schema-utils": "^3.0.0"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^4.0.0 || ^5.0.0"
}
},
"node_modules/raw-loader/node_modules/loader-utils": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"dev": true,
"dependencies": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
},
"engines": {
"node": ">=8.9.0"
}
},
"node_modules/raw-loader/node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/react": { "node_modules/react": {
"version": "16.14.0", "version": "16.14.0",
"resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",

View File

@ -26,7 +26,8 @@
"@vue/cli-service": "~5.0.0", "@vue/cli-service": "~5.0.0",
"element-plus": "^2.3.14", "element-plus": "^2.3.14",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3" "eslint-plugin-vue": "^8.0.3",
"raw-loader": "^4.0.2"
}, },
"eslintConfig": { "eslintConfig": {
"root": true, "root": true,

View File

@ -1,8 +1,10 @@
<template> <template>
<div id="navibar" class="pconly" > <div id="navibar" class="pconly" >
<div class="navi-item"><el-link @click="go('/')" id="logo">wordIn</el-link></div> <div class="navi-item"><el-link @click="go('/')" id="logo">wordIn</el-link></div>
<div class="navi-item"><el-link @click="go('/recite')" class="link">背诵</el-link></div> <div class="navi-item"><el-link @click="go('/select')" class="link">背诵</el-link></div>
<div class="navi-item"><el-link @click="go('/manage')" class="link">编辑单词本</el-link></div> <div class="navi-item"><el-link @click="go('/manage')" class="link">编辑单词本</el-link></div>
<div class="navi-item"><el-link @click="go('/manual')" class="link">使用说明</el-link></div>
<div class="navi-item"><el-link @click="go('/about')" class="link">关于</el-link></div>
</div> </div>
<router-view style="margin-top: 10px;"></router-view> <router-view style="margin-top: 10px;"></router-view>
<!-- <div id="navibar" class="mbonly"></div> --> <!-- <div id="navibar" class="mbonly"></div> -->
@ -10,6 +12,7 @@
<script> <script>
import _wordsets from './js/wordsets' import _wordsets from './js/wordsets'
import _history from './js/history';
window.htmlClasses = []; window.htmlClasses = [];
window.addHtmlclasses = (classname) => { window.addHtmlclasses = (classname) => {
@ -44,12 +47,13 @@ export default {
}, },
created() { created() {
this.$store.state.wordsets = new _wordsets(); this.$store.state.wordsets = new _wordsets();
let res = localStorage.getItem("bgimg"); this.$store.state.history = new _history();
if (res) { // let res = localStorage.getItem("bgimg");
document.body.style.backgroundImage = `url("${res}")`; // if (res) {
document.body.style.backgroundSize = "cover"; // document.body.style.backgroundImage = `url("${res}")`;
window.addHtmlclasses("bgimged"); // document.body.style.backgroundSize = "cover";
} // window.addHtmlclasses("bgimged");
// }
const isDarkTheme = window.matchMedia("(prefers-color-scheme: dark)"); const isDarkTheme = window.matchMedia("(prefers-color-scheme: dark)");
if (isDarkTheme.matches) window.addHtmlclasses("dark"); if (isDarkTheme.matches) window.addHtmlclasses("dark");
isDarkTheme.addEventListener('change', (event) => { isDarkTheme.addEventListener('change', (event) => {
@ -105,7 +109,7 @@ export default {
html { html {
--bg-color: #ffffffae; --bg-color: #ffffffae;
--text-color: #464646; --text-color: #464646;
--bd-color: #737373; --bd-color: #bbbbbb99;
} }
html.dark { html.dark {
@ -118,7 +122,7 @@ html.dark {
.container { .container {
height: calc(100% - 20px); height: calc(100% - 20px);
width: calc(100% - 20px); width: calc(100% - 20px);
padding: 20px; /* padding: 20px; */
animation: enter ease-out .6s backwards; animation: enter ease-out .6s backwards;
} }
@ -168,7 +172,7 @@ html.bgimged .card {
<style scoped> <style scoped>
#navibar { #navibar {
height: 50px; height: 60px;
border-bottom: solid 1px var(--bd-color); border-bottom: solid 1px var(--bd-color);
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -1,17 +1,29 @@
<template> <template>
<div class="container"> <div class="container rowbox" style="height: calc(100% - 65px); align-items: center;width: 100%;overflow: auto;">
<div id="ball" class="pconly"></div> <div class="colbox" style="width: 95%;margin-top: 10px;">
<div id="main"> <div style="flex-grow: 2;margin:30px;" class="rowbox">
<div id="title"> WordIn</div> <div style="font-size: 25px;color: var(--text-color);font-weight: 600;">欢迎使用</div>
<div class="colbox"> <div id="title">wordIn</div>
<div>当前版本: 1.01 更新时间:2024年1月11日 10:43 AM</div>
</div>
<div style="flex-grow: 1;align-items: center;" class="colbox card">
<router-link to="/select" class="button"> <router-link to="/select" class="button">
背诵 开始新背诵
</router-link> </router-link>
<router-link to="/manage" class="button"> <router-link to="/manage" class="button">
编辑单词本 查看单词本
</router-link> </router-link>
</div> </div>
</div> </div>
<div id="history" class="wrapper">
<div class="title" style="margin-left: 20px;margin-top: 10px;width: 95%;">历史记录</div>
<div class="card colbox history-item" v-for="(history, index) in $store.state.history._content" :key="index">
<div style="width: 25px;">{{ index + 1 }}</div>
<div style="width: 200px;">背诵进度: {{ history.current }}/{{ history.total || "Unknown" }}</div>
<div style="flex-grow: 1;"> {{ (new Date(history.modified)) }}</div>
<el-button @click="restart(index)"></el-button>
</div>
</div>
</div> </div>
<div id="setting"> <div id="setting">
<box-icon class="btn" color="var(--text-color)" name='cog' @click="open_setting_dialog"></box-icon> <box-icon class="btn" color="var(--text-color)" name='cog' @click="open_setting_dialog"></box-icon>
@ -52,6 +64,12 @@ export default {
} else { } else {
window.delHtmlclasses("dark"); window.delHtmlclasses("dark");
} }
},
restart(index){
this.$router.push({
path: "./recite",
query: { index}
})
} }
}, },
created() { created() {
@ -92,6 +110,7 @@ export default {
.button { .button {
margin-top: 30px; margin-top: 30px;
} }
.container { .container {
overflow: hidden; overflow: hidden;
} }
@ -99,17 +118,18 @@ export default {
@media screen and (min-width: 500px) { @media screen and (min-width: 500px) {
#title { #title {
font-size: 180px; font-size: 100px;
color: var(--text-color); color: var(--text-color);
text-shadow: #00000057 5px 5px 20px; text-shadow: #00000057 5px 5px 20px;
line-height: 100px;
font-weight: 600;
margin-bottom: 15px;
} }
.title { .title {
font-weight: 800; font-weight: 800;
color: var(--text-color); color: var(--text-color);
font-size: 35px; font-size: 35px;
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
@ -121,6 +141,7 @@ export default {
.button { .button {
margin: 30px; margin: 30px;
} }
#ball { #ball {
width: 1350px; width: 1350px;
height: 1350px; height: 1350px;
@ -129,6 +150,21 @@ export default {
} }
} }
#history{
width: calc(95% - 20px);
flex-grow: 1;
}
.wrapper {
border: 1px solid var(--bd-color);
border-radius: 5px;
margin-top: 10px;
}
.history-item {
margin: 20px;
padding: 15px;
}
#setting { #setting {
position: absolute; position: absolute;

View File

@ -206,7 +206,8 @@ export default {
.container{ .container{
padding: 10px; padding: 10px;
height: 100%; height: calc(100% - 70px);
padding-top: 0;
} }
} }

View File

@ -23,8 +23,7 @@
name='plus'></box-icon></el-button> name='plus'></box-icon></el-button>
</div> </div>
</el-header> </el-header>
<el-container style="height:calc(100% - 40px);"> <el-container style="height:calc(100% - 200px);position: relative;">
<el-aside id="sidebar"> <el-aside id="sidebar">
<div class="sidebar-title"> <div class="sidebar-title">
|本地 |本地
@ -341,12 +340,14 @@ export default {
#sidebar { #sidebar {
background-color: var(--bg-color); background-color: var(--bg-color);
border-radius: 0 5px 5px 0; border-radius: 5px;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
animation: sidebar-enter ease-out .6s backwards; animation: sidebar-enter ease-out .6s backwards;
height: calc(100% - 50px); height: calc(100% - 100px);
padding-left: 10px;
margin: 10px; margin: 10px;
border: 1px solid var(--bd-color);
} }
.sidebar-title { .sidebar-title {
@ -395,7 +396,7 @@ html.bgimged .wordset {
#wordsets-container { #wordsets-container {
overflow-x: auto; overflow-x: auto;
height: 100%; height: calc(100% - 60px);
position: relative; position: relative;
} }

View File

@ -0,0 +1,39 @@
<template>
<div id="wrapper" class="rowbox container">
<Post id="post"/>
</div>
</template>
<script>
import Post from '@/md/about.md'
export default {
name: "ManualPage",
components:{
Post
},
created() {
}
}
</script>
<style scoped>
#post {
height:auto;
margin-top: 20px;
margin-bottom: 10px;
padding-top:0;
padding:30px;
border-radius:10px;
width: 80%;
border:solid 1px var(--bd-color);
color:var(--text-color);
}
#wrapper {
height:calc(100% - 80px);
width: 100%;
align-items: center;
overflow: auto;
}
</style>

View File

@ -0,0 +1,39 @@
<template>
<div id="wrapper" class="rowbox container">
<Post id="post"/>
</div>
</template>
<script>
import Post from '@/md/manual.md'
export default {
name: "ManualPage",
components:{
Post
},
created() {
}
}
</script>
<style scoped>
#post {
height:auto;
margin-top: 20px;
margin-bottom:20px;
padding-top:0;
padding:30px;
border-radius:10px;
width: 80%;
border:solid 1px var(--bd-color);
color:var(--text-color);
}
#wrapper {
height:calc(100% - 90px);
width: 100%;
align-items: center;
overflow: auto;
}
</style>

View File

@ -1,58 +1,6 @@
<template> <template>
<div v-if="mode === 0" class="wrapper"> <div>
<el-page-header style="margin-top:20px;margin-left:20px" @back="back_home"> <el-page-header style="margin-top:20px;margin-left:20px" @back="$router.push('/')">
<template #content>
<span class="text-large font-600 mr-3"> 选择范围 </span>
</template>
</el-page-header>
<div id="range" class="container colbox">
<div class="rowbox item">
<div id="lastrecite" class="card">
<el-button style="margin-bottom: 20px;" class="tbtn" type="primary" @click="init"></el-button><br />
<el-button style="margin:0;" class="tbtn" type="success" @click="last_recite"></el-button>
</div>
<div id="online-set-container" class="card">
<div class="title">选择测验范围</div>
<el-checkbox v-model="local.checkAll" :indeterminate="local.isIndeterminate"
@change="(res) => { handleCheckAllChange(local, res) }" size="large">全选</el-checkbox>
<el-checkbox-group v-model="local.checkedSets" @change="(res) => { handleChange(local, res) }">
<div class="set_radios" v-for="(set_class, set_class_name) in wordsets" :key="set_class_name">
<p>{{ set_class_name }}</p>
<el-checkbox class="checkbox" v-for="set in set_class" :key="set.id" :label="set.id"
size="large" border>
<div style="font-size: 18px;font-weight: 500;">
{{ set.name }}
</div>
</el-checkbox>
</div>
</el-checkbox-group>
</div>
</div>
<div class="rowbox item">
<div id="rangeselect" class="card">
<div class="title">在线词库</div>
<el-checkbox v-model="online.checkAll" :indeterminate="online.isIndeterminate"
@change="(res) => { handleCheckAllChange(online, res) }" size="large">全选</el-checkbox>
<el-checkbox-group v-model="online.checkedSets" @change="(res) => { handleChange(online, res) }">
<div class="set_radios" v-for="(set, set_name) in online_sets" :key="set_name">
<div class="subtitle">{{ set_name }}</div>
<div v-for="(book, book_name) in set" :key="book_name">
<p>{{ book_name }}</p>
<el-checkbox class="checkbox" v-for="(config, id) in book" :key="id" :label="id"
size="large" border>
<div style="font-size: 18px;font-weight: 500;">
{{ config.name }}
</div>
</el-checkbox>
</div>
</div>
</el-checkbox-group>
</div>
</div>
</div>
</div>
<div v-if="mode === 1">
<el-page-header style="margin-top:20px;margin-left:20px" @back="mode = 0">
<template #content> <template #content>
<span class="text-large font-600 mr-3"> 背诵 </span> <span class="text-large font-600 mr-3"> 背诵 </span>
</template> </template>
@ -82,13 +30,15 @@
<div class="colbox para"> <div class="colbox para">
<div class="mid-text">分组:</div> <div class="mid-text">分组:</div>
<el-select v-model="set_class" class="m-2" placeholder="Select"> <el-select v-model="set_class" class="m-2" placeholder="Select">
<el-option v-for="item in Object.keys(wordsets)" :key="item" :label="item" :value="item" /> <el-option v-for="item in $store.state.wordsets._allclass" :key="item" :label="item"
:value="item" />
</el-select> </el-select>
</div> </div>
<div class="colbox para"> <div class="colbox para">
<div class="mid-text">单词本:</div> <div class="mid-text">单词本:</div>
<el-select v-model="set_id" class="m-2" placeholder="Select"> <el-select v-model="set_id" class="m-2" placeholder="Select">
<el-option v-for="item in wordsets[set_class]" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in $store.state.wordsets.getClass(set_class)" :key="item.id"
:label="item.name" :value="item.id" />
</el-select> </el-select>
</div> </div>
<el-button @click="add_to" type="primary">添加</el-button> <el-button @click="add_to" type="primary">添加</el-button>
@ -98,7 +48,6 @@
</template> </template>
<script> <script>
import axios from 'axios';
import { ElMessage, ElNotification, ElMessageBox } from 'element-plus'; import { ElMessage, ElNotification, ElMessageBox } from 'element-plus';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
@ -106,20 +55,7 @@ export default {
name: "WordRecite", name: "WordRecite",
data() { data() {
return { return {
mode: 0,
wordsets: this.$store.state.wordsets, wordsets: this.$store.state.wordsets,
local: {
allsets: [],
checkedSets: [],
isIndeterminate: false,
checkAll: false,
},
online: {
allsets: [],
checkedSets: [],
isIndeterminate: false,
checkAll: false,
},
testWords: [], testWords: [],
seq: [], seq: [],
total: 0, total: 0,
@ -129,12 +65,14 @@ export default {
set_class: "", set_class: "",
set_id: "", set_id: "",
online_sets: {}, online_sets: {},
online_ids: {} online_ids: {},
settings: {
direct_answer: false,
}
}; };
}, },
methods: { methods: {
key_listener(e) { key_listener(e) {
if (this.mode === 1) {
let ctrlKey = e.ctrlKey || e.metaKey; let ctrlKey = e.ctrlKey || e.metaKey;
if (ctrlKey) { if (ctrlKey) {
switch (e.key) { switch (e.key) {
@ -162,141 +100,17 @@ export default {
} }
e.preventDefault(); e.preventDefault();
return; return;
}
},
async init() {
if (this.local.checkedSets.length + this.online.checkedSets.length <= 0) {
ElMessage({
message: "请至少选择一个单词本",
type: "error"
})
return;
}
let seed = (new Date()).getTime();
this.testWords = [];
for (let i of this.local.checkedSets) {
let words = JSON.parse(localStorage.getItem(i));
this.testWords = this.testWords.concat(...words);
}
for (let i of this.online.checkedSets) {
let config = this.online_ids[i];
let res = await axios.get("/wordset/detail", {
params: {
set: config.set,
book: config.book,
id: i
}
})
if (res.status != 200) {
ElMessage({
message: "获取单词本时出现错误",
type: "error"
});
return;
}
this.testWords = this.testWords.concat(...res.data);
}
localStorage.setItem("lastrecite", JSON.stringify({
checkedSets: this.local.checkedSets,
onlineSets: this.online.checkedSets,
seed: seed
}))
this.total = this.testWords.length;
if (this.total === 0) {
ElMessage({
message: "单词本为空",
type: "error"
});
return;
}
let arr = Array.from(new Array(this.total).keys());
for (let i = this.total - 1; i > 0; i--) {
[arr[i], arr[seed % i]] = [arr[seed % i], arr[i]];
}
this.seq = arr;
this.current = 0;
this.answered = 0;
this.mode = 1;
nextTick(() => {
this.show();
});
},
async last_recite() {
let lr = JSON.parse(localStorage.getItem("lastrecite"));
if (!lr) {
ElMessage({
message: "没有记录",
type: "error"
})
return;
}
this.testWords = [];
for (let i of lr.checkedSets) {
let content = localStorage.getItem(i);
if (content) this.testWords = this.testWords.concat(...JSON.parse(content));
else {
ElMessage({
message: "单词本不存在",
type: "error"
});
return;
}
}
for (let i of lr.onlineSets) {
let config = this.online_ids[i];
if (config) {
let res = await axios.get("/wordset/detail", {
params: {
set: config.set,
book: config.book,
id: i
}
})
if (res.status != 200) {
ElMessage({
message: "获取单词本时出现错误",
type: "error"
});
return;
}
this.testWords = this.testWords.concat(...res.data);
}
else {
ElMessage({
message: "在线单词本不存在",
type: "error"
});
return;
}
}
this.total = this.testWords.length;
let arr = Array.from(new Array(this.total).keys());
for (let i = this.total - 1; i > 0; i--) {
[arr[i], arr[lr.seed % i]] = [arr[lr.seed % i], arr[i]];
}
this.seq = arr;
let cur = Number(localStorage.getItem("lastcurrent"));
this.current = cur;
this.answered = cur;
this.mode = 1;
nextTick(() => {
this.show();
});
}, },
next() { next() {
if (this.current < this.answered) { if (this.current < this.answered) {
this.current++; this.current++;
if (this.current === this.total) { if (this.current === this.total) {
this.mode = 0;
ElNotification({ ElNotification({
message: "您已完成所有单词的背诵", message: "您已完成所有单词的背诵",
title: "Congratulations!", title: "Congratulations!",
type: "success" type: "success"
}) })
localStorage.removeItem("lastrecite"); this.$router.push('/');
localStorage.removeItem("lastcurrent");
return; return;
} }
nextTick(() => { nextTick(() => {
@ -314,7 +128,7 @@ export default {
type: "info" type: "info"
}); });
this.answered++; this.answered++;
localStorage.setItem("lastcurrent", this.answered.toString()); this.$store.state.history.count(this.answered);
this.next(); this.next();
return; return;
}, },
@ -329,6 +143,7 @@ export default {
}) })
}, },
showAnswer() { showAnswer() {
if (!this.settings.direct_answer) {
let e = document.getElementById("input"); let e = document.getElementById("input");
if (e.value === "_".repeat(this.word.word.length)) { if (e.value === "_".repeat(this.word.word.length)) {
e.value = this.word.word[0] + "_".repeat(this.word.word.length - 1); e.value = this.word.word[0] + "_".repeat(this.word.word.length - 1);
@ -336,7 +151,9 @@ export default {
ElMessage(`首字母为:${this.word.word[0]}`); ElMessage(`首字母为:${this.word.word[0]}`);
return; return;
} }
else ElMessage(`答案:${this.word.word}`); }
ElMessage(`答案:${this.word.word}`);
return;
}, },
show() { show() {
this.word = this.testWords[this.seq[this.current]]; this.word = this.testWords[this.seq[this.current]];
@ -362,7 +179,7 @@ export default {
type: "success" type: "success"
}); });
this.answered++; this.answered++;
localStorage.setItem("lastcurrent", this.answered.toString()); this.$store.state.history.count(this.answered);
this.next(); this.next();
return; return;
} }
@ -402,7 +219,7 @@ export default {
terminate() { terminate() {
ElMessageBox.confirm("确定要终止吗?") ElMessageBox.confirm("确定要终止吗?")
.then(() => { .then(() => {
this.mode = 0; this.$router.push('/');
ElNotification({ ElNotification({
message: "背诵已终止", message: "背诵已终止",
type: "info", type: "info",
@ -410,9 +227,6 @@ export default {
}); });
}); });
}, },
back_home() {
this.$router.push("/");
},
add_to() { add_to() {
if (this.set_id) { if (this.set_id) {
let data = JSON.parse(localStorage.getItem(this.set_id)); let data = JSON.parse(localStorage.getItem(this.set_id));
@ -440,33 +254,36 @@ export default {
}, },
}, },
created() { created() {
for (let i of Object.values(window.wordsets)) {
for (let j of i) {
this.local.allsets.push(j.id);
}
}
document.addEventListener('keyup', this.key_listener); document.addEventListener('keyup', this.key_listener);
axios.get("/wordset/list").then((res, err) => { let index = this.$route.query.index;
if (err) { if (index) {
console.log(err); this.$store.state.history.use(Number(index));
}
this.$store.state.history.init_recite((words, current, seq, settings) => {
this.testWords = words;
this.current = current;
this.total = words.length;
this.answered = current;
this.settings = settings;
this.seq = seq;
if (this.answered >= this.total) {
this.answered = 0;
this.current = 0;
ElMessage({ ElMessage({
message: "在线词库加载失败", type: 'warning',
type: "error" message: "进度已经重置"
}) })
return; this.$store.state.history.count(this.answered);
}
this.online_sets = res.data;
for (let i in this.online_sets) {
for (let j in this.online_sets[i]) {
for (let k in this.online_sets[i][j]) {
this.online.allsets.push(k);
this.online_ids[k] = {
set: i,
book: j
}
}
}
} }
nextTick(() => {
this.show();
})
}, (msg) => {
ElMessage({
type: 'error',
message: msg
})
this.$router.push('/select');
}) })
}, },
beforeUnmount() { beforeUnmount() {
@ -477,20 +294,6 @@ export default {
<style scoped> <style scoped>
@media screen and (max-width: 500px) { @media screen and (max-width: 500px) {
#range {
max-height: calc(100% - 40px);
flex-direction: column;
overflow-y: scroll;
}
#rangeselect {
height: auto;
}
.item {
/* background-color: var(--bg-color); */
width: 100%;
}
.title { .title {
font-size: 30px; font-size: 30px;
@ -542,15 +345,6 @@ export default {
} }
@media screen and (min-width: 500px) { @media screen and (min-width: 500px) {
#range {
height: 100%;
}
#rangeselect {
max-height: 100%;
overflow-x: auto;
}
.item { .item {
margin-bottom: 20px; margin-bottom: 20px;
/* background-color: var(--bg-color); */ /* background-color: var(--bg-color); */
@ -625,15 +419,4 @@ export default {
resize: vertical; resize: vertical;
width: 100%; width: 100%;
} }
#lastrecite {
max-height: 150px;
}
#online-set-container {
height: auto;
overflow-y: auto;
}
</style> </style>

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="container" style="width: calc(100% - 60px);align-items: center;display: flex;flex-direction: column"> <div class="container" id="page">
<el-page-header @back="this.$router.push('/manage');" style="width: 100%;"> <el-page-header @back="this.$router.push('/');" style="width: calc(100% - 30px);margin-left: 30px;margin-top: 5px;">
<template #content> <template #content>
<span class="text-large font-600 mr-3">开始新背诵</span> <span class="text-large font-600 mr-3">开始新背诵</span>
</template> </template>
@ -145,7 +145,13 @@ export default {
}, 250) }, 250)
}, },
startRecite() { startRecite() {
let onlinesets = [];
for(let i of this.online.checkedSets){
onlinesets.push(Object.assign({id:i},this.online_ids[i]))
}
this.$store.state.history.add(this.local.checkedSets,onlinesets,this.settings,()=>{
this.$router.push('/recite');
});
} }
}, },
created() { created() {
@ -157,7 +163,6 @@ export default {
document.addEventListener('keyup', this.key_listener); document.addEventListener('keyup', this.key_listener);
axios.get("/wordset/list").then((res, err) => { axios.get("/wordset/list").then((res, err) => {
if (err) { if (err) {
console.log(err);
ElMessage({ ElMessage({
message: "在线词库加载失败", message: "在线词库加载失败",
type: "error" type: "error"
@ -197,6 +202,13 @@ export default {
} }
} }
#page{
width: 100%;
/* margin: 20px; */
align-items: center;
display: flex;
flex-direction: column
}
.subtitle { .subtitle {
font-size: 23px; font-size: 23px;
line-height: 35px; line-height: 35px;

View File

@ -1,3 +1,4 @@
import axios from 'axios'
const limit = 10; const limit = 10;
export default class _history { export default class _history {
constructor() { constructor() {
@ -8,45 +9,115 @@ export default class _history{
} }
} }
save() { save() {
localStorage.setItem('historties',JSON.stringify(this.histories)); localStorage.setItem('histories', JSON.stringify(this.histories));
} }
get length() { get length() {
return this.histories.length; return this.histories.length;
} }
get top() { get top() {
return this.histories[this.histories.length-1]; return this.histories[0];
}
get _content(){
return this.histories;
} }
use(index) { use(index) {
if (this.histories.length > index) { if (this.histories.length > index) {
console.log(this.histories);
let history = this.histories.splice(index, 1); let history = this.histories.splice(index, 1);
this.histories = [history].concat(this.histories); history.modified = (new Date()).getTime();
this.histories = history.concat(this.histories);
console.log(this.histories);
this.save();
return true; return true;
} }
else return false; else return false;
} }
del(index) { del(index) {
this.histories.splice(index, 1); this.histories.splice(index, 1);
this.save();
} }
add(localsets,onlinesets,settings){ add(localsets, onlinesets, settings, callback) {
let time = (new Date()).getTime();
let data = { let data = {
localsets, onlinesets, settings, localsets, onlinesets, settings,
current:0 current: 0,
modified: time
}; };
if (settings.shuffle) { if (settings.shuffle) {
data.settings.seed = (new Date()).getTime(); data.settings.seed = time;
} }
this.histories = [data].concat(this.histories); this.histories = [data].concat(this.histories);
if(this.histories.length > limit){
this.histories = this.histories.slice(0,limit);
} }
count(){ this.save();
if(this.histories.length>0){
this.histories[0].current++;
}
}
async init_recite(callback){
if (typeof callback === 'function') { if (typeof callback === 'function') {
callback(); callback();
} }
} }
count(cnt) {
if (this.histories.length > 0) {
this.histories[0].current = cnt;
this.save();
}
}
async init_recite(callback, err) {
let history = this.histories[0];
let words = [];
for (let i of history.localsets) {
let content = localStorage.getItem(i);
if (content){
let temp = JSON.parse(content);
if(history.settings.ignore_phrases){
for(let j of temp){
console.log(j);
if(j.type != 'phr.'){
words.push(j);
}
}
}
else words = words.concat(...temp);
}
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);
}
let temp = res.data;
if(history.settings.ignore_phrases){
for(let j of temp){
if(j.type != 'phr.'){
words.push(j);
}
}
}
else words = words.concat(...temp);
}
let arr = Array.from(new Array(words.length).keys());
if(history.settings.shuffle){
let seed = history.settings.seed;
for (let i = this.total - 1; i > 0; i--) {
[arr[i], arr[seed % i]] = [arr[seed % i], arr[i]];
}
}
if(words.length <= 0){
return this.handle_err("单词本为空",err);
}
history.total = words.length;
this.save();
if (typeof callback === 'function') {
callback(words,history.current,arr,history.settings);
}
}
handle_err(msg,err){
this.histories.splice(0,1);
this.save();
if (typeof err === 'function') {
err(msg);
}
}
} }
// {"checkedSets":[],"onlineSets":["d8dbc5a0-7d63-11ee-b3a5-cdb8688a6b1b"],"seed":1704712586455}

View File

@ -0,0 +1,15 @@
const md = require('marked')
module.exports = function (source) {
this.cacheable()
// source 是原始文件内容html 是用 markdown-it 编译后的 html 内容
const html = md.parse(source);
const template = (
`<template>
<div class="markdown-body">
${html}
</div>
</template>`
)
return template
}

55
src/md/about.md 100644
View File

@ -0,0 +1,55 @@
# 关于 wordIn
---
## 1. wordIn 是什么
wordIn 是一款基于 vue.js 的单词默写器,致力于为用户提供便捷有效的单词记忆方式
## 2.获取源代码
wordIn 的前端代码已经开源 [Git Repository](https://git.zjueva.net/cast1e/wordIn)
!!需要从浙江大学内网访问
如果有获取后端代码的需求请联系作者
## 3.联系作者
- 通过电子邮件
- <rooger@zju.edu.cn>
- <rooger233@gmail.com>
- 通过QQ
- 1176075089
## 3.更新日志
### 1.0.1 Release
#### 1.0.1 是wordIn第一个正式版
1. 更新内容
- 暂时禁用了自定义背景功能预计1.0.2恢复)
- 增加了背诵选择界面
- 增加了历史记录功能,最多可以存储十条记录
- 重写了主页
- 优化了过渡动画
- 优化了全局变量管理
2. Bug修复进度
- (已修复)背诵时插入导致指针归位
- (已修复)查看之前的单词时溢出的错误
- (解决中)手机端适配问题
### 1.0
#### 1.0 及之前的版本汇总
1. 内容
- 1.0是整个项目的开始,项目包括前端界面(/),运维管理界面(/manage后端服务器。
## 4.开发者的话
整个项目的开发已经有半年了,在这期间我为 wordIn 花费了很多时间和精力,也收获了很多。希望 wordIn 能够帮助到大家的英语学习,祝各位大英都能满绩!

53
src/md/manual.md 100644
View File

@ -0,0 +1,53 @@
# wordIn 使用说明
---
- 近期发现ZJUWLAN无法访问unpkg.com导致部分图标无法显示,请使用ZJUWLAN-Secure访问
## 1. 背诵单词
### 开始新的背诵
- 在主页点击“开始新背诵”按钮
- 选择在线或者本地单词本(可以多选)
- 调整设置开始背诵
### 背诵中
- 快捷键说明
- ctrl+B 显示答案
- ctrl+M 跳过单词
- ctrl+I 插入到单词本
- ctrl+Y 播放音频
- ctrl+方向键左键 上一个单词
- ctrl+方向键右键 下一个单词
- 错题本功能
- wordIn内置丰富的单词管理功能首先新建一个单词本
- 在背诵过程中选择新建的单词本
- 点击插入到单词本即可将现在显示的单词插入到单词本中
- 进度记忆功能
- 一下子没有背完?不必担心,wordIn会自动记忆您的背诵进度您可以从主页随时返回到背诵
## 2. 单词本管理
### 新建单词本
- 单击新建单词本
- 输入单词本的名称
- 选择单词本的分类
- 选择现有的分类
- 在选择框中输入新的分类并 ***回车*** 创建新的分类
- 创建完毕
### 管理单词本
- 在界面左侧选择单词本分类
- 将鼠标移至单词本,选项会自动出现
- 手动添加单词输入完毕后按下回车键会自动focus下一个输入框
### 备份本地单词本
- 暂不支持Andriod & iOS等系统的设备
- 选择导出单词本将单词本导出为json格式文件
- 选择导入单词本导入备份的单词本(不会覆盖已经存在的单词本)
- 如果没反应,请确认您是否使用了支持的浏览器(推荐Edge,Chrome等基于Chromium的浏览器) [MDN Reference](https://developer.mozilla.org/zh-CN/docs/Web/API/File_System_API)

View File

@ -6,7 +6,9 @@ const recite = ()=>import('./components/recite/recite.vue');
const setlist = ()=>import('./components/manage/SetList.vue'); const setlist = ()=>import('./components/manage/SetList.vue');
const newset = ()=>import('./components/manage/NewSet.vue'); const newset = ()=>import('./components/manage/NewSet.vue');
const display = ()=>import('./components/manage/Display.vue'); const display = ()=>import('./components/manage/Display.vue');
const select = ()=>import('./components/recite/select.vue') const select = ()=>import('./components/recite/select.vue');
const manual = ()=>import('./components/post/manual.vue');
const about = ()=>import('./components/post/about.vue');
const routes = [ const routes = [
{ path: '/', component: home }, { path: '/', component: home },
@ -19,6 +21,8 @@ const routes = [
{path: 'edit',component:editor}, {path: 'edit',component:editor},
{path: 'show',component:display} {path: 'show',component:display}
]}, ]},
{ path: '/manual',component:manual},
{ path: '/about',component:about},
] ]
export default createRouter({ export default createRouter({

View File

@ -1,8 +1,20 @@
const { defineConfig } = require('@vue/cli-service') const { defineConfig } = require('@vue/cli-service')
const path = require('path');
module.exports = defineConfig({ module.exports = defineConfig({
transpileDependencies: true, transpileDependencies: true,
publicPath:"./", publicPath:"./",
devServer: { devServer: {
proxy: 'https://localhost:443' proxy: 'https://localhost:443'
},
chainWebpack: config => {
config.module
.rule('markdown')
.test(/\.md$/)
.use('vue-loader')
.loader('vue-loader')
.end()
.use('md-loader')
.loader(path.resolve(__dirname, 'src/loader/md-loader.js'))
.end()
} }
}) })