简易电商系统开发记录(Nuxtjs+Koa)

简易电商系统开发记录(Nuxtjs+Koa)

使用 Nuxtjs + Koa + Mysql 实现的极简风格电商系统,此为个人大学课余期间(2018)的练手项目,部分功能未完全实现。

DEMO 系统在线预览地址: http://eidea.kongfandong.cn

系统功能

✅ 登录注销
✅ 首页商品轮播
✅ 首页商品推荐
✅ 商品分类搜索
✅ 商品详情
✅ 购物车增删改
✅ 商品收藏
✅ 生成订单
❌ 个人资料/地址管理
❌ 商品规格模块
❌ 支付模块

Nuxtjs 相关

axios 封装

封装 axios,加入拦截器,统一处理接口返回的错误请求,只有code为 200 才为正确请求,其余统一弹窗错误message

Nuxtjs 一个特性就是服务端渲染,同时可以先由服务端请求到异步初始数据后再直出页面,使用asyncData方法。若要在 asyncData 里面使用封装后添加了拦截器的 axios,则需要将 axios 实例注入到 nuxt 上下文中。这里采用了同时注入的方法将 Axios 注入 Vue 实例与 context 上下文中。

// axios.js
import axios from "axios";
const baseURL = "/api/";
const onRequest = (config) => config;
const onResponse = (response) => {
  if (response.data.code == 200) {
    return response;
  } else {
    return Promise.reject(
      (response.data && response.data.message) || "未知错误"
    );
  }
};
const onRequestError = (err) => Promise.reject(err);
const onResponseError = (err) => Promise.reject(err);
const instance = axios.create({
  baseURL,
  headers: {
    "Content-Type": "application/json",
  },
});
instance.interceptors.request.use(
  (config) => onRequest(config),
  (err) => onRequestError(err)
);
instance.interceptors.response.use(
  (response) => onResponse(response),
  (err) => onResponseError(err)
);
const get = (url, params) => instance.get(url, { params });
const post = (url, data) => instance.post(url, data);
export { get, post };
export default ({ app, store }, inject) => {
  const nuxtAxios = app.$axios;
  nuxtAxios.setHeader("Content-Type", "application/json");
  nuxtAxios.onRequest((config) => onRequest(config));
  nuxtAxios.onRequestError((err) => onRequestError(err));
  nuxtAxios.onResponse((response) => onResponse(response));
  nuxtAxios.onResponseError((err) => onResponseError(err));
  inject("get", (url, params) => nuxtAxios.$get(baseURL + url, { params }));
  inject("post", (url, data) => nuxtAxios.$post(baseURL + url, data));
  inject("baseURL", baseURL);
};

其余插件

加入购物车动画

使用定位 + transition + transform + 贝塞尔曲线模拟抛物线动画效果

抛物线动画

将过渡动画 left 设为线性,top 设为为 cubic-bezier(0.56, 0.15, 0.43, 0.85)时,在执行过渡时就能模拟出元素一个抛物线运动,曲线函数可以在 Chrome 控制台试出来

// 主要代码
showAnimateAddCart () {
  const imgEl = document.getElementById('goodsImg')
  const { offsetTop, offsetLeft, offsetWidth, offsetHeight } = imgEl
  const newImgEl = new Image()
  newImgEl.src = this.goodsimg
  newImgEl.style.cssText = `
    position:fixed;
    width: ${offsetWidth}px;
    height: ${offsetHeight}px;
    left: ${offsetLeft}px;
    top: ${offsetTop}px;
    opacity: 1;
    transform: rotate(0)`
  document.body.appendChild(newImgEl)
  const cartEl = document.getElementById('ShoppingCartBtn')
  const { offsetTop: cartTop, offsetLeft: cartLeft } = cartEl.parentNode
  newImgEl.style.cssText = `
    position:fixed;
    width: 0;
    height: 0;
    left: ${cartLeft}px;
    top: ${cartTop}px;
    opacity: 0;
    transform: rotate(360deg);
    transition: width 1s,
    height 1s,
    left 1s,
    top 1s cubic-bezier(0.56, 0.15, 0.43, 0.85),
    opacity 1s ease-out,
    transform 1s ease-out`
  setTimeout(() => {
    document.body.removeChild(newImgEl)
  }, 2000)
}

关于 Nuxtjs 部署

因为网站有动态数据,所有无法使用 npm run generate 的方式生成静态资源来部署。只能通过 npm run build,然后 npm run start 启动一个服务来跑。这是还是要保留开发环境用的 proxy 环境,使用反向代理方式代理所有后端接口。

// nuxt.config.js
proxy: {
  '/api/': {
    target: 'http://localhost:3001',
    pathRewrite: {
      '^/api/': ''
    }
  }
}

后端 Koa 相关

后端只用作一个普通的接口服务器,涉及的技术不多,只用到了几个基本的 koa 插件

  • koa-router
  • koa-session
  • koa-static
  • koa-bodyparser
  • mysql

备注

  1. SQL 语句尽量都采用escaping query values,使用?占位符代替变量,可以防止 SQL 注入攻击
  2. SQL 查询分页,查询总数时可以采用子查询方法,虽然性能差别不大,但是代码看上去可以较简洁
// 搜索商品
router.get("/query", async (ctx) => {
  const {
    sex,
    classify,
    page = 1,
    pageSize = 12,
    minPrice = 0,
    maxPrice = 99999,
    order = "default",
    word,
  } = ctx.query;
  let sql = `select * from goods where goodsprice between ? and ? `;
  let paramsArr = [minPrice, maxPrice];
  if (sex) {
    sql += `and sex = ? `;
    paramsArr.push(sex);
  }
  if (classify) {
    sql += `and classify = ? `;
    paramsArr.push(classify);
  }
  if (word) {
    sql += `and goodsname like ? or goodsdetail like ? `;
    paramsArr.push(`%${word}%`, `%${word}%`);
  }
  const totalSql = `select count(*) as total from (${sql}) as temp `;
  const totalResult = await query(totalSql, paramsArr);
  if (!totalResult) {
    ctx.body = r.error();
    return;
  }
  if (order) {
    if (order === "low") {
      sql += `order by goodsprice `;
    } else if (order === "high") {
      sql += `order by goodsprice desc `;
    }
  }
  sql += `limit ?, ? `;
  paramsArr.push((page - 1) * pageSize, pageSize);
  const result = await query(sql, paramsArr);
  if (!result) {
    ctx.body = r.error;
    return;
  }
  ctx.body = r.successPage(result, page, pageSize, totalResult[0].total);
});

网站截图

  1. 首页 首页.jpg
  2. 搜索页 搜索.jpg
  3. 购物车 购物车.jpg
  4. 订单 订单.jpg
  5. 个人中心 个人中心.jpg

PS: 该项目仅作学习交流所用,不可作商业用途,所有图片来源于网上

以上内容未经授权请勿随意转载。

DEMO 系统在线预览地址: http://eidea.kongfandong.cn