add: history,wordclass & bug fixes

master
cast1e 2023-10-19 19:05:11 +08:00
parent 913b4a9438
commit 357be521de
12 changed files with 1950 additions and 103 deletions

959
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,8 +8,14 @@
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"@alicloud/alimt20181012": "^1.2.0",
"axios": "^1.5.1",
"boxicons": "^2.1.4",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"vue": "^3.2.13" "crypto-js": "^4.1.1",
"node-uuid": "^1.4.8",
"vue": "^3.2.13",
"vue-router": "^4.2.5"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.12.16", "@babel/core": "^7.12.16",
@ -17,6 +23,7 @@
"@vue/cli-plugin-babel": "~5.0.0", "@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0", "@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0", "@vue/cli-service": "~5.0.0",
"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"
}, },

View File

@ -1,17 +1,39 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang=""> <html lang="">
<head>
<meta charset="utf-8"> <head>
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<title><%= htmlWebpackPlugin.options.title %></title> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
</head> <title>
<body> <%= htmlWebpackPlugin.options.title %>
<noscript> </title>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> <style>
</noscript> html,
<div id="app"></div> body {
<!-- built files will be auto injected --> margin: 0;
</body> padding: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
#app {
height: 100%;
width: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html> </html>

1
result.json 100644
View File

@ -0,0 +1 @@
{"requestId":"8f5a2a3c-2a9e-4929-906d-d1f5195fdef4","errorCode":"202","l":"en2zh-CHS"}

View File

@ -1,26 +1,115 @@
<template> <template>
<img alt="Vue logo" src="./assets/logo.png"> <router-view></router-view>
<HelloWorld msg="Welcome to Your Vue.js App"/>
</template> </template>
<script> <script>
import HelloWorld from './components/HelloWorld.vue'
export default { export default {
name: 'App', name: 'App',
components: { data() {
HelloWorld return {
index: 0,
}
},
methods: {
change(index) {
this.index = index;
}
},
created() {
let wordsets = localStorage.getItem("wordsets");
if (wordsets) {
window.wordsets = JSON.parse(wordsets);
} else window.wordsets = {};
let res = localStorage.getItem("bgimg");
document.body.style.backgroundImage = `url("${res}")`;
const isDarkTheme = window.matchMedia("(prefers-color-scheme: dark)");
if(isDarkTheme.matches) document.getElementsByTagName("html")[0].className= "dark";
isDarkTheme.addEventListener('change', (event) => {
if (event.matches) {
document.getElementsByTagName("html")[0].className= "dark";
} else {
document.getElementsByTagName("html")[0].className= "";
}
});
} }
} }
</script> </script>
<style> <style>
#app { html {
font-family: Avenir, Helvetica, Arial, sans-serif; --bg-color: #ffffffae;
-webkit-font-smoothing: antialiased; --text-color: #464646;
-moz-osx-font-smoothing: grayscale; }
text-align: center;
color: #2c3e50; html.dark {
margin-top: 60px; --bg-color: #2a2a2aae;
--text-color: #c0c0c0;
}
@keyframes enter {
0% {
translate: 100px 0;
opacity: 0;
}
100% {
translate: 0 0;
opacity: 1;
}
}
.container {
height: calc(100%-20px);
width: calc(100%-20px);
padding: 20px;
animation: enter ease-out .6s;
}
.colbox {
display: flex;
flex-direction: row;
}
.rowbox {
display: flex;
flex-direction: column;
}
.card {
border-radius: 8px;
box-shadow: var(--el-box-shadow);
padding: 20px;
margin: 10px;
background-color: var(--bg-color);
color: var(--text-color);
}
.wordset {
border-radius: 13px;
padding: 20px;
height: 220px;
width: 170px;
box-shadow: var(--el-box-shadow);
margin: 20px;
background-color: var(--bg-color);
color: var(--text-color);
}
.btn {
cursor: pointer;
}
.mid-text {
width: 65px;
transform: translate(0, 6px);
color: var(--text-color);
margin-right: 5px;
margin-left: 5px;
}
.para {
margin: 15px;
} }
</style> </style>

View File

@ -0,0 +1,322 @@
<template>
<el-page-header style="margin:20px" @back="back_home">
<template #content>
<span class="text-large font-600 mr-3"> 编辑单词本 </span>
</template>
</el-page-header>
<el-container v-if="mode < 2" class="container card">
<el-header id="header" class="colbox">
<div class="no">共有{{ Object.keys(this.wordsets).length }}个单词集合</div>
<div>
<el-button @click="export_set" type="success"><box-icon color="white" name='export'
size="15px"></box-icon></el-button>
<el-button @click="import_set" type="warning"><box-icon color="white" name='import'
size="15px"></box-icon></el-button>
<el-button @click="mode = 1" type="primary"><box-icon color="white" name='plus'></box-icon></el-button>
</div>
</el-header>
<el-main>
<div v-if="mode === 0" class="colbox" if="sets-container">
<el-collapse accordion v-model="active_set_class" style="width: 100%;">
<el-collapse-item v-for="(set_class, class_name) in wordsets" :title="class_name" :key="class_name"
:name="class_name">
<div class="colbox">
<div v-for="(wordset, index) in set_class" :key="index" class="wordset rowbox">
<div class="no">
{{ index + 1 }}
</div>
<div class="title">
{{ wordset.name }}
</div>
<div style="font-size: 17px;font-weight: 200;">
创建日期{{ (new Date(wordset.created)).toLocaleString() }}
</div>
<div class="option">
<box-icon class="btn" name='edit' color="var(--text-color)" @click="edit(wordset, class_name)"></box-icon>
<box-icon class="btn" name='trash' color="var(--text-color)"
@click="del(class_name, wordset.id, index)"></box-icon>
</div>
</div>
</div>
</el-collapse-item>
</el-collapse>
</div>
<div v-if="mode === 1" id="new" class="rowbox">
<el-page-header @back="mode = 0">
<template #content>
<span class="text-large font-600 mr-3"> 新建单词本 </span>
</template>
</el-page-header>
<div class="colbox">
<div class="mid-text">名称:</div>
<el-input v-model="set_name"></el-input>
<div class="mid-text">分组:</div>
<el-select allow-create filterable v-model="new_set_class" class="m-2" placeholder="Select">
<el-option v-for="item in Object.keys(wordsets)" :key="item" :label="item" :value="item" />
</el-select>
</div>
<el-button style="width: 50%;" type="primary" @click="new_wordset()"></el-button>
</div>
</el-main>
</el-container>
<div v-if="mode === 2" class="container" style="overflow: auto;">
<el-page-header @back="mode = 0">
<template #content>
<span class="text-large font-600 mr-3"> {{ editing.name }} ({{ editing.id }}) </span>
</template>
</el-page-header>
<div class="colbox" style="margin-top: 20px;">
<div class="rowbox">
<div class="card" style="margin-bottom: 20px;">
<div class="title">添加单词</div>
<el-input v-model="new_word"></el-input>
<div class="colbox">
<el-text class="mx-1" style="width: 60px;">翻译:</el-text>
<el-input v-model="new_trans"></el-input>
</div>
<div class="colbox">
<el-text class="mx-1" style="width: 80px;">词性</el-text>
<el-select v-model="word_type" class="m-2" placeholder="Select">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<el-button @click="add_word()" type="primary">添加</el-button>
</div>
<div class="card">
<div class="title">更改名称</div>
<div class="colbox">
<el-text class="mx-1" style="width: 60px;">新名称:</el-text>
<el-input v-model="new_name"></el-input>
</div>
<el-button @click="change_name()" type="primary">更改名称</el-button>
</div>
</div>
<div style="width: 100%;" class="card">
<el-table :data="table" style="max-height: 550px;overflow: auto;border-radius: 10px;">
<el-table-column type="index" />
<el-table-column prop="word" label="单词" />
<el-table-column prop="type" label="词性" />
<el-table-column prop="trans" label="翻译" />
<el-table-column fixed="right" label="操作">
<template #default="scope">
<div style="display: flex;flex-direction: row;">
<box-icon color="var(--text-color)" size="14px" class="btn" name='trash' @click="del_word(scope.$index)"></box-icon>
</div>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</template>
<script>
import { ElNotification, ElMessage, ElMessageBox } from 'element-plus';
import uuid from 'node-uuid';
export default {
name: "NoteEditor",
data() {
return {
mode: 0,
wordsets: window.wordsets,
set_name: "",
new_set_class: "",
editing: {},
editingClass: "",
new_word: "",
new_name: "",
word_type: "adj.",
options: [{ label: "verb(v.)", value: "v." }, { label: "adverb(adv.)", value: "adv." }, { label: "noun(n.)", value: "n." }, { label: "adjective(adj.)", value: "adj." }, { label: "prepositions(prep.)", value: "prep." }, { label: "phrase(phr.)", value: "phr." },],
table: [],
new_trans: "",
active_set_class: ""
}
},
methods: {
back_home() {
this.$router.push("/");
},
new_wordset() {
if (!this.new_set_class) {
ElMessage({
message: "集合不能为空",
type: "error"
})
return;
}
if (!this.wordsets[this.new_set_class]) {
this.wordsets[this.new_set_class] = [];
}
let id = uuid.v1();
this.wordsets[this.new_set_class].push({
name: this.set_name,
created: (new Date()).getTime(),
id: id
});
localStorage.setItem("wordsets", JSON.stringify(this.wordsets));
localStorage.setItem(id, JSON.stringify([]));
this.mode = 0;
return;
},
del(set_class_name, id, index) {
ElMessageBox.confirm("确定要删除吗?")
.then(() => {
localStorage.removeItem(id);
this.wordsets[set_class_name].splice(index, 1);
if (!this.wordsets[set_class_name].length) {
delete this.wordsets[set_class_name];
}
localStorage.setItem("wordsets", JSON.stringify(this.wordsets));
ElMessage(`已经删除 ${set_class_name} `);
});
},
edit(set, set_class_name) {
this.editing = set;
this.table = JSON.parse(localStorage.getItem(set.id));
this.mode = 2;
this.editingClass = set_class_name;
},
add_word() {
this.table.push({
word: this.new_word,
type: this.word_type,
trans: this.new_trans
})
localStorage.setItem(this.editing.id, JSON.stringify(this.table));
ElMessage({
message: `已添加 ${this.new_word} (${this.word_type})`,
type: 'success',
});
},
change_name() {
let old_name = this.editing.name;
this.editing.name = this.new_name;
localStorage.setItem("wordsets", JSON.stringify(this.wordsets));
ElMessage({
message: `已更改 ${old_name}${this.new_name}`,
type: 'success',
});
return;
},
del_word(index) {
this.table.splice(index, 1);
localStorage.setItem(this.editing.id, JSON.stringify(this.table));
},
async export_set() {
let fileHandle = await window.showSaveFilePicker({
types: [
{
description: "JSON file",
accept: {
'text/json': ['.json'],
},
},
],
});
let writestream = await fileHandle.createWritable();
let data = {
wordsets: this.wordsets,
words: {}
};
for (let i of Object.values(this.wordsets)) {
for (let j of i) {
let temp = localStorage.getItem(j.id);
data.words[j.id] = temp;
}
}
writestream.write(JSON.stringify(data));
writestream.close();
},
async import_set() {
let [fileHandle] = await window.showOpenFilePicker({
types: [
{
description: "JSON file",
accept: {
'text/json': ['.json'],
},
},
],
});
let file = await fileHandle.getFile();
let data = JSON.parse(await file.text());
let cnt = 0;
for (let i of Object.keys(data.wordsets)) {
for (let j of data.wordsets[i]) {
if (!localStorage.getItem(j.id)) {
if (!this.wordsets[i]) this.wordsets[i] = [];
this.wordsets[i].push(j);
localStorage.setItem(j.id, data.words[j.id]);
cnt++;
}
}
}
localStorage.setItem("wordsets", JSON.stringify(this.wordsets));
ElNotification({
type: "success",
title: "添加成功",
message: `已添加 ${cnt} 本单词本`
});
}
},
created() {
this.active_set_class = Object.keys(this.wordsets)[0];
}
}
</script>
<style scoped>
#header {
border-bottom: solid 1px #bcbcbc;
justify-content: space-between;
height: 40px;
}
html.dark #header {
border-bottom: solid 1px #616161;
}
.no {
font-size: 20px;
font-weight: 700;
}
.title {
font-weight: 800;
color: var(--text-color);
font-size: 35px;
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.option {
display: flex;
justify-content: space-around;
opacity: 0;
transition: .5s;
}
.wordset:hover .option {
opacity: 1;
}
.card div {
margin-bottom: 10px;
}
#new div {
margin-bottom: 20px;
}
#sets-container {
background-color: var(--bg-color);
padding: 5px;
border-radius: 8px;
}
</style>

View File

@ -1,58 +0,0 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@ -0,0 +1,135 @@
<template>
<div id="ball"></div>
<div class="container">
<div id="main">
<div id="title">WordIn</div>
<div class="colbox">
<router-link to="/recite" class="button">
背诵
</router-link>
<router-link to="editor" class="button">
编辑单词本
</router-link>
</div>
</div>
</div>
<div id="setting">
<box-icon class="btn" color="var(--text-color)" name='cog' @click="open_setting_dialog"></box-icon>
</div>
<el-dialog v-model="settingVisible" title="设置">
<div class="title">更换自定义背景</div>
<div class="colbox" style="margin: 10px;">
<el-input v-model="img_url"></el-input>
<el-button type="primary" style="margin-left: 10px;" @click="set_bg"></el-button>
</div>
<div class="title">黑暗模式</div>
<el-switch @change="toggleDark" v-model="isdark" size="large" active-text="Dark" inactive-text="Light" />
</el-dialog>
</template>
<script>
export default {
name: "HomePage",
data() {
return {
img_url: "",
settingVisible: false,
isdark: false,
}
},
methods: {
set_bg() {
localStorage.setItem("bgimg", this.img_url);
window.location.reload();
},
open_setting_dialog() {
this.settingVisible = true;
},
toggleDark(){
if(this.isdark){
document.getElementsByTagName("html")[0].className= "dark";
}
else{
document.getElementsByTagName("html")[0].className= "";
}
}
},
created(){
if(document.getElementsByTagName("html")[0].className === "dark"){
this.isdark = true;
}
}
}
</script>
<style scoped>
.title {
font-weight: 800;
color: var(--text-color);
font-size: 35px;
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#setting {
position: absolute;
right: 0;
bottom: 0;
margin: 20px;
}
#title {
font-size: 180px;
color: var(--text-color);
}
.button {
width: 200px;
height: 100px;
font-size: 25px;
font-weight: 800;
border-radius: 5px;
border: solid 1px #FAFAFA;
display: flex;
justify-content: center;
flex-direction: column;
text-align: center;
box-shadow: var(--el-box-shadow);
background-color: rgba(255, 255, 255, 0.237);
backdrop-filter: blur(20px);
cursor: pointer;
transition: .5s;
margin: 30px;
color: var(--text-color);
text-decoration: none;
}
html.dark .button{
background-color: rgba(56, 56, 56, 0.301);
border: solid 1px #848484;
box-shadow: 0px 12px 32px 4px rgba(198, 198, 198, 0.078), 0px 8px 20px rgba(216, 216, 216, 0.171);
}
.button:hover {
box-shadow: var(--el-box-shadow) inset #00000017 0px 500px;
}
#ball {
background-image: linear-gradient(120deg, #e0c3fcca 0%, #8ec5fcc4 100%);
width: 1500px;
height: 1500px;
border-radius: 100%;
position: absolute;
top: -100%;
right: -20%;
animation: enter .8s ease-out;
z-index: -1;
}
#main {
margin: 80px;
}
</style>

View File

@ -0,0 +1,343 @@
<template>
<el-page-header style="margin:20px" @back="back_home">
<template #content>
<span class="text-large font-600 mr-3"> 背诵 </span>
</template>
</el-page-header>
<div v-if="mode === 0" class="container">
<div class="card item">
<div class="title">选择测验范围</div>
<el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" @change="handleCheckAllChange"
size="large">全选</el-checkbox>
<el-checkbox-group v-model="checkedSets" @change="handleChange">
<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>
<el-button style="margin: 20px;width: 50%;" type="primary" @click="init"></el-button>
</div>
<div class="card item">
<div class="title" style="font-size: 35px;">继续上一次的背诵</div>
<el-button style="margin: 20px;width: 50%;" type="primary" @click="last_recite"></el-button>
</div>
</div>
<div id="test" class="colbox" v-if="mode === 1">
<div style="flex-grow: 1;" class="card">
<div id="status">背诵进度: {{ current + 1 }}/{{ total }}</div>
<div>
<textarea autofocus id="input" v-if="current === answered" @input="change" class="answer"
spellcheck="false">
</textarea>
<div v-if="current < answered" class="answer">{{ word.word }}</div>
</div>
<p id="explain">
<el-button v-if="current > 0" @click="prev"></el-button>
<el-button v-if="current < answered" @click="next"></el-button>
<el-button v-if="current === answered" @click="showAnswer"></el-button>
<el-button @click="terminate" type="danger">停止背诵</el-button>
<box-icon class="btn" style="margin-left: 10px;translate: 0 4px;" @click="audio_play"
name='volume-full'></box-icon>
</p>
<div id="trans">{{ word.type }} {{ word.trans }}</div>
</div>
<div id="add-to-box" class="card">
<el-text class="mx-1" style="font-size: 35px;font-weight: 800;">加入至</el-text>
<div class="colbox para">
<div class="mid-text">分组:</div>
<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-select>
</div>
<div class="colbox para">
<div class="mid-text">单词本:</div>
<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-select>
</div>
<el-button @click="add_to" type="primary">添加</el-button>
</div>
</div>
</template>
<script>
import { ElMessage, ElNotification, ElMessageBox } from 'element-plus';
import { nextTick } from 'vue';
export default {
name: "WordRecite",
data() {
return {
mode: 0,
allsets: [],
wordsets: window.wordsets,
checkedSets: [],
isIndeterminate: false,
checkAll: false,
testWords: [],
seq: [],
total: 0,
current: 0,
answered: 0,
word: {},
set_class: "",
set_id: "",
};
},
methods: {
handleCheckAllChange(val) {
this.checkedSets = val ? this.allsets : [];
this.isIndeterminate = false;
},
handleChange(value) {
console.log(this.checkedSets);
this.checkAll = value.length === this.allsets.length;
this.isIndeterminate = value.length > 0 && value.length < this.allsets.length;
},
init() {
if (this.checkedSets.length <= 0) {
ElMessage({
message: "请至少选择一个单词本",
type: "error"
})
return;
}
let seed = (new Date()).getTime();
this.testWords = [];
for (let i of this.checkedSets) {
let words = JSON.parse(localStorage.getItem(i));
this.testWords = this.testWords.concat(...words);
}
localStorage.setItem("lastrecite", JSON.stringify({
checkedSets: this.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();
});
},
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;
}
}
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() {
this.current++;
if (this.current === this.total) {
this.mode = 0;
ElNotification({
message: "您已完成所有单词的背诵",
title: "Congratulations!",
type: "success"
})
return;
}
nextTick(() => {
this.show();
});
},
prev() {
this.current--;
this.show();
},
showAnswer() {
ElMessage(`答案:${this.word.word}`);
},
show() {
this.word = this.testWords[this.seq[this.current]];
console.log(this.word);
if (this.current === this.answered) {
let e = document.getElementById("input");
e.value = "_".repeat(this.word.word.length);
e.setSelectionRange(0, 0);
e.style.height = `${e.scrollHeight}px`;
}
},
change() {
let e = document.getElementById("input");
let t = e.value, n = t.indexOf("_"), o;
if (n == this.word.word.length) {
if (t.substring(0, t.length - 1).toLowerCase() == this.word.word.toLowerCase()) {
ElMessage({
title: "Success",
message: "正确",
type: "success"
});
this.answered++;
localStorage.setItem("lastcurrent",this.answered.toString());
this.next();
return;
}
ElMessage({
title: "Error",
message: "错误",
type: "error"
}),
o = "_".repeat(this.word.word.length);
n = 0;
} else
n == -1 ? o = "_".repeat(this.word.word.length) : (o = t.substring(0, n),
o += "_".repeat(this.word.word.length - n));
e.value = o;
e.setSelectionRange(n, n);
e.style.height = `${e.scrollHeight}px`;
e.focus();
},
audio_play() {
var t = new Audio("https://dict.youdao.com/dictvoice?audio=" + this.word.word);
t.play();
},
terminate() {
ElMessageBox.confirm("确定要终止吗?")
.then(() => {
this.mode = 0;
ElNotification({
message: "背诵已终止",
type: "info",
title: "Terminate"
});
});
},
back_home() {
this.$router.push("/");
},
add_to() {
let data = JSON.parse(localStorage.getItem(this.set_id));
data.push(this.word);
localStorage.setItem(this.set_id, JSON.stringify(data));
return ElMessage({
message: `已添加 ${this.word.word} (${this.word.type})`,
type: 'success',
});
}
},
created() {
for (let i of Object.values(window.wordsets)) {
for (let j of i) {
this.allsets.push(j.id);
}
}
console.log(this.allsets);
},
}
</script>
<style scoped>
.item {
margin-bottom: 20px;
background-color: var(--bg-color);
width: 50%;
}
.title {
font-size: 50px;
font-weight: 800;
color: var(--text-color);
}
.checkbox {
font-size: 30px;
}
#status {
font-size: 20px;
}
.answer {
margin-top: 10px;
border-radius: 8px;
background-color: var(--bg-color);
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-size: 70px;
letter-spacing: 30px;
border: none;
outline-style: none;
height: 100px;
padding: 0;
transition: .5s;
resize: vertical;
width: 100%;
min-height: 100px;
}
#test {
padding: 50px;
animation: enter .5s ease-out;
}
#explain {
font-size: 25px;
margin-top: 20px;
}
#trans {
font-size: 50px;
transition: .5s;
}
.set_radios {
margin: 10px;
}
.set_radios p {
font-size: 25px;
font-weight: 700;
color: var(--text-color);
}
#add-to-box {
width: 30%;
min-width: 200px;
}
</style>

View File

@ -1,4 +1,12 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import 'element-plus/theme-chalk/dark/css-vars.css'
import 'boxicons'
import router from './router.js'
createApp(App).mount('#app') const app = createApp(App);
app.use(ElementPlus);
app.use(router);
app.mount('#app');

15
src/router.js 100644
View File

@ -0,0 +1,15 @@
import {createRouter,createWebHashHistory} from 'vue-router'
import editor from './components/Editor.vue'
import recite from './components/Recite.vue'
import home from "./components/Home.vue"
const routes = [
{ path: '/', component: home },
{ path: '/recite', component: recite },
{ path: '/editor', component: editor },
]
export default createRouter({
history: createWebHashHistory(),
routes,
})

36
test.js 100644
View File

@ -0,0 +1,36 @@
const axios = require("axios");
const CryptoJS = require("crypto-js");
const fs = require("fs");
function truncate(q){
var len = q.length;
if(len<=20) return q;
return q.substring(0, 10) + len + q.substring(len-10, len);
}
var appKey = "73ff413042ffde53";
var key = "FAt2w0mFLoDDIz6ZoAY8WJFgZvLFtIYe";
var salt = (new Date).getTime();
var curtime = Math.round(new Date().getTime()/1000);
var query = 'hello world';
var str1 = appKey + truncate(query) + salt + curtime + key;
var sign = CryptoJS.SHA256(str1).toString(CryptoJS.enc.Hex);
console.log(sign);
axios.post('https://openapi.youdao.com/api',{
q: query.toString(),
appKey: appKey,
salt: (new Date).getTime(),
from: 'en',
to: 'zh-CHS',
sign: sign,
signType: "v3",
curtime:Math.round(new Date().getTime()/1000),
},{
headers:{
'Content-Type': 'multipart/form-data'
}
}).then((res)=>{
fs.writeFileSync("result.json",JSON.stringify(res.data));
})