在前2篇中,我将这次代码你需要学会的前置知识都总结了一下,感兴趣的话,可以去搜索一下,补充知识,避免下方的代码看不懂,我在下面使用对应知识不会在讲一遍,默认你们都会。

        第一篇:SQL完整入门指南:基础语法+实战技巧+安全规范

        第二篇:从零搭建Node.js后台系统:连接MySQL、Express与CORS配置详解

        下面的图,是数据库和连接数据库的配置,在上面2篇文章中都有讲。        

const mysql = require('mysql2')
const Mock = require('mockjs')

const db = mysql.createPool({
    host:'127.0.0.1',//数据库的IP地址
    user:'xinghe',//登录数据库的账号
    password:'Cqy@2005',//登录数据库的密码
    database:'lists'//要操作的数据库
})

const length = 200

//导入
const express = require('express')

const app = express()

//导入数据库数据和对应的方法
const list = require('./list')

//导入cors
const cors = require('cors')

app.use(express.json()) // 解析JSON请求体
app.use(express.urlencoded({ extended: true })) // 解析URL编码请求体
app.use(cors({
    origin: ['http://localhost:5173', 'http://127.0.0.1:5173'], // 明确指定允许的源
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With']
}))

        list 是增删改查初始化函数等代码存放的文件名字,之所以这样安排是因为这样对萌新比较友好,当然你可以选择使用路由,使用路由会有更好的维护性。

1、初始化函数:

        在第一篇中,我们曾经提到过初始化的问题,下面我们就仔细讲解如何在 Node.js 中对数据库中的数据进行初始化。

        首先,我们可以使用数据库中的 count 指令,来统计你所连接的数据库的数据是否为空。

        下面是全部的代码:

function init() {
    return new Promise((resolve, reject) => {
        // 检查是否已有数据
        const sqlStr = 'SELECT COUNT(*) as count FROM users'
        db.query(sqlStr, (err, results) => {
            if (err) {
                reject(err)
                return
            }
            console.log(results)
            const count = results[0].count
            if (count === 0) {
                // 生成模拟数据
                const users = []
                for (let i = 0; i < length; i++) {
                    users.push(Mock.mock({
                        id: i + 1,
                        username: Mock.Random.cname(),
                        address: Mock.Random.county(true),
                        status: 0,
                        sex: Mock.Random.integer(0, 1),
                        'age|18-60': 1,
                        birthday: Mock.Random.date()
                    }))
                }

                // 插入模拟数据
                let completed = 0
                for (let i = 0; i < length; i++) {
                    const sqlStr1 = 'INSERT INTO users (id, username, address, status, sex, age, birthday) VALUES (?, ?, ?, ?, ?, ?, ?)'
                    db.query(sqlStr1, [users[i].id, users[i].username, users[i].address, users[i].status, users[i].sex, users[i].age, users[i].birthday], (err, results) => {
                        if (err) {
                            reject(err)
                            return
                        }
                        completed++
                        if (completed === length) {
                            console.log('初始化数据成功')
                            resolve() // 我们不返回users数组,因为之后从数据库取
                        }
                    })
                }
            } else {
                console.log('数据库中已有数据,跳过初始化')
                resolve()
            }
        })
    })
}

       1.1、count返回值:

        我们慢慢解析,首先我们先创建一个空表 person (目的是详细查看数据库会返回给后端什么东西),然后连接。

const mysql = require('mysql2')
const db = mysql.createPool({
    host:'127.0.0.1',//数据库的IP地址
    user:'xinghe',//登录数据库的账号
    password:'Cqy@2005',//登录数据库的密码
    database:'lists'//要操作的数据库
})

const sqlStr = 'select count(*) as count from person'

db.query(sqlStr,(err,results)=>{
    if(err) return
    if(results){
        const count = results
        console.log(count)
    }
})

        运行结果是 [{count:0}],随后我们可以,让 count = results[0]。

        最后让 count = results[0].count 即可得到表中数据的全部数量,当 count = 0 时调用初始化函数,达到只调用一次初始化函数,不会有过多的数据(不会多次调用初始化函数)。

        我们在函数的最外面定义了全局变量 length = 200 ,这是我们预设的模拟数据的总量。

        1.2、mock.js--模拟数据:

        在使用 if 来判断数据表的数据为空后,我们使用了 for 循环去进行模拟200条数据,在这里我们使用之前安装的 Mock.js (官方文档里面有对应的模拟数据方式),对数据进行模拟,使用 push 方法将模拟数据放入到 users 数组里面,里面就放着我们模拟的全部数据。

        随后就是插入数据了,在这里我们有 2 种方式去插入数据,一种是使用循环一条一条的插入,一种是一次性全部插入,在这里我们选择前者,论性能的话,在数据量庞大时,后者性能远大于前者,但我们是萌新,求稳和理解,并且我们模拟的数据量比较小,所以选择前者来进行插入。

        1.3、insert--插入数据:

        const sqlStr1 = 'INSERT INTO users (id, username, address, status, sex, age, birthday) VALUES (?, ?, ?, ?, ?, ?, ?)'        

        在这里我们使用占位符进行占位,所以在使用 db.query()  时我们需要使用一个数组,去将占位符中的数据进行一 一对位,确保数据不会插入错误。

        里面的 affectedRows 表示受影响的数据数量,这里的 affectedRows = 1 表示插入成功 1 条数据, 所以在判断插入是否成功是 便可以是 if ( affectedRows > 0) 说明操作执行成功。

        我们使用 completed 来做计数器来记录插入的数据,当它和我们预定数量一样时,就完成插入操作。

        到这里 初始化函数就讲完了,里面的 Promise 会在下面讲解,这涉及到一个小 bug ,放在那里讲更合适。

        2、获取用户数据--getUser:

        这是全部的代码,当然这个是写在 module.exports 里面暴露出去的,所以结尾有逗号。

        注意:这里先查询总数再查询数据,在极高并发场景下可能会出现数据不一致的情况,对应于大多数项目,这种方式已经足够,如果对一致性要求极高的话,可以考虑其他方案。

 getUsers: function(page = 1, pageSize = 10) {
        return new Promise((resolve, reject) => {
            const offset = (page - 1) * pageSize;
            // 只查询 status=0 的数据
            const countSql = 'SELECT COUNT(*) as count FROM users WHERE status = 0';
            const dataSql = 'SELECT * FROM users WHERE status = 0 LIMIT ? OFFSET ?';
            console.log(`获取用户数据 - page: ${page}, pageSize: ${pageSize}, offset: ${offset}`);
        
            // 先查询总数
            db.query(countSql, (err, countResults) => {
                if (err) {
                    console.error('查询总数失败:', err);
                    reject(err);
                    return;
                }
            
                const totalCount = countResults[0].count;
                console.log(`用户总记录数: ${totalCount}`);
            
                // 再查询分页数据
                db.query(dataSql, [parseInt(pageSize), parseInt(offset)], (err, results) => {
                    if (err) {
                        console.error('查询数据失败:', err);
                        reject(err);
                    } else {
                        console.log(`返回数据条数: ${results.length}`);
                        resolve({
                            list: results,
                            count: totalCount,
                            page: parseInt(page),
                            pageSize: parseInt(pageSize)
                        });
                    }
                });
            });
        });
    },

        2.1、分页公式:

        我们使用了分页公式  offset = (page - 1) * pageSize ,但需要注意,我们传入该函数中的参数 page 是指当前的数据所在页数,pageSize 是当前页面要展示的数据量。

        2.2、记录总数据:

        我们在这里没有使用 select * from users 查询总数据,是因为我们使用了软删除的思想,即我们并没有真正的把数据从数据库中删除而是以 status 这个的值来规定该数据是否被删除了。

        所以我们在这里需要使用 :

        const countSql = 'SELECT COUNT(*) as count FROM users WHERE status = 0';

        来查询前端可以渲染的数据量是多少。

        2.3、分页渲染数据:

        我们在前面定义了 offset 来为下面的分页渲染做准备。

        const dataSql = 'SELECT * FROM users WHERE status = 0 LIMIT ? OFFSET ?'; 

        我们使用这行代码来查询接下来不同页面的数据做处理,也就是分页功能。

        2.4、调试代码:

        为了保证下面我们查询的数据是否有误,我们下面的代码下面,添加了 console.log 取打印重要的数据是否有误,如果有误的话方便后面代码的修改。

        这些 console.log 在开发阶段很有用,可以帮助我们快速定位问题,在生产环境中可以移除或改用专业的日志工具。

        const dataSql = 'SELECT * FROM users WHERE status = 0 LIMIT ? OFFSET ?'; 

        3、异步代码:

        3.1、隐藏bug:

        写这一篇对应了上文提到的 Promise 的小bug。

list.init().then(()=>{
    app.get('/',(req,res)=>{
        list.getUsers().then(users=>{
            console.log('我在这里',users[1]||{})
            res.send(users[1]||{})
        }).catch(err=>{
            res.status(500).send('数据库错误')
        })
    })
})

        在原先我写的代码里面,仅仅只 list.init() 这样看代码是没有问题的,但我在最初运行的时候拿不到一点数据,在查找资料后得知,list.init() 它是同步代码,而我们上面写的数据库的初始化函数是异步代码(我最开始的版本代码初始化函数不是一个 Promise 对象)。

        为此我们就需要 Promise 帮助,他会强制让代码在 list.init() 这里停止运行(简单来讲是这样的),等里面的数据库数据初始化完成之后才去运行接下来的代码,这样这个隐藏的 bug 就可以解决了。

        3.2、测试:

        而这里的 list.init().then() 是为了测试,我们的初始化代码是否被成功的执行,如果成的执行那么对应的数据库会有数据,我们可以在后端上看到对应的数据,只要不为空,就代表着我们成功了。

        4、getUserData--前端接口:

        在上面的讲解下,我们知道了函数是怎么去写的,但在写后端接口前,我们需要先写前端的接口,这样会更夹方便后面对后端接口的讲解。

        这是前端对应模块的部分代码:

import {ref,getCurrentInstance,onMounted,reactive} from 'vue'
import {ElMessage,ElMessageBox} from 'element-plus'
const {proxy}=getCurrentInstance()
const config = reactive({
    total:0,
    page:1,
    limit:10,
})
const transformUserData = (dataArray) => {
    return dataArray.map(item => ({
        ...item,
        sexLabel: item.sex === 1 ? '男' : '女',
        name: item.username || item.name,
        age: item.age,
        birth: item.birthday || item.birth,
        addr: item.address || item.addr,
        status: item.status || 0,
    }))
}
const getUserData = async () => {
    try {
        const params = {
            page: config.page,
            limit: config.limit,
            name: formInline.keyWord || ''  // 搜索关键词
        }
        
        console.log('请求参数:', params)
        const data = await proxy.$api.getUserData(params)
        console.log('响应数据:', data)
        
        if (data && data.list) {
            const activeUsers = data.list.filter(item => item.status === 0 || item.status === undefined)
            tableData.value = transformUserData(activeUsers)
            config.total = data.count 
            console.log('分页信息 - 总数:', config.total, '当前页:', config.page)
            // 如果当前页没有有效数据且不是第一页,自动跳转到上一页
            if (activeUsers.length === 0 && config.page > 1) {
                console.log('当前页无有效数据,跳转到上一页')
                config.page -= 1
                getUserData() // 重新获取
                return
            }

        } else {
            console.error('返回数据格式错误:', data)
            tableData.value = []
            config.total = 0
        }
        
    } catch (err) {
        console.error('获取数据失败:', err)
        ElMessage.error('获取数据失败,请重试')
    }
}

        4.1、try / catch:

        我们在代码中使用 try / catch 来捕捉代码运行时的错误,便于随时得到错误信息,方便修改。

        4.2、config:

        这是我们定义的全部变量,目的就是记录当前的页数(翻页功能的当前页数),以及数据总量,和每一页的限制数据是多少。

        4.3、params:

        我们在代码开头就定义了 params 这个变量,为的就是记录当前的页面的页数、最大渲染数据量、以及搜索的关键词(这个在后面搜索功能会用到)。

        我们定义的这个变量是为了给后端的 getUsers 传参数,方便后端返回对应页数的数据,实现展示数据页面的分页功能。

        4.4、console.log:

        在 const data = await proxy.$api.getUserData(params) 代码运行前后都添加 console.log 是为了查看参数是否按照我们想要的模式去传递,以及后端返回给我们的数据是什么格式,主要是查看后端返回的数据(一般是对象)上面有什么属性。

        可以看到,当我们使用 console.log 打印后,我们可以很直观的看到数据里面有什么属性,这样方便我们之后的使用和开发,可以提高我们开发的效率。

        4.5、data&&data.list :

        这是一个常见的 js 安全访问模式,首先是需要 data 变量不为空或者 undefined ,这样是为了防止在 data 为 null/undefined 时访问其属性,data.list 则是确保 data 对象里面含义我们需要的数据,这样可以有效避免运行时出错,确保代码在数据异常时不会崩溃。

        4.6、filter:

        我们使用 filter 来确保我们渲染的数据没有被删除(status=0)。

        4.7、赋值:

        我们将 getUserData 和 getSearch 函数里面的公用部分给写出一个函数,来减少项目的体积,优化我们的项目。

        我们将 data.count 赋值给 config.total 确保我们的分页功能不会出现问题,这个主要是和 element plus 里面的翻页功能有关,里面的 total 是翻页功能里面渲染的数据总条数,layout 则对应上一页、省略、下一页、跳转功能,current-page 则是记录当前的页数,page-size 则是当前页面渲染允许的最大数据量。

        

        <el-pagination
        class="pager"
        background="true"
        layout="prev, pager, next, jumper" 
        size="small"
        :total="config.total" 
        :current-page="config.page"
        :page-size="config.limit"
        @current-change="handleChange"
        />

        Pagination 分页 | Element Plus 这是 element  plus 中文官网。

        5、api接口:

        里面的 request 文件是 axios 请求的封装后的文件,api 这个文件存储了我们项目里面需要 axios 请求的所有函数,集中在这个文件是因为这样方便维护。

// 整个项目api的统一管理
import request from './request'

//请求首页左侧的表格的数据

export default {
    getTableData() {
        return request({
            url: '/home/getTableData',
            method: 'get',
        })
    },
    getCountData() {
        return request({
            url: '/home/getCountData',
            method: 'get',
        })
    },
    getChartData() {
        return request({
            url: '/home/getChartData',
            method: 'get',
        })
    },
    getUserData(data){
        return request({
            url:'/home/getUserData',
            method:'post',
            data,
            mock:false,
        }).then(response => {
        return response
    }).catch(error => {

        throw error
    })
    },
    getMenu(params){
        return request({
            url:'/permission/getMenu',
            method:'post',
            data:params,
        })
    },
    deleteUser(data) {
        return request({
            url: '/home/deleteUser',
            method: 'post',
            data,
        })
    },
    addUser(data) {
         return request({
          url: '/home/addUser',
          method: 'post',
          data,
        })
    },
  // 编辑用户(新增接口,Mock 中也要对应配置)
    updateUser(data) {
        return request({
         url: '/home/updateUser',
         method: 'post',
        data,
        })
    },
    getSearch(data) {
        return request({
         url: '/home/getSearch',
         method: 'post',
         data,
        })
    },
}

        在这里我们可以很容易看到里面的 getUserData 的请求路径和方法,这样我们就可以去写对应的后端接口了。

        6、getUserData--后端接口:

        

// 在后端路由中包装数据结构
app.post('/home/getUserData', (req, res) => {
    console.log('收到用户数据请求:', req.body)
    const { page = 1, limit = 10, name = '' } = req.body
    
    console.log(`用户数据请求参数 - page: ${page}, limit: ${limit}, name: "${name}"`);
    
    // 如果有搜索条件,调用搜索接口
    if (name && name.trim() !== '') {
        console.log('检测到搜索条件,调用搜索接口');
        list.getSearch(name, page, limit).then(data => {
            res.json(data);
        }).catch(err => {
            console.error('搜索失败:', err);
            res.status(500).json({
                code: 500,
                message: '搜索失败: ' + err.message
            });
        });
    } else {
        // 没有搜索条件,调用普通分页接口
        list.getUsers(page, limit).then(data => {
            console.log(`用户数据获取成功 - 返回数据: ${data.list.length}条, 总数: ${data.count}`);
            res.json(data);
        }).catch(err => {
            console.error('获取用户数据失败:', err);
            res.status(500).json({
                code: 500,
                message: '获取用户数据失败: ' + err.message
            });
        });
    }
});

        我们的路径接口可以从 api 里面直接找到。

        6.1、请求内容:

        我们使用 req.body 来看到前端给后端的数据内容,我们可以只管看到前端给后端成功的传递了 page、limit、name。

        我们首先看 else 这部分,因为这是我们一开始不进行任何操作就能看到数据的关键,前面讲过的函数我就在这里不在讲一遍了,值得一提的是我们将 后端放回的数据通过 res.json 转换成立    json 字符串的格式。

        7、getSearch--搜索:

        函数里面的部分代码我们在上面的 getUsers 中讲过部分,重复的我们就不会在这里继续讲。

    getSearch: function(uname, page = 1, pageSize = 10){
        return new Promise((resolve,reject) => {
            const offset = (page - 1) * pageSize;
        
            console.log(`搜索参数 - uname: ${uname}, page: ${page}, pageSize: ${pageSize}, offset: ${offset}`);
        
            // 只查询 status=0 的数据
            const countSql = "SELECT COUNT(*) as count FROM users WHERE username LIKE CONCAT('%',?,'%') AND status = 0";
            const dataSql = "SELECT * FROM users WHERE username LIKE CONCAT('%',?,'%') AND status = 0 LIMIT ? OFFSET ?";
            db.query(countSql, [uname], (err, countResults) => {
                if(err) {
                    console.error('查询总数失败:', err);
                    reject(err);
                    return;
                }
            
                const totalCount = countResults[0].count;
                console.log(`搜索到总记录数: ${totalCount}`);
            
                // 再查询分页数据
                db.query(dataSql, [uname, parseInt(pageSize), parseInt(offset)], (err, results) => {
                    if(err) {
                        console.error('查询数据失败:', err);
                     reject(err);
                    } else {
                        console.log(`返回数据条数: ${results.length}`);
                        resolve({
                            list: results,
                            count: totalCount,
                            page: parseInt(page),
                            pageSize: parseInt(pageSize)
                        });
                    }
                });
            });
        })
    },

        const countSql = "SELECT COUNT(*) as count FROM users WHERE username LIKE CONCAT('%',?,'%') AND status = 0";

        这行代码会返回你搜索的名字中含有这个字的数据。

        const dataSql = "SELECT * FROM users WHERE username LIKE CONCAT('%',?,'%') AND status = 0 LIMIT ? OFFSET ?";

        这行代码是确保搜索出来的数据按照之前设定的进行渲染(每次最多是渲染 10 条数据)。 

        下面的代码不是不讲,是上面基本都讲过了类似的,剩下的你可以推出了。

        7.1、接口代码:

app.post('/home/getSearch', (req, res) => {
    console.log('搜索请求数据:', req.body)
    const { uname = '', page = 1, limit = 10 } = req.body
    
    console.log(`搜索参数 - uname: "${uname}", page: ${page}, limit: ${limit}`);
    
    list.getSearch(uname, page, limit).then(data => {
        console.log(`搜索成功 - 返回数据: ${data.list.length}条, 总数: ${data.count}`);
        
        const responseData = {
            list: data.list,
            count: data.count,
            page: data.page,
            pageSize: data.pageSize
        }
        res.json(responseData)
    }).catch(err => {
        console.error('搜索失败:', err)
        res.status(500).json({
            code: 500,
            message: '搜索失败: ' + err.message
        })
    })
})

        7.2、前端接口代码:

const getSearch = async() => {
    config.page = 1  // 搜索时重置到第一页
    getUserData()
}

        8、前端--提交函数:

<el-dialog 
    v-model="show"
    :title="dialogTitle"
    width="500px"
    align-center
     class="custom-dialog"
  >
    <el-form
    ref="ruleFormRef"
    style="max-width: 600px ; margin: 0 auto;"
    :model="ruleForm"
    status-icon
    :rules="rules"
    label-width="auto"
     class="custom-form"
  >
    <el-form-item label="姓名:" prop="username"  class="form-item">
      <el-input placeholder="请输入你的名字" type="text" autocomplete="off" v-model="ruleForm.username"/>
    </el-form-item>
    <el-form-item label="年龄:" prop="userage"  class="form-item">
      <el-input placeholder="请输入你的年龄" type="number" autocomplete="off" v-model="ruleForm.userage"  @input="() => ruleFormRef.value?.clearValidate('userage')" @change="val => ruleForm.userage = Number(val)"/>
    </el-form-item>
    <el-form-item label="性别:" prop="usersex"  class="form-item">
      <el-select v-model="ruleForm.usersex" placeholder="请选择性别" class="custom-select">
          <el-option label="男" value="男"></el-option>
          <el-option label="女" value="女"></el-option>
        </el-select>
    </el-form-item>
    <el-form-item label="出生日期:" prop="userbirthday"  class="form-item">
      <el-date-picker placeholder="选择出生日期" autocomplete="off" v-model="ruleForm.userbirthday" value-format="YYYY-MM-DD"  type="date"/>
    </el-form-item>
    <el-form-item label="地址:" prop="useradress"  class="form-item">
      <el-input placeholder="请输入你的地址" type="text" autocomplete="off" v-model="ruleForm.useradress"/>
    </el-form-item>
  </el-form>
    <div class="button-container">
    <el-form-item class="create-body">
      <el-button type="primary" @click="submitForm(ruleFormRef)">
        提交
      </el-button>
      <el-button @click="resetForm(ruleFormRef)">全部重填</el-button>
    </el-form-item>
    </div>
  </el-dialog>
const rules = reactive({
  username: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
  userage: [
    { required: true, message: '请输入年龄', trigger: 'blur' },
    { type: 'number', message: '年龄必须为数字', trigger: 'blur' }
  ],
  usersex: [{ required: true, message: '请选择性别', trigger: 'change' }],
  userbirthday: [{ required: true, message: '请输入出生日期', trigger: 'blur' }],
  useradress: [{ required: true, message: '请输入地址', trigger: 'blur' }]
})
const submitForm = (formEl) => {
  if (!formEl) return
  formEl.validate(async (valid) => {
    if (valid) {
      const formData = {
        name: ruleForm.username,
        age: ruleForm.userage,
        sex: ruleForm.usersex === '男' ? 1 : 0,
        birth: ruleForm.userbirthday,
        addr: ruleForm.useradress
      }
      try {
        if (currentEditId.value) {
          formData.id = currentEditId.value
          await proxy.$api.updateUser(formData)
          ElMessage.success('修改成功')
        } else {
          await proxy.$api.addUser(formData)
          ElMessage.success('新增成功')
        }
        
        show.value = false
        resetForm(formEl)
        getUserData() // 刷新数据
        
      } catch (error) {
        console.error('操作失败:', error)
        ElMessage.error(currentEditId.value ? '修改失败' : '新增失败')
      }
    } else {
      ElMessage.error('表单验证失败')
    }
  })
}

        代码 1 是图片中的运行效果,而我们之前的数据库数据都绑定到了 ruleFormRef 对象上面,提交函数的第一个 if 就是判断是否对象为空,为空就不执行(无法进行提交)。

        

        在 element plus 里面的 form 表单里的 api 他规定了我们表单输入数据的类型,也就是 年龄必须是数字等,都在上面的代码里面规定的有。

        formEl.validate()是 element plus 里面的内置 api 他会自己检验数据类型是否正确,正确则 vaild 的值就为真,否则为假,之后就是我们通过使用 currentEditId.value 添加到我们定义的对象里面,对象的其他属性里的值,里面是我们更改的值,将 currentEditId.value 添加进去是为了传递给后端,为更改数据做索引。

app.post('/home/updateUser',(req,res)=>{
    console.log('我拿到数据了',req.body)
    const target = {
        username:req.body.name,
        age:req.body.age,
        sex:req.body.sex,
        birthday:req.body.birth,
        address:req.body.addr,
        id:req.body.id
    }
    list.updateUser(target).then((data)=>{
        console.log(data)
        res.send('更改成功')
    })
})
    updateUser: function(target){
        return new Promise((resolve,reject)=>{
            const sqlStr = "update users set ? where id = ?"
            db.query(sqlStr,[target,target.id],(err,results)=>{
                if(err){
                    reject(err)
                    return
                }
                else resolve(results)
            })
        })
    },

        剩下的增加,删除这两个接口我就不写了,他们可以使用我们上面讲的知识写出来,这是最简单的接口,写这么多也很累,见谅哈。

        9、下期预告:             

        《进阶实战:TypeScript重构与数据可视化大屏》

        🚀 技术升级

  • 使用TypeScript重构前后端代码,提升类型安全和开发体验

  • 接口类型定义、泛型应用、装饰器实战

  • 从JavaScript到TypeScript的平滑迁移策略

        📊 数据可视化

  • 集成ECharts实现用户数据统计大屏

  • 年龄分布饼图、地域分布地图、注册趋势折线图

  • 实时数据更新与动画效果优化

        🔧 工程化增强

  • 更严格的ESLint配置与Prettier代码格式化

  • 环境变量管理与多环境部署配置

  • 性能优化与错误监控实践

        🎯 新功能拓展

  • 用户行为分析报表生成

  • 数据导出功能(Excel/PDF)

  • 权限管理模块设计与实现

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐