“艾在救援” 登录、查药功能实现(Ajax,JSON,JavaScript)
一、背景与问题
在开发“艾在救援”系统时,我们面临两个核心功能需求:用户登录认证和药品信息查询。这两个功能需要在保持页面交互性的同时,避免传统页面刷新带来的体验问题。
传统方案的痛点在于:
- 用户登录需要完整页面跳转,影响操作连贯性
- 药品查询需要重新加载整个页面,导致数据丢失
- 多次请求需要手动处理状态保持
- 无法实现实时数据更新
为了解决这些问题,我们选择使用Ajax技术实现异步通信,结合JSON数据格式进行数据交换。这种方案能够保持页面状态,实现局部更新,同时降低服务器负载。
二、基本原理
1. Ajax工作原理
Ajax(Asynchronous JavaScript and XML)通过XMLHttpRequest对象实现异步通信,其核心流程如下:
- 创建XMLHttpRequest对象
- 配置请求参数(URL、方法、头信息)
- 发起异步请求
- 监听状态变化事件
- 处理服务器响应数据
2. JSON数据格式
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,其特点包括:
- 与JavaScript对象直接映射
- 支持嵌套结构
- 无类型声明
- 易于阅读和编写
三、环境准备
1. 前端开发环境
项目结构:
/艾在救援 │ ├── index.html # 主页面 ├── login.html # 登录页面 ├── drug.html # 药品查询页面 ├── js/ # JavaScript代码 │ ├── login.js # 登录功能 │ ├── drug.js # 药品查询功能 │ └── utils.js # 工具函数 └── css/ # 样式文件
2. 后端模拟(Node.js示例)
// server.js
const express = require('express');
const app = express();
const port = 3000;
app.use(express.json());
// 模拟用户数据
const users = [
{ id: 1, name: '张三', password: '123456' },
{ id: 2, name: '李四', password: '654321' }
];
// 登录接口
app.post('/api/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.name === username && u.password === password);
if (user) {
res.json({
code: 200,
message: '登录成功',
data: user
});
} else {
res.json({
code: 401,
message: '用户名或密码错误'
});
}
});
// 药品查询接口
app.get('/api/drugs', (req, res) => {
const drugs = [
{ id: 1, name: '头孢丙烯', price: 25.5, stock: 100 },
{ id: 2, name: '阿奇霉素', price: 32.0, stock: 85 },
{ id: 3, name: '布洛芬', price: 15.0, stock: 200 }
];
res.json({
code: 200,
message: '查询成功',
data: drugs
});
});
app.listen(port, () => {
console.log(`服务器运行在 http://localhost:${port}`);
});四、核心实现
1. 登录功能实现(login.js)
// login.js
async function login(username, password) {
const url = 'http://localhost:3000/api/login';
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (data.code === 200) {
// 保存登录状态
localStorage.setItem('user', JSON.stringify(data.data));
return true;
} else {
alert(data.message);
return false;
}
} catch (error) {
console.error('登录失败:', error);
alert('网络错误,请重试');
return false;
}
}关键代码解释:
- 使用async/await处理异步请求
- 设置正确的Content-Type头
- 处理JSON响应数据
- 使用localStorage保存用户状态
- 错误处理机制
2. 药品查询功能(drug.js)
// drug.js
async function getDrugs() {
const url = 'http://localhost:3000/api/drugs';
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
const data = await response.json();
if (data.code === 200) {
return data.data;
} else {
alert(data.message);
return null;
}
} catch (error) {
console.error('查询失败:', error);
alert('网络错误,请重试');
return null;
}
}关键代码解释:
- GET请求的参数设置
- 处理服务器响应
- 错误处理机制
- 返回结构化数据
3. 数据展示(index.html)
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<title>艾在救援</title>
<script src="js/utils.js"></script>
</head>
<body>
<div id="app">
<h1>欢迎使用艾在救援</h1>
<div id="user-info"></div>
<button onclick="loadDrugs()">查看药品</button>
<div id="drug-list"></div>
</div>
<script>
// 检查登录状态
window.onload = function() {
const user = localStorage.getItem('user');
if (user) {
showUserInfo(JSON.parse(user));
}
};
function showUserInfo(user) {
const infoDiv = document.getElementById('user-info');
infoDiv.innerHTML = `
<p>欢迎,${user.name}</p>
<p>登录时间:${new Date().toLocaleString()}</p>
`;
}
async function loadDrugs() {
const drugs = await getDrugs();
if (drugs) {
displayDrugs(drugs);
}
}
function displayDrugs(drugs) {
const listDiv = document.getElementById('drug-list');
listDiv.innerHTML = `
<h2>药品库存</h2>
<ul>
${drugs.map(drug => `
<li>${drug.name} - ¥${drug.price.toFixed(2)} (库存: ${drug.stock})</li>
`).join('')}
</ul>
`;
}
</script>
</body>
</html>关键代码解释:
- 页面加载时检查登录状态
- 动态更新用户信息
- 药品数据展示的动态生成
- 调用getDrugs()函数获取数据
五、完整案例
1. 项目结构说明
/艾在救援
│
├── index.html # 主页面
├── login.html # 登录页面
├── drug.html # 药品查询页面
├── js/ # JavaScript代码
│ ├── login.js # 登录功能
│ ├── drug.js # 药品查询功能
│ └── utils.js # 工具函数
└── css/ # 样式文件2. 登录页面(login.html)
<!-- login.html -->
<!DOCTYPE html>
<html>
<head>
<title>登录</title>
<script src="js/login.js"></script>
</head>
<body>
<div id="login-form">
<h2>用户登录</h2>
<input type="text" id="username" placeholder="用户名" required>
<input type="password" id="password" placeholder="密码" required>
<button onclick="handleLogin()">登录</button>
</div>
<script>
async function handleLogin() {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
if (await login(username, password)) {
window.location.href = 'index.html';
}
}
</script>
</body>
</html>3. 工具函数(utils.js)
// utils.js
function formatPrice(price) {
return parseFloat(price).toFixed(2);
}
function formatDate(date) {
return date.toLocaleString();
}六、源码解析
1. 登录功能源码分析
async function login(username, password) {
const url = 'http://localhost:3000/api/login';
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (data.code === 200) {
// 保存登录状态
localStorage.setItem('user', JSON.stringify(data.data));
return true;
} else {
alert(data.message);
return false;
}
} catch (error) {
console.error('登录失败:', error);
alert('网络错误,请重试');
return false;
}
}关键点分析:
- 使用fetch API发起HTTP请求
- 设置Content-Type头确保数据格式正确
- 处理服务器返回的JSON数据
- 使用localStorage持久化保存用户信息
- 异常处理机制确保程序健壮性
2. 药品查询源码分析
async function getDrugs() {
const url = 'http://localhost:3000/api/drugs';
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
const data = await response.json();
if (data.code === 200) {
return data.data;
} else {
alert(data.message);
return null;
}
} catch (error) {
console.error('查询失败:', error);
alert('网络错误,请重试');
return null;
}
}关键点分析:
- GET请求的参数设置
- 处理服务器响应数据
- 错误处理机制
- 返回结构化数据供前端展示
七、进阶使用
1. 增强登录功能
// login.js
async function login(username, password) {
const url = 'http://localhost:3000/api/login';
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (data.code === 200) {
// 保存登录状态
localStorage.setItem('user', JSON.stringify(data.data));
// 记录登录时间
data.data.loginTime = new Date().toISOString();
return true;
} else {
alert(data.message);
return false;
}
} catch (error) {
console.error('登录失败:', error);
alert('网络错误,请重试');
return false;
}
}2. 增强药品查询功能
async function getDrugs() {
const url = 'http://localhost:3000/api/drugs';
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
const data = await response.json();
if (data.code === 200) {
// 添加格式化后的价格
data.data = data.data.map(drug => ({
...drug,
formattedPrice: formatPrice(drug.price)
}));
return data.data;
} else {
alert(data.message);
return null;
}
} catch (error) {
console.error('查询失败:', error);
alert('网络错误,请重试');
return null;
}
}八、性能与工程实践
1. 性能优化策略
缓存机制:对频繁访问的数据进行缓存
function getDrugs() { const cached = localStorage.getItem('drugCache'); if (cached) { return JSON.parse(cached); } return fetchDrugs().then(data => { localStorage.setItem('drugCache', JSON.stringify(data)); return data; }); }请求合并:合并多次请求减少网络开销
let pendingRequest = null; function getDrugs() { if (pendingRequest) { return pendingRequest; } pendingRequest = new Promise((resolve, reject) => { fetchDrugs().then(resolve).catch(reject).finally(() => { pendingRequest = null; }); }); return pendingRequest; }- 压缩数据:使用Gzip压缩响应数据
2. 安全风险分析
CSRF攻击:需要添加CSRF token验证
// 在登录请求中添加token const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify({ username, password, _token: csrfToken }) });JSON注入:需要对返回数据进行验证
function validateData(data) { if (typeof data !== 'object' || data === null) { throw new Error('Invalid data format'); } }
九、常见问题与踩坑
1. 跨域问题(CORS)
错误示例:
fetch('http://localhost:3000/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});错误原因: 浏览器出于安全考虑会阻止跨域请求
解决办法:
在服务器端添加CORS头
res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Headers', 'Content-Type');使用代理服务器
// 本地开发服务器 const proxy = require('http-proxy-middleware'); module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true, pathRewrite: { '^/api': '' } } } } };
2. 数据格式错误
错误示例:
const data = await response.json();
console.log(data);错误原因: 服务器返回的数据格式不正确
解决办法:
添加类型检查
function isJsonString(str) { try { JSON.parse(str); return true; } catch (e) { return false; } }添加错误处理
try { const data = await response.json(); if (!isJsonString(data)) { throw new Error('Invalid JSON response'); } } catch (error) { console.error('数据解析失败:', error); alert('数据解析失败,请重试'); }
十、最佳实践
1. 推荐的实现方案
- 使用fetch API:现代浏览器支持良好,易于使用
- 添加错误处理:确保程序健壮性
- 使用localStorage:持久化保存用户状态
- 格式化数据:提高可读性
- 添加防重放机制:防止重复请求
2. 推荐的代码规范
- 使用ES6的async/await替代Promise链
- 添加详细的错误日志
- 使用TypeScript进行类型校验
- 添加单元测试
- 使用代码格式化工具(如Prettier)
十一、总结
本文深入探讨了"艾在救援"系统登录和药品查询功能的实现方案。通过使用Ajax技术和JSON数据格式,我们实现了异步通信,保持了页面状态,提升了用户体验。
在实现过程中,我们深入分析了Ajax的工作原理,探讨了JSON的数据结构,提供了完整的代码示例,并分析了性能优化和安全风险。同时,我们讨论了常见错误和解决办法,强调了代码规范的重要性。
在实际开发中,这种方案适用于需要保持页面状态、需要频繁更新数据、需要实时交互的场景。但需要注意避免过度使用,防止造成服务器负载过高。同时,需要特别注意安全风险,采取必要的防护措施。
最终,我们推荐使用这种方案来实现需要异步交互的功能,同时遵循最佳实践,确保代码的可维护性和可扩展性。