2022年,这一年真的不好过,但它也终于要过去了
在8月的时候就从上一家公司离开了,在家待了两个月,媳妇又怀孕了,所以也就一直没找工作,想等宝宝出生后在找了,不知道到时候还能找到工作不,现在的行情真的是特别的不好,不过,管他呢,总会有出路的。
主要技术
- 原型: axure
- 前端: vue3+vite+element-plus
- 后端: node+express+nedb
声明
本人初次投稿,写的不好也很啰嗦,技术水平也有限,希望大家多鼓励
项目背景
在家待的时候玩了一款游戏,应该从2022年一月的时候开始玩的,这也快玩了有一年了,因为我们打公会战的时候总是掉级,有很多人在公会里就是佛系的玩家,想打就打一打,不想打就摆烂了,这对公会里付出努力的人是不太公平的。。。实在编不下去了,好吧,就是在家待的无聊,时间长不写代码手艺是会退步的,给自己找点事干,所以创造需求,自己既是产品,又是美工,又是前端,还要做后端接口。。。
没有系统之前都是用excel做个统计,方便看一期中得了多少分和人员的表现。。。但是有几个痛点
- 不能实现多期单人分数统计
- 排序操作太过复杂
- 一般游戏里都是给出当前期的总分,往excel里写的时候还要自己算每个阶段的分数
- 人员的增删改查操作过于复杂
- 就是用着不爽
开始
ue
把主要的功能在ue软件中写布置一下,这样后边做页面也会快很多,不会ue的前端不是好前端, 这个出来后大概的功能,逻辑点,和要用到哪些接口的规划也就差不多了。。
先写前端
首先就是vite+vue3安装了,这个教程应该有很多,我这就不cv了,按着ue把所需要的页面搭建出来,主要功能页有以下几页
目录结构
前端这块也遇到了一些问题,我集中到后边去写
后端接口
这块采用的node+express+nedb的技术栈,其实这块我之前真的是知识盲区,之前有一点理解但没有做过具体的项目,所以这块还是很懵的,做完之后也不知道自己写的方式,目录结构什么的合理不合理,有大佬看到给指点一下就非常感谢了。 数据库用的nedb, 其实也想用mongodb的,但是都差不多,这个nedb比mongo小一些,就选择这个了,毕竟自己还是产品,可以说了算了,哈哈,实现自驱了。。。
先根据原型把接口都定下来,因为太多,只列两个,就是做好前后端的一个约定
phase: {
name: '2022年12月第一期',
stage: '准备',
beginDate: '2016-05-03',
endDate: '2016-05-03',
level: '登封',
status: '已结束',
}
1 活动列表接口
name: getPhaselist
参数: 无
返回值: phase数组
2 获取某一期活动信息
name: getPhaseInfoById
参数: id
返回值: phase
前端参数处理,比如我要存一个数据,这些数据是要从前端接口传过来的,这时候就用了body-parser这个中间件了,这个还挺简单,下面代码有使用方法
接口写完下来,大概有了一点后端处理数据的印象和概念,总结大致模型如下
后端文件结构
主文件index.js代码如下:
const express = require('express');
const app = express();
const port = 5000
// 引入路由处理模块
const phase = require('./module/phase.js')
const user = require('./module/user.js')
const score = require('./module/score.js')
// 引入参数处理中间件
const bodyParser = require('body-parser')
app.use(bodyParser.json())
// phase request
//--------------------------------------------------------------------------------------------
app.get('/getPhaseList', phase.getPhaseListRouter)
app.get('/getPhaseInfoById', phase.getPhaseInfoByIdRouter)
app.post('/createPhase', phase.createPhaseRouter)
app.post('/editPhase', phase.editPhaseRouter)
app.post('/deletePhase', phase.deletePhaseRouter)
//--------------------------------------------------------------------------------------------
// user request
//--------------------------------------------------------------------------------------------
app.get('/getUserList', user.getUserListRouter)
app.get('/getUserInfoById', user.getUserInfoByIdRouter)
app.post('/addUser', user.addUserRouter)
app.post('/editUser', user.editUserRouter)
app.post('/deleteUser', user.deleteUserRouter)
//--------------------------------------------------------------------------------------------
// score request
//--------------------------------------------------------------------------------------------
app.get('/getScoreList', score.getScoreListRouter)
app.post('/updateScore', score.updateScoreRouter)
//--------------------------------------------------------------------------------------------
const server = app.listen(port, function () {
// const host = server.address().address
// const port = server.address().port
console.log("server is run at http://127.0.0.1:5000")
})
路由处理模块之一, module/phase.js
const methods = require('../db/methods.phase.js')
const { createPhase, getPhaseList,getPhaseInfoById,editPhase, deletePhase } = methods
function getPhaseListRouter (req, res) {
getPhaseList().then((data) => {
res.send({
data,
status: 0
});
})
}
function getPhaseInfoByIdRouter(req, res) {
getPhaseInfoById(req.query.id).then((data) => {
res.send({
data,
status: 0
});
}).catch((err) => {
res.send({
message: '没有找到相关期号!',
status: 1
});
})
}
function createPhaseRouter(req, res) {
let phase = req.body
//console.log(req.body)
if(phase.name === ''){
res.send({
status: 1,
message: '没有期号!'
})
return
}
createPhase(phase).then((data) => {
res.send({
status: 0,
data: data
})
})
}
function deletePhaseRouter(req, res) {
let id = req.body.id
//console.log(req.body)
if(id === undefined ){
res.send({
status: 1,
message: '没有这个id!'
})
return
}
deletePhase(id).then(() => {
res.send({
status: 0
})
})
}
function editPhaseRouter(req, res) {
let phase = req.body
//console.log(req.body)
if(phase.name === ''){
res.send({
status: 1,
message: '没有期号!'
})
return
}
editPhase(phase).then((data) => {
res.send({
status: 0,
data: data
})
})
}
module.exports = {
getPhaseListRouter,
getPhaseInfoByIdRouter,
createPhaseRouter,
deletePhaseRouter,
editPhaseRouter
}
还有数据库处理模块文件, db/methods.phase.js
const db = require('./index.js')
// 添加期号
function createPhase(data) {
return new Promise((resolve, reject) => {
db.phase.insert(data, (error, result) => {
if (error === null) {
console.log('result is:', result);
db.score.insert({pid: result._id, score: []}, (err, res) => {
if(err === null) {
resolve(result)
}
})
}else{
console.log('error:', error);
reject(error)
}
});
})
}
//编辑期号信息
function editPhase(data) {
return new Promise((resolve, reject) => {
db.phase.update({_id: data._id},data,{}, (error, result) => {
if (error === null) {
console.log('result:', result.data);
resolve(result)
}else{
console.log('error:', error);
reject(error)
}
});
})
}
// 获取期号列表
function getPhaseList() {
return new Promise((resolve, reject) => {
db.phase.find({}).sort({beginDate: -1}).skip(0).limit(10).exec( (error, result) => {
if (error === null) {
//console.log('result:', result);
resolve(result)
}else{
console.log('error:', error);
reject(error)
}
});
})
}
// 删除一个期号
function deletePhase(id) {
return new Promise((resolve, reject) => {
db.phase.remove({_id: id}, (error, result) => {
if (error === null) {
//console.log('result:', result);
resolve(result)
}else{
console.log('error:', error);
reject(error)
}
});
})
}
// 获取一期信息
function getPhaseInfoById(id) {
return new Promise((resolve, reject) => {
db.phase.find({_id: id}, (error, result) => {
if (error === null) {
console.log('result:', result);
if(result.length === 1) resolve(result[0])
else reject(error)
}else{
console.log('error:', error);
reject(error)
}
});
})
}
module.exports = {
createPhase,
editPhase,
deletePhase,
getPhaseList,
getPhaseInfoById
}
然后后端这里在命令行运行 node index.js 就会在5000端口上跑起来一个后端的服务器,接口也都挂在这个服务上的,前端可以通过相应的url链接来调用接口
注意: 由于 vite默认的端口是跑在5173上的,所以肯定这里是会跨域的,这里踩了个小坑,开始以为还要在vue.config.js里去配置,其实vite的项目,要配置在vite.config.js这个文件上,代理和配置设置如下, 注意这里的写法,属性名和vue.config.js里的写法都不一样
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
},
server: {
proxy: {
//端口号
//port: 5173,
'/api': {// api 表示拦截以 /api开头的请求路径
target: 'http://127.0.0.1:5000',//跨域的域名(不需要写路径)process.env.VUE_APP_URL
changeOrigin: true, //是否开启跨域
//ws: true, //是否代理websocked
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
}
})
这里配置好后前端就可以通过接口去调用数据了,因为比较简单,请求也没有对axios再进行封装, 这里列一个请求示例,status等于0表明后台返回正常
const getPhaseData = () => {
axios.get('/api/getPhaselist').then(({data:req})=> {
if (req.status === 0) {
req.data.forEach((item) => {
item.beginDate = dayjs(item.beginDate).format("YYYY-MM-DD")
item.endDate = dayjs(item.endDate).format("YYYY-MM-DD")
item.stage = item.stage === 0 ? '准备中' : `第${item.stage}阶段`
item.status = item.status === 0 ? '未开始' : item.status === 1 ? '进行中' : '已结束'
} )
phase.tableData = [...req.data]
}
})
}
再来说下前端的那些问题
- 表格数据处理
这里因为要用到排序,就直接想用到el-table里的sort属性了,这个还是比较简单的,如果列表数据是不可编辑的,自带的这个sort完全能胜任,可是我想要的是,它既能查看,我也能在这个页面进行编辑,这时候el-table就有问题了,下图所示功能 ,如果el-talbe的sort排好的表格,你在改其内容的时候,他还会实时的进行排序,就导致你输入的时候行的内容就已经变了,所以就自己写了个简单的排序,反正自己说了算,就是任性,这也是自己写着玩和企业级项目之间最大的区别吧。
列一些表格里数据处理的逻辑吧,这块感觉还有优化空间
// 设置单人合计值
const setRowTotal = (row) => {
nextTick(() => {
// 输入合计值时,计算出当前阶段分数并进行回填
switch (data.phaseInfo.stage) {
case 0:
break;
case 1:
row.s1 = row.total - row.s2 - row.s3 - row.s4
break;
case 2:
row.s2 = row.total - row.s1 - row.s3 - row.s4
break;
case 3:
row.s3 = row.total - row.s1 - row.s2 - row.s4
break;
case 4:
row.s4 = row.total - row.s1 - row.s2 - row.s3
break;
default:
break;
}
})
}
// 设置阶段值,自动计算单人合计
const setStageScore = (row) => {
nextTick(() => {
// 更新合计值
row.total = row.s1 + row.s2 + row.s3 + row.s4
})
}
最后就是将最终数据导出为图片的功能了,这里用到了canvas画图,也是好长时间没练过这个功能了。看一下生成图片的效果.
主要功能就是画块,画线,写字,找好坐标点就行了。
代码:
import dayjs from 'dayjs'
export default (data, outputPanel) => {
let ctx = outputPanel.value.getContext('2d');
let phaseInfo = data.phaseInfo
let drawData = JSON.parse(JSON.stringify(data.tableData))
let tableTitle = ['','名字', '第1阶段', '第2阶段', '第3阶段', '第4阶段', '合计']
let colPosition = [10, 50, 260, 400, 540, 680, 820]
ctx.fillStyle = "white"
ctx.fillRect(0, 0, 900, 2700)
// 画头部
drawTitle()
// 表格头行
drawTableTitle()
// 表格数据
drawTableData()
function drawTitle(){
ctx.fillStyle = "#b71c1c"
ctx.fillRect(0, 0, 900, 70)
ctx.font = "40px '得意黑'";
ctx.fillStyle = "yellow"
//ctx.moveTo(300,300)
ctx.fillText(phaseInfo.title, 30,50)
ctx.font = "12px 'Arial'";
let beginDate = dayjs(phaseInfo.beginDate).format('YYYY-MM-DD')
let endDate = dayjs(phaseInfo.endDate).format('YYYY-MM-DD')
let time = `本期时间:${beginDate} -- ${endDate}`
ctx.fillText(time, 700, 50)
}
function drawTableTitle() {
ctx.fillStyle = "pink"
ctx.fillRect(0, 70, 900,45)
ctx.font = "16px '微软雅黑'";
ctx.fillStyle = "#333333"
tableTitle.forEach((item,index) => {
ctx.fillText(item, colPosition[index], 100)
})
}
function drawTableData() {
ctx.font = "14px '微软雅黑'";
ctx.fillStyle = "#333333"
let [s1Summary,s2Summary,s3Summary,s4Summary,totalSummary] = [0,0,0,0,0]
drawData.forEach((item, index) => {
// 合计行数据统计
s1Summary += item.s1
s2Summary += item.s2
s3Summary += item.s3
s4Summary += item.s4
totalSummary += item.total
// 单人数据打印
let row = [index+1, item.name, item.s1,item.s2,item.s3,item.s4, item.total]
ctx.strokeStyle = "#ddd";
ctx.moveTo(0.5, 155.5 + index * 40)
ctx.lineTo(900.5, 155.5 + index * 40)
ctx.lineWidth = 1
ctx.stroke()
for(let n=0; n<row.length; n++){
ctx.fillText(row[n], colPosition[n], 140+ index* 40)
}
})
// 合计部分
ctx.fillStyle = "#ddd"
ctx.fillRect(0, 115 + drawData.length * 40, 900, 50)
let row = ['', '合计', s1Summary, s2Summary, s3Summary, s4Summary, totalSummary]
ctx.fillStyle = "#333"
for (let n = 0; n < row.length; n++) {
ctx.fillText(row[n], colPosition[n], 145 + drawData.length * 40)
}
}
}
那么问题来了,怎么在画布上画1像素的线呢?
总结
这个项目还是起到了练手的作用,那种写代码的舒适感又回来了,完全解决了之前的所有痛点,自已也基本摸通了node + nosql这种开发接口的方式,而且在公会群里起到了很好的装X效果,这不群主立马给我开了副阁主的位置,哇哈哈哈。。。
后续
项目是跑起来了但是还有一些问题,现在项目分为前端和后端,运行的时候要跑两个服务,所以这块其实也考虑过做成electron的,这样最络可以打包个exe出来,给不懂程序的小伙伴也能继续使用,而且成本也不大。
想要看原码的可以留言我。
回顾2022,展望2023,我正在参与2022年终总结征文大赛活动」