06 前台与后台交互——2代码实现

一、 前言

Express 是 Node.js 的一个流行框架,用于构建 Web 应用程序和 API。它提供了许多功能强大的工具和中间件,使得开发者能够轻松地创建高性能的服务器端应用。然而,实现一个完整的 Web 应用一个重要的方面是前台(客户端)与后台(服务器端)之间的交互。在这篇文章中,我们将探讨如何在 Express 框架下实现有效的前后端交互。

二、简介

实现前台与后台的交互通常要处理以后台路由、前台交互等内容:

1. 后台路由

在 Express 中,后台路由是定义了端点(endpoint)和处理函数的部分。这些路由负责处理来自客户端的请求,并返回相应的数据或执行相应的操作。下面是一个简单的后台路由示例:

const express = require('express');
const app = express();

// 定义一个 GET 请求的路由
app.get('/api/data', (req, res) => {
    // 处理 GET 请求
    const data = { message: '这是来自服务器的数据' };
    res.json(data);
});

// 启动服务器
app.listen(3000, () => {
    console.log('服务器已启动,端口号: 3000');
});

在上面的例子中,我们定义了一个 GET 请求的路由 /api/data,当客户端发送 GET 请求到这个路由时,服务器将返回一个 JSON 格式的数据。

2. 前台交互

要在前台与后台进行交互,通常使用 JavaScript 来发送请求并处理响应。在浏览器端,可以使用 Fetch API 或 XMLHttpRequest 对象来实现。以下是一个使用 Fetch API 的简单示例:

// 发送 GET 请求到服务器
fetch('/api/data')
    .then(response => {
        if (!response.ok) {
            throw new Error('网络请求失败');
        }
        return response.json();
    })
    .then(data => {
        // 处理从服务器返回的数据
        console.log(data);
    })
    .catch(error => {
        console.error('发生错误:', error);
    });

在这个示例中,我们向 /api/data 路由发送了一个 GET 请求,并在收到响应后处理返回的数据。

三、真实案例

我们以视频网站的【分类编辑】的页面来介来看看在项目中如何实现前台与后台的异步交互;这里涉及到的重点内容有:

  • 前台如何通过后台读、写数据库中的数据
  • 前台页面中的控件事件挂接

3.1前台通过后台获取数据库中的数据

在实现前台通过后台获取数据库中的数据时,首先需要后台提供相应的数据获取方法供前台调用。在较为优良的实践中,常见的做法是将数据表进行封装,形成模型。这种模型封装可以提供一种结构化的方式来管理和操作数据库中的数据,使得后台的数据访问更加可维护和可扩展。我们以Mysql数据库为例介绍后台的方法,其中数据库链接部分请参阅前面文章,这里只在末尾给出代码以方便调试;

  • 数据表创建代码如下
CREATE TABLE `video_site`.`video_categories2` (
  `ID` INT NOT NULL AUTO_INCREMENT,
  `CategoriesName` VARCHAR(255) NOT NULL,
  PRIMARY KEY (`ID`, `CategoriesName`),
  UNIQUE INDEX `ID_UNIQUE` (`ID` ASC));
3.1.1 在NodeJs中提供数据表访问代码

创建Model\videoCategoryModel.js做为视频分类的模型封装,提供对数据表的增、删、改、查功能,其代码如下:

const db = require('./dbUtils');  
  
class VideoCategory {  
  constructor(data) {  
    this.id = data.ID;  
    this.categoryName = data.CategoriesName;  
  }  
  
  // 根据分类ID查询分类  
  static async findById(id) {  
    try {  
      const results = await db.query.withoutConnection('SELECT * FROM video_categories WHERE ID = ?', [id]);  
      if (results.length > 0) {  
        const categoryData = results[0];  
        return new VideoCategory({  
          id: categoryData.ID,  
          categoryName: categoryData.CategoriesName  
        });  
      }  
    } catch (err) {  
      console.error('Error fetching video category by ID:', err);  
      throw err;  
    }  
  }  
  
  // 静态方法,用于查询所有分类  
  static async findAll() {  
    try {  
      const results = await db.query.withoutConnection('SELECT * FROM video_categories');  
      return results.map(categoryData => new VideoCategory(categoryData));  
    } catch (err) {  
      console.error('Error fetching all video categories:', err);  
      throw err;  
    }  
  }  
  
  // 实例方法,用于保存分类到数据库  
  async save() {  
    try {  
      const results = await db.query.withoutConnection(  
        'INSERT INTO video_categories (CategoriesName) VALUES (?)',  
        [this.categoryName]  
      );  
      this.id = results.insertId;  
    } catch (err) {  
      console.error('Error saving video category:', err);  
      throw err;  
    }  
  }  
}  
  
module.exports = VideoCategory;
3.1.2 提供路由

增加routes\categories.js文件作为视频分类的路由文件,提供数据表内容访问API接口

const express = require('express');
const router = express.Router();
const CategoryModel = require('../model/CategoryModel');

// 渲染分类维护页面,当用户切换到视频分类页面时显示分类编辑页面
router.get('/admin/categories', async (req, res) => {
    try {
        const categories = await CategoryModel.getAllCategories();
        res.render('categories', { categories });
    } catch (error) {
        console.error('Error rendering categories page:', error);
        res.status(500).send('Internal Server Error');
    }
});

// API调用获取所有分类
router.get('/admin/categories/api/Categories', async (req, res) => {
    try {
        const categories = await CategoryModel.getAllCategories();
        res.json(categories);
    } catch (error) {
        console.error('Error fetching categories:', error);
        res.status(500).json({ error: 'Internal Server Error' });
    }
});

// API调用添加分类
router.post('/admin/categories/api/Categories', async (req, res) => {
    try {
        const { CategoriesName } = req.body;
        await CategoryModel.addCategory(CategoriesName);
        res.status(201).json({ message: 'Category added successfully' });
    } catch (error) {
        console.error('Error adding category:', error);
        res.status(500).json({ error: 'Internal Server Error' });
    }
});

// API调用更新分类
router.put('/admin/categories/api/Categories/:id', async (req, res) => {
    try {
        const { id } = req.params;
        const { CategoriesName } = req.body;
        await CategoryModel.updateCategory(id, CategoriesName);
        res.json({ message: 'Category updated successfully' });
    } catch (error) {
        console.error('Error updating category:', error);
        res.status(500).json({ error: 'Internal Server Error' });
    }
});

// API调用删除分类
router.delete('/admin/categories/api/Categories/:id', async (req, res) => {
    try {
        const { id } = req.params;
        await CategoryModel.deleteCategory(id);
        res.json({ message: 'Category deleted successfully' });
    } catch (error) {
        console.error('Error deleting category:', error);
        res.status(500).json({ error: 'Internal Server Error' });
    }
});

module.exports = router;

3.1.3 将路由添加到由表中

App.js文件中添加以下代码

//……其它代码
const categoriesRoutes = require('./routes/categories');
//……其它代码
app.use('/', categoriesRoutes); // 挂载分类维护页面的路由
//……其它代码

3.2前台通过后台获取数据库中的数据

3.2.1 前台页面事件说明

在页面中提供一个文本框用于增加分类时输入分类名称,一个【增加】按钮用于增加分类,以及一个表格用于显示分类数据;数据修改及删除通过表格完成;表格显示如下

ID分类名称操作
1初中删除
2高中删除

增加views\categories.ejs页面,并添加相应的事件,下面就所需要事件展开说明
1 document.addEventListener 事件说明请参阅附2
2 "DOMContentLoaded" 页面加载事件,内部主要实现:

  • 挂接submit事件,用于添加新分类;
  • 定义window.deleteCategory删除分类方法,用于挂接在表格中的删除按钮单击事件;
  • 定义fetchCategories()用于加载所有分类;
    以上三个方法放到【"DOMContentLoaded"】是为保证事件挂接成功,避免某些情况下因页面未完全加载完毕所挂接的控件未定义从而造成的挂接失败;

3 function createTableRow(category) 用于创建表格的方法;
4 async function validateAndUpdateCategory(element, id) 用于在表格中修改分类名称响应函数;
在创建表格时 <td contenteditable="true" oninput="validateAndUpdateCategory(this, ${category.ID})">${category.CategoriesName}</td>时,指定了单元修输入后的响应函数;
5 async function updateCategory(id, newName) 用于修改分类名称后调用相应的后台API

3.2.1 前台页面代码细节说明
  • categoryForm.addEventListener('submit'页面提交事件中event.preventDefault()用于阻止页面的默认提交事件,这样我们才能机会对用户输入内容进行验证,并根据验证结果决定处理动作;
  • <td>单元格默认是不能编辑的,通过设置contenteditable="true"至使单元可以编辑。
  • 在增加分类后,这里直接重新加载了页面,若不想重新加载整个页面,可参见单元格编辑响应事件修改此处代码;

3.3 views\categories.ejs 代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>课程分类管理</title>
<style>
  table {
    border-collapse: collapse;
    width: 50%;
    margin: 20px auto;
  }
  th, td {
    border: 1px solid #ddd;
    padding: 8px;
    text-align: left;
    cursor: pointer; /* Add cursor pointer for better UX */
  }
  th {
    background-color: #f2f2f2;
  }
  form {
    margin: 20px auto;
    text-align: center;
  }
  .tooltip {
    position: relative;
    display: inline-block;
    cursor: pointer;
  }
  .tooltip .tooltiptext {
    visibility: hidden;
    width: 300px;
    background-color: black;
    color: #fff;
    text-align: center;
    border-radius: 6px;
    padding: 5px 0;
    position: absolute;
    z-index: 1;
    bottom: 125%;
    left: 50%;
    margin-left: -60px;
    opacity: 0;
    transition: opacity 0.3s;
  }
  .tooltip:hover .tooltiptext {
    visibility: visible;
    opacity: 1;
  }
</style>
</head>
<body>
  <form id="categoryForm">
    <label for="categoryName">分类名称:</label>
    <input type="text" id="categoryName" name="categoryName">
    <button type="submit">添加分类</button>
  </form>
  <table id="categoryTable">
    <thead>
      <tr>
        <th width="50px">ID</th>
        <th>分类名称<span class="tooltip"><sup>?</sup><span class="tooltiptext">分类名称不能包含以下字符:\ / : * ? " < > |,长度不能超过255个字符</span></span></th>
        <th>操作</th>
      </tr>
    </thead>
    <tbody>
    </tbody>
  </table>
<script>
document.addEventListener("DOMContentLoaded", async function() {
    const categoryForm = document.getElementById("categoryForm");
    
    // Event listener for category form submission
    categoryForm.addEventListener('submit', async function(event) {
        event.preventDefault();
        const categoryNameInput = document.getElementById('categoryName');
        const categoryName = categoryNameInput.value.trim();
        // Validate category name
        if (!validateCategoryName(categoryName)) {
            alert('分类名称中不能包含以下字符:\\ / : * ? " < > |,长度不能超过255个字符');
            return;
        }
        try {
            const response = await fetch('/admin/categories/api/categories', {
                method: 'POST',
                headers: {
                'Content-Type': 'application/json'
                },
                body: JSON.stringify({ CategoriesName: categoryName })
            });
            if (response.ok) {
                fetchCategories();
                categoryNameInput.value = '';
            } else {
                console.error('Error adding category:', response.statusText);
            }
        } catch (error) {
            console.error('Error adding category:', error);
        }
    });

    // 删除分类
    window.deleteCategory = async function(id) {
        try {
            const response = await fetch(`/admin/categories/api/categories/${id}`, {
              method: 'DELETE'
        });
        if (response.ok) {
            fetchCategories();
        } else {
            console.error('Error deleting category:', response.statusText);
        }
        } catch (error) {
            console.error('Error deleting category:', error);
        }
    };

    // 在页面中创建分类表格
    async function fetchCategories() {
        try {
            const response = await fetch('/admin/categories/api/categories');
            const categories = await response.json();
            const categoryTableBody = document.querySelector('#categoryTable tbody');
            categoryTableBody.innerHTML = '';
            categories.forEach(category => {
                categoryTableBody.appendChild(createTableRow(category));
            });
        } catch (error) {
            console.error('Error fetching categories:', error);
        }
    }
    // 在页面加载完毕后加载分类名称
    fetchCategories();
});

// 分类名称校验函数
function validateCategoryName(name) {
    const regex = /^[^\\/:\*\?"<>\|]{1,255}$/; // Windows filename constraints
    return regex.test(name);
}

// 创建分类维护表格
function createTableRow(category) {
    const row = document.createElement('tr');
    row.innerHTML = `
        <td>${category.ID}</td>
        <td contenteditable="true" oninput="validateAndUpdateCategory(this, ${category.ID})">${category.CategoriesName}</td>
        <td>
            <button onclick="deleteCategory(${category.ID})">删除</button>
        </td>`;
    return row;
}

// 校验输入的分类名称,校验成功后调用后updateCategory函数修改分类名称
async function validateAndUpdateCategory(element, id) {
    const newName = element.innerText.trim();
    if (!validateCategoryName(newName)) {
        // Show tooltip or other validation indication
        alert('分类名称中不能包含以下字符:\\ / : * ? " < > |,长度不能超过255个字符');
        // Restore the original value
        element.textContent = element.dataset.originalValue || '';
    } else {
        // Update the original value
        element.dataset.originalValue = newName;
        // Update the category
        await updateCategory(id, newName);
    }
}

// 修改分类名称
async function updateCategory(id, newName) {
    try {
        const response = await fetch(`/admin/categories/api/updateCategories/${id}`, {
            method: 'PUT',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ CategoriesName: newName })
        });
        if (!response.ok) {
            console.error('Error updating category:', response.statusText);
        }
    } catch (error) {
        console.error('Error updating category:', error);
    }
}
</script>
    
</body>
</html>

4. 结论

通过 Express 框架,我们可以轻松地实现前后端之间的数据交互。在后台,通过定义路由来处理来自客户端的请求;在前台,通过 JavaScript 来发送请求并处理响应。这种前后端交互的方式使得我们能够构建功能丰富、动态的 Web 应用程序。

1 数据库链接代码

文件名Model\dbUtils.js,代码如下

const mysql = require('mysql');

// 创建数据库连接池
const pool = mysql.createPool({
  connectionLimit: 10,
  host: 'localhost',
  user: '数据库用户名',
  password: '数据库密码',
  database: 'video_site',   //使用的数据库名
  debug: false // 开启调试模式,会输出详细的 SQL 执行日志
});

// 获取数据库连接的方法
const getDBConnection = () => {
  return new Promise((resolve, reject) => {
    pool.getConnection((error, connection) => {
      if (error) {
        reject(error);
      } else {
        resolve(connection);
      }
    });
  });
};

// 执行查询的方法(重载1:带 connection 参数)
const query = {
  // 当调用者已经拥有连接时使用的查询方法
  withConnection: (connection, sql, params) => {
    return new Promise((resolve, reject) => {
      connection.query(sql, params, (error, results, fields) => {
        if (error) {
          reject(error);
        } else {
          resolve(results);
        }
      });
    });
  },

  // 当调用者没有连接时使用的查询方法,这个方法会负责获取和释放连接
  withoutConnection: async (sql, params) => {
    let connection;
    try {
      connection = await getDBConnection();
      const results = await query.withConnection(connection, sql, params);
      return results;
    } catch (error) {
      throw error;
    } finally {
      if (connection) {
        connection.release();
      }
    }
  }
};

module.exports = {
  getDBConnection,
  query
};

2 document.addEventListener说明

document.addEventListener 是 JavaScript 中用于添加事件监听器的方法之一。它允许开发者在特定的文档对象上监听各种类型的事件,比如鼠标点击、键盘按下、页面加载完成等,以便在事件发生时执行相应的操作。

使用方法

document.addEventListener(event, function, useCapture);
  • event: 要监听的事件类型,比如 “click”、“keydown”、“load” 等。
  • function: 事件发生时要执行的函数,也称为事件处理程序。
  • useCapture(可选): 一个布尔值,表示事件是在捕获阶段(true)还是冒泡阶段(false)触发事件处理程序。默认为 false

示例

document.addEventListener('click', function(event) {
    console.log('点击了文档');
});

document.addEventListener('keydown', function(event) {
    console.log('按下了键盘键:', event.key);
});

在这个示例中,我们分别监听了文档的点击事件和键盘按键事件。当用户点击文档时,会在控制台输出 “点击了文档”;当用户按下键盘时,会在控制台输出相应的按键值。

优势

  • 多事件处理: 可以同时监听多个不同类型的事件,而不需要像传统的 onclickonkeydown 一样,将事件处理程序直接赋值给特定的属性。
  • 灵活性: 可以使用匿名函数或命名函数作为事件处理程序,使代码更加模块化和可维护。
  • 事件委托: 可以利用事件冒泡机制,在父元素上添加一个事件监听器,来代理处理子元素的事件,提高性能和代码简洁度。

总之,document.addEventListener 是 JavaScript 中一种强大的事件处理机制,它为开发者提供了一种灵活、高效的方式来处理各种类型的事件。

  • 29
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 前台后台在线咨询功能可以通过以下步骤实现: 1. 前台咨询功能:在网站或应用程序的前台页面添加一个聊天窗口,让用户可以输入问题并发送给客服人员。 2. 后台咨询功能:在后台管理界面添加一个聊天窗口,让客服人员可以实时接收用户的问题并回复。 3. 实现方式:可以使用第三方聊天工具,如Zendesk Chat、Intercom等,也可以自己开发一个聊天系统。 4. 集成方式:将第三方聊天工具的代码嵌入到网站或应用程序中,或者使用API接口进行集成。 5. 注意事项:在线咨询功能需要保证客服人员能够及时回复用户的问题,同时需要注意保护用户的隐私和安全。 ### 回答2: 前台后台在线咨询功能的实现涉及到前后端技术的结合。首先,前台需要一个用户界面,提供在线咨询的入口和交互界面。用户可以通过填写表单、发送消息等方式与前台进行沟通。这个用户界面通常是由HTML、CSS和JavaScript来实现的,可以使用前端技术框架来简化开发。 接下来,前台需要将用户的咨询请求发送到后台,以便后台处理。这可以通过AJAX技术来实现,通过异步请求将用户输入的内容发送到后台的接口。 在后台,需要一个处理咨询的接口。这个接口接收到用户的咨询请求后,可以进行一系列的处理操作,比如保存咨询记录、发送邮件通知、分配客服人员等。后台的接口可以使用服务器端的编程语言来实现,比如Java、Python、PHP等。 在处理咨询请求时,后台可能需要与数据库进行交互,比如保存咨询记录、查询历史记录等。这时可以使用数据库管理系统来管理和操作数据库。 另外,为了实现在线咨询功能,通常还需要一个客服系统。这个系统提供了后台客服人员与用户进行实时对话的界面,可以通过文字、语音、视频等方式与用户进行沟通。客服系统一般有独立的界面和功能,可以通过集成或者对接前台后台实现在线咨询的功能。 总的来说,前台后台在线咨询功能的实现需要前端技术来搭建用户界面、后台编程语言来处理咨询请求、数据库管理系统来存储和查询数据,同时还需要一个专门的客服系统来实现实时对话功能。这些要素的结合和协同操作,才能实现一个完善的在线咨询功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值