更新文章:某咸鱼的ToDoList实践

添加了 npm run serve
This commit is contained in:
DefectingCat
2021-02-11 18:01:16 +08:00
parent 791cfdf072
commit f357b80877
24 changed files with 573 additions and 369 deletions

View File

@ -7,6 +7,7 @@
"clean": "hexo clean",
"deploy": "hexo deploy",
"server": "hexo server",
"serve": "hexo cl && hexo s",
"dev": "hexo cl && hexo g && gulp",
"go": "hexo cl && hexo g && gulp && hexo d",
"cz": "cz",

View File

@ -1,31 +0,0 @@
pipeline {
agent any
stages {
stage('检出') {
steps {
checkout([
$class: 'GitSCM',
branches: [[name: env.GIT_BUILD_REF]],
userRemoteConfigs: [[
url: env.GIT_REPO_URL,
credentialsId: env.CREDENTIALS_ID
]]])
}
}
stage('自定义构建过程') {
steps {
echo '自定义构建过程开始'
}
}
stage('同步至Github') {
steps {
echo '-- Start push --'
sh 'ls'
sh 'git remote set-url origin https://DefectingCat:$ID@github.com/DefectingCat/DefectingCat.github.io.git'
sh 'git remote -v'
sh 'git push origin HEAD:backup'
echo '-- All Done --'
}
}
}
}

View File

@ -1,53 +0,0 @@
pipeline {
agent any
stages {
stage('检出') {
steps {
checkout([
$class: 'GitSCM',
branches: [[name: env.GIT_BUILD_REF]],
userRemoteConfigs: [[
url: env.GIT_REPO_URL,
credentialsId: env.CREDENTIALS_ID
]]])
}
}
stage('构建') {
steps {
echo '-- 构建过程开始 --'
sh 'npm -v'
sh 'apt install nasm -y'
sh 'npm install hexo -g'
sh 'npm install'
echo '-- hexo环境构建完成 --'
}
}
stage('编译') {
steps {
echo '-- 开始生成文章 --'
sh 'hexo cl'
sh 'hexo g'
echo '-- 文章生成结束 --'
}
}
stage('同步至Github') {
steps {
echo '-- Push to Github --'
sh 'ls'
sh 'git remote set-url origin https://DefectingCat:$ID@github.com/DefectingCat/DefectingCat.github.io.git'
sh 'git remote -v'
sh 'git push origin HEAD:file'
echo '-- All Done --'
}
}
stage('部署至云存储') {
steps {
echo '-- 开始部署至云存储 --'
sh "coscmd config -a ${env.COS_SECRET_ID} -s ${env.COS_SECRET_KEY} -b ${env.COS_BUCKET_NAME} -r ${env.COS_BUCKET_REGION}"
sh 'rm -rf .git'
sh 'coscmd upload -r ./public/ /'
echo '-- 部署完成 --'
}
}
}
}

View File

@ -1,7 +0,0 @@
## 自定义配置项备份
`custom.styl`自定义css`source/css/_custom`
`tranquil-heart.min.css`高亮css`source/lib/prettify`
`zh-Hans.yml``languages`

View File

@ -1,95 +0,0 @@
// Custom styles
//
img
border-radius 0.8rem
transition all .5s
-webkit-transition all .5
-ms-transition all .5
img:hover
transition: all 0.5s
-webkit-transition: all 0.5s
-ms-transition: all 0.5s
transform: scale(1.05)
/*ul and ol*/
.markdown-body a
background-color: transparent;
text-decoration: none;
color: #00f4e8;
transition: all .12s;
.markdown-body li:hover
text-shadow: 3px 3px 2px #2f2f2f57;
/*ol使css*/
.markdown-body ol
counter-reset: xxx 0 !important;
.markdown-body ol li:before
content: counter(xxx,decimal) "." !important;
counter-increment: xxx 1 !important;
position:absolute;
font-family:'Comic Sans MS','Open Sans','Microsoft Yahei','Microsoft Yahei',-apple-system,sans-serif !important;
color:#000;
top:0;
left:0;
text-align:center;
font-size:1.2em;
opacity:.5;
/*使*/
line-height:1.33;
text-shadow:4px 4px 1px rgba(0,0,0,.1);
-webkit-transition:.5s;transition:.5s
.markdown-body ol li:hover:before
-webkit-transform:scale(2);-ms-transform:scale(2);transform:scale(2);
opacity:1;
text-shadow:2px 2px 1px rgba(0,0,0,.1);
-webkit-transition:.1s;transition:.1s
.markdown-body ol li
list-style:none;
position:relative;
padding:0 0 0 2.1em;
margin:0 0 0 10px;
text-shadow:0px 0px 0px rgba(0,0,0,.1);
-webkit-transition:.12s;transition:.12s;
/*ul使*/
.markdown-body ul li:before
position:absolute;
content:'\2022';
font-family:Arial;
color:#000;
top:0;
left:0;
text-align:center;
font-size:1.5em;
opacity:.5;
/*使*/
line-height:1;
text-shadow:4px 4px 1px rgba(0,0,0,.1);
-webkit-transition:.5s;transition:.5s
.markdown-body ul li:hover:before
-webkit-transform:scale(2);-ms-transform:scale(2);transform:scale(2);
opacity:1;
text-shadow:2px 2px 1px rgba(0,0,0,.1);
-webkit-transition:.1s;transition:.1s
.markdown-body ul li
list-style:none;
position:relative;
padding:0 0 0 1.5em;
margin:0 0 0 10px;
text-shadow:0px 0px 0px rgba(0,0,0,.1);
-webkit-transition:.12s;transition:.12s;
//CC
.note.note-warning
background-color: #a3f3f1a1;
border-color: #ff81c0 !important;
//selection
::selection
background: #a2f1f1
text-shadow: 0px 0px 25px

View File

@ -0,0 +1,62 @@
version: "3.2"
services:
#frontend
nginx:
container_name: nginx
image: nginx:alpine
networks:
- frontend
- backend
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d/:/etc/nginx/conf.d:ro
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
restart: always
certbot:
image: certbot/certbot
container_name: certbot
volumes:
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
command: certonly --webroot -w /var/www/certbot -d git.defectink.com --non-interactive --agree-tos -m defect.y@outlook.com
networks:
- frontend
gitea:
container_name: gitea
image: gitea/gitea
volumes:
- ./gitea:/data
depends_on:
- nginx
# - db
networks:
- backend
ports:
# - "3000:3000"
- "22:22"
restart: always
vlmcsd:
container_name: vlmcsd
build: './vlmcsd'
restart: always
ports:
- "1688:1688"
# db:
# image: mariadb
# environment:
# MYSQL_ROOT_PASSWORD: 68682582
# MYSQL_DATABASE: gitea
# MYSQL_USER: gitea
# MYSQL_PASSWORD: 68682582
# volumes:
# - ./db/:/var/lib/mysql
# networks:
# - backend
# restart: always
networks:
frontend:
backend:

View File

@ -0,0 +1,45 @@
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}

View File

@ -0,0 +1,40 @@
server {
listen 80;
server_name git.defectink.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
error_page 500 502 503 504 /50x.html;
}
server {
listen 443 ssl http2;
server_name git.defectink.com;
ssl_certificate /etc/letsencrypt/live/git.defectink.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/git.defectink.com/privkey.pem;
#include /data/letsencrypt/options-ssl-nginx.conf;
#ssl_dhparam /data/letsencrypt/ssl-dhparams.pem;
location / {
set $upstream "site_upstream";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-Port $server_port;
proxy_set_header X-Real-Scheme $scheme;
proxy_set_header X-NginX-Proxy true;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Ssl on;
proxy_pass http://gitea:3000;
}
}

View File

@ -0,0 +1,29 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
gzip on;
include /etc/nginx/conf.d/*.conf;
}

View File

@ -0,0 +1 @@
File not found. 文件不存在。

View File

@ -0,0 +1,15 @@
FROM alpine:latest as builder
WORKDIR /root
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
&& apk update \
&& apk upgrade \
&& apk add --no-cache git make build-base \
&& git clone --branch master --single-branch https://gitee.com/mirrors/vlmcsd.git \
&& cd vlmcsd/ \
&& make
FROM alpine:latest
WORKDIR /root/
COPY --from=builder /root/vlmcsd/bin/vlmcsd /usr/bin/vlmcsd
EXPOSE 1688/tcp
CMD [ "/usr/bin/vlmcsd", "-D", "-d" ]

View File

@ -1,5 +0,0 @@
```bash
docker pull nginx:alpine
docker run --rm nginx:alpine cat /etc/nginx/nginx.conf > nginx.conf
docker run --rm nginx:alpine cat /etc/nginx/conf.d/default.conf > default.conf
```

View File

@ -1,90 +0,0 @@
## 使用
在页面中引入jQuery有多种方法最常用的方法就是下载jQuery库到本地和使用公共CDN。jQuery 库是一个 JavaScript 文件,可以直接使用`script`标签引入到html页面中。
```html
<head>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.js"></script>
</head>
```
另外现在的jQuery也支持在node上下载和使用。
```js
npm install jquery
yarn add jquery
```
[下载]:(https://jquery.com/download/)
## 语法
jQuery简化了原生JS对DOM的操作语法也与原生JS不尽相同。jQuery 语法是通过选取 HTML 元素,并对选取的元素执行某些操作。
基础语法: $(selector).action()
* 美元符号定义 jQuery
* 选择符selector"查询"和"查找" HTML 元素
* jQuery 的 action() 执行对元素的操作
示例:
* `$(this).hide()` - 隐藏当前元素
* `$("p").hide()` - 隐藏所有 <p> 元素
* `$("p.test").hide()` - 隐藏所有 class="test" 的 <p> 元素
* `$("#test").hide()` - 隐藏 id="test" 的元素
> jQuery 使用的语法是 XPath 与 CSS 选择器语法的组合。
### 文档就绪函数
文档就绪函数是问了防止jQuery在DOM没有加载完之前就开始运行的一个函数它通常是这样的
```js
$(document).ready(function() {
// code
})
```
如果在文档没有完全加载之前就运行函数,操作可能失败。下面是两个具体的例子:
* 试图隐藏一个不存在的元素
* 获得未完全加载的图像的大小
文档就绪函数还有一个简洁的写法,效果完全一样。
```js
$(function() {
// code
})
```
原生JS也有类似的入口函数
```js
window.onload = function () {
// code
}
```
jQuery 入口函数与 JavaScript 入口函数的区别:
* jQuery 的入口函数是在 html 所有标签(DOM)都加载之后,就会去执行。
* JavaScript 的 window.onload 事件是等到所有内容,包括外部图片之类的文件加载完后,才会执行。
<p style="text-align: center">load和ready区别<p>
| | window.onload() | $(document).ready() |
| -------- | ---------------------------------------------- | --------------------------------------- |
| 执行时机 | 必须等待网页加载完才能执行(包括图片等) | 只需等待网页中的DOM结构加载完毕就能执行 |
| 执行次数 | 只能执行一次,如果第二次,那么第一次就会被覆盖 | 可以执行多次,多次都不会被覆盖 |
| 简写方式 | 无 | $(function() { <br> // code <br> }) |
## 选择器
jQuery 中所有选择器都以美元符号开头:`$()`。选择器基于元素的 id、类、类型、属性、属性值等"查找"或选择HTML 元素。 它基于已经存在的 CSS 选择器,除此之外,它还有一些自定义的选择器。
## 事件
jQuery 是为事件处理特别设计的。

View File

@ -1,15 +0,0 @@
## Nginx 与反代
在很久以前,曾今尝试过将自己当时的小破站[所有的服务都使用 Docker 来部署](https://www.defectink.com/defect/docker-container-all.html),在未来迁移也会更加方便。也就是那时,正真的用上了 Docker。
不过这次水的部分不同,没想到过直接把小破站迁移到了 Hexo。这次准备搭建一些其他的服务而有些东西对于 SSL 的配置很是复杂,并且多个应用在一起端口也不方便分配。于是就想到了使用 Nginx 来做反代。
## Nginx 的安装与配置
### 修改配置文件
## 使用 certbot 获取证书
### 为 Nginx 配置 SSL
### 自动续期

View File

@ -0,0 +1,27 @@
## Nginx 与反代
在很久以前,曾今尝试过将自己当时的小破站[所有的服务都使用 Docker 来部署](https://www.defectink.com/defect/docker-container-all.html),在未来迁移也会更加方便。也就是那时,正真的用上了 Docker。
不过这次水的部分不同,没想到过直接把小的配置很是复杂,并且多个应用在一起端口也不方便分配。于是就想到了使用 Nginx 来做反代。
## Nginx 的配置
安装已是不在话下的事情,主要还是优雅的获取配置文件。
```bash
docker run --rm nginx:alpine cat /etc/nginx/nginx.conf > nginx.conf
```
```bash
docker run --rm nginx:alpine cat /etc/nginx/conf.d/ > conf.d/
```
### 修改配置文件破站迁移到了 Hexo。这次准备搭建一些其他的服务而有些东西对于 SSL
在之后的 compose 文件中可以再将其映射回去。
## 使用 certbot 获取证书
### 为 Nginx 配置 SSL
### 自动续期

View File

@ -1,70 +0,0 @@
为了深入了解同步与异步编程,和充分理解这一理念。打算详细的了解与对比这两种编程模式。
## Node编程风格
随便搜寻几篇 Node.js 的文章便会发现node 主要的编程风格便是使用非阻塞(异步)编码风格。先通过一个日常的举例来了解下阻塞(同步)编码与非阻塞(异步)编码之间的差异。
## 同步与异步的对比 场景一
Node.js 官网关于阻塞式 I/O 的定义如下:
> “阻塞表示 Node.js 进程中其他 JavaScript 的执行必须等到非 JavaScript 操作完成后才能继续的情况。发生这种情况的原因在于,当发生阻塞操作时,事件循环无法继续运行 JavaScript。
> 在 Node.js 中,由于 CPU 占用率高而不是等待非 JavaScript 操作(如 I/O导致性能欠佳的 JavaScript 通常并不被称为阻塞。”
### 同步
普通的同步风格程序。它在 V8 线程上自上而下运行,仅占用少量 CPU这是非技术层面的阻塞。
```js
'use strict'
console.log(Date.now().toString() + ': 主进程开始;');
console.log(Date.now().toString() + ': 创造一个延迟;');
let start = Date.now();
let end = start;
while(end < start + 20) {
end = Date.now(); // 阻塞延迟
}
console.log(Date.now().toString() + ': 主进程结束;');
```
这里使用了`while`通过不断的检查系统时间来创建一个阻塞的延迟动作,根据最后打印的时间戳,总共运行时间在 21~23 毫秒之间。
### 异步
使用异步的方式创建了与上述同样效果的实例。
```js
'use strict'
console.log(Date.now().toString() + ': 主进程开始;');
setTimeout(() => {
console.log(Date.now().toString() + ': 事件循环回调');
}, 20);
console.log(Date.now().toString() + ': 主进程结束;');
```
这里的异步使用了`setTimeout`来将事件超时运行。根据最后打印的时间戳,总共运行时间在 25~30 毫秒之间。
## 场景一小结
经过上述的小实验,不难发现同步的运行时间比异步还短那么几毫秒。异步反而更慢,异步编程风格并不关乎单纯的速度,而是关乎可扩展性。
## 模块系统
模块化是现代软件开发中的一个关键概念。它使我们能够构建更健壮的代码,并在多处复用代码,而无需重复编写相同的代码。
Node 模块化不仅为我们提供了上述所有好处,还提供了:
* 自动封装。默认情况下,一个模块中的所有代码都被打包到函数包装程序中,以便对模块外部的其他 JavaScript 代码隐藏。
* 一种公开模块接口的方式。模块中的函数、变量和其他构造必须通过 module.exports或者其简短表示exports对模块外部的 JavaScript 模块显式公开。
### 引入模块
在 ES6 发布后module 成为标准。曾经的我们采用的是 CommonJS 规范,使用`require()`来引入模块。目前的 ES6 标准使用`import`来引入模块。
```js
let fs = require('fs');
import fs from 'fs';
```
ES2015 的 module

View File

@ -0,0 +1,3 @@
## 传统方式
传统方式实现轮播也算一个不小的工程量了,首先需要将首尾两个图片节点分别克隆。

View File

@ -77,7 +77,7 @@ data() {
```js
activated() {
console.log("activated");
if (!(location.pathname === this.path)) {
if (!(this.$route.path === this.path)) {
this.$router.push(this.path);
}
},
@ -87,7 +87,7 @@ beforeRouteLeave(to, from, next) {
},
```
另外这个判断是必须的:`if (!(location.pathname === this.path))`。如果不判断当前的 URL 是否与缓存的 URL 一致,那么当组件激活时就会无条件的运行`this.$router.push(this.path);`,导致同一条路由被重写两遍。
另外这个判断是必须的:`if (!(this.$route.path === this.path))`。如果不判断当前的 URL 是否与缓存的 URL 一致,那么当组件激活时就会无条件的运行`this.$router.push(this.path);`,导致同一条路由被重写两遍。
并得到这样的错误:
@ -101,4 +101,7 @@ Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to curr
### 不缓存呢
在组件被创建时,`data`方法也会初始化,其中的值也会跟着初始化,没有办法记录路由离开前的 URL。
在组件被创建时,会调用`created`方法,但是`data`方法也会初始化,其中的值也会跟着初始化,没有办法记录路由离开前的 URL。
如果将其`path`存储在组件之外那么也可以实现记录 URL不过这种情况还是在组件内定义单独的数据为好并用不上 Vuex。

View File

@ -0,0 +1,344 @@
---
title: 某咸鱼的 ToDoList 实践🐟
date: 2021-02-11 17:47:25
tags: [JavaScript, Vue]
categories: 实践
url: todolist-exercise
index_img: /images/某咸鱼的ToDoList实践/logo.webp
---
## 为什么要迫害 ToDoList
摸鱼了也挺长时间,是时候该尝试写个小[案例](https://defectingcat.github.io/todolist/)安慰下自己了。
![](../images/某咸鱼的ToDoList实践/2021-02-11-17-49-56.webp)
## 组件划分
对于 ToDoList 来说,没有多少组件化的东西。主要还是对一些基本知识的练习。所以我将其划分了一个父组件和两个子组件,他们分别是:用于输入的 Button 和用于展示数据 List。
两个子组件分别对存在于父组件内保存的列表进行增删改的操作。
## 整体功能
目录结构:
```
$ tree -l 4 --ignore 'node_modules'
└── src
├── App.vue
├── assets
| ├── css
| | └── base.css
| └── logo.png
├── components
| ├── common
| | ├── InputButton.vue
| | └── Lists.vue
| └── content
| └── MainTab.vue
└── main.js
```
MainTab 作为两个子组件 InputButton 和 Lists 的父组件:
```html
<div class="main-tab">
<InputButton @addMsg="addMsg" />
<Lists :lists="lists" @removeItem="removeItem" @editMsg="editMsg" />
</div>
```
### 添加数据
InputButton 在 DOM 上添加一个 Input 与 Button 按钮,负责向父组件的 data 添加数据。在 InputButton 内:
```html
<div>
<input type="text" v-model="msg" @keyup.enter="emitMsg" />
<button @click="emitMsg">+</button>
</div>
```
先使用`v-model`将其输入的数据双向绑定到子组件内的一个变量,然后再在按钮上绑定对应的方法将其发送给父组件。
```js
export default {
data() {
return {
msg: ''
};
},
methods: {
emitMsg() {
this.$emit('addMsg', this.msg);
this.msg = '';
}
}
};
```
父组件这边使用一个数组来保存数据:
```
[{"id":1,"msg":"这是一个添加测试"},{"id":0,"msg":"这是一个测试"}]
```
id 主要用于给`v-for``key`绑定用,在父组件内添加时,按照`{"id":1,"msg":"这是一个添加测试"}`的对象格式来添加即可,值得注意的就是 id 需要递增。
### 展示数据
Lists 组件通过接受父组件传递的 props 来展示数据。通过一个简单的 `v-for`循环来遍历所有数据:
```html
<li v-for="(item, i) in lists" :key="item.id" :id="item.id">
<span v-show="isEdit != item.id">
{{ item.msg }}
</span>
<button @click="editItem(i, item.id)">编辑</button>
<button @click="removeItem(i)">删除</button>
</li>
</ul>
```
目前的 id 用来绑定 key
### 操作数据
在 Lists 组件内还需要实现删除某一项和编辑某一项的功能,由于数据都保存在父组件,所有在 Lists 内所有操作都通过`$emit`将事件发射出去。
```js
methods: {
removeItem(i) {
this.$emit('removeItem', i);
},
editItem(i, id) {
this.isEdit = id;
this.editedMsg = this.lists[i].msg;
},
editMsg(i) {
this.$emit('editMsg', i, this.editedMsg);
this.isEdit = -1;
}
}
```
`removeItem(i)`是一个简单的删除当前项的方法,它通过传递 lists 的下标来确定当前项。在父组件中通过数组方法`splice()`来删除当前项:
```js
removeItem(i) {
this.lists.splice(i, 1);
},
```
较为复杂的是编辑当前项的文字,首先需要解决的就是隐藏当前的文字,显示出一个`<input>`框。
```html
<input
type="text"
v-show="isEdit == item.id"
v-model="editedMsg"
@keyup.enter="editMsg(i)"
:key="item.id"
@blur="editMsg(i)"
/>
<span v-show="isEdit != item.id">
{{ item.msg }}
</span>
```
第一个想法就是通过`v-show`来判断是否显示,这里相比使用`v-if`要好点,因为这里需要来回切换。而简单的通过`true``false`来判断的条件是不够的,当一个添加满足了,所有的`<input>`框都会被显示出来,最终导致数据混乱。
这里使用一个经典的方法,以前做 tab 切换的选项卡的时候用到过。通过给`<li>`标签添加一个 ID并保存当前数据内的 id 来判断选中的哪一个`<li>`标签。
```js
data() {
return {
isEdit: -1,
editedMsg: ''
};
},
editItem(i, id) {
this.isEdit = id;
this.editedMsg = this.lists[i].msg;
},
```
然后通过`v-model`来绑定已经有的数据,再编辑完之后,发送一个事件到父组件,并传递修改的数据,在父组件内完成修改。现在使用`$emit`传递多个参数时可以直接在后面追加参数,在父组件中可以接受到多个参数。
并且在事件发送后就表示编辑结束,需要将用于判断`v-show`的值`isEdit: -1`修改为默认状态。
```js
editMsg(i) {
this.$emit('editMsg', i, this.editedMsg);
this.isEdit = -1;
}
```
父组件内接受到下标和数据中可以轻松实现修改:
```js
editMsg(i, message) {
this.lists[i].msg = message;
}
```
`$emit`方法发送事件时可以传递多个参数,现在的版本支持多个参数分开传递,而不是对象的方式存储多个参数。在父组件中可以接受到多个参数。
### Done
既然是 Todolist那么少不了的必然是完成项目。这里用的是一个 checkbox 的`<input>`来做完成的勾选框。所以 List 组件内的结构也需要稍微变动下:
```html
<input
type="checkbox"
:name="item.id"
:id="item.id"
:key="item.id"
@change="doneItem(i)"
/>
<label :for="item.id">
<span
v-show="isEdit != item.id"
:class="{ done: item.done }"
@dblclick="editItem(i, item.id)"
class="message"
>
{{ item.msg }}
</span>
</label>
```
通知父组件修改数据的方法还是同样的简单,监听`@change`属性,当 checkbox 被勾选时,发送一个下标给父组件用于定位当前的数据即可。
```js
doneItem(i) {
this.$emit('doneItem', i);
}
```
这里给 lists 数组内的对象额外增加了一个`done`属性,默认值为`false`,它现在类似于这样`{ id: this.id, msg: msg, done: false }`
在父组件中接受到下标时,定位到指定的属性中,将`done`属性修改为`true`则为项目完成。
```js
doneItem(i) {
this.lists[i].done
? (this.lists[i].done = false)
: (this.lists[i].done = true);
}
```
自然完成了一个项目后,需要将其使用~~删除线~~进行标记。这里能够轻松的拿到 boolean 值的数据,配合`v-bind`一个 class 即可实现样式的切换。
```html
:class="{ done: item.done }"
```
```css
.done {
text-decoration: line-through;
}
```
## 美化
基本功能已经完成的差不多了,但是它还缺少一样灵魂,那就是 CSS。现在的样子惨目忍睹
![](../images/某咸鱼的ToDoList实践/2021-02-02-11-40-13.webp)
本🐟 CSS 比较差,所以在添加样式上花了不少的时间。不过期间也学习到了一些东西,所以也所了一些记录。
### scoped 继承
在父组件中`<style>`添加`scoped`属性后,因为子组件的根组件会被渲染到父组件内,所以随机添加的 Attribute 会被继承的添加到子组件渲染后**根**的标签上。
![](../images/某咸鱼的ToDoList实践/2021-02-02-11-48-57.webp)
### 基本样式
样式的主要思路是:将父组件`fixed`到中央,其中两个子组件通过父组件上的`flex`定位,使用`align-items: center;`实现垂直居中。组件内的垂直居中也是使用同样的方法实现的。
### 进度条
已标记为完成的项目除以总项目再乘以 100% 就是完成进度了,既然现在可以轻松绑定到 style那么根据完成的进度就可以轻松绑定其宽度`:style="{ width: process }"`。配合上 500ms 的 transition一个简易的进度条就做好了🎈
首先是使用一个 computed 来计算完成的百分比:
```js
computed: {
process() {
let doneItem = [];
let result = 0;
let len = this.lists.length;
for (let i of this.lists) {
if (i.done) {
doneItem.push(i);
}
}
result = (doneItem.length / len) * 100;
if (Math.floor(result)) {
return `${Math.floor(result)}%`;
} else {
return 0;
}
}
}
```
然后就是创建一个用于显示的标签:
```html
<div class="processed" :style="{ width: process }">
```
## 动画
Vue 提供了 transition 的封装组件,可以为我们的元素过渡添加优美的动画。
![](../images/某咸鱼的ToDoList实践/2021-02-02-14-52-48.webp)
在本次的案例中,使用`<transition-group>`可以为列表添加一个过渡。
```html
<transition-group name="list" tag="ul">
<li>...</li>
<transition>
```
`enter``leave-to`分别是进入和离开时的过渡:
```css
.list-enter {
opacity: 0;
transform: translateY(-30px);
}
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
```
我们可以只在进入或离开时添加过渡动画:
```css
.list-enter-active,
.list-leave-active {
transition: all 500ms ease;
}
```
但是这样还不够这时只是被添加的那一个元素有了过渡动画而其他元素都是“闪现”到其他位置的。Vue 使用了一个叫 FLIP 简单的动画队列使用 `transforms`将元素从之前的位置平滑过渡新的位置。
这时不需要单独的设置`.list-enter-active, .list-leave-active`的变换效果了,可以将`li`添加一个` transition: all 500ms ease;`所有变换的效果。
并且,在删除元素时,需要在`.list-leave-active`上添加`position: absolute`才能触发剩下元素的`v-move`属性。
```css
.list-leave-active {
/* 脱离文档才能触发v-move */
position: absolute;
}
```
这样在添加新数据时看上去就不再那么突兀了。

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB