找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

5300

积分

0

好友

738

主题
发表于 3 小时前 | 查看: 5| 回复: 0

本文将基于之前的 TypeScript 表单处理项目,扩展实现删除用户编辑用户本地持久化(localStorage)功能,并附有详细代码注释。同时,文中也会探讨如何平滑切换到真实后端以及引入状态管理的思路。


1. 修改后的 index.html(增加编辑模态框与样式)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>TS + Vite 示例 - 第三步:本地持久化 + 删除/编辑</title>
    <style>
        body {
            font-family: sans-serif;
            max-width: 700px;
            margin: 2rem auto;
            padding: 0 1rem;
        }
        button, input {
            margin: 0.5rem 0;
            padding: 0.5rem;
        }
        .error {
            color: red;
        }
        .loading {
            color: gray;
            font-style: italic;
        }
        /* 用户列表项样式 */
        .user-item {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 0.5rem;
            padding: 0.5rem;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        .user-info {
            flex: 1;
        }
        .user-actions button {
            margin-left: 0.5rem;
            padding: 0.2rem 0.5rem;
        }
        /* 模态框样式 */
        .modal {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0,0,0,0.5);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 1000;
        }
        .modal-content {
            background: white;
            padding: 2rem;
            border-radius: 8px;
            width: 300px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.3);
        }
        .modal-content form div {
            margin-bottom: 1rem;
        }
        .modal-content label {
            display: inline-block;
            width: 60px;
        }
        .modal-content input {
            width: 200px;
        }
        .modal-buttons {
            display: flex;
            justify-content: flex-end;
            gap: 0.5rem;
        }
    </style>
</head>
<body>
    <div id="app">
        <h1>用户管理(本地存储版)</h1>

        <!-- 刷新按钮 -->
        <button id="refresh-btn" type="button">刷新列表</button>

        <!-- 添加用户表单 -->
        <h2>添加新用户</h2>
        <form id="add-user-form">
            <div>
                <label for="name">姓名:</label>
                <input type="text" id="name" name="name" required />
            </div>
            <div>
                <label for="email">邮箱:</label>
                <input type="email" id="email" name="email" required />
            </div>
            <button type="submit">提交</button>
        </form>

        <!-- 用户列表容器 -->
        <h2>用户列表</h2>
        <ul id="user-list" style="list-style: none; padding: 0;"></ul>
    </div>

    <!-- 编辑用户模态框 -->
    <div id="edit-modal" class="modal" style="display: none;">
        <div class="modal-content">
            <h3>编辑用户</h3>
            <form id="edit-user-form">
                <input type="hidden" id="edit-id" />
                <div>
                    <label>姓名:</label>
                    <input type="text" id="edit-name" required />
                </div>
                <div>
                    <label>邮箱:</label>
                    <input type="email" id="edit-email" required />
                </div>
                <div class="modal-buttons">
                    <button type="submit">保存</button>
                    <button type="button" id="close-modal">取消</button>
                </div>
            </form>
        </div>
    </div>

    <script type="module" src="/src/main.ts"></script>
</body>
</html>

2. 重构后的 src/main.ts(含详细注释)

/**
 * TypeScript 第三步示例:
 * 1. 使用 localStorage 模拟后端,实现本地持久化
 * 2. 增加删除用户功能
 * 3. 增加编辑用户功能(通过模态框)
 * 4. 保留原有的添加用户和刷新功能
 * 5. 所有操作均模拟异步延迟,便于理解真实后端的交互模式
 */

// ---------- 类型定义 ----------
interface User {
    id: number;
    name: string;
    email: string;
}

interface NewUser {
    name: string;
    email: string;
}

// ---------- 用户服务层(封装所有数据操作,便于未来切换到真实 API)----------
class UserService {
    private storageKey = ‘users‘; // localStorage 的键名

    /**
     * 初始化:如果 localStorage 中没有数据,则从 jsonplaceholder 获取初始数据并存储
     * 模拟首次加载时的数据填充
     */
    async init(): Promise<void> {
        const stored = localStorage.getItem(this.storageKey);
        if (!stored) {
            const users = await this.fetchInitialUsers();
            localStorage.setItem(this.storageKey, JSON.stringify(users));
        }
    }

    /**
     * 从远程 API 获取初始用户数据(仅用于初始化)
     */
    private async fetchInitialUsers(): Promise<User[]> {
        const response = await fetch(‘https://jsonplaceholder.typicode.com/users‘);
        if (!response.ok) {
            throw new Error(`获取初始数据失败: ${response.status}`);
        }
        const users = await response.json();
        // 只保留我们需要的字段,确保类型安全
        return users.map((u: any) => ({
            id: u.id,
            name: u.name,
            email: u.email,
        }));
    }

    /**
     * 获取所有用户(模拟异步延迟)
     */
    async getUsers(): Promise<User[]> {
        await this.delay();
        const stored = localStorage.getItem(this.storageKey);
        return stored ? JSON.parse(stored) : [];
    }

    /**
     * 添加新用户(id 自动生成,基于当前最大 id + 1)
     */
    async addUser(newUser: NewUser): Promise<User> {
        await this.delay();
        const users = await this.getUsers();
        const newId = users.length > 0 ? Math.max(...users.map(u => u.id)) + 1 : 1;
        const user: User = { id: newId, ...newUser };
        users.push(user);
        localStorage.setItem(this.storageKey, JSON.stringify(users));
        return user;
    }

    /**
     * 更新用户信息
     * @param id 用户ID
     * @param updatedUser 要更新的字段(可部分更新)
     */
    async updateUser(id: number, updatedUser: Partial<User>): Promise<User> {
        await this.delay();
        const users = await this.getUsers();
        const index = users.findIndex(u => u.id === id);
        if (index === -1) {
            throw new Error(`用户不存在,id: ${id}`);
        }
        const updated = { ...users[index], ...updatedUser };
        users[index] = updated;
        localStorage.setItem(this.storageKey, JSON.stringify(users));
        return updated;
    }

    /**
     * 删除用户
     */
    async deleteUser(id: number): Promise<void> {
        await this.delay();
        let users = await this.getUsers();
        users = users.filter(u => u.id !== id);
        localStorage.setItem(this.storageKey, JSON.stringify(users));
    }

    /**
     * 模拟网络延迟,使操作更接近真实后端
     */
    private delay(ms: number = 300): Promise<void> {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}

// ---------- 创建服务实例 ----------
const userService = new UserService();

// ---------- DOM 元素获取(原有)----------
const userListElement = document.getElementById(‘user-list‘) as HTMLUListElement;
const refreshBtn = document.getElementById(‘refresh-btn‘) as HTMLButtonElement;
const addUserForm = document.getElementById(‘add-user-form‘) as HTMLFormElement;
const nameInput = document.getElementById(‘name‘) as HTMLInputElement;
const emailInput = document.getElementById(‘email‘) as HTMLInputElement;

// ---------- 编辑模态框相关元素 ----------
const editModal = document.getElementById(‘edit-modal‘) as HTMLDivElement;
const editForm = document.getElementById(‘edit-user-form‘) as HTMLFormElement;
const editIdInput = document.getElementById(‘edit-id‘) as HTMLInputElement;
const editNameInput = document.getElementById(‘edit-name‘) as HTMLInputElement;
const editEmailInput = document.getElementById(‘edit-email‘) as HTMLInputElement;
const closeModalBtn = document.getElementById(‘close-modal‘) as HTMLButtonElement;

// ---------- 辅助函数:显示加载/错误状态 ----------
function setLoading(loading: boolean): void {
    if (loading) {
        userListElement.innerHTML = ‘<li class="loading">加载中...</li>‘;
    }
}

function showError(message: string): void {
    userListElement.innerHTML = `<li class="error">错误:${message}</li>`;
}

// ---------- 渲染用户列表(为每个用户添加编辑和删除按钮)----------
function renderUserList(users: User[]): void {
    if (!users.length) {
        userListElement.innerHTML = ‘<li>暂无用户</li>‘;
        return;
    }

    userListElement.innerHTML = ‘‘; // 清空容器

    users.forEach(user => {
        // 创建列表项容器
        const li = document.createElement(‘li‘);
        li.className = ‘user-item‘;

        // 用户信息区域
        const infoSpan = document.createElement(‘span‘);
        infoSpan.className = ‘user-info‘;
        infoSpan.textContent = `${user.name} (${user.email})`;

        // 按钮区域
        const actionsDiv = document.createElement(‘div‘);
        actionsDiv.className = ‘user-actions‘;

        // 编辑按钮
        const editBtn = document.createElement(‘button‘);
        editBtn.textContent = ‘编辑‘;
        editBtn.addEventListener(‘click‘, () => openEditModal(user)); // 绑定编辑事件

        // 删除按钮
        const deleteBtn = document.createElement(‘button‘);
        deleteBtn.textContent = ‘删除‘;
        deleteBtn.addEventListener(‘click‘, async () => {
            if (confirm(`确定删除用户 ${user.name} 吗?`)) {
                try {
                    await userService.deleteUser(user.id);
                    await loadUsers(); // 删除后重新加载列表
                } catch (error) {
                    alert(error instanceof Error ? error.message : ‘删除失败‘);
                    console.error(error);
                }
            }
        });

        actionsDiv.appendChild(editBtn);
        actionsDiv.appendChild(deleteBtn);

        li.appendChild(infoSpan);
        li.appendChild(actionsDiv);
        userListElement.appendChild(li);
    });
}

// ---------- 核心:加载用户并渲染(从 localStorage)----------
async function loadUsers(): Promise<void> {
    setLoading(true);
    try {
        const users = await userService.getUsers();
        renderUserList(users);
    } catch (error) {
        showError(error instanceof Error ? error.message : ‘未知错误‘);
        console.error(error);
    } finally {
        // 加载状态已由 renderUserList 覆盖
    }
}

// ---------- 添加新用户(使用 service 操作 localStorage)----------
async function handleAddUser(event: Event): Promise<void> {
    event.preventDefault();

    const name = nameInput.value.trim();
    const email = emailInput.value.trim();
    if (!name || !email) {
        alert(‘请填写姓名和邮箱‘);
        return;
    }

    // 禁用提交按钮,防止重复提交
    const submitBtn = addUserForm.querySelector(‘button[type="submit"]‘) as HTMLButtonElement;
    const originalText = submitBtn.textContent;
    submitBtn.disabled = true;
    submitBtn.textContent = ‘提交中...‘;

    try {
        await userService.addUser({ name, email });
        // 清空表单
        nameInput.value = ‘‘;
        emailInput.value = ‘‘;
        // 重新加载列表
        await loadUsers();
    } catch (error) {
        alert(error instanceof Error ? error.message : ‘添加失败‘);
        console.error(error);
    } finally {
        submitBtn.disabled = false;
        submitBtn.textContent = originalText || ‘提交‘;
    }
}

// ---------- 编辑用户相关 ----------
// 打开模态框,填充当前用户数据
function openEditModal(user: User): void {
    editIdInput.value = String(user.id);
    editNameInput.value = user.name;
    editEmailInput.value = user.email;
    editModal.style.display = ‘flex‘; // 显示模态框
}

// 关闭模态框
function closeModal(): void {
    editModal.style.display = ‘none‘;
    editForm.reset(); // 可选:重置表单
}

// 处理编辑表单提交
async function handleEditUser(event: Event): Promise<void> {
    event.preventDefault();

    const id = parseInt(editIdInput.value, 10);
    const name = editNameInput.value.trim();
    const email = editEmailInput.value.trim();

    if (!name || !email) {
        alert(‘姓名和邮箱不能为空‘);
        return;
    }

    // 禁用保存按钮(可选)
    const submitBtn = editForm.querySelector(‘button[type="submit"]‘) as HTMLButtonElement;
    const originalText = submitBtn.textContent;
    submitBtn.disabled = true;
    submitBtn.textContent = ‘保存中...‘;

    try {
        await userService.updateUser(id, { name, email });
        closeModal();
        await loadUsers(); // 刷新列表
    } catch (error) {
        alert(error instanceof Error ? error.message : ‘更新失败‘);
        console.error(error);
    } finally {
        submitBtn.disabled = false;
        submitBtn.textContent = originalText;
    }
}

// ---------- 绑定事件 ----------
refreshBtn.addEventListener(‘click‘, loadUsers);
addUserForm.addEventListener(‘submit‘, handleAddUser);
editForm.addEventListener(‘submit‘, handleEditUser);
closeModalBtn.addEventListener(‘click‘, closeModal);
// 点击模态框背景也可关闭(可选)
editModal.addEventListener(‘click‘, (e) => {
    if (e.target === editModal) closeModal();
});

// ---------- 初始化:先初始化数据,再加载用户列表 ----------
async function initApp(): Promise<void> {
    try {
        await userService.init(); // 确保 localStorage 中有数据
        await loadUsers();
    } catch (error) {
        console.error(‘初始化失败:‘, error);
        showError(‘初始化失败,请刷新重试‘);
    }
}

initApp();

/**
 * 扩展说明:
 *
 * 1. 本地持久化 (localStorage):
 *    - 所有用户数据存储在 localStorage 中,页面刷新后数据不丢失。
 *    - UserService 封装了所有 CRUD 操作,便于测试和维护。
 *
 * 2. 删除用户:
 *    - 每个用户旁添加了“删除”按钮,点击后确认删除,调用 userService.deleteUser。
 *    - 删除成功后重新加载列表。
 *
 * 3. 编辑用户:
 *    - 每个用户旁添加了“编辑”按钮,点击后弹出模态框。
 *    - 模态框中显示当前用户信息,提交后调用 userService.updateUser 更新数据。
 *    - 更新成功后关闭模态框并刷新列表。
 *
 * 4. 模拟异步延迟:
 *    - UserService 中的每个方法都添加了 delay(),模拟网络请求的延迟,让操作更真实。
 *
 * 5. 切换到真实后端:
 *    - 只需修改 UserService 中的方法,将 localStorage 操作替换为 fetch/axios 调用即可。
 *    - 例如:getUsers() 改为 fetch(‘/api/users‘),addUser 改为 POST /api/users 等。
 *    - 可以保留 init 方法用于获取初始数据,但不再需要 localStorage。
 *
 * 6. 引入状态管理(如 Pinia):
 *    当应用规模变大,可将用户列表和加载状态抽离到全局 store。
 *    - 创建 store(例如 useUserStore),包含 users, loading, error 等状态。
 *    - 在组件中调用 store 的 actions(如 fetchUsers, addUser, deleteUser, updateUser)。
 *    - 在 Vue 中使用 Pinia 管理响应式状态,简化组件间数据传递。
 *
 * 7. 使用真实后端(Node.js + Express + TypeScript):
 *    可另外搭建一个简单的 REST API,提供 /users 端点支持 GET, POST, PUT, DELETE。
 *    前端只需修改 UserService 中的请求地址和 HTTP 方法。
 */

3. 关键改动说明

功能 实现方式 注释要点
本地持久化 使用 localStorage 存储 users 数组,所有增删改查均读写该存储。 UserService 类封装操作,init() 负责首次数据填充(从 jsonplaceholder 获取初始数据)。
删除用户 为每个用户生成删除按钮,点击后调用 userService.deleteUser(id) 并刷新列表。 使用 confirm 二次确认,删除后重新调用 loadUsers() 更新视图。
编辑用户 添加编辑按钮,点击弹出模态框;模态框中提交时调用 userService.updateUser 模态框显示/隐藏通过 CSS 控制;编辑前将当前用户数据填入表单。
模拟异步 所有 UserService 方法内部调用 delay(),模拟网络请求延迟。 让代码更接近真实后端交互,方便理解异步流程。
扩展性 如需切换到真实后端,只需修改 UserService 中的方法实现(例如用 fetch 替换 localStorage)。 注释中给出了切换思路,保持前端 UI 层代码不变。
状态管理 当前未引入,但注释中说明了何时需要使用 Pinia 等工具。 当应用复杂、多个组件共享数据时,可将数据层抽离到 store。

4. 运行方式

  1. 确保已安装 Node.js,并在项目目录执行 npm install(如果之前已创建 Vite 项目)。
  2. 将上述 index.htmlsrc/main.ts 替换原有文件。
  3. 运行 npm run dev,打开浏览器即可测试。
  4. 所有数据将保存在浏览器的 localStorage 中,刷新页面不会丢失。可通过浏览器开发者工具查看/清除存储的数据。

5. 总结

通过本次扩展,我们实现了一个功能相对完整的前端用户管理原型:

  • 使用 TypeScript 定义清晰的数据模型,保证了类型安全。
  • 将数据操作封装为独立的服务层(UserService),使业务逻辑与UI分离,极大提升了代码的可维护性和可测试性。未来若要接入真实后端,只需改动此服务层。
  • 在用户列表项中动态创建并绑定了编辑和删除按钮,展示了如何操作动态生成的DOM元素。
  • 利用模态框(Modal)实现了友好的用户编辑交互。
  • 核心在于使用 localStorage 实现了前端数据的持久化,完整模拟了后端的 CRUD 操作流程。这非常适合用于原型开发、演示或离线应用场景。
  • 对所有异步操作进行了统一的错误处理和加载状态管理,提升了用户体验。

这个项目麻雀虽小,五脏俱全,涵盖了现代前端开发中的许多核心概念。希望这个实践能帮助你更好地理解如何组织TypeScript代码、进行状态管理和规划应用架构。当你需要构建更复杂的应用时,可以考虑引入如 Vue.js或 React 等前端框架,并配合专业的状态管理库。

如果你在实践过程中有更多想法或问题,欢迎到云栈社区与其他开发者一起交流探讨。




上一篇:从EventLoop报错到架构治理:Hermes四重事件循环设计详解
下一篇:TypeScript表单交互实战:从数据获取到用户添加的完整实现
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-18 23:57 , Processed in 0.607080 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表