在上一篇文章中,我们完成了个人中心的对应路由设计,接下来 我们就开始写个人中心的页面内容。
基本思路
个人中心也需要若干个子路由,包括“个人信息”、“文章列表”等。因此我们如法炮制,创建对应的子路由即可。这里就不再展示代码了。
个人信息包括用户的用户名、注册邮箱、出生日期、头像和个性签名等。进入之后首先展示的便是用户的个人信息,并且还需要有一个修改按钮,去修改除了用户头像之外的信息(通常来讲用户头像都是用户点击头像时弹出询问)。
首先要在对应的后端中创建一个新的数据表userdata用来存储用户的个人信息,我们会存数字和字符串,但是图片我们应该如何存储?图片本身并不是使用文本格式存储的,而是类似于.exe的二进制文件,虽然MySQL本身提供了二进制文件类型blob,但是这会导致数据库查询速度明显下降,因此我们可以曲线救国,即数据库中只存储图片的URL,前端再根据url获取图片。
HTML5原生提供了FileReader类来完成文件上传的工作。但是首先我们要解决一个问题:一般的生产环境中,这种上传头像使用的都是页面中悬浮的消息弹框,而不是跳转到一个新的页面,下面我们就新建一个uploadAvatar.vue组件来进行弹框的模拟。
<script setup>
</script>
<template>
<div>
上传头像
<img>
<button> 上传</button>
</div>
</template>
单元测试
为了单独的一个组件设定一个新的路由不光费时费力还没有必要,因此我们引入软件开发中一个很重要的思维——单元测试。也就是测试vue应用的最小组成单位:组件,是否能够正常工作。vite官方的单元测试包为vitest,搭配Vue3的话会有很好的使用体验。因此我们使用vitest配合Vue官方的组件测试库vue test utils来进行单元测试。安装完vitest之后要在package.json中添加如下内容:
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "vitest"
},
别忘了在vite.config.js也就是vite的配置文件中设定测试环境,我们这里使用的是happy-dom环境来进行测试(所以也要安装happy-dom)。
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server:{
// ...
test:{
environment:'happy-dom',
}
})
之后就可以创建测试文件了。vitest会自动识别*.test.js文件并运行。接下来我们可以在根目录下创建对应的tests/uploadAvatar.test.js并进行单元测试了。
import { describe, expect, it } from "vitest";
import { mount } from "@vue/test-utils";
import uploadAvatar from '../src/components/uploadAvatar.vue'
// 描述一组测试,包括名称和包括的测试项目
describe('test uploadAvatar',()=>{
// 一个测试
it('should render',()=>{
// 需要将组件实例化之后才可以进行测试
const wrapper = mount(uploadAvatar,{
})
// 看看组件里面包不包括'头像'v这个字符串
expect(wrapper.text()).contain('头像')
})
})
当我们运行npm run test的时候,vitest便会自动寻找测试文件并进行运行,测试完成后告诉我们测试结果。
文件的后端读取
HTML5自带了文件上传元素,即<input type ="file"/>,并且有配套的事件,即@change,也就是说当存储的文件有改变时进行对应的上传操作,具体可以看下面的代码。
<script setup>
import axios from 'axios'
function upload(event){
const file = event.target.files[0];
}
</script>
<template>
<div>
上传头像
<img>
<input type="file" @change="upload"/>
</div>
</template>
<style scoped>
</style>
接下来就要先进行后端接收的设计。首先我们要允许后端把一部分的文件URL暴露出来,可以使用app.use('/files/avatars',express.static('files/avatars')); ,其也是一个中间件,可以将files/avatars目录下的文件全部暴露出来(通过server/files/avatars访问)。
不过首先我们要整理一下已经写过的后端代码,因为它有点过长了,阅读起来的观感不好,因此我们要对其进行分离,但是按传统的分离方法对于这种全是定义的后端很难弄,因此Express为我们提供了router组件,官方称之为mini-app,我们以测试接口为例,给大家演示一下他是如何工作的。对于connecton的问题,需要注意一下不要循环引用。
// test.js
const express = require('express')
const router = express.Router();
const connection = require('../index').connection;
router.get('/test',(req, res)=>{
res.send('1');
})
module.exports = router;
// index.js
const test = require('./gets/test');
app.use(test)
module.exports.connection = connection;
接下来就可以进行正式的后端编写了,使用multer包可以express处理上传的文件(也就是form-data类型)。
const e = require('express');
const express = require('express');
const router = express.Router();
const multer = require('multer');
const storage = multer.diskStorage({
destination: 'files/avatars',
// 目标文件夹的相对路径
filename(req, file, callback){
callback(null,`${Date.now()}${file.originalname}`);
}
// 目标文件名,通过函数回调中的第二个参数来实现
})
const upload = multer({storage: storage});
// 添加配置
const connection = require('../funcitons/sqlConnection');
router.post('/uploadAvatar',upload.single('avatar'),(req, res)=>{
const id = req.body.id;
const url = req.file.filename;
const selectSql = `select * from userInformation where id = '${id}'`;
connection.query(selectSql,(err, result)=>{
if(result.length ===0){
// 如果没有头像那么添加一个新的
const addSql = `insert into userInformation (id,avatar) values (?,?)`;
connection.query(addSql, [id,url], (err, result)=>{
if(err){
console.log(err);
}else{
res.send(url);
}
})
}else{
// 如果有头像的话那么便更新头像的链接
const updateSql = `update userInformation set avatar = '${url}'`;
connection.query(updateSql, (err, result)=>{
if(err){
console.log(err);
}else{
res.send(url);
}
})
}
})
})
module.exports = router;