用 Vue+Node 搭建网上购物商城——记录篇

5,173 阅读5分钟

PC端网上购物商城

前言

项目采用的技术栈: Vue+Node+MySQL
前端:用Vue-cli搭建,使用Vue全家桶+element-ui
后端:express框架
数据库:MySQL

其实项目初版本早就写完了,但感觉完成度不高,所以现在想完善这个项目,熟练下vue和node的开发运用,然后也记录一下我的开发过程

一、功能

普通用户

  • 注册、登录(图形验证码)
  • 定位 (腾讯地图定位功能)、自主选择所在城市
  • 商品
    • 分类
    • 简单展示商品
    • 查看商品详情
    • 商品评论
  • 分页功能
  • 购物车功能
    • 加入购物车
    • 购物车商品数量增减
    • 清空购物车
    • 商品结算
  • 多关键词模糊搜索商品 (关键词需为商品名称)
  • 用户个人中心
    • 修改用户信息 (头像、昵称、简介...)
    • 修改手机号
    • 修改密码

管理员

  • 登录(固定账号密码)
  • 查看数据库商品信息
  • 商品上架(添加商品)
  • 删除/修改商品
  • 分页功能

运行

项目后端服务器是基于node、MySQL开发,运行前请确认系统已安装相关应用

服务器端

  • cd web-server
  • 将web-server文件夹下的shop.sql导入MySQL数据库中
  • npm install 安装依赖
  • 修改web-server/src/config.js文件,此文件记录项目的全局变量,在文件中找到password、database、user属性,根据你的数据库信息修改它们的值
  • npm run dev 在本地运行,启动服务器

客户端

  • cd web-client
  • npm install 安装依赖
  • npm run dev 在本地运行
  • 接着就可以在http://localhost:8080下访问到该项目

二、项目效果预览

商城前台部分

首页

选择所在城市

首页右侧的购物车提示区

分类展示页面

分类展示页

购物车

商品详情页

商品详情页

登录/注册

登录/注册 登录/注册

用户中心

商城后台部分

商品信息页

商品信息页

添加商品

添加商品

三、目录结构

web-client 前端部分:
├── build                      // 构建相关  
├── config                     // 配置相关
├── src                        // 源代码
│   ├── api                    
│   |   ├── ajax.js            // 使用axios,封装ajax
│   |   ├── index.js           // 后端接口,封装请求接口
│   ├── common                 // 字体、图片等静态资源
│   ├── components             // 全局公用组件
│   ├── config                 // 全局配置
│   |   ├── filters.js         // 全局过滤器
│   |   ├── utils.js           // 全局方法
│   |   ├── area.js            // 全国城市区域,配合element-ui级联选择器
│   ├── pages                  // 项目视图页面
│   ├── router                 // 路由配置
│   ├── store                  // Vuex数据存储器
│   ├── App.vue                // 入口页面
│   ├── main.js                // 入口文件
├── static                     // 静态资源
├── .babelrc                   // babel-loader 配置
├── .gitignore                 // git 忽略
├── index.html                 // 项目html
└── package.json               // 包依赖配置

web-server 后端部分:
├── db                         // MySQL数据连接  
├── public                     // 静态资源
├── routes                     // 后端路由,项目接口
├── static                     // 静态资源
├── src                        // 源代码
│   ├── app.js                 // 项目入口文件,配置,初始化
│   ├── config.js              // 全局公用参数记录
├── utils                      // 全局公用方法
├── views                      // 视图页面
├── .babelrc                   // babel-loader 配置
├── main.js                    // 入口文件
├── package.json               // 包依赖配置
├── README.md                  // 项目说明文件
└── shop.sql                   // 注入MySQL的sql语句

四、开发中的一些细节

1. 跨域问题

  • 后端部分使用cors来解决跨域
    cd web-server
    npm i -S cors
    
    // 在 web-server/src/app.js 中引入
    import cors from 'cors'
    app.use(cors());
    
  • 前端部分进行跨域配置
    // 在 web-client/config/index.js 中进行配置
    proxyTable: {
        '/api': {  //使'api'代替服务器地址
           target: 'http://localhost:3000/', //源地址
           changeOrigin: true, //改变源
           pathRewrite: {
              '^/api': '' //路径重写
            }
        },
    },
    

2. 登录的图形验证码

  • 项目中用的是node中的svg-captcha模块
import svgCaptcha from 'svg-captcha'

router.get('/api/captcha', (req, res) => {
    // 生成随机验证码
    let captcha = svgCaptcha.create({
        color: true,
        noise: 3,
        ignoreChars: '0o1iIO',
        size: 4 
    });

    // 保存到session
    req.session.captcha = captcha.text.toLocaleLowerCase(); // 验证码的文本

    // 返回客户端,客户端接收到的是svg格式
    res.type('svg');
    res.send(captcha.data);
});
  • 除获取后端生成的图形验证码,前端部分还需实现点击验证码,能刷新验证码功能:
<img 
  ref="captcha" 
  src="http://localhost:3000/api/captcha" 
  @click.prevent="getCaptcha()"
>

// 获取图形验证码
getCaptcha() {
  // 通过在url后面添加查询字符串 ?time=' + new Date(),
  // 这样当用户每次点击验证码时,src的地址都不一样,浏览器将重新发起请求,而不是简单的304缓存
  this.$refs.captcha.src = 'http://localhost:3000/api/captcha?time=' + new Date();
}

3. session失效

项目中用的是express-session来记录session,主要是记录已登录的用户id和当前前端页面获取的图形验证码文本。最开始,前端部分跨域配置中源地址写的是:http://127.0.0.1:3000/,且获取图形验证码中也使用的<img src:"http://127.0.0.1:3000/api/captcha">,但后端中就是获取不到session值。

后来看到别人的项目总结里面也发现类似问题,提到说:后端验证时,http://localhost:3000http://127.0.0.1:3000代表的不是同一个客户端,所以访问不到session

于是尝试把前端部分关于后端接口的地址全都改为http://localhost:3000/xxx形式,验证后,还真成功了

4. 按需加载组件(优化首屏加载速度)

  • 路由懒加载
    // web-client/src/router/index.js 示例
    const Home = ()=> import('./../pages/Home/Home');
    
    routes: [
    	{
    	  path: '/home',
    	  component: Home
    	},
    ]
    

5. 前台页面顶部搜索区的显示和隐藏

  • 通常在购物商城中,会发现页面顶部会实现搜索框,考虑到它在多个页面都会出现,于是封装成全局组件,即 src\components\HeaderSearch\HeaderSearch.vue 并在App.vue中引入
  • 然后问题出现了,有些页面其实并不需要出现搜索框,比如商品详情页、登录/注册..于是决定在router内动态设置meta,来控制全局组件的显示和隐藏
    // 在 router\index.js 中设置mate  示例
    routes: [
    	{
    	  path: '/home',
    	  component: Home,
    	  // true 代表该路由页面显示相关的全局组件
    	  meta: {showHeaderTop: true, showHeaderSearch: true}
    	},
    ]
    
    // 在 App.vue 中根据 mate 控制组件显示与隐藏
    <template>
      <div id="app">
    	<HeaderTop v-show="$route.meta.showHeaderTop"/>  
            <HeaderSearch v-show="$route.meta.showHeaderSearch"/>
        	<keep-alive>
              <router-view></router-view>
            </keep-alive>
      </div>
    </template>
    

6. 使用三级表达式出错

在Vue中,我们通常使用{{data}}插值表达式来页面取值,但是遇到需要异步请求获取数据后,再渲染到页面上时,实际上,页面渲染时会先显示数据的初始值,请求得到数据后,才会正常显示。所以有些三级表达式刚开始加载,得不到数据会报错。此时,可以结合v-if解决,在明确得到数据后,再渲染元素

7. 封装Node与MySQL连接

  • 可以新建一个db\db.js文件,里面放数据库连接的代码,最后用export default conn;输出conn,这样在项目中操作数据库时,只需要连接一次即可,提升效率
    import mysql from 'mysql'
    import config from '../src/config'
    
    const  conn = mysql.createConnection({
        host: config.host, // 数据库的地址
        user: config.user, // 账号
        password: config.password, // 密码
        database: config.database, // 数据库名称
        multipleStatements: true,  // 允许多条sql同时查询
    });
    
    conn.connect();
    
    export default conn;
    

五、疑问

这个项目初版本是19年5月左右写的,所以跟现在的vue脚手架创建的项目结构会稍有不同,但其代码是适用vue2.x版本的,对于初学者应该也是一个不错的练手项目

1. 无法找到static文件夹

这个问题只需在web-client目录下新建一个static文件夹,重新运行即可

2. 注册后台管理员

项目没有实现后台管理员的增删改查,这也是个不足之处,小伙伴们可仿照之前的接口用例自己实现。默认管理员的账号密码都是admin

3. 后台商品无法添加

很可能是因为web-server/public目录下没有uploads文件夹,导致上传的商品图片无法保存,可以依此新建一个uploads文件夹,重新运行即可