第一次作业——通讯录管理系统

832302215 欧成泽 2025-11-11 16:05:36

姓名: 欧成泽       学号:832302215          MU ID:23124865

目录

项目描述

一、Git仓库链接

   1. 前端仓库

   2.后端仓库

2. PSP表格

三、展示成品

四、设计与实现流程

1.整体架构

2.技术选型理由

功能模块设计

1. 数据模型设计

2. API接口设计

3. 前端功能模块

具体实现流程

第一阶段:环境搭建与项目初始化

第二阶段:后端服务开发

数据库层实现

第三阶段:前端界面开发

第四阶段:系统集成测试

第五阶段:部署与文档

关键设计决策

1. 前后端分离架构

2. 无框架前端设计

3. MySQL数据库选择

五、关键代码说明

1.后端

1. 数据库初始化

2. API接口实现

2.1 获取所有联系人 (GET)

2.2 添加联系人 (POST)

2.3 更新联系人 (PUT)

2.4 删除联系人 (DELETE)

3. 应用启动

2.前端 

联系人保存与加载功能

联系人编辑和单独删除功能

联系人批量删除功能

用户登入,注册和退出功能

列表 / 搜索 / 分页 功能

系统架构总结

 

六、总结

技术实现

核心成果

项目价值


 

项目描述

    一个基于前后端分离架构的轻量级联系人管理应用。使用原生HTML/CSS/JavaScript构建前端界面,Python Flask框架提供后端API服务,SQLite作为数据存储,实现了完整的联系人信息增删改查功能。项目采用清晰的前后端分离设计,提供简洁直观的用户界面和流畅的操作体验。

一、Git仓库链接

   1. 前端仓库

        GitHub地址:https://github.com/HappyParite/concacts_frontend

   2.后端仓库

        GitHub地址:https://github.com/HappyParite/concacts_backend

  网站地址:http://localhost:63342/untitled/concacts/forntend/src/concacts.html

2. PSP表格

PSP阶段任务内容预估时间(h)实际时间(h)
计划明确作业要求,选择技术栈(HTML+JavaScript+MySQL),规划项目结构0.50.5
开发环境准备:安装webstorm,MySQL,JavaScript配置开发环境1.01.5
 创建项目目录结构,初始化Git仓库0.30.3
 后端开发:编写API和MySQL数据库操作2.02.5
 前端开发:编写HTML界面和JavaScript交互2.02.5
 前后端联调:测试API接口和数据交互1.01.5
测试功能测试:验证添加、修改、删除联系人功能0.50.7
 兼容性测试:检查不同浏览器下的表现0.30.4
部署项目部署到服务器(可选)1.0-
文档编写README.md和codestyle.md文档0.50.6
 撰写博客文章,整理截图和说明1.52.0
总计 10.612.4

三、展示成品

                                                                                                                                   主页面展示👇

  • 添加联系人区域:有姓名、电话、邮箱 3 个输入框,以及 “保存”“重置” 按钮,用于录入新联系人信息。
  • 联系人列表区域:表头包含姓名、电话、邮箱、操作,列表展示已添加的联系人(如张三、李四),操作列提供 “编辑”“删除” 功能。

 

 

                                                                                   

                                                                                        以下是删除联系人功能,点击删除按键删除你的联系人,结果如下

                                                                                        

                                                                                 以下是编辑联系人功能,点击编辑按键编辑你的联系人,结果如下

 

 

四、设计与实现流程

1.整体架构

  • 前端:基于原生 HTML + CSS + JavaScript 的轻量级 Web 应用,使用 fetch 调用后端 API,JWT 保存在 localStorage。

  • 后端:基于 Node.js + Express 的 API 服务,提供 /api/auth 与 /api/contacts 等 REST 接口,启用 CORS、鉴权与错误处理中间件。

  • 通信方式:HTTP / JSON(受保护接口携带 Authorization: Bearer <JWT>)。

  • 数据存储:MySQL,核心表:

    • users(id, username UNIQUE, password_hash, created_at)

    • contacts(id, name, phone, email, note, user_id, created_at, updated_at)

    • 关键约束:FOREIGN KEY(contacts.user_id → users.id ON DELETE CASCADE);UNIQUE(user_id, phone)

2.技术选型理由

  • 前端选择原生技术:上手快、依赖少、体积小、浏览器原生支持,便于课程演示与后续迁移到框架(Vue/React)。

  • 后端选择 JavaScript:前后端同语种降低心智负担;Express 生态成熟,中间件丰富;事件驱动对 I/O 友好,开发效率高。

  • 数据库选择 MySQL:关系模型贴合“用户-联系人”一对多;事务与约束完善;工具链成熟;utf8mb4 兼容中英文与 emoji。

功能模块设计

1. 数据模型设计


CREATE DATABASE IF NOT EXISTS contacts_db
  DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE contacts_db;

CREATE TABLE IF NOT EXISTS users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(50) UNIQUE NOT NULL,
  password_hash VARCHAR(255) NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS contacts (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  phone VARCHAR(32) NOT NULL,
  email VARCHAR(120),
  note VARCHAR(255),
  user_id INT NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_contacts_user (user_id),
  UNIQUE KEY uk_user_phone (user_id, phone)
);

2. API接口设计



import "dotenv/config";
import dotenv from "dotenv";
import express from "express";
import cors from "cors";

dotenv.config();

import authRouter from "./backend/src/routes/auth.js";
import contactsRouter from "./backend/src/routes/contacts.js";
import { authRequired } from "./backend/src/middleware/auth.js";

const app = express();
app.use(cors());
app.use(express.json());

// 健康检查(前端“关于系统”会调用)
app.get("/api/health", (_req, res) => res.json({ status: "ok", db: true }));

// 先挂无需鉴权的登录/注册
app.use("/api/auth", authRouter);

// 受保护资源:联系人
app.use("/api/contacts", authRequired, contactsRouter);

const port = process.env.PORT || 8000;
app.listen(port, () => console.log(`Server running on http://localhost:${port}`));

3. 前端功能模块


  • 联系人列表展示模块

  • 联系人表单模块(添加/编辑)

  • 数据交互模块(API调用)

  • 用户界面交互模块


     

具体实现流程

第一阶段:环境搭建与项目初始化

  1. 开发环境配置

    • 安装webstorm,MySQL,JavaScript

    • 配置JavaScript扩展和必要插件

    • 验证开发环境正常运行

  2. 项目结构创建

    • 创建前后端分离的目录结构

    • 初始化Git仓库,建立版本控制

    • 配置代码规范文档

第二阶段:后端服务开发

  1. 数据库层实现

    • 设计contact_db表结构

    • 实现MySQL数据库连接

    • 编写数据库初始化脚本

  2. API 服务层实现

    • 使用 Node.js + Express 搭建后端,按 controller / routes / middleware / db 模块化组织,提供统一 API 服务。

    • 实现 RESTful 接口
      提供 /api/auth/register/api/auth/login/api/contacts(增删改查)等接口,统一使用 JSON 请求/响应。

    • 开启 CORS
      启用 cors() 中间件,允许前端域名跨域访问后端接口,支持前后端分离部署。

    • 统一错误与响应格式
      集中捕获异常并返回统一结构(如 success/message/code/data),便于前端处理,提升可维护

第三阶段:前端界面开发

  1. 用户界面设计

    • 设计响应式布局结构

    • 实现联系人列表展示

    • 创建联系人表单组件

  2. 业务逻辑实现

    • 实现联系人数据获取和渲染

    • 实现添加联系人功能

    • 实现编辑联系人功能

    • 实现删除联系人功能

第四阶段:系统集成测试

  1. 前后端联调

    • 测试API接口连通性

    • 验证数据格式一致性

    • 调试跨域请求问题

  2. 功能完整性测试

    • 测试联系人CRUD操作

    • 验证数据持久化

    • 检查错误处理机制

第五阶段:部署与文档

  1. 项目部署

    • 配置生产环境

    • 部署到云服务器

  2. 文档编写

    • 编写技术文档和使用说明

    • 整理项目结构和代码说明

关键设计决策

1. 前后端分离架构

           优势:职责清晰,易于维护,支持多端复用

           实现:通过API进行数据交换

2. 无框架前端设计

           优势:零依赖,体积小,学习成本低

           挑战:需要手动处理DOM操作和事件绑定

3. MySQL数据库选择

           优势:无需安装配置,适合作业场景

           考虑:虽然性能有限,但完全满足作业需求

五、关键代码说明

1.后端

1. 数据库初始化

CREATE DATABASE IF NOT EXISTS contacts_db
  DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE contacts_db;

CREATE TABLE IF NOT EXISTS users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(50) UNIQUE NOT NULL,
  password_hash VARCHAR(255) NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS contacts (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  phone VARCHAR(32) NOT NULL,
  email VARCHAR(120),
  note VARCHAR(255),
  user_id INT NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_contacts_user (user_id),
  UNIQUE KEY uk_user_phone (user_id, phone)
);


 

作用

  • 创建SQLite数据库文件 contacts_db

  • 定义联系人表结构:

    • id:主键,自动增长

    • name:姓名,必填字段

    • phone:电话,必填字段

    • email:邮箱,可选字段

    • note:  备注, 可选字段

2. API接口实现

2.1 获取所有联系人 (GET)

// 列表(当前登录用户)
export async function getAll(req, res) {
    try {
        const uid = req.user?.id;
        const [rows] = await db.query(
            "SELECT id,name,phone,email,note,created_at,updated_at FROM contacts WHERE user_id=? ORDER BY name COLLATE utf8mb4_zh_0900_as_cs ASC, id ASC",
            [uid]
        );
        res.json(rows);
    } catch (e) {
        console.error("[contacts/getAll]", e);
        res.status(500).json({ error: "服务器内部错误" });
    }
}

作用:前端页面加载时调用,返回所有联系人的JSON数据

2.2 添加联系人 (POST)

// 新增
export async function addOne(req, res) {
    try {
        const uid = req.user?.id;
        const { name, phone, email, note } = req.body || {};
        const payload = { name: String(name || "").trim(), phone: normPhone(phone), email: email || null, note: note || null };

        const bad = validate(payload);
        if (bad) return res.status(400).json({ error: bad });

        await db.query(
            "INSERT INTO contacts (name,phone,email,note,user_id) VALUES (?,?,?,?,?)",
            [payload.name, payload.phone, payload.email, payload.note, uid]
        );
        res.json({ message: "添加成功" });
    } catch (e) {
        if (e.code === "ER_DUP_ENTRY") return res.status(409).json({ error: "该手机号已在你的联系人中存在" });
        console.error("[contacts/addOne]", e);
        res.status(500).json({ error: "服务器内部错误" });
    }
}

作用:接收前端提交的表单数据,将新联系人保存到数据库

2.3 更新联系人 (PUT)

// 更新
export async function updateOne(req, res) {
    try {
        const uid = req.user?.id;
        const id = Number(req.params.id);
        const { name, phone, email, note } = req.body || {};
        const payload = { name: String(name || "").trim(), phone: normPhone(phone), email: email || null, note: note || null };

        const bad = validate(payload);
        if (bad) return res.status(400).json({ error: bad });

        const [ret] = await db.query(
            "UPDATE contacts SET name=?,phone=?,email=?,note=? WHERE id=? AND user_id=?",
            [payload.name, payload.phone, payload.email, payload.note, id, uid]
        );
        if (ret.affectedRows === 0) return res.status(404).json({ error: "未找到或无权限" });
        res.json({ message: "Updated" });
    } catch (e) {
        if (e.code === "ER_DUP_ENTRY") return res.status(409).json({ error: "该手机号已在你的联系人中存在" });
        console.error("[contacts/updateOne]", e);
        res.status(500).json({ error: "服务器内部错误" });
    }
}

作用:根据联系人ID更新对应的联系人信息

2.4 删除联系人 (DELETE)

// 删除
export async function deleteOne(req, res) {
    try {
        const uid = req.user?.id;
        const id = Number(req.params.id);
        const [ret] = await db.query("DELETE FROM contacts WHERE id=? AND user_id=?", [id, uid]);
        if (ret.affectedRows === 0) return res.status(404).json({ error: "未找到或无权限" });
        res.status(204).send();
    } catch (e) {
        console.error("[contacts/deleteOne]", e);
        res.status(500).json({ error: "服务器内部错误" });
    }
}

作用:根据联系人ID从数据库中删除对应的联系人

3. 应用启动


import "dotenv/config";
import dotenv from "dotenv";
import express from "express";
import cors from "cors";

dotenv.config();

import authRouter from "./backend/src/routes/auth.js";
import contactsRouter from "./backend/src/routes/contacts.js";
import { authRequired } from "./backend/src/middleware/auth.js";

const app = express();
app.use(cors());
app.use(express.json());

// 健康检查(前端“关于系统”会调用)
app.get("/api/health", (_req, res) => res.json({ status: "ok", db: true }));

// 先挂无需鉴权的登录/注册
app.use("/api/auth", authRouter);

// 受保护资源:联系人
app.use("/api/contacts", authRequired, contactsRouter);

const port = process.env.PORT || 8000;
app.listen(port, () => console.log(`Server running on http://localhost:${port}`));

作用

  • 环境加载:读取 .env 环境变量(端口、数据库配置、JWT 密钥等)。

  • 创建服务:初始化 Express 应用实例。

  • CORS 配置:开启 cors(),解决前后端分离下的跨域访问问题。

  • 请求解析:启用 express.json(),支持前端以 JSON 方式提交数据。

  • 健康检查:提供 /api/health 接口,供前端检测服务与数据库状态。

  • 鉴权与路由

    • /api/auth:挂载登录/注册(无需鉴权)。

    • /api/contacts:挂载联系人 CRUD,并通过 authRequired 校验 JWT

  • 启动监听:使用 PORT(默认 8000)启动服务,对外提供 API。

2.前端 

联系人保存与加载功能

/** ====== 加载与保存 ====== */
    async function loadList(){
        const r = await apiFetch(API);
        if(!r.ok) return;
        allRows = await r.json();
        applyFilter();
    }
    function resetForm(){
        ["id","name","phone","email","note"].forEach(k=>$("#"+k).value="");
    }
    document.getElementById("resetBtn").onclick = ()=>{ resetForm(); closeEditor(); };

    document.getElementById("form").onsubmit = async (e)=>{
        e.preventDefault();
        const id = $("#id").value || null;
        const payload = {
            name: $("#name").value.trim(),
            phone: $("#phone").value.trim(),
            email: $("#email").value.trim(),
            note: $("#note").value.trim()
        };
        const r = await apiFetch(id? `${API}/${id}`: API, {
            method: id? "PUT":"POST",
            headers:{ "Content-Type":"application/json" },
            body: JSON.stringify(payload)
        });
        const t = await r.json().catch(()=> ({}));
        if(!r.ok) return alert(t.error || "保存失败");
        resetForm();
        await loadList();
        closeEditor();                 // 保存后自动收起
        alert("保存成功");
    };

    document.getElementById("tbl").addEventListener("click", async (ev)=>{
        const btn = ev.target.closest("button"); if(!btn) return;
        const tr = ev.target.closest("tr");
        const id = tr.dataset.id;                 // 真实数据库 id(用于请求)
        const act = btn.dataset.act;

        if(act==="edit"){
            $("#id").value = id;
            $("#name").value = tr.children[2].textContent.trim();
            $("#phone").value= tr.children[3].textContent.trim();
            $("#email").value= tr.children[4].textContent.trim();
            $("#note").value = tr.children[5].textContent.trim();
            openEditor();
        }else if(act==="del"){
            const seq = tr.querySelector(".idx")?.textContent || "?";   // 页面序号
            if(!confirm(`确定要删除 第 ${seq} 条吗?`)) return;
            const r = await apiFetch(`${API}/${id}`, { method:"DELETE" });
            if(!r.ok) return alert("删除失败");
            await loadList();
        }
    });

关键代码解析
  • document.getElementById() / querySelector() 读取/填充表单与表格元素

  • apiFetch(url, opts) 统一加 Authorization 与 401 登录失效处理

  • form.onsubmit 判断隐藏域 id:有→PUT /api/contacts/:id,无→POST /api/contacts

  • tbody.addEventListener('click', ...) 事件委托处理编辑/删除按钮

  • tr.dataset.id 保存数据库主键,.idx 显示页内序号

  • renderRows(list.map(...)).join('') 将数据渲染为表格行

  • applyFilter() 关键词 includes 过滤 + 排序(localeCompare/数值比较)+ 分页 slice()

  • confirm() 删除二次确认,fetch(...,{method:'DELETE'}) 执行删除

  • resetForm() 清空表单,openEditor()/closeEditor() 控制编辑区显示

  • alert(t.error || '保存成功') 统一提示后端返回结果

联系人编辑和单独删除功能


 
 // 表格:编辑 /删除
    document.getElementById("tbl").addEventListener("click", async (ev)=>{
        const btn = ev.target.closest("button"); if(!btn) return;
        const tr = ev.target.closest("tr");
        const id = tr.dataset.id;
        const act = btn.dataset.act;

        if(act==="edit"){
            openEditor();
            document.getElementById("id").value    = id;
            document.getElementById("name").value  = tr.children[2].textContent.trim();
            document.getElementById("phone").value = tr.children[3].textContent.trim();
            document.getElementById("email").value = tr.children[4].textContent.trim();
            document.getElementById("note").value  = tr.children[5].textContent.trim();
        }else if(act==="del"){
            const seq = tr.querySelector(".idx")?.textContent || "?";
            if(!confirm(`确定要删除 第 ${seq} 条吗?`)) return;
            const r = await apiFetch(`${API}/${id}`, { method:"DELETE" });
            if(!r.ok) return alert("删除失败");
            await loadList();
        }
    });

关键代码解析

  • 事件委托:给 #tbl 绑定一次点击,closest('button') 找到被点的按钮,closest('tr') 找到当前行。

  • 取标识:tr.dataset.id 为数据库主键;btn.dataset.act 区分 edit/del

  • 编辑流:openEditor() → 把行内第 3~6 列文本填入表单 → 把隐藏域 #id 设为真实主键。

  • 删除流:读 .idx 作为界面序号 → confirm() 二次确认 → DELETE /api/contacts/:id → 成功后 loadList() 重新渲染。

  • 注意:表单是否“新增/编辑”取决于隐藏域 #id 是否有值;tr.children[2..5] 索引要与表头列顺序一致。

联系人批量删除功能


 
 /** ====== 勾选 & 批量删除 ====== */
    document.getElementById("chk-all").onchange = (e)=>{
        document.querySelectorAll(".row-chk").forEach(c=> c.checked = e.target.checked);
    };
    document.getElementById("btn-batch-del").onclick = async ()=>{
        const ids = Array.from(document.querySelectorAll(".row-chk:checked"))
            .map(c=> c.closest("tr").dataset.id);
        if(ids.length===0) return alert("请先勾选要删除的行");
        if(!confirm(`确定批量删除 ${ids.length} 条吗?`)) return;
        for(const id of ids){
            const r = await apiFetch(`${API}/${id}`, { method:"DELETE" });
            if(!r.ok){ alert(`删除失败(id=${id})`); break; }
        }
        await loadList();
    };

关键代码解析

  • 设置主复选框 indeterminate(部分选中态)。

  • 批量接口/并发:后端支持时用 POST /api/contacts/batch-delete 或前端 Promise.allSettled(ids.map(...))

  • 操作中禁用按钮并显示进度,失败汇总展示。

用户登入,注册和退出功能

 $("#btn-login").onclick = async ()=>{
        const u = $("#lg-username").value.trim(), p = $("#lg-password").value.trim();
        if(!u || !p) return alert("请输入用户名和密码");
        const r = await fetch(API.replace("/contacts","/auth/login"), {
            method:"POST", headers:{ "Content-Type":"application/json" },
            body: JSON.stringify({ username:u, password:p })
        });
        const j = await r.json();
        if(!r.ok) return alert(j.error || "登录失败");
        setToken(j.token);
        goto("#/home");
    };
    $("#btn-register").onclick = async ()=>{
        const u = $("#lg-username").value.trim(), p = $("#lg-password").value.trim();
        if(!u || !p) return alert("请输入用户名和密码");
        const r = await fetch(API.replace("/contacts","/auth/register"), {
            method:"POST", headers:{ "Content-Type":"application/json" },
            body: JSON.stringify({ username:u, password:p })
        });
        const j = await r.json();
        if(!r.ok) return alert(j.error || "注册失败");
        alert("注册成功,请登录");
    };
    $("#btn-logout").onclick = ()=>{ clearToken(); goto("#/login"); };

关键代码解析

  •  #btn-login 读取用户名/密码 → POST /api/auth/loginContent-Type: application/json)→ 成功取回 tokensetToken(token) 保存到本地 → goto("#/home") 进入主页。

  • #btn-register 读取表单 → POST /api/auth/register → 失败弹后端 error,成功提示“注册成功,请登录”。

  • clearToken() 清除本地令牌 → goto("#/login") 返回登录页。

  • 任何一步若用户名或密码为空,直接 alert("请输入用户名和密码") 并中止。

列表 / 搜索 / 分页 功能


 
  /** ====== 列表 / 搜索 / 分页 ====== */
    let allRows = [];       // 后端返回的全部(当前用户)
    let viewRows = [];      // 搜索/排序后的视图
    let pageSize = 10, page = 1;

    function applyFilter(){
        const kw = $("#kw").value.trim().toLowerCase();
        viewRows = allRows.filter(r=>{
            const s = `${r.name||""} ${r.phone||""} ${r.email||""} ${r.note||""}`.toLowerCase();
            return !kw || s.includes(kw);
        });
        // 按姓名排序,再按 id
        viewRows.sort((a,b)=> (a.name||"").localeCompare(b.name||"", "zh") || a.id - b.id);
        page = 1;
        render();
    }
    function render(){
        const total = viewRows.length;
        const start = (page-1)*pageSize, end = Math.min(start+pageSize, total);
        const rows = viewRows.slice(start, end);
        const tbody = $("#tbl tbody");
        if(!rows.length){
            tbody.innerHTML = `<tr><td colspan="7" class="muted" style="padding:14px">暂无数据,试试添加或修改搜索条件。</td></tr>`;
        }else{
            tbody.innerHTML = rows.map((r, i)=>`
      <tr data-id="${r.id}">
        <td><input type="checkbox" class="row-chk"></td>
        <td class="idx">${start + i + 1}</td>  <!-- 页面序号 -->
        <td>${r.name||""}</td>
        <td>${r.phone||""}</td>
        <td>${r.email||""}</td>
        <td>${r.note||""}</td>
        <td>
          <button class="btn success" data-act="edit">编辑</button>
          <button class="btn danger" data-act="del">删除</button>
        </td>
      </tr>`).join("");
        }
        $("#summary").textContent = `共 ${total} 条,显示 ${total? (start+1) : 0}-${end}`;
        renderPager(total);
        $("#chk-all").checked = false;
    }
    function renderPager(total){
        const pages = Math.max(1, Math.ceil(total/pageSize));
        const el = $("#pager"); el.innerHTML = "";
        const mk = (p, txt, cur=false)=>{
            const b = document.createElement("button"); b.textContent=txt; if(cur) b.classList.add("current");
            b.onclick = ()=>{ page=p; render(); };
            el.appendChild(b);
        };
        mk(Math.max(1,page-1), "上一页");
        for(let i=1;i<=pages;i++) mk(i, String(i), i===page);
        mk(Math.min(pages,page+1), "下一页");
    }

关键代码解析

  • allRows 保存后端返回的全集,viewRows 为搜索/排序后的视图;pageSize/page 管分页。

  • applyFilter() 取关键词,对 allRowsincludes 过滤;再按姓名 localeCompare('zh') 排序,次序用 id 兜底;重置到第 1 页并 render()

  • render() 计算当前页起止索引,用 slice() 取当页数据;表格行里用 data-id 放数据库主键、.idx 显示页内序号;更新统计与全选框状态,然后画分页。

  • renderPager() 依据总数算总页数,生成“上一页 / 数字页 / 下一页”按钮,点击切页后重新 render()

系统架构总结

这个联系人管理系统实现了完整的CRUD(增删改查)功能:

  1. 数据存储:使用JavaScript数组在内存中存储联系人数据

  2. 用户界面:通过HTML表单和表格提供用户交互界面

  3. 功能实现

    • 添加联系人:收集表单数据并添加到数组

    • 编辑联系人:将现有数据填充到表单进行修改

    • 删除联系人:从数组中移除指定联系人

    • 查看联系人:在表格中显示所有联系人信息

  4. 数据验证:确保姓名和电话字段不为空

 

六、总结

本项目成功开发了一个前后端分离的联系人管理系统,圆满完成了作业的所有基础要求。

技术实现

采用HTML+JavaScript+CSS前端 + JavaScript后端 + MySQL数据库的技术组合,实现了完整的联系人增删改查功能。前后端通过API进行数据交互,架构清晰,代码规范。

核心成果

    ✅ 联系人添加、修改、删除功能

    ✅ 前后端分离架构,数据实时同步

    ✅ 规范的GitHub仓库管理和代码文档

    ✅ 简洁易用的Web界面

项目价值

这个项目不仅实现了具体的功能需求,更重要的是提供了一个完整的前后端分离开发范例。代码结构清晰,技术选型合理,既满足了作业要求,也为后续的Web开发学习奠定了良好基础。

整个项目从环境搭建到功能实现,完整展示了现代Web应用的基本开发流程,是一次很有价值的实践。

 

...全文
164 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
内容概要:本文详细介绍了一种基于节点不连续伽辽金方法(Discontinuous Galerkin Method)在求解线性和非线性平流方程中的一维数值实现方案,并提供了完整的MATLAB代码实现。该方法在处理偏微分方程特别是具有间断解或高梯度特征的问题时展现出优异的稳定性和精度。文中系统阐述了算法的核心原理、空间离散化策略、时间推进机制以及边界条件的处理方式,通过具体编程实例展示如何在MATLAB环境中实现该数值方法,并辅以典型算例验证其有效性和可靠性。此外,文章还强调科研工作中“借力”与创新思维的重要性,鼓励研究者在夯实理论基础的同时勇于探索新思路。; 适合人群:具备偏微分方程数值解法基础知识、熟悉MATLAB编程,从事计算数学、流体力学、物理建模及相关领域的研究生、科研人员及工程技术开发者。; 使用场景及目标:① 学习并掌握节点不连续伽辽金方法的基本理论与实现流程;② 利用所提供的MATLAB代码开展线性和非线性平流方程的数值模拟实验;③ 将该方法作为基础算法应用于高分辨率数值模拟、守恒律方程求解等科研项目中的扩展与改进; 阅读建议:建议读者结合经典数值分析教材深入理解DG方法的数学背景,逐段调试并运行所附MATLAB代码,通过调整初始条件、网格划分和时间步长等方式观察算法表现,从而深化对数值稳定性与计算精度之间平衡关系的理解。

164

社区成员

发帖
与我相关
我的任务
社区描述
2501_MU_SE_FZU
软件工程 高校
社区管理员
  • FZU_SE_LQF
  • 助教_林日臻
  • 朱仕君
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧