codex 制作DIY 装机助手小程序 全套可部署完整教程

内容分享2小时前发布
1 1 0

一、整体说明

  1. 1.1 技术栈
  • 小程序端:微信原生 WXML/WXSS/JS
  • 后端:Node.js + Express + mysql2
  • 数据库:MySQL 8.0
  • 数据源:京东联盟开放 API(商品价格 /sku)
  • 核心能力:硬件兼容校验、实时价、历史价、跳转京东加购、配置本地 / 云端存储
  1. 2. 部署流程:创建数据库→导入 SQL→后端安装依赖启动→小程序修改接口域名上传
  2. 3. 所有代码分段完整复制可用,无缺漏

二、目录总结构

pc-assistant/
├── mini-program/          # 微信小程序完整代码
│   ├── app.json
│   ├── app.js
│   ├── utils/
│   ├── pages/
│   └── components/
├── server/                # NodeJS后端服务
│   ├── app.js             # 服务入口
│   ├── db.js              # 数据库连接
│   ├── router/            # 接口路由
│   ├── controller/        # 业务逻辑
│   ├── config.js          # 配置文件(京东密钥、数据库)
│   └── package.json
└── sql/
    └── pc_assistant.sql   # MySQL建表+测试数据

第一部分:MySQL 数据库 SQL 文件 sql/pc_assistant.sql

直接复制到 Navicat/DBeaver 执行

-- 创建数据库
CREATE DATABASE IF NOT EXISTS pc_assistant DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE pc_assistant;
-- 1.硬件库表
DROP TABLE IF EXISTS hardware;
CREATE TABLE hardware (
  id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
  type ENUM('cpu','mainboard','gpu','ram','ssd','power'NOT NULL COMMENT '硬件类型',
  brand VARCHAR(50NOT NULL COMMENT '品牌',
  model VARCHAR(100NOT NULL COMMENT '型号',
  platform VARCHAR(20NULL COMMENT '平台 intel/amd',
  socket VARCHAR(30NULL COMMENT '插槽 LGA1700/AM5',
  suggest_watt INT NULL COMMENT '显卡提议电源',
  watt INT NULL COMMENT '电源额定功率',
  jd_sku_id VARCHAR(60NOT NULL UNIQUE COMMENT '京东商品SKU',
  create_time DATETIME DEFAULT NOW()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 2.历史价格记录表
DROP TABLE IF EXISTS price_history;
CREATE TABLE price_history (
  id INT PRIMARY KEY AUTO_INCREMENT,
  sku_id VARCHAR(60NOT NULL,
  price DECIMAL(10,2NOT NULL,
  record_time DATETIME DEFAULT NOW(),
  INDEX idx_sku (sku_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 3.用户云端配置表
DROP TABLE IF EXISTS user_config;
CREATE TABLE user_config (
  id INT PRIMARY KEY AUTO_INCREMENT,
  openid VARCHAR(100NOT NULL COMMENT '微信用户openid',
  config_json TEXT NOT NULL COMMENT '配置JSON',
  total_price DECIMAL(10,2NOT NULL,
  create_time DATETIME DEFAULT NOW(),
  INDEX idx_openid (openid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 测试硬件数据
INSERT INTO hardware(type,brand,model,platform,socket,suggest_watt,watt,jd_sku_id) VALUES
-- CPU Intel
('cpu','Intel','i5-14600K','intel','LGA1700',NULL,NULL,'100001'),
-- CPU AMD
('cpu','AMD','R5 7600X','amd','AM5',NULL,NULL,'100002'),
-- 主板 Intel LGA1700
('mainboard','华硕','B760M-A','intel','LGA1700',NULL,NULL,'200001'),
-- 主板 AMD AM5
('mainboard','微星','B650M迫击炮','amd','AM5',NULL,NULL,'200002'),
-- 显卡
('gpu','七彩虹','RTX4060 8G',NULL,NULL,550,NULL,'300001'),
('gpu','影驰','RTX4070Ti 12G',NULL,NULL,750,NULL,'300002'),
-- 内存
('ram','金百达','DDR5 32G 6000',NULL,NULL,NULL,NULL,'400001'),
-- 固态
('ssd','西数','SN770 1TB',NULL,NULL,NULL,NULL,'500001'),
-- 电源
('power','长城','550W金牌',NULL,NULL,NULL,550,'600001'),
('power','航嘉','750W金牌全模',NULL,NULL,NULL,750,'600002');

第二部分:NodeJS 后端完整代码 server/

1. server/package.json

{
  "name""pc-assistant-server",
  "version""1.0.0",
  "main""app.js",
  "dependencies": {
    "express""^4.18.2",
    "mysql2""^3.6.0",
    "axios""^1.5.0",
    "cors""^2.8.5"
  }
}

2. server/config.js 核心配置(修改数据库、京东联盟密钥)

// 数据库配置
exports.DB_CONFIG = {
  host: "127.0.0.1",
  user: "root",
  password: "你的数据库密码",
  database: "pc_assistant",
  port: 3306
};
// 京东联盟开放平台密钥(自行去京东联盟申请)
exports.JD_CONFIG = {
  appKey: "你的京东appKey",
  appSecret: "你的京东appSecret",
  apiUrl: "https://api.jd.com/routerjson"
};
// 后端服务端口
exports.SERVER_PORT = 3000;

3. server/db.js 数据库连接封装

const mysql = require('mysql2/promise');
const { DB_CONFIG } = require('./config');
// 创建连接池
const pool = mysql.createPool({
  host: DB_CONFIG.host,
  user: DB_CONFIG.user,
  password: DB_CONFIG.password,
  database: DB_CONFIG.database,
  port: DB_CONFIG.port,
  waitForConnectionstrue,
  connectionLimit10,
  queueLimit0
});
// 通用执行SQL函数
async function query(sql, params = []{
  const [rows] = await pool.query(sql, params);
  return rows;
}
module.exports = { pool, query };

4. server/controller/hardwareController.js 硬件、价格业务逻辑

const { query } = require('../db');
const axios = require('axios');
const { JD_CONFIG } = require('../config');
// 1. 获取全部指定类型硬件
exports.getHardwareByType = async (req, res) => {
  const { type } = req.query;
  try {
    const list = await query("SELECT * FROM hardware WHERE type = ?", [type]);
    return res.json({ code: 200, data: list });
  } catch (err) {
    return res.json({ code: 500, msg: err.message });
  }
};
// 2. 根据sku查询京东实时价格
exports.getJdPrice = async (req, res) => {
  const { skuId } = req.query;
  const timestamp = Date.now();
  const params = {
    method: "jd.union.open.goods.jingfen.query",
    app_key: JD_CONFIG.appKey,
    timestamp,
    skuIds: skuId
  };
  try {
    const result = await axios.get(JD_CONFIG.apiUrl, { params });
    const goods = result.data?.jingfenQueryResult?.jingfenInfoList?.[0];
    if (!goods) return res.json({ code: 400, msg: "未获取商品价格" });
    const price = parseFloat(goods.price);
    // 写入历史价格表
    await query("INSERT INTO price_history(sku_id,price) VALUES(?,?)", [skuId, price]);
    return res.json({
      code: 200,
      data: {
        realPrice: price,
        marketPrice: parseFloat(goods.marketPrice)
      }
    });
  } catch (err) {
    return res.json({ code: 500, msg: "京东接口请求失败" });
  }
};
// 3. 获取30天历史价格
exports.getPriceHistory = async (req, res) => {
  const { skuId } = req.query;
  try {
    const list = await query(`
      SELECT DATE_FORMAT(record_time,'%m-%d') date, price
      FROM price_history
      WHERE sku_id = ?
      ORDER BY record_time ASC LIMIT 30
    `, [skuId]);
    return res.json({ code: 200, data: list });
  } catch (err) {
    return res.json({ code: 500, msg: err.message });
  }
};
// 4. 保存用户云端配置
exports.saveUserConfig = async (req, res) => {
  const { openid, configJson, totalPrice } = req.body;
  try {
    await query(
      "INSERT INTO user_config(openid,config_json,total_price) VALUES(?,?,?)",
      [openid, JSON.stringify(configJson), totalPrice]
    );
    return res.json({ code: 200, msg: "保存成功" });
  } catch (err) {
    return res.json({ code: 500, msg: err.message });
  }
};
// 5. 获取用户所有云端配置
exports.getUserConfig = async (req, res) => {
  const { openid } = req.query;
  try {
    const list = await query("SELECT * FROM user_config WHERE openid = ? ORDER BY create_time DESC", [openid]);
    const formatList = list.map(item => ({
      id: item.id,
      createTime: item.create_time,
      hardList: JSON.parse(item.config_json),
      total: item.total_price
    }));
    return res.json({ code: 200, data: formatList });
  } catch (err) {
    return res.json({ code: 500, msg: err.message });
  }
};
// 6. 删除云端配置
exports.delUserConfig = async (req, res) => {
  const { id } = req.query;
  try {
    await query("DELETE FROM user_config WHERE id = ?", [id]);
    return res.json({ code: 200, msg: "删除成功" });
  } catch (err) {
    return res.json({ code: 500, msg: err.message });
  }
};

5. server/router/index.js 接口路由

const express = require('express');
const router = express.Router();
const hardwareCtrl = require('../controller/hardwareController');
// 获取硬件列表
router.get('/api/getHardware', hardwareCtrl.getHardwareByType);
// 获取实时京东价格
router.get('/api/getJdPrice', hardwareCtrl.getJdPrice);
// 获取历史价格
router.get('/api/getPriceHistory', hardwareCtrl.getPriceHistory);
// 保存云端配置
router.post('/api/saveConfig', hardwareCtrl.saveUserConfig);
// 获取用户配置
router.get('/api/getUserConfig', hardwareCtrl.getUserConfig);
// 删除云端配置
router.get('/api/delConfig', hardwareCtrl.delUserConfig);
module.exports = router;

6. server/app.js 服务入口文件

const express = require('express');
const cors = require('cors');
const { SERVER_PORT } = require('./config');
const router = require('./router/index');
const app = express();
// 跨域
app.use(cors());
// 解析post json
app.use(express.json());
// 挂载路由
app.use(router);
// 启动服务
app.listen(SERVER_PORT, () => {
  console.log(`装机助手后端服务启动成功,端口:${SERVER_PORT}`);
});

后端部署启动步骤

  1. 上传 server 文件夹到云服务器
  2. 进入 server 目录执行 npm install 安装依赖
  3. 修改 config.js 数据库账号密码、京东联盟密钥
  4. 执行启动:node app.js
  5. 后台常驻运行(推荐 pm2)
npm install pm2 -g
pm2 start app.js --name pc-server
pm2 startup
pm2 save
  1. 6. 服务器安全组放行 3000 端口

第三部分:微信小程序完整全套代码 mini-program/

1. mini-program/app.json

{
  "pages": [
    "pages/index/index",
    "pages/select/select",
    "pages/cartList/cartList",
    "pages/myConfig/myConfig"
  ],
  "usingComponents": {
    "priceModal""/components/priceModal/priceModal"
  },
  "window": {
    "backgroundTextStyle""light",
    "navigationBarBackgroundColor""#2563eb",
    "navigationBarTitleText""DIY装机助手",
    "navigationBarTextStyle""white"
  }
}

2. mini-program/app.js

App({
  globalData: {
    openid"",
    baseUrl"http://你的服务器公网IP:3000" // 替换为你的后端地址
  },
  // 获取用户openid(登录接口可自行补充)
  getOpenid() {
    return new Promise(resolve => {
      if (this.globalData.openid) resolve(this.globalData.openid);
      wx.login({
        successres => {
          // 真实项目调用后端code2session获取openid,此处模拟
          this.globalData.openid = "mock_openid_123456";
          resolve(this.globalData.openid);
        }
      })
    })
  }
})

3. 公共工具 mini-program/utils/hardCompat.js 硬件兼容引擎

const compatEngine = {
  platformMap: {
    intel: ['LGA1700''LGA1200''LGA2066'],
    amd: ['AM5''AM4''TRX4']
  },
  selected: {
    cpunull,
    mainboardnull,
    gpunull,
    ramnull,
    ssdnull,
    powernull
  },
  filterBoardByCpu(cpu, allBoardList) {
    if (!cpu) return allBoardList.map(item => ({ ...item, disabledfalsecompatTip'' }));
    const cpuBrand = cpu.platform;
    let allowSocket = [];
    if (cpuBrand === 'intel') allowSocket = this.platformMap.intel;
    if (cpuBrand === 'amd') allowSocket = this.platformMap.amd;
    return allBoardList.map(board => {
      const pass = allowSocket.includes(board.socket);
      return {
        ...board,
        disabled: !pass,
        compatTip: pass ? '兼容' : `冲突:${cpu.brand} CPU 无法搭配此主板(Intel/AMD平台不互通)`
      }
    })
  },
  fullCheck() {
    const warnList = [];
    const { cpu, mainboard, gpu, power } = this.selected;
    if (cpu && mainboard && mainboard.disabled) warnList.push(mainboard.compatTip);
    if (gpu && power && power.watt < gpu.suggest_watt) {
      warnList.push(`电源不足,显卡提议${gpu.suggest_watt}W,当前电源仅${power.watt}W`);
    }
    return warnList;
  },
  clearAll() {
    Object.keys(this.selected).forEach(key => this.selected[key] = null);
  }
}
module.exports = compatEngine;

4. mini-program/utils/jdApi.js 网络请求 + 京东跳转

const app = getApp();
const JD_APPID = "wx91f5c36844f8d88c";
// 通用后端请求封装
function request(url, data = {}, method = "GET") {
  return new Promise((resolve, reject) => {
    wx.request({
      url: app.globalData.baseUrl + url,
      data,
      method,
      successres => resolve(res.data),
      failerr => reject(err)
    })
  })
}
// 跳转京东加购商品
function jumpAddCart(skuId) {
  wx.navigateToMiniProgram({
    appId: JD_APPID,
    path`pages/item/item?skuId=${skuId}&action=addcart`,
    success() { wx.showToast({ title'跳转京东加购' }); },
    fail() {
      wx.setClipboardData({
        data`https://item.jd.com/${skuId}.html`,
        success() => {
          wx.showModal({ title'跳转失败'content'链接已复制,打开京东APP访问'showCancelfalse })
        }
      })
    }
  })
}
// 获取硬件实时价格
async function getHardwarePrice(skuId) {
  const res = await request('/api/getJdPrice', { skuId });
  if (res.code !== 200throw res.msg;
  return res.data;
}
// 获取历史价格
async function getPriceHistory(skuId) {
  const res = await request('/api/getPriceHistory', { skuId });
  if (res.code !== 200throw res.msg;
  return res.data;
}
// 获取硬件列表
async function getHardwareList(type) {
  const res = await request('/api/getHardware', { type });
  if (res.code !== 200throw res.msg;
  return res.data;
}
// 云端配置相关
async function saveCloudConfig(configJson, totalPrice) {
  const openid = await app.getOpenid();
  const res = await request('/api/saveConfig', { openid, configJson, totalPrice }, "POST");
  return res;
}
async function getCloudConfig() {
  const openid = await app.getOpenid();
  const res = await request('/api/getUserConfig', { openid });
  return res.data;
}
async function deleteCloudConfig(id) {
  const res = await request('/api/delConfig', { id });
  return res;
}
module.exports = {
  request, jumpAddCart, getHardwarePrice, getPriceHistory, getHardwareList,
  saveCloudConfig, getCloudConfig, deleteCloudConfig
};

页面 1:首页 pages/index

index.wxml

<view class="container">
  <view class="header">
    <text class="title">DIY装机助手</text>
    <text class="desc">硬件兼容检测 · 京东实时价 · 一键加购</text>
  </view>
  <view class="start-box" bindtap="goSelect">
    <text class="start-text">开始选配主机</text>
    <text class="start-tip">自动过滤不兼容硬件</text>
  </view>
  <view class="func-row">
    <view class="func-item" bindtap="goMyConfig">
      <text class="func-icon"></text>
      <text class="func-text">我的云端配置</text>
    </view>
  </view>
</view>

index.wxss

page {background:#f5f7fa;padding:20rpx;}
.header{text-align:center;margin:40rpx 0 60rpx;}
.title{font-size:48rpx;font-weight:bold;color:#1f2937;display:block;margin-bottom:16rpx;}
.desc{font-size:26rpx;color:#6b7280;}
.start-box{background:linear-gradient(135deg,#2563eb,#3b82f6);border-radius:20rpx;padding:60rpx 0;text-align:center;margin-bottom:40rpx;}
.start-text{font-size:36rpx;color:#fff;font-weight:bold;display:block;}
.start-tip{font-size:24rpx;color:#dbeafe;margin-top:10rpx;}
.func-row{display:flex;gap:20rpx;}
.func-item{flex:1;background:#fff;text-align:center;padding:40rpx 0;border-radius:16rpx;}
.func-icon{font-size:40rpx;display:block;margin-bottom:10rpx;}
.func-text{font-size:28rpx;color:#374151;}

index.js

Page({
  goSelect(){wx.navigateTo({url:'/pages/select/select'})},
  goMyConfig(){wx.navigateTo({url:'/pages/myConfig/myConfig'})}
})

页面 2:硬件选配 pages/select

select.wxml

<view class="container">
  <scroll-view scroll-x class="tab-scroll">
    <view class="tab-item {{currentTab==index?'tab-active':''}}" wx:for="{{hardwareTypeList}}" wx:key="index" bindtap="switchTab" data-index="{{index}}">
      {{item.name}}
    </view>
  </scroll-view>
  <view wx:if="{{compatWarning.length>0}}" class="warn-box">
    <block wx:for="{{compatWarning}}" wx:key="idx"><text class="warn-text">{{item}}</text></block>
  </view>
  <scroll-view scroll-y class="hardware-list">
    <view class="hard-item {{item.disabled?'item-disabled':''}}" wx:for="{{hardList}}" wx:key="jd_sku_id" bindtap="selectHardware" data-sku="{{item.jd_sku_id}}">
      <view class="hd-left">
        <text class="hd-name">{{item.brand}} {{item.model}}</text>
        <text wx:if="{{item.socket}}" class="hd-socket">插槽:{{item.socket}}</text>
      </view>
      <view class="hd-right">
        <text class="hd-price">¥{{item.realPrice}}</text>
        <button size="mini" bindtap="showPriceChart" data-sku="{{item.jd_sku_id}}" catchtap="stopEvent">历史价</button>
      </view>
      <view wx:if="{{item.disabled}}" class="mask-tip">不兼容</view>
    </view>
  </scroll-view>
  <view class="bottom-bar">
    <view class="selected-info">已选{{selectedNum}}件 | 合计:<text class="total-price">¥{{totalPrice}}</text></view>
    <button class="go-cart-btn" bindtap="goCartList">查看配置清单</button>
  </view>
  <priceModal show="{{showModal}}" priceData="{{priceData}}" bind:close="closeModal"/>
</view>

select.wxss

page{background:#f5f7fa;}
.container{height:100vh;display:flex;flex-direction:column;}
.tab-scroll{display:flex;background:#fff;padding:20rpx;}
.tab-item{padding:16rpx 30rpx;font-size:28rpx;color:#666;border-radius:12rpx;margin-right:16rpx;white-space:nowrap;}
.tab-active{background:#2563eb;color:#fff;}
.warn-box{background:#fef2f2;padding:20rpx;margin:20rpx;border-radius:12rpx;}
.warn-text{display:block;font-size:26rpx;color:#dc2626;line-height:1.6;}
.hardware-list{flex:1;padding:20rpx;}
.hard-item{background:#fff;border-radius:16rpx;padding:30rpx;margin-bottom:20rpx;display:flex;justify-content:space-between;position:relative;}
.item-disabled{opacity:0.5;}
.mask-tip{position:absolute;left:0;top:0;width:100%;height:100%;background:rgba(0,0,0,0.3);color:#fff;display:flex;align-items:center;justify-content:center;font-size:32rpx;border-radius:16rpx;}
.hd-left{flex:1;}
.hd-name{font-size:30rpx;color:#111;display:block;}
.hd-socket{font-size:24rpx;color:#666;margin-top:8rpx;}
.hd-right{display:flex;flex-direction:column;align-items:flex-end;}
.hd-price{font-size:32rpx;color:#ef4444;font-weight:bold;margin-bottom:10rpx;}
.bottom-bar{display:flex;align-items:center;justify-content:space-between;padding:24rpx 30rpx;background:#fff;border-top:1rpx solid #eee;}
.selected-info{font-size:28rpx;}
.total-price{color:#ef4444;font-weight:bold;}
.go-cart-btn{background:#2563eb;color:#fff;border-radius:50rpx;padding:0 30rpx;}

select.js

const compatEngine = require('../../utils/hardCompat');
const { jumpAddCart, getHardwarePrice, getPriceHistory, getHardwareList } = require('../../utils/jdApi');
Page({
  data:{
    currentTab:0,
    hardwareTypeList:[{name:'CPU',type:'cpu'},{name:'主板',type:'mainboard'},{name:'显卡',type:'gpu'},{name:'内存',type:'ram'},{name:'固态',type:'ssd'},{name:'电源',type:'power'}],
    hardList:[],compatWarning:[],selectedNum:0,totalPrice:0,showModal:false,priceData:[]
  },
  onLoad(){this.loadHardwareList();},
  switchTab(e){
    const idx = e.currentTarget.dataset.index;
    this.setData({currentTab:idx});
    this.loadHardwareList();
  },
  async loadHardwareList(){
    const type = this.data.hardwareTypeList[this.data.currentTab].type;
    let list = await getHardwareList(type);
    if(type === 'mainboard' && compatEngine.selected.cpu){
      list = compatEngine.filterBoardByCpu(compatEngine.selected.cpu, list);
    }
    for(let i of list){
      const price = await getHardwarePrice(i.jd_sku_id);
      i.realPrice = price.realPrice;
      i.marketPrice = price.marketPrice;
    }
    this.setData({hardList:list});
    this.calcTotal();
    this.checkCompatWarn();
  },
  selectHardware(e){
    const sku = e.currentTarget.dataset.sku;
    const item = this.data.hardList.find(v=>v.jd_sku_id === sku);
    if(item.disabled){wx.showToast({title:item.compatTip,icon:'none'});return;}
    const type = this.data.hardwareTypeList[this.data.currentTab].type;
    compatEngine.selected[type] = item;
    this.calcTotal();
    this.checkCompatWarn();
  },
  async showPriceChart(e){
    e.stopPropagation();
    const sku = e.currentTarget.dataset.sku;
    const data = await getPriceHistory(sku);
    this.setData({showModal:true,priceData:data});
  },
  stopEvent(){},
  closeModal(){this.setData({showModal:false,priceData:[]});},
  calcTotal(){
    const s = compatEngine.selected;
    let total = 0,num=0;
    Object.values(s).forEach(i=>{if(i&&i.realPrice){total+=i.realPrice;num++;}});
    this.setData({totalPrice:total.toFixed(2),selectedNum:num});
  },
  checkCompatWarn(){this.setData({compatWarning:compatEngine.fullCheck()});},
  goCartList(){wx.navigateTo({url:'/pages/cartList/cartList'});}
})

页面 3:配置清单 pages/cartList

cartList.wxml

<view class="container">
  <view class="page-title">整机配置清单</view>
  <view wx:if="{{configList.length==0}}" class="empty-box">
    <text>暂无硬件,前往选配</text>
    <button bindtap="goSelect">去选配</button>
  </view>
  <scroll-view wx:else scroll-y class="list-wrap">
    <view class="list-item" wx:for="{{configList}}" wx:key="jd_sku_id">
      <view class="item-info">
        <text class="type-tag">{{item.typeName}}</text>
        <text class="name">{{item.brand}} {{item.model}}</text>
      </view>
      <view class="item-price">¥{{item.realPrice}}</view>
      <view class="item-op">
        <button size="mini" bindtap="singleAddCart" data-sku="{{item.jd_sku_id}}">京东加购</button>
        <button size="mini" bindtap="deleteItem" data-sku="{{item.jd_sku_id}}">移除</button>
      </view>
    </view>
  </scroll-view>
  <view class="total-box"><text class="total-text">整机总价:</text><text class="total-num">¥{{totalPrice}}</text></view>
  <view class="btn-row">
    <button class="btn-save" bindtap="saveConfig">云端保存配置</button>
    <button class="btn-all-cart" bindtap="batchAddCart">批量加入京东购物车</button>
  </view>
</view>

cartList.wxss

page{background:#f5f7fa;padding:30rpx;}
.page-title{font-size:40rpx;font-weight:bold;text-align:center;margin-bottom:40rpx;color:#1f2937;}
.empty-box{text-align:center;margin-top:200rpx;color:#999;font-size:30rpx;}
.list-wrap{height:calc(100vh - 320rpx);}
.list-item{background:#fff;border-radius:16rpx;padding:30rpx;margin-bottom:20rpx;}
.type-tag{font-size:22rpx;background:#dbeafe;color:#2563eb;padding:4rpx 12rpx;border-radius:6rpx;}
.name{font-size:28rpx;margin-left:10rpx;color:#333;}
.item-price{font-size:30rpx;color:#ef4444;margin:16rpx 0;}
.item-op{display:flex;gap:20rpx;}
.total-box{text-align:right;padding:30rpx 10rpx;border-top:1rpx solid #eee;margin-top:20rpx;}
.total-text{font-size:32rpx;}
.total-num{font-size:38rpx;color:#ef4444;font-weight:bold;}
.btn-row{display:flex;gap:20rpx;margin-top:30rpx;}
.btn-save{flex:1;background:#10b981;color:#fff;}
.btn-all-cart{flex:1;background:#e11d48;color:#fff;}

cartList.js

const compatEngine = require('../../utils/hardCompat');
const { jumpAddCart, saveCloudConfig } = require('../../utils/jdApi');
Page({
  data:{configList:[],totalPrice:0},
  onShow(){this.buildConfigList();},
  buildConfigList(){
    const s = compatEngine.selected;
    const typeMap = {cpu:"处理器",mainboard:"主板",gpu:"显卡",ram:"内存",ssd:"固态硬盘",power:"电源"};
    const list = [];let total=0;
    Object.keys(s).forEach(k=>{
      const item = s[k];
      if(item){
        list.push({...item,typeName:typeMap[k]});
        total += Number(item.realPrice);
      }
    });
    this.setData({configList:list,totalPrice:total.toFixed(2)});
  },
  singleAddCart(e){jumpAddCart(e.currentTarget.dataset.sku);},
  deleteItem(e){
    const sku = e.currentTarget.dataset.sku;
    const target = this.data.configList.find(v=>v.jd_sku_id === sku);
    const map = {"处理器":"cpu","主板":"mainboard","显卡":"gpu","内存":"ram","固态硬盘":"ssd","电源":"power"};
    compatEngine.selected[map[target.typeName]] = null;
    this.buildConfigList();
  },
  batchAddCart(){
    const list = this.data.configList;
    if(!list.length){wx.showToast({title:'暂无硬件',icon:'none'});return;}
    wx.showModal({
      title:'批量加购',content:'将依次跳转京东添加全部硬件',
      success:res=>{if(res.confirm) list.forEach((item,i)=>setTimeout(()=>jumpAddCart(item.jd_sku_id),i*1200));}
    })
  },
  async saveConfig(){
    const {configList,totalPrice} = this.data;
    if(!configList.length){wx.showToast({title:'无硬件无法保存',icon:'none'});return;}
    await saveCloudConfig(configList, totalPrice);
    wx.showToast({title:'云端保存成功'});
  },
  goSelect(){wx.navigateBack();}
})

页面 4:我的配置 pages/myConfig

myConfig.wxml

<view class="container">
  <view class="title">我的云端保存配置</view>
  <view wx:if="{{configList.length==0}}" class="empty"><text>暂无保存的装机方案</text></view>
  <scroll-view wx:else scroll-y>
    <view class="config-item" wx:for="{{configList}}" wx:key="id">
      <view class="config-top">
        <text class="cfg-time">{{item.createTime}}</text>
        <text class="cfg-total">总价 ¥{{item.total}}</text>
      </view>
      <view class="cfg-hard-list"><text wx:for="{{item.hardList}}" wx:key="jd_sku_id">{{item.brand}}{{item.model}} </text></view>
      <view class="cfg-btn-row">
        <button size="mini" bindtap="loadConfig" data-data="{{item}}">载入选配</button>
        <button size="mini" bindtap="delConfig" data-id="{{item.id}}">删除</button>
      </view>
    </view>
  </scroll-view>
</view>

myConfig.wxss

page{background:#f5f7fa;padding:30rpx;}
.title{font-size:40rpx;font-weight:bold;text-align:center;margin-bottom:40rpx;}
.empty{text-align:center;margin-top:200rpx;font-size:30rpx;color:#999;}
.config-item{background:#fff;border-radius:16rpx;padding:30rpx;margin-bottom:24rpx;}
.config-top{display:flex;justify-content:space-between;margin-bottom:16rpx;}
.cfg-time{font-size:24rpx;color:#666;}
.cfg-total{font-size:28rpx;color:#ef4444;font-weight:bold;}
.cfg-hard-list{font-size:26rpx;color:#444;line-height:1.6;margin-bottom:20rpx;}
.cfg-btn-row{display:flex;gap:20rpx;justify-content:flex-end;}

myConfig.js

const compatEngine = require('../../utils/hardCompat');
const { getCloudConfig, deleteCloudConfig } = require('../../utils/jdApi');
Page({
  data:{configList:[]},
  async onShow(){
    const list = await getCloudConfig();
    this.setData({configList:list});
  },
  loadConfig(e){
    const data = e.currentTarget.dataset.data;
    compatEngine.clearAll();
    const map = {"处理器":"cpu","主板":"mainboard","显卡":"gpu","内存":"ram","固态硬盘":"ssd","电源":"power"};
    data.hardList.forEach(item=>compatEngine.selected[map[item.typeName]] = item);
    wx.navigateTo({url:'/pages/select/select'});
  },
  async delConfig(e){
    const id = e.currentTarget.dataset.id;
    await deleteCloudConfig(id);
    wx.showToast({title:'已删除'});
    this.onShow();
  }
})

组件:历史价格弹窗 components/priceModal

priceModal.wxml

<view wx:if="{{show}}" class="modal-mask" bindtap="close">
  <view class="modal-wrap" catchtap="stop">
    <view class="modal-head">
      <text class="modal-title">近30天历史价格</text>
      <text class="close-icon" bindtap="close">×</text>
    </view>
    <view class="chart-box">
      <view class="price-row" wx:for="{{priceData}}" wx:key="date">
        <text>{{item.date}}</text>
        <text>¥{{item.price}}</text>
      </view>
    </view>
  </view>
</view>

priceModal.wxss

.modal-mask{position:fixed;left:0;top:0;width:100%;height:100%;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:999;}
.modal-wrap{width:88%;background:#fff;border-radius:20rpx;overflow:hidden;}
.modal-head{display:flex;justify-content:space-between;align-items:center;padding:30rpx;border-bottom:1rpx solid #eee;}
.modal-title{font-size:32rpx;font-weight:bold;}
.close-icon{font-size:40rpx;color:#999;}
.chart-box{padding:30rpx;max-height:60vh;overflow-y:auto;}
.price-row{display:flex;justify-content:space-between;padding:12rpx 0;font-size:26rpx;border-bottom:1rpx #f0f0f0 solid;}

priceModal.js

Component({
  properties:{show:Boolean,priceData:Array},
  methods:{close(){this.triggerEvent('close');},stop(){}}
})

四、完整部署操作步骤

1. 数据库

  • 服务器安装 MySQL8.0
  • 执行 sql/pc_assistant.sql 创建库、表、测试硬件数据
  • 修改 server/config.js 数据库账号密码

2. 后端服务

  • 上传 server 文件夹到服务器
  • cd server && npm install
  • 填入京东联盟 appKey/appSecret
  • pm2 start app.js 常驻启动
  • 安全组开放 3000 端口

3. 微信小程序

  1. 修改 app.js globalData.baseUrl 为 http://服务器公网IP:3000
  2. 开发工具上传代码
  3. 小程序后台开发设置:
  4. 不校验合法域名(开发调试)
  5. 业务域名添加服务器 IP
  6. 跳转小程序白名单添加京东小程序

五、项目扩展与注意事项

  1. 京东 API 限制:个人联盟调用频次有限,企业资质接口额度更高;无密钥可替换第三方商品价格爬虫接口
  2. HTTPS:上线必须后端配置 Nginx 反向代理 HTTPS,小程序仅支持 https 域名
  3. 图表:可引入 wx-charts 替换历史价格纯文本列表,实现折线图
  4. 兼容规则拓展:hardCompat.js 内新增 DDR4/DDR5、机箱显卡长度校验
  5. 登录完善:当前 openid 为模拟,对接微信 code2session 获取真实用户 openid 实现云端配置区分用户
© 版权声明

相关文章

1 条评论

none
暂无评论...