...
 
Commits (12)
    https://gitcode.net/weixin_43881430/finger-dance/-/commit/1d14eb53784699fad96d55a53af413e58649b4d6 init 2021-01-24T19:47:04+08:00 郭维嘉 guoweijia@guoweijiadeMacBook-Pro.local https://gitcode.net/weixin_43881430/finger-dance/-/commit/025da8bbcc1cc2734f0ad019d3d215f6a2c57de9 feat:初始化项目,添加keyboard组件 2021-01-25T00:54:17+08:00 郭维嘉 guoweijia@guoweijiadeMacBook-Pro.local https://gitcode.net/weixin_43881430/finger-dance/-/commit/3a520971e7603c9208b98211b5f442d229c4c653 feat:keyboard complete 2021-01-25T14:03:47+08:00 guoweijia guowj@csdn.net https://gitcode.net/weixin_43881430/finger-dance/-/commit/de9f66480ca4b72d248c84793db87858c4c787c3 feat:添加键盘点击事件 2021-01-25T23:09:00+08:00 郭维嘉 guoweijia@guoweijiadeMacBook-Pro.local https://gitcode.net/weixin_43881430/finger-dance/-/commit/d8dd4d4e6d6bb96cff1c51cb14a5c420d04a7288 feat:添加生成文本模块 2021-01-26T19:39:09+08:00 guoweijia guowj@csdn.net https://gitcode.net/weixin_43881430/finger-dance/-/commit/7a820ff4fa5e006fe4eda5c26e6a2e67f97ea8cf feat:添加输入校验 2021-01-27T00:23:22+08:00 郭维嘉 guoweijia@guoweijiadeMacBook-Pro.local https://gitcode.net/weixin_43881430/finger-dance/-/commit/f9cc83adb4eb093092f7050819fc7b5d48b71ea1 feat:添加练习按键设置组件 2021-01-27T19:49:07+08:00 guoweijia guowj@csdn.net https://gitcode.net/weixin_43881430/finger-dance/-/commit/44755cc53383f81f9bf7b2bf132edeb6379c6aa1 feat:添加键盘主题色和指位显示设置 2021-01-28T14:02:00+08:00 guoweijia guowj@csdn.net https://gitcode.net/weixin_43881430/finger-dance/-/commit/23be6bd40f90a07d149aaca4657acf0114c971be feat:添加速度模块 2021-01-29T00:24:48+08:00 郭维嘉 guoweijia@guoweijiadeMacBook-Pro.local https://gitcode.net/weixin_43881430/finger-dance/-/commit/ba3f9d767f335599fac0b1c6571f932bb2161ad4 feat:添加速度和得分计算 2021-01-30T19:31:14+08:00 guoweijia guowj@csdn.net https://gitcode.net/weixin_43881430/finger-dance/-/commit/a32bb72c01640aa5b626a13701d1623cdf78eb20 feat:添加测速模块 2021-02-01T00:35:52+08:00 guoweijia guowj@csdn.net https://gitcode.net/weixin_43881430/finger-dance/-/commit/e116e5448d91b83216212382e47464b32d17729f feat:添加状态箭头 2021-02-03T19:27:58+08:00 guoweijia guowj@csdn.net
> 1%
last 2 versions
not dead
module.exports = {
root: true,
env: {
node: true
},
extends: ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"],
parserOptions: {
parser: "babel-eslint"
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
}
};
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# typing
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
plugins: [
[
"component",
{
libraryName: "element-ui",
styleLibraryName: "theme-chalk"
}
]
]
};
此差异已折叠。
{
"name": "typing",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.6.5",
"element-ui": "^2.15.0",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/eslint-config-prettier": "^6.0.0",
"babel-eslint": "^10.1.0",
"babel-plugin-component": "^1.1.1",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-vue": "^6.2.2",
"less": "^3.0.4",
"less-loader": "^6.0.0",
"prettier": "^1.19.1",
"style-resources-loader": "^1.4.1",
"vue-cli-plugin-style-resources-loader": "^0.1.4",
"vue-template-compiler": "^2.6.11"
}
}
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</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>
<template>
<div id="app">
<router-view />
</div>
</template>
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
</style>
<svg t="1612351014950" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2903" width="200" height="200"><path d="M548.352 241.152L716.8 409.6a32.768 32.768 0 0 1 0 46.592 30.72 30.72 0 0 1-45.568 0l-116.736-115.2v464.896a32.768 32.768 0 1 1-65.024 0V340.992L372.736 460.8a39.424 39.424 0 0 1-45.568-6.656 32.768 32.768 0 0 1 0-46.592l162.816-166.4a35.328 35.328 0 0 1 58.368 0z" fill="#d81e06" p-id="2904"></path></svg>
\ No newline at end of file
<svg t="1612349681139" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2565" width="200" height="200"><path d="M548.352 241.152L716.8 409.6a32.768 32.768 0 0 1 0 46.592 30.72 30.72 0 0 1-45.568 0l-116.736-115.2v464.896a32.768 32.768 0 1 1-65.024 0V340.992L372.736 460.8a39.424 39.424 0 0 1-45.568-6.656 32.768 32.768 0 0 1 0-46.592l162.816-166.4a35.328 35.328 0 0 1 58.368 0z" fill="#67C23A" p-id="2566"></path></svg>
\ No newline at end of file
此差异已折叠。
class CreateText {
constructor(
practiceLetters = "ENITRL",
keyLetter = "E",
minLength = 3,
maxLenth = 6
) {
this.practiceLetters = practiceLetters;
this.keyLetter = keyLetter;
this.minLength = minLength;
this.maxLenth = maxLenth;
}
// 长度
length() {
return Math.floor(
Math.random() * (this.maxLenth - this.minLength) + this.minLength + 1
);
}
// 生成字母组
create() {
const len = this.length();
const randomPosition = Math.floor(Math.random() * len);
let str = "";
for (let i = 0; i < len; i++) {
str += randomPosition === i ? this.keyLetter : this.getOneRandomLetter();
}
return str;
}
// 随机取一个字母
getOneRandomLetter() {
const letters = this.practiceLetters;
const len = letters.length;
return letters[Math.floor(Math.random() * len)];
}
}
export default CreateText;
const keys = [
// 第一行
{ id: 1, type: "Letter", primaryName: "`", subName: "~" },
{ id: 1, type: "Letter", primaryName: "1", subName: "!" },
{ id: 1, type: "Letter", primaryName: "2", subName: "@" },
{ id: 1, type: "Letter", primaryName: "3", subName: "#" },
{ id: 1, type: "Letter", primaryName: "4", subName: "$" },
{ id: 1, type: "Letter", primaryName: "5", subName: "%" },
{ id: 1, type: "Letter", primaryName: "6", subName: "^" },
{ id: 1, type: "Letter", primaryName: "7", subName: "&" },
{ id: 1, type: "Letter", primaryName: "8", subName: "*" },
{ id: 1, type: "Letter", primaryName: "9", subName: "(" },
{ id: 1, type: "Letter", primaryName: "0", subName: ")" },
{ id: 1, type: "Letter", primaryName: "-", subName: "_" },
{ id: 1, type: "Letter", primaryName: "=", subName: "+" },
{ id: 1, type: "Fill", primaryName: "Backspace", subName: "" },
// 第二行
{ id: 1, type: "Tab", primaryName: "Tab", subName: "" },
{ id: 1, type: "Letter", primaryName: "Q", subName: "" },
{ id: 2, type: "Letter", primaryName: "W", subName: "" },
{ id: 3, type: "Letter", primaryName: "E", subName: "" },
{ id: 4, type: "Letter", primaryName: "R", subName: "" },
{ id: 4, type: "Letter", primaryName: "T", subName: "" },
{ id: 4, type: "Letter", primaryName: "Y", subName: "" },
{ id: 4, type: "Letter", primaryName: "U", subName: "" },
{ id: 4, type: "Letter", primaryName: "I", subName: "" },
{ id: 4, type: "Letter", primaryName: "O", subName: "" },
{ id: 4, type: "Letter", primaryName: "P", subName: "" },
{ id: 4, type: "Letter", primaryName: "[", subName: "{" },
{ id: 4, type: "Letter", primaryName: "]", subName: "}" },
{ id: 4, type: "Fill", primaryName: "\\", subName: "|" },
// 第三行
{ id: 4, type: "Caps Lock", primaryName: "CapsLock", subName: "" },
{ id: 4, type: "Letter", primaryName: "A", subName: "" },
{ id: 4, type: "Letter", primaryName: "S", subName: "" },
{ id: 4, type: "Letter", primaryName: "D", subName: "" },
{ id: 4, type: "Letter", primaryName: "F", subName: "" },
{ id: 4, type: "Letter", primaryName: "G", subName: "" },
{ id: 4, type: "Letter", primaryName: "H", subName: "" },
{ id: 4, type: "Letter", primaryName: "J", subName: "" },
{ id: 4, type: "Letter", primaryName: "K", subName: "" },
{ id: 4, type: "Letter", primaryName: "L", subName: "" },
{ id: 4, type: "Letter", primaryName: ";", subName: ":" },
{ id: 4, type: "Letter", primaryName: "'", subName: '"' },
{ id: 4, type: "Fill", primaryName: "Enter", subName: "" },
// 第四行
{ id: 4, type: "Shift", primaryName: "Shift", subName: "" },
{ id: 4, type: "Letter", primaryName: "Z", subName: "" },
{ id: 4, type: "Letter", primaryName: "X", subName: "" },
{ id: 4, type: "Letter", primaryName: "C", subName: "" },
{ id: 4, type: "Letter", primaryName: "V", subName: "" },
{ id: 4, type: "Letter", primaryName: "B", subName: "" },
{ id: 4, type: "Letter", primaryName: "N", subName: "" },
{ id: 4, type: "Letter", primaryName: "M", subName: "" },
{ id: 4, type: "Letter", primaryName: ",", subName: "<" },
{ id: 4, type: "Letter", primaryName: ".", subName: ">" },
{ id: 4, type: "Letter", primaryName: "/", subName: "?" },
{ id: 4, type: "Fill", primaryName: "Shift", subName: "" },
// 第五行
{ id: 4, type: "Function", primaryName: "Ctrl", subName: "" },
{ id: 4, type: "Letter", primaryName: "Alt", subName: "" },
{ id: 4, type: "Fill", primaryName: " ", subName: "" },
{ id: 4, type: "Letter", primaryName: "Alt", subName: "" },
{ id: 4, type: "Function", primaryName: "Ctrl", subName: "" }
];
export default keys;
* {
padding: 0;
margin: 0;
outline: none;
}
html,
body {
min-height: 100vh;
background: rgba(203, 242, 144, 0.2);
}
ul,
li,
ol {
list-style: none;
}
body {
padding: 100px 0;
box-sizing: border-box;
}
.col-2 {
width: 16.66666667%;
}
.col-3 {
width: 25%;
}
.col-9 {
width: 75%;
}
.col-10 {
width: 83.33333333%;
}
* {
padding: 0;
margin: 0;
outline: none;
}
html,
body {
min-height: 100vh;
background: rgba(203, 242, 144, 0.2);
}
ul,
li,
ol {
list-style: none;
}
body {
padding: 100px 0;
box-sizing: border-box;
}
.col-2 {
width: 2/12 * 100%;
}
.col-3 {
width: 25%;
}
.col-9 {
width: 75%;
}
.col-10 {
width: 10/12 * 100%;
}
@keyboard-bg-color: #dfd9d9;
@border-style: 1px solid rgba(0, 10, 20, 0.1);
<template>
<ul class="menu">
<li>Login</li>
<li>Settings</li>
<li>Data</li>
<li>登录</li>
</ul>
</template>
<script>
export default {
data() {
return {};
}
};
</script>
<style lang="less" scoped>
.menu {
border: @border-style;
padding: 20px 30px;
box-sizing: border-box;
min-height: 100%;
li {
font-size: 20px;
height: 3em;
line-height: 3em;
border-bottom: @border-style;
}
}
</style>
<template>
<div class="settings-container">
<div class="setting-item">
<label for="practiceLetters">预设练习键</label>
<span
:class="[
'letter',
{
practiceLetter: isPracticeLetter(item),
keyLetter: isKeyLetter(item)
}
]"
v-for="(item, index) in allLetters"
:key="index"
@click="setKeyLetter(item, index)"
>{{ item }}</span
>
<el-slider
:min="min"
:max="allLetters.length"
:show-tooltip="false"
v-model="practiceLetterRange"
@input="changePracticeLetters"
></el-slider>
</div>
<div class="setting-item">
<label for="showFingers">显示指位</label>
<el-switch
:value="showFingers"
active-color="#13ce66"
@change="changeFingersShow"
>
</el-switch>
</div>
<div class="setting-item">
<label for="keyboardColor">键盘主题色</label>
<el-color-picker
@change="changeKeyboardColor"
:value="keyboardColor"
show-alpha
size="mini"
></el-color-picker>
</div>
</div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
data() {
return {
practiceLetterRange: this.minRange,
min: 0
};
},
computed: {
...mapState([
"allLetters",
"showFingers",
"keyboardColor",
"practiceLetters",
"keyLetter",
"minRange"
])
},
methods: {
...mapMutations(["changeFingersShow", "changeKeyboardColor"]),
isKeyLetter(val) {
if (!val) return false;
return val === this.keyLetter;
},
isPracticeLetter(val) {
if (!val) return false;
return this.practiceLetters.includes(val);
},
// 滑动设置预设练习按键范围
changePracticeLetters(val) {
if (val < this.minRange) {
this.practiceLetterRange = this.minRange;
return;
}
this.$store.commit("settingPracticeLetters", val);
// 调整重点练习按键
const keyLetterIndex = this.practiceLetters.indexOf(this.keyLetter);
if (keyLetterIndex === -1) {
const lastPracticeLetter = this.practiceLetters.slice(-1);
this.setKeyLetter(lastPracticeLetter, this.practiceLetters.length - 1);
}
},
// 点击设置重点练习按键
setKeyLetter(letter, index) {
if (index >= this.practiceLetters.length) return;
this.$store.commit("setKeyLetter", letter);
}
}
};
</script>
<style lang="less" scoped>
@practice-color: #67c23a;
@key-color: #f56c6c;
@default-color: #888;
.settings-container {
width: fit-content;
margin: 0 auto;
text-align: left;
label {
display: inline-block;
min-width: 80px;
}
.letter {
display: inline-block;
width: 1em;
height: 1em;
line-height: 1em;
border: 1px solid @default-color;
color: @default-color;
margin-left: 0.5em;
text-align: center;
&.practiceLetter {
border-color: @practice-color;
color: @practice-color;
}
&.keyLetter {
border-color: @key-color;
color: @key-color;
}
}
.el-slider {
margin-left: 84px;
}
.el-switch {
vertical-align: text-top;
margin-left: 0.5em;
}
.el-color-picker {
vertical-align: bottom;
margin-left: 0.5em;
}
}
</style>
<template>
<div>
<img src="@/assets/img/fingers.svg" alt="" />
</div>
</template>
<style lang="less" scoped>
img {
width: 100%;
}
</style>
<template>
<div
:class="['key', { active: iscurrentLetter($store.state.currentLetter) }]"
:style="{
'min-width': keyWidth + 'px',
flex: type === 'Fill' ? 1 : 'none',
background: keyboardColor
}"
>
<div class="multiple" v-if="subName">
<span v-html="subName"></span>
<span v-html="primaryName"></span>
</div>
<span v-else v-html="primaryName"></span>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
props: {
type: {
type: String,
default: ""
},
primaryName: {
type: String,
default: ""
},
subName: {
type: String,
defualt: ""
}
},
computed: {
...mapState(["keyboardColor"]),
keyWidth() {
let keyWidth = 44;
const keyType = this.type;
if (!keyType) return keyWidth;
switch (keyType) {
case "Letter":
keyWidth = 44;
break;
case "Tab":
keyWidth = 72;
break;
case "Caps Lock":
keyWidth = 82;
break;
case "Shift":
keyWidth = 110;
break;
case "Function":
keyWidth = 80;
break;
default:
break;
}
return keyWidth;
}
},
methods: {
iscurrentLetter(key) {
if (key === null) return false;
return key.toLowerCase() === this.primaryName.toLowerCase();
}
}
};
</script>
<style lang="less" scoped>
.key {
height: 44px;
line-height: 44px;
// background: skyblue;
border-radius: 4px;
margin: 2px;
.multiple {
display: flex;
flex-direction: column;
justify-content: space-around;
height: 100%;
span {
line-height: 1em;
}
}
&.active {
opacity: 0.4;
}
}
</style>
<template>
<div class="keyboard">
<key v-for="(item, index) in list" :key="index" v-bind="item" />
</div>
</template>
<script>
import key from "@/components/index/key.vue";
import mock from "@/assets/js/mock";
export default {
components: { key },
data() {
return {
list: mock
};
},
mounted() {
this.keyboardInit();
},
methods: {
keyboardInit() {
document.addEventListener("keydown", e => {
e.preventDefault();
this.$store.commit("onkeydown", e.key);
});
document.addEventListener("keyup", () => {
this.$store.commit("onkeyup");
});
}
}
};
</script>
<style lang="less" scoped>
.keyboard {
width: 722px;
margin: 80px auto;
padding: 12px;
background: @keyboard-bg-color;
border-radius: 10px;
display: flex;
flex-wrap: wrap;
}
</style>
<template>
<div class="speed-container">
<div class="item">
<label for="speed">速度</label>
<span>{{ speed }}</span
>&nbsp;&nbsp;
<span :class="['arrow', { down: speedGrowth < 0 }]" v-if="speedGrowth">{{
speedGrowth | percentage
}}</span>
</div>
<div class="item">
<label for="error">错误</label>
<span>{{ error }}</span
>&nbsp;&nbsp;
<span
:class="['arrow error', { down: errorGrowth > 0 }]"
v-if="errorGrowth"
>{{ errorGrowth }}</span
>
</div>
<div class="item">
<label for="score">得分</label>
<span>{{ score }}</span
>&nbsp;&nbsp;
<span :class="['arrow', { down: scoreGrowth < 0 }]" v-if="scoreGrowth">{{
scoreGrowth | percentage
}}</span>
</div>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
data() {
return {};
},
computed: {
...mapState([
"speed",
"speedGrowth",
"error",
"errorGrowth",
"score",
"scoreGrowth"
])
},
filters: {
percentage(val) {
if (!val) return "";
return parseInt(val * 100) + "%";
}
}
};
</script>
<style lang="less" scoped>
.speed-container {
width: 756px;
margin: 0 auto;
text-align: left;
display: flex;
justify-content: space-between;
.item {
label {
margin-right: 4em;
}
.arrow {
display: inline-block;
padding-right: 20px;
position: relative;
margin-left: 20px;
&::after {
position: absolute;
top: -4px;
right: -2px;
width: 20px;
height: 28px;
content: "";
background: url("~@/assets/img/arrow-up.svg") center / cover no-repeat;
}
&.down::after {
background: url("~@/assets/img/arrow-down.svg") center / cover no-repeat;
transform: rotateZ(180deg);
}
&.error::after {
transform: rotateZ(180deg);
}
&.error.down::after {
transform: rotateZ(0deg);
}
}
}
}
</style>
<template>
<div class="text">
<span
v-for="(item, index) in text"
:key="index"
:class="{ active: index === currentIndex }"
>{{ item | formatLetter }}</span
>
</div>
</template>
<script>
import CreateText from "@/assets/js/createText";
import { mapMutations, mapState } from "vuex";
export default {
data() {
return {
length: 2,
currentIndex: 0,
text: "",
errorNum: 0,
startTime: ""
};
},
computed: {
...mapState(["currentLetter", "practiceLetters", "keyLetter"])
},
watch: {
currentLetter(val) {
if (val === null) return;
const currentLetter = this.text[this.currentIndex];
if (val === currentLetter.toLowerCase()) {
this.next();
} else {
this.errorNum++;
}
},
practiceLetters() {
this.createTextList();
},
keyLetter() {
this.createTextList();
}
},
created() {
this.createTextList();
},
methods: {
...mapMutations(["saveError", "saveSpeed", "saveScore"]),
createTextList() {
const textModel = new CreateText(this.practiceLetters, this.keyLetter);
const arr = new Array(this.length).fill(0);
const res = arr.map(() => textModel.create());
this.text = res.join(" ").toLowerCase();
this.startTime = new Date();
this.errorNum = 0;
},
getSpeed() {
const lastStartTime = this.startTime;
this.startTime = new Date();
const speed = Math.floor(
(this.text.length / (this.startTime - lastStartTime)) * 60000
);
return speed;
},
getScore(speed, errorNum) {
return speed * 5 - errorNum * 10;
},
next() {
this.currentIndex++;
if (this.currentIndex === this.text.length) {
this.currentIndex = 0;
const speed = this.getSpeed();
const score = this.getScore(speed, this.errorNum);
this.saveSpeed(speed); // 保存上次速度
this.saveError(this.errorNum); // 保存上次错误数量
this.saveScore(score); // 保存上次得分
this.createTextList();
}
}
},
filters: {
formatLetter(val) {
if (!val) return "";
return val === " " ? "" : val;
}
}
};
</script>
<style lang="less" scoped>
@keyframes cursor {
0% {
background: none;
color: #000;
}
50% {
background: #000;
color: #fff;
}
100% {
background: none;
color: #000;
}
}
@font-face {
font-family: "Ubuntu Mono";
src: url("../../assets/font/UbuntuMono-RI.ttf");
}
.text {
width: 700px;
margin: 80px auto;
text-align: left;
span {
font-family: Ubuntu Mono, Lucida Console, monospace;
display: inline-block;
text-align: center;
font-size: 24px;
font-weight: 500;
min-width: 0.5em;
&.active {
animation: cursor 0.6s ease infinite;
}
}
}
</style>
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import "element-ui/lib/theme-chalk/index.css";
import { Slider, Switch, ColorPicker } from "element-ui";
Vue.use(Slider);
Vue.use(Switch);
Vue.use(ColorPicker);
Vue.config.productionTip = false;
new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "Home",
component: () => import("@/views/index.vue")
}
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes
});
export default router;
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
allLetters: "ENITRLSAOHUDYCGMPBKVWFZXQJ", // 所有按键
currentLetter: "", // 当前按下按键
practiceLetters: "ENITRL", // 预设练习按键组
minRange: 4, // 预设按键组最小长度
keyLetter: "E", // 预设重点练习按键
showFingers: true, // 是否显示指位
keyboardColor: "#87CEEB", // 键盘颜色
startTime: "", // 开始时间
speed: "",
speedGrowth: "",
error: "",
errorGrowth: "",
score: "",
scoreGrowth: ""
},
mutations: {
onkeydown(state, val) {
state.currentLetter = val;
},
onkeyup(state) {
state.currentLetter = null;
},
settingPracticeLetters(state, val) {
state.practiceLetters = state.allLetters.slice(0, val);
},
setKeyLetter(state, val) {
state.keyLetter = val;
},
changeFingersShow(state, val) {
state.showFingers = val;
},
changeKeyboardColor(state, val) {
state.keyboardColor = val;
},
saveError(state, num) {
const lastErrorNum = state.error;
state.error = num;
if (lastErrorNum === "") return;
state.errorGrowth = num - lastErrorNum;
},
saveSpeed(state, speed) {
const lastSpeed = state.speed;
state.speed = speed;
if (lastSpeed === "") return;
state.speedGrowth = (speed - lastSpeed) / lastSpeed;
},
saveScore(state, score) {
const lastScore = state.score;
state.score = score;
if (lastScore === "") return;
state.scoreGrowth = (score - lastScore) / lastScore;
}
},
actions: {},
modules: {}
});
<template>
<div class="index">
<div class="col-10">
<settings-info />
<speed />
<text-component />
<div class="keyboard-container">
<keyboard />
<fingers v-if="showFingers" class="fingers" />
</div>
</div>
<div class="col-2">
<menu-component />
</div>
</div>
</template>
<script>
import settingsInfo from "@/components/common/settings.vue";
import textComponent from "@/components/index/text.vue";
import keyboard from "@/components/index/keyboard.vue";
import fingers from "@/components/index/fingers.vue";
import speed from "@/components/index/speed.vue";
import menuComponent from "@/components/common/menu.vue";
import { mapState } from "vuex";
export default {
name: "Home",
components: {
keyboard,
textComponent,
fingers,
speed,
settingsInfo,
menuComponent
},
computed: {
...mapState(["showFingers"])
}
};
</script>
<style lang="less" scoped>
.index {
display: flex;
justify-content: space-between;
.keyboard-container {
position: relative;
width: fit-content;
margin: 0 auto;
.fingers {
position: absolute;
top: 28px;
left: -4px;
width: 663px;
}
}
}
</style>
const path = require("path");
module.exports = {
pluginOptions: {
"style-resources-loader": {
preProcessor: "less",
patterns: [path.resolve(__dirname, "./src/assets/style/global.less")]
}
}
};