截止目前,我们已完成前端页面的核心布局,今天我们实现前后端数据打通与整体增删改查功能实现。
1.查找全部武器
再次回顾一下我们前面学习的查找sql,这里我们会使用分页查找语句,语法是
select 字段 from 表名 [where 条件] [order by 排序字段] limit 起始索引,查询记录数;
为确保大家环境都一致,这里再给一下建表语句
CREATE TABLE IF NOT EXISTS weapon_skins (
-- 数据唯一ID
id INT(20) NOT NULL AUTO_INCREMENT COMMENT '数据唯一ID',
-- 武器名称(如AK-47、M4A1)
name VARCHAR(255) NOT NULL COMMENT '武器名称(AK-47、M4A1等)',
-- 武器型号(如消音型)
baseWeapon VARCHAR(255) NOT NULL COMMENT '武器型号(消音型)',
-- 武器价格(10位长度、2位小数)
price DECIMAL(10,2) NOT NULL COMMENT '武器价格',
-- 外观状态
appearance VARCHAR(50) NOT NULL COMMENT '外观:崭新出厂、久经沙场、略有磨损',
-- 商品类别
category VARCHAR(20) NOT NULL COMMENT '类别(武器皮肤、印花)',
-- 品质等级
quality VARCHAR(20) NOT NULL COMMENT '品质(全息、闪耀、冠军等)',
-- 收藏品标识(1=是、0=否)
isCollectible TINYINT(1) NOT NULL DEFAULT 0 COMMENT '收藏品:是(1)、否(0)',
-- 商品图片URL
imgUrl VARCHAR(500) DEFAULT '' COMMENT '商品图片(存储图片URL)',
-- 在售数量
stock INT(10) NOT NULL DEFAULT 0 COMMENT '在售数量',
-- 主键约束
PRIMARY KEY (id)
) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci
COMMENT='武器皮肤信息表(匹配字段表设计)';
提前插入几条数据,便于稍后测试
-- 穿越火线武器皮肤表初始化20条测试数据(图片参考CF官方武器百科)
INSERT INTO weapon_skins (name, baseWeapon, price, appearance, category, quality, isCollectible, imgUrl, stock) VALUES
-- 山海厂牌-英雄级武器
('AK47-火麒麟 (崭新出厂)', 'AK47', 888.00, '崭新出厂', '武器皮肤', '传说', 1, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C7140.png', 15),
('RPK-盘龙 (久经沙场)', 'RPK', 688.00, '久经沙场', '武器皮肤', '史诗', 1, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C5470.png', 20),
-- 超导厂牌-英雄级武器
('M4A1-雷神 (崭新出厂)', 'M4A1', 888.00, '崭新出厂', '武器皮肤', '传说', 1, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C5469.png', 18),
('AK47-无影 (略有磨损)', 'AK47', 888.00, '略有磨损', '武器皮肤', '传说', 1, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C8019.png', 12),
('AA12-雷霆 (久经沙场)', 'AA12', 498.00, '久经沙场', '武器皮肤', '精英', 0, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C8506.png', 25),
-- 血禁厂牌-英雄级武器
('AWM-裁决 (崭新出厂)', 'AWM', 688.00, '崭新出厂', '武器皮肤', '史诗', 1, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C4635.png', 10),
('AK47-黑鲨 (略有磨损)', 'AK47', 468.00, '略有磨损', '武器皮肤', '精英', 0, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C5375.png', 30),
-- 神匠厂牌-英雄级武器
('沙鹰-天神 (崭新出厂)', '沙鹰', 488.00, '崭新出厂', '武器皮肤', '史诗', 1, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C7247.png', 22),
('M4A1-神工天巧 (久经沙场)', 'M4A1', 688.00, '久经沙场', '武器皮肤', '史诗', 1, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C9166.png', 16),
-- 花影厂牌-英雄级武器
('QBZ03-金色蔷薇 (崭新出厂)', 'QBZ03', 468.00, '崭新出厂', '武器皮肤', '精英', 0, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C7957.png', 28),
('M4A1-玫瑰精灵 (略有磨损)', 'M4A1', 408.00, '略有磨损', '武器皮肤', '精英', 0, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C6480.png', 35),
-- 戏班厂牌-特色武器
('Scar Light-马枪 (崭新出厂)', 'Scar Light', 328.00, '崭新出厂', '武器皮肤', '稀有', 0, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C9165.png', 40),
('牛牛-AK47 (久经沙场)', 'AK47', 388.00, '久经沙场', '武器皮肤', '稀有', 0, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C8132.png', 32),
-- 竞界厂牌-竞技武器
('竞技荣光-M4A1 (崭新出厂)', 'M4A1', 588.00, '崭新出厂', '武器皮肤', '史诗', 1, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C5151.png', 14),
('炽芒蝶刃 (崭新出厂)', '近战武器', 488.00, '崭新出厂', '武器皮肤', '史诗', 1, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C5777.png', 18),
-- 幻灵厂牌-隐匿武器
('M200-幻神 (略有磨损)', 'M200', 688.00, '略有磨损', '武器皮肤', '传说', 1, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C7123.png', 9),
('G36C-幻影 (久经沙场)', 'G36C', 398.00, '久经沙场', '武器皮肤', '精英', 0, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C4396.png', 26),
-- 黯流厂牌-暗黑武器
('M4A1-黑骑士 (崭新出厂)', 'M4A1', 888.00, '崭新出厂', '武器皮肤', '传说', 1, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C1504.png', 17),
('沙鹰-修罗 (略有磨损)', '沙鹰', 488.00, '略有磨损', '武器皮肤', '史诗', 1, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C1594.png', 24),
-- 印花-官方标识
('印花-CF国服LOGO (全息)', '印花', 88.00, '崭新出厂', '印花', '全息', 0, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C6704.png', 150),
('印花-英雄级武器徽章 (闪耀)', '印花', 128.00, '略有磨损', '印花', '闪耀', 0, 'https://mcdn.gtimg.com/bbcdn/cf/serial/C5912.png', 120);
1.1 修改weaponSkinModel.js
在weaponSkinModel.js中添加分页查询方法,支持筛选条件
// model/weaponSkinModel.js
const { pool } = require('../config/db');
// 👉 第一个功能:查询所有武器皮肤
async function getAllWeaponSkins() {
try {
const [rows] = await pool.execute('SELECT * FROM weapon_skins ORDER BY id DESC');
return rows;
} catch (error) {
console.error('❌ 查询所有数据失败:', error.message);
throw error;
}
}
// 👉 第2个功能:新增单条数据
async function addWeaponSkin(weaponSkin) {
const { name, baseWeapon, skinName, price, appearance, category, stock } = weaponSkin;
// 基础校验
if (!name || !baseWeapon || !price) {
throw new Error('必填项:商品名称、基础武器型号、价格');
}
if (price < 0) {
throw new Error('价格不能为负数');
}
try {
const [result] = await pool.execute(
INSERT INTO weapon_skins (name, baseWeapon, skinName, price, appearance, category, stock) VALUES (?, ?, ?, ?, ?, ?, ?),
[name, baseWeapon, skinName || '', price, appearance || '未知', category || '武器皮肤', stock || 0]
);
return {
success: true,
insertId: result.insertId,
message: '新增成功'
};
} catch (error) {
console.error('❌ 新增数据失败:', error.message);
throw error;
}
}
// 👉 第3个功能:按 ID 删除数据
async function deleteWeaponSkin(id) {
if (!id) throw new Error('ID 不能为空');
try {
const [result] = await pool.execute(
'DELETE FROM weapon_skins WHERE id = ?',
[id]
);
if (result.affectedRows === 0) throw new Error(未找到 ID=${id} 的数据);
return { success: true, message: ID=${id} 数据已删除 };
} catch (error) {
console.error(❌ 删除 ID=${id} 失败:, error.message);
throw error;
}
}
// 👉 第4个功能:按 ID 修改价格(新增价格变动日志)
async function updateWeaponSkinPrice(id, newPrice) {
if (!id) throw new Error('ID 不能为空');
if (typeof newPrice !== 'number' || newPrice < 0) {
throw new Error('价格必须为非负数值');
}
// 新增:查询商品旧价格(修复类型问题)
let oldPrice;
try {
const [oldData] = await pool.execute(
'SELECT price FROM weapon_skins WHERE id = ?',
[id]
);
if (oldData.length === 0) {
throw new Error(未找到 ID=${id} 的数据);
}
// 关键修复:将数据库返回的价格转为 Number 类型(兼容 DECIMAL/BigInt)
oldPrice = Number(oldData[0].price);
} catch (error) {
console.error(❌ 修改 ID=${id} 价格失败:, error.message);
throw error;
}
try {
const [result] = await pool.execute(
'UPDATE weapon_skins SET price = ? WHERE id = ?',
[newPrice, id]
);
if (result.affectedRows === 0) throw new Error(未找到 ID=${id} 的数据);
// 核心新增:打印价格变动日志(已修复类型问题)
const logTime = formatTime();
// 确保 newPrice 也转为 Number(避免传入字符串类型的数字)
const finalNewPrice = Number(newPrice);
console.log(
`[${logTime}] 管理员修改ID=${id}的商品价格:旧价格${oldPrice.toFixed(2)} → 新价格${finalNewPrice.toFixed(2)}`
);
return {
success: true,
message: `价格更新为 ${finalNewPrice.toFixed(2)} 元`,
oldPrice: oldPrice.toFixed(2),
newPrice: finalNewPrice.toFixed(2)
};
} catch (error) {
console.error(❌ 修改 ID=${id} 价格失败:, error.message);
throw error;
}
}
// 新增:辅助函数 - 格式化时间为「YYYY-MM-DD HH:mm:ss」
function formatTime() {
const date = new Date();
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hour = String(date.getHours()).padStart(2, '0');
const minute = String(date.getMinutes()).padStart(2, '0');
const second = String(date.getSeconds()).padStart(2, '0');
return ${year}-${month}-${day} ${hour}:${minute}:${second};
}
// 👉 第5个功能:分页查询武器皮肤(支持筛选)
async function getWeaponSkinsWithPagination(options = {}) {
const {
page = 1, // 当前页码
pageSize = 12, // 每页数量
minPrice = null, // 最低价格
maxPrice = null, // 最高价格
appearance = [], // 外观筛选(数组)
category = [], // 类别筛选(数组)
quality = [], // 品质筛选(数组)
isCollectible = null, // 收藏品筛选(null=全部, true=是, false=否)
baseWeapon = [] // 基础武器筛选(数组)
} = options;
try {
// 构建 WHERE 条件
const conditions = [];
const params = [];
// 价格筛选
if (minPrice !== null && minPrice !== '') {
conditions.push('price >= ?');
params.push(parseFloat(minPrice));
}
if (maxPrice !== null && maxPrice !== '') {
conditions.push('price <= ?');
params.push(parseFloat(maxPrice));
}
// 外观筛选
if (appearance && appearance.length > 0) {
// 需要将常量值转换为数据库中的中文值
const appearanceMap = {
'factory_new': '崭新出厂',
'minimal_wear': '略有磨损',
'field_tested': '久经沙场',
'well_worn': '破损不堪',
'battle_scarred': '战痕累累'
};
const appearanceValues = appearance.map(val => appearanceMap[val] || val).filter(Boolean);
if (appearanceValues.length > 0) {
conditions.push(`appearance IN (${appearanceValues.map(() => '?').join(',')})`);
params.push(...appearanceValues);
}
}
// 类别筛选
if (category && category.length > 0) {
const categoryMap = {
'weapon_skin': '武器皮肤',
'sticker': '印花',
'glove': '手套',
'knife': '刀具',
'music_kit': '音乐盒'
};
const categoryValues = category.map(val => categoryMap[val] || val).filter(Boolean);
if (categoryValues.length > 0) {
conditions.push(`category IN (${categoryValues.map(() => '?').join(',')})`);
params.push(...categoryValues);
}
}
// 品质筛选
if (quality && quality.length > 0) {
const qualityMap = {
'consumer': '消费级',
'industrial': '工业级',
'milspec': '军规级',
'restricted': '受限',
'classified': '保密',
'covert': '隐秘',
'exceedingly_rare': '非凡'
};
const qualityValues = quality.map(val => qualityMap[val] || val).filter(Boolean);
if (qualityValues.length > 0) {
conditions.push(`quality IN (${qualityValues.map(() => '?').join(',')})`);
params.push(...qualityValues);
}
}
// 收藏品筛选
if (isCollectible !== null && isCollectible !== '') {
conditions.push('isCollectible = ?');
params.push(isCollectible === true || isCollectible === 'true' || isCollectible === 1 ? 1 : 0);
}
// 基础武器筛选
if (baseWeapon && baseWeapon.length > 0) {
conditions.push(`baseWeapon IN (${baseWeapon.map(() => '?').join(',')})`);
params.push(...baseWeapon);
}
// 构建 WHERE 子句
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
// 计算偏移量
const offset = (page - 1) * pageSize;
// 查询总数
const [countResult] = await pool.execute(
`SELECT COUNT(*) as total FROM weapon_skins ${whereClause}`,
params
);
const total = countResult[0].total;
// 查询数据
const [rows] = await pool.execute(
`SELECT * FROM weapon_skins ${whereClause} ORDER BY id DESC LIMIT ? OFFSET ?`,
[...params, pageSize, offset]
);
// 计算总页数
const totalPages = Math.ceil(total / pageSize);
return {
success: true,
data: rows,
pagination: {
page: parseInt(page),
pageSize: parseInt(pageSize),
total: total,
totalPages: totalPages,
hasNext: page < totalPages,
hasPrev: page > 1
}
};
} catch (error) {
console.error('❌ 分页查询失败:', error.message);
throw error;
}
}
// 只暴露当前开发的功能
module.exports = {
getAllWeaponSkins,
addWeaponSkin,
deleteWeaponSkin,
updateWeaponSkinPrice,
getWeaponSkinsWithPagination
};
1.2 修改server.js
添加分页查询API的入口
// server.js - Web 服务器,提供静态文件服务
const express = require('express');
const path = require('path');
const { getAllFilterOptions } = require('./config/filterOptions');
const weaponSkinModel = require('./model/weaponSkinModel');
const app = express();
const PORT = process.env.PORT || 3000;
// 解析 JSON 请求体
app.use(express.json());
// 提供静态文件服务(CSS、JS、图片等)
app.use(express.static(path.join(__dirname, 'public')));
// 主页路由
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
// API: 获取所有筛选选项
app.get('/api/filter-options', (req, res) => {
try {
const options = getAllFilterOptions();
res.json({
success: true,
data: options
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取筛选选项失败',
error: error.message
});
}
});
// API: 分页查询武器皮肤
app.get('/api/weapon-skins', async (req, res) => {
try {
const {
page = 1,
pageSize = 12,
minPrice,
maxPrice,
appearance,
category,
quality,
isCollectible,
baseWeapon
} = req.query;
// 处理数组参数
const parseArrayParam = (param) => {
if (!param) return [];
if (Array.isArray(param)) return param;
if (typeof param === 'string') {
try {
const parsed = JSON.parse(param);
return Array.isArray(parsed) ? parsed : [parsed];
} catch {
return param.split(',').filter(Boolean);
}
}
return [];
};
const options = {
page: parseInt(page),
pageSize: parseInt(pageSize),
minPrice: minPrice || null,
maxPrice: maxPrice || null,
appearance: parseArrayParam(appearance),
category: parseArrayParam(category),
quality: parseArrayParam(quality),
isCollectible: isCollectible !== undefined ? (isCollectible === 'true' || isCollectible === '1') : null,
baseWeapon: parseArrayParam(baseWeapon)
};
const result = await weaponSkinModel.getWeaponSkinsWithPagination(options);
res.json(result);
} catch (error) {
res.status(500).json({
success: false,
message: '查询武器皮肤失败',
error: error.message
});
}
});
// 启动服务器
app.listen(PORT, () => {
console.log(服务器运行在 http://localhost:${PORT});
console.log('请在浏览器中打开上述地址查看页面');
});
1.3 修改index.html
<!DOCTYPE html>
无畏契约手游交易平台
¥输入最低
¥输入限高
共 0 条,第 1 页
至此,我们就完成了核心的代码部分修改,为了让样式更加优美,我们建议剩余公共的js、css文件也修改一下
- 修改:/public/css/style.css 文件:
- 修改:/public/js/main.js 文件:
1.4 运行测试
C:\Users\admin\Desktop\weapon>node server.js
此时代码
2.了解AI编程工具Cursor
Cursor(https://www.cursor.com)是一款AI代码编辑器,基于它可以轻松完成前后端代码的编写、调试。
所有安装包大家都需官网下载,因为更新的非常快,随时都存在老版本不能用的情况。
配置
选择AI工具的语言
输入AI工具的语言为 "中文" ,输入完语言之后,直接点击 "Continue" 下一步,下一步的操作即可。
选择了 "Use Extensions" 就表示,如果我们本地安装了VS Code,就会将VS Code中的拓展插件、配置、快捷键的配置直接导入进来。(因为Cursor底层就是基于VS Code包装而来的)
登录/注册
要使用Cursor需要先登录Cursor的账号,基于邮箱进行登录,如果有账号直接选择 "Login In" 进行登录;如果没有账号,直接选择 "Sign Up"注册账号即可。
点击登录之后,会跳转到官方的登录页面进行登录。输入邮箱,然后选择 "Continue",下一步。
可以基于密码登录,也可以基于邮箱验证码登录。 这里我选择基于邮箱验证码登录。
输入完验证码后就可以完成登录了。 然后我们就可以授权登录桌面端的Cursor应用了。
完成授权
回到Cursor中,我们可以看到已经登录成功了。默认有150次调用次数,如果需要新的可以购买或换邮箱
(如果Cursor中未加载出来,可以再点击一下 Login In 进行登录授权)
配置Cursor
选择 File -> Preferences -> VS Code Settings,配置Cursor中集成的 VS Code的基本信息,包括字体、字体大小、行高、主题颜色等信息。
然后将下方的这段配置,直接粘贴到 settings.json 文件中,覆盖掉原有的内容。
{
"window.commandCenter": 1,
"update.enableWindowsBackgroundUpdates": false,
"update.mode": "none",
"workbench.colorTheme": "Default Light+",
"workbench.statusBar.visible": false,
"editor.fontFamily": "Fira Code, Consolas,'Courier New', monospace",
"editor.fontSize": 15,
"editor.lineHeight": 1.8,
"editor.tabSize": 2,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit"
},
"editor.minimap.enabled": true,
"liveServer.settings.donotShowInfoMsg": true,
"git.confirmSync": false,
"terminal.integrated.defaultProfile.windows": "Command Prompt"
}
最终效果(如果出现闪退,重新右键管理员打开即可):
安装插件(非必须)
然后我们就可以根据对应的插件名称来搜索对应的插件了。
中文插件
Vue - Official
一个专门为 Vue 3 构建的语言支持插件。
3.AI实现查看武器详情
有了AI,接下来的实现就简单了,我会输入下面的提示词:
继续生成查看单个武器详情的页面
回车执行,此时我们就会发现Cursor开始帮我们修改了代码
点开任意一个文件看一下,就可以看到修改的痕迹
生成的部分建议先做测试,在控制台输入启动命令
验证发现不符合预期,就继续调试修改,如图:
然后再来验证效果,发现符合预期为止
此时参考代码如下
4.AI实现添加购物车
提示词可参考:
继续完成立即购买功能,直接提示购买成功即可
完美,参考代码如下
5.练习:AI实现加入购物车功能
这里为了简单实现,所以我的提示词如下
实现一下加入购物车功能,存在客户端缓存localstorage中,先可以不用数据库
● 加入购物车
● 查看购物车
参考代码
6.功能追加与优化
借助于AI我们可以快速完成部分功能,大家可以根据自己的需要,自己考虑追加什么功能。我追加了:
● 用户管理模块(登录、注册、用户信息)
● 创建评论功能模块
● 创建收藏功能模块
● 在详情页添加评论和收藏UI
● 添加用户中心和个人信息展示
因前面已经有数据库的操作,这里我的实现没有借助于数据库,算是偷懒了,大家时间充足,需加上数据库
我的提示词如下
继续追加登录注册功能,评论、收藏等功能,这些都可以先不用数据库表,实现纯前端效果,增加一点功能
参考代码如下
7.今日作业
- 完成今日案例练习
- 确定好接下来小组实战的题目,需要具有现实意义
a. 不要有图书管理系统、酒店管理系统、学生管理系统之类的
b. 需要有表结构设计的部分,实战答辩需要能够讲出来