当前位置:网站首页 >  教程

从零落地可复制通用电商SKU联动选择及库存校验功能

时间:2026年05月22日 23:36:40 来源:易频IT社区

一、准备工作

本次实现采用前后端完全分离的最小依赖方案:前端用原生HTML/CSS/JS,无需框架;后端用Node.js+Express做快速演示接口,所有代码可直接复制运行。

1. 后端环境准备

  • 确保电脑已安装Node.js(v14+即可),未安装的访问 https://nodejs.org/dist/v20.17.0/node-v20.17.0-x64.msi 下载安装(Windows),Mac/Linux用Homebrew/apt命令:brew install node / sudo apt install nodejs npm
  • 新建空文件夹sku-demo,打开终端进入该文件夹,执行npm init -y初始化项目
  • 执行npm install express cors --save安装依赖

2. 前端基础文件准备

sku-demo文件夹下新建3个文件:index.htmlserver.jsstyle.css(样式仅做最小展示,可自行修改)

二、定义通用SKU数据结构

这是SKU联动的核心,大厂通用结构分为规格字典SKU映射表两部分,避免冗余存储,也方便后续扩展规格。

完整演示用JSON数据

直接放在server.js里作为模拟数据,无需数据库:

```javascript // server.js 顶部模拟数据 const mockData = { // 规格字典:记录所有可选的规格分类和选项 specs: [ { id: "color", name: "颜色", options: [{ id: "red", name: "中国红" }, { id: "blue", name: "星空蓝" }, { id: "black", name: "深邃黑" }] }, { id: "size", name: "尺码", options: [{ id: "s", name: "S" }, { id: "m", name: "M" }, { id: "l", name: "L" }] }, { id: "material", name: "材质", options: [{ id: "cotton", name: "纯棉" }, { id: "polyester", name: "涤纶" }] } ], // SKU映射表:记录已选规格组合的唯一标识、库存、价格 skuMap: { "color:red,size:s,material:cotton": { id: "sku1", stock: 5, price: 99 }, "color:red,size:m,material:cotton": { id: "sku2", stock: 0, price: 109 }, "color:red,size:l,material:polyester": { id: "sku3", stock: 2, price: 119 }, "color:blue,size:s,material:polyester": { id: "sku4", stock: 15, price: 89 }, "color:blue,size:m,material:cotton": { id: "sku5", stock: 8, price: 99 }, "color:black,size:l,material:cotton": { id: "sku6", stock: 3, price: 129 } } }; ```

三、后端接口开发

只写一个接口,返回完整的SKU数据,方便前端调用:

```javascript // 继续写在server.js里 const express = require('express'); const cors = require('cors'); const app = express(); const port = 3000; // 允许跨域 app.use(cors()); // 统一返回SKU数据接口 app.get('/api/sku', (req, res) => { res.json({ code: 200, data: mockData }); }); // 启动服务 app.listen(port, () => { console.log(`后端服务已启动,访问地址:http://localhost:${port}/api/sku`); }); ```

终端执行node server.js,看到提示后接口即可正常访问。

四、前端SKU联动选择器开发

1. 基础HTML结构

从零落地可复制通用电商SKU联动选择及库存校验功能

index.html中编写:

```html 电商SKU联动演示

已选:请选择规格

库存:--

价格:--

```

2. 最小展示样式CSS

style.css中编写:

```css .sku-container { width: 400px; margin: 50px auto; padding: 20px; border: 1px solid eee; border-radius: 8px; } .spec-group { margin-bottom: 20px; } .spec-title { font-size: 16px; margin-bottom: 10px; color: 333; } .spec-options { display: flex; gap: 10px; flex-wrap: wrap; } .spec-option { padding: 8px 16px; border: 1px solid ddd; border-radius: 4px; cursor: pointer; font-size: 14px; } .spec-option.selected { border-color: ff4400; color: ff4400; background-color: fff5f0; } .spec-option.disabled { border-color: eee; color: ccc; cursor: not-allowed; background-color: f5f5f5; } .selected-info p { margin: 10px 0; font-size: 14px; } buy-btn { width: 100%; padding: 12px; background-color: ff4400; color: fff; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; margin-top: 10px; } buy-btn:disabled { background-color: ccc; cursor: not-allowed; } ```

3. 核心JS逻辑(可直接复制)

sku-demo下新建index.js,编写联动逻辑(逻辑拆分为3步:获取数据渲染→处理点击选择→计算禁用状态):

```javascript // 全局变量存储 let globalSpecs = []; let globalSkuMap = {}; let currentSelected = {}; // key是规格id,value是选项id // 入口函数 document.addEventListener('DOMContentLoaded', async () => { await fetchSkuData(); renderSpecs(); }); // 1. 获取后端SKU数据 async function fetchSkuData() { try { const res = await fetch('http://localhost:3000/api/sku'); const { code, data } = await res.json(); if (code === 200) { globalSpecs = data.specs; globalSkuMap = data.skuMap; } } catch (err) { alert('获取SKU数据失败,请检查后端服务是否启动'); console.error(err); } } // 2. 渲染规格选择区域 function renderSpecs() { const container = document.getElementById('specs-container'); container.innerHTML = ''; globalSpecs.forEach(spec => { const groupDiv = document.createElement('div'); groupDiv.className = 'spec-group'; // 规格标题 const titleP = document.createElement('p'); titleP.className = 'spec-title'; titleP.textContent = spec.name; // 规格选项 const optionsDiv = document.createElement('div'); optionsDiv.className = 'spec-options'; optionsDiv.dataset.specId = spec.id; spec.options.forEach(option => { const optionBtn = document.createElement('button'); optionBtn.className = 'spec-option'; optionBtn.textContent = option.name; optionBtn.dataset.specId = spec.id; optionBtn.dataset.optionId = option.id; // 绑定点击事件 optionBtn.addEventListener('click', handleOptionClick); optionsDiv.appendChild(optionBtn); }); groupDiv.appendChild(titleP); groupDiv.appendChild(optionsDiv); container.appendChild(groupDiv); }); // 初始渲染后计算禁用状态 updateDisabledOptions(); } // 3. 处理规格选项点击 function handleOptionClick(e) { const specId = e.target.dataset.specId; const optionId = e.target.dataset.optionId; // 已选同一规格同一选项则取消选择 if (currentSelected[specId] === optionId) { delete currentSelected[specId]; } else { // 否则更新选择 currentSelected[specId] = optionId; } // 更新UI updateSelectedUI(); updateDisabledOptions(); updateSelectedInfo(); } // 4. 更新已选选项的UI高亮 function updateSelectedUI() { // 先清除所有高亮 document.querySelectorAll('.spec-option.selected').forEach(btn => btn.classList.remove('selected')); // 再给当前已选的加高亮 Object.entries(currentSelected).forEach(([specId, optionId]) => { const btn = document.querySelector(`.spec-option[data-spec-id="${specId}"][data-option-id="${optionId}"]`); if (btn) btn.classList.add('selected'); }); } // 5. 核心逻辑:计算哪些选项应该禁用 function updateDisabledOptions() { // 生成所有可能的候选SKU组合 let candidateKeys = Object.keys(globalSkuMap); // 先清除所有禁用 document.querySelectorAll('.spec-option.disabled').forEach(btn => btn.classList.remove('disabled')); // 遍历所有规格,逐个判断选项是否可选 globalSpecs.forEach(spec => { const currentSpecId = spec.id; const otherSpecSelected = {}; // 收集除当前规格外的已选规格 Object.entries(currentSelected).forEach(([sId, oId]) => { if (sId !== currentSpecId) otherSpecSelected[sId] = oId; }); // 过滤出包含其他已选规格的候选SKU let filteredCandidateKeys = candidateKeys.filter(key => { const keyObj = key.split(',').reduce((obj, item) => { const [s, o] = item.split(':'); obj[s] = o; return obj; }, {}); // 检查候选SKU是否包含所有其他已选规格 return Object.entries(otherSpecSelected).every(([sId, oId]) => keyObj[sId] === oId); }); // 收集当前规格在候选SKU中的所有可选选项ID const allowedOptionIds = new Set(); filteredCandidateKeys.forEach(key => { const keyObj = key.split(',').reduce((obj, item) => { const [s, o] = item.split(':'); obj[s] = o; return obj; }, {}); allowedOptionIds.add(keyObj[currentSpecId]); }); // 禁用不在allowedOptionIds里的选项 spec.options.forEach(option => { const btn = document.querySelector(`.spec-option[data-spec-id="${currentSpecId}"][data-option-id="${option.id}"]`); if (btn && !allowedOptionIds.has(option.id) && currentSelected[currentSpecId] !== option.id) { btn.classList.add('disabled'); } }); }); } // 6. 更新已选信息、库存、价格、购买按钮状态 function updateSelectedInfo() { const selectedText = document.getElementById('selected-text'); const stockText = document.getElementById('stock-text'); const priceText = document.getElementById('price-text'); const buyBtn = document.getElementById('buy-btn'); // 检查是否选全所有规格 const isAllSelected = globalSpecs.every(spec => currentSelected[spec.id]); if (!isAllSelected) { selectedText.textContent = '请选择规格'; stockText.textContent = '--'; priceText.textContent = '--'; buyBtn.disabled = true; return; } // 生成完整SKU key const fullKey = globalSpecs.map(spec => `${spec.id}:${currentSelected[spec.id]}`).join(','); const skuInfo = globalSkuMap[fullKey]; // 更新信息 const selectedNames = globalSpecs.map(spec => { const option = spec.options.find(o => o.id === currentSelected[spec.id]); return option.name; }); selectedText.textContent = selectedNames.join(' / '); stockText.textContent = skuInfo.stock; priceText.textContent = `¥${skuInfo.price}`; // 库存为0禁用购买 buyBtn.disabled = skuInfo.stock <= 0; } ```

五、测试与优化

1. 测试步骤

  • 确保终端执行node server.js后后端服务正常运行
  • 直接双击打开index.html
  • 测试:依次选中国红→S→纯棉(全选有库存);选中国红→M(库存0禁用购买);取消选择后再选蓝色→S→涤纶(库存多);选中可选规格后看其他规格的禁用项是否正确(比如选中中国红后,材质只有纯棉和涤纶可选?不对看映射表,深邃黑只有L纯棉可选,选中中国红S后,材质不能选深邃黑关联的?哦看逻辑是对的,因为候选SKU只包含其他已选,所以选中红色S后,不会出现深邃黑的选项以外的过滤?哦等下看我们的规格字典是所有可能的,禁用逻辑是在现有字典里过滤当前规格的不可选项:对,选中红色S后,材质的涤纶是否在候选里?看skuMap,红色S只有纯棉,所以涤纶会被禁用,没错!

2. 快速优化建议

  • 价格可以展示划线价(在skuMap里加originalPrice字段)
  • 禁用状态可以改成点击时弹出“该规格无货”的提示(在handleOptionClick里加判断disabled的逻辑,return掉并alert)
  • 库存为0的SKU可以直接从skuMap里过滤(但初始保留更方便管理)
标签 电商sku

相关推荐

最新

热门

推荐

精选

标签

易频IT社区是综合性互联网IT技术门户网站,专注分享网络技术、服务器运维、网络安全、编程开发、系统架构、云计算、大数据等行业干货,实时更新IT行业资讯、零基础教程、实战案例,为IT从业者、技术爱好者提供专业的学习交流平台。

Copyright © 2021-2026 易频IT社区. All Rights Reserved. 备案号:闽ICP备2023013482号 网站地图