vue 购物 WebApp---蘑菇购

vue 购物 WebApp---蘑菇购 项目概述 简介 项目名 蘑菇购 ,与一般购物 WebApp 类似,包括首页

本文包含相关资料包-----> 点击直达获取<-------

vue 购物 WebApp---蘑菇购

项目概述

简介

项目名 蘑菇购 ,与一般购物 WebApp 类似,包括首页、分类、购物车、个人中心、详情。

项目基于 vue vue-router vue-cli3 api 请求相关部分采用 axios ,数据部分并非来自服务器,而是本地基于 express 启动相关数据服务。原因一是网络接口更新快、数据变化大、依赖性高,二是项目本身不大,基于项目启动本机服务灵活性较高,代码安装依赖即可运行,故最终考虑 express 爬取相关接口数据保存本地。

状态管理未使用 vuex ,仅仅是少部分使用 vuex 功能显得多余,项目大才完全有必要。基于 vue.observable 能实现部分 vuex 功能。图片加载部分异步更新 DOM 采用事件总线进行组件通讯。

项目第三方开源组件包括 better-scroll 滚动插件、 vue-awesome-swiper swiper 轮播组件、 normalize.css 初始化样式、 vue-lazyload 懒加载、移动端 click300ms 延时采用 fastclick

项目难度不高,适合新手练手,此篇仅是练习组件化封装和目录配置的相关记录。

预览地址

蘑菇购

示例图

文件目录配置

```javascript ├── public │ ├── favicon.ico │ ├── index.html ├── server │ ├── static │ │ ├── image │ ├── app.js │ ├── db.js │ ├── router.js ├── src │ ├── api │ │ ├── home.js │ │ ├── category.js │ ├── assets │ │ ├── iconfont │ │ ├── img │ │ ├── placeholder.png │ ├── components │ │ ├── BetterScroll │ │ ├── CheckButton │ │ ├── IndexBar │ │ ├── Message │ │ │ ├── Message.vue │ │ │ ├── index.js │ │ ├── Navbar │ │ ├── Swiper │ │ ├── SwiperSlide │ │ ├── Tabbar │ │ ├── TabbarItem │ ├── layout │ │ ├── Tabbar │ ├── router │ │ ├── index.js │ │ ├── routes.js │ ├── store │ │ ├── index.js │ │ ├── vuex.js │ ├── styles │ │ ├── index.less │ ├── utils │ │ ├── index.js │ │ ├── request.js │ ├── views │ │ ├── home │ │ ├── category │ │ ├── cart │ │ ├── profile │ │ ├── detail │ ├── App.vue │ ├── main.js │ ├── .env.development │ ├── package.json │ ├── README.md │ ├── vue.config.js

```

初始化

脚手架初始化

初始空脚手架 vue-cli3 仅配置 Babel Router CSS Pre-processors less ),删除其余业务不相关部分,文件夹部分通过需求逐步新建。

Tabbar 组件、路由

项目目前正常运行为空白,先搭建路由相关部分,抽离 routes 静态数据,同级目录下新增 routes.js 导出静态数据, index.js 引入静态数据。

```javascript // router -> index.js import Vue from 'vue' import VueRouter from 'vue-router' import routes from './routes'

Vue.use(VueRouter)

const router = new VueRouter({ mode: 'history', routes })

export default router

// router -> routes.js export default [{ path: '/', redirect: '/home' }, { path: '/home', name: 'home', component: () => import('views/home') } ... ]

```

项目启动,会发生路由路径加载错误,需要配置文件夹别名,空脚手架不含 vue.config.js ,需要手动新增。路径 src/views 修改别名 views ,其余别名后续会用到,全部配置。

```javascript // vue.config.js const path = require('path')

function resolve(dir) { return path.join(__dirname, dir) }

module.exports = { chainWebpack: (config) => { config.resolve.alias .set('@', resolve('src')) .set('views', resolve('src/views')) ... } }

```

文件夹别名配置后,懒加载路径下并没有文件。 views 新增 home 文件夹,其下新增 index.vue ,其余文件同理,重启运行。

此时项目依旧空白,但是 home index.vue 已被重定向,接下来封装 Tabbar Tabbar 较为公共, components 下新建 Tabbar TabbarItem ,文件夹下均新增 index.vue Tabbar 一般高度 49px 最为舒适,同时定位屏幕底部,层级高于其他组件。 TabbarItem 内部引入 router-link ,组件接收参数参考 vant-ui 并做了部分修改,通过当前 routes.path 参数和计算属性配置高亮。

```javascript // Tabbar -> index.vue

// TabbarItem -> index.vue

export default { props: { to: String, activeColor: String, inactiveColor: String }, computed: { path() { return this.$route.path } } }

```

公共组件 Tabbar 封装完成,项目相关 Tabbar 还未封装及引用。由于项目相关 Tabbar 有关于项目页面布局。故 src 下新增 layout 文件夹,相关布局组件不会太多,不用文件夹下再放 index.vue 形式。

```javascript // layout -> Tabbar.vue 首页 ...

import Tabbar from "components/Tabbar" import TabbarItem from "components/TabbarItem"

export default { components: { MTabbar: Tabbar, MTabbarItem: TabbarItem }

```

图标采用 iconfont ,官网选择合适的 Tabbar 图标,下载压缩包解压。 assets 文件夹下新建 iconfont 文件夹,引入解压的全部文件。其中 demo_index.html 关于字体图标使用方式做了详细阐述, iconfont.css 需要手动引入, iconfont 也是一种字体,最终归结为 css 样式。 src 下新建 styles 文件夹,创建 index.less index.less 放置公共初始化样式, main.js 最终引入 index.less

```javascript ├── iconfont │ ├── demo_index.html │ ├── demo.css │ ├── iconfont.css ...

// index.less @import '~assets/iconfont/iconfont.css'

// main.js import 'styles/index.less'

```

Tabbar 业务组件封装完成, App.vue 引入,项目运行下 Tabbar 展示屏幕底部,点击 Tabbar 发生路由跳转和 URL 更新。

```javascript // App.vue

import Tabbar from "layout/Tabbar"

export default { components: { Tabbar } }

```

样式、页面标题、图标初始化

路由重定向至 home 页面,发现 body 存在 margin ,安装 normalize.css mian.js 引入。

```javascript // 安装 cnpm i normalize.css --save

// main.js import 'normalize.css'

```

styles 文件夹下 index.less ,初始化 html body #app 高度,删除 App.vue 相关样式。

```javascript html, body,

app {

height: 100%;

}

```

路由添加导航守卫 router.beforeEach ,用于初始化页面标题,但是目标路由 to 并不含有 meta.title ,修改 routes.js ,其余同理,正确运行页面标题切换。替换 public favicon.ico ,项目刷新显示图标。

```javascript // router -> index.js router.beforeEach((to, from, next) => { document.title = to.meta.title next() })

// router -> routes.js { path: '/home', name: 'home', meta: { title: '首页' }, component: () => import('views/home') },

```

NavBar

NavBar 也是一般较通用组件, components 新建 NavBar ,组件开放插槽,一般高度 44px 最为舒适,路由页面、详情均使用,组件传值 background-color home 页引入,页面引入组件顺序遵循引入公共组件、定制组件、公共 js 、定制 js

数据服务

Express

项目目前可实现路由跳转,相关 api 以及数据还未准备。网络接口常更新、数据不稳定,采用 express superagent 爬取保存接口数据,爬虫 crawler 参考其他文章。大致拆分项目需要用到的后端接口,首页轮播图、特色、推荐、详情、列表、分类等,项目新建 serve 文件夹。 image 保存数据图片。

```javascript ├── serve │ ├── static │ │ ├── image │ ├── app.js │ ├── db.js │ ├── router.js

```

app.js 启动数据服务,开放静态 static 文件夹,映射 /static serve/static

```javascript app.use("/static/", express.static("./serve/static/"))

```

db.js 本地数据库, baseURL 为本机局域网 ip ,便于移动端访问本机数据,也方便调试。项目之前使用 ipconfig 手动输入的方式,这种方式不免显得繁琐,数据服务一启动便与项目没有实际关联性,没有必要再去修改一次 ip 地址。故使用 os 模块动态获取本机局域网 ip ,当然此种方式如若 PC 端访问图片失败,大概率是动态获取 ip 部分有误,注释相关代码,通过上一种方式修改 ip 即可。

```javascript const os = require("os")

const interfaces = os.networkInterfaces()

const port = 3000

var baseURL = "http://127.0.0.1:3000"

for (const key of Object.keys(interfaces)) { const el = interfaces[key].find(el => el.family === "IPv4" && el.address !== "127.0.0.1")

el && (baseURL = http://${el.address}:${port} ) }

```

router.js 后端路由部分,由于业务相关接口不是特别多,不用 router 再去分级,也不存在 post 相关请求,不需要额外安装 body-parser

```javascript const db = require('./db')

router.get('/api/getBann', function (req, res) { res.send({ message: "success", result: db.banner, status: "0", success: true }) }) ...

```

package.json 配置快速启动命令。

```javascript // 安装 cnpm i nodemon --save-dev

scripts: { serve: "nodemon serve/app.js" }

```

axios、API 封装

项目使用 axios 第三方插件,安装步骤参考 axios

```javascript ├── src │ ├── api │ │ ├── home.js │ ├── utils │ │ ├── request.js ├── .env.development ├── vue.config.js

```

request.js baseURL 独立出来,使用环境变量,放置 utils 工具类函数文件夹下。

```javascript const server = axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 5000, })

```

开发与产品 URL 一般不一致,通常是配置环境变量,根目录创建 .env.development 文件,后期需要添加 .env.production 配置产品环境变量。

```javascript VUE_APP_BASE_API = "/api"

```

项目下尝试访问 express 请求通常情况会发生跨域报错,服务端可设置跨域部分,或者项目设置代理。

```javascript // vue.config.js devServer: { port: 8000, proxy: { [process.env.VUE_APP_BASE_API]: { target: 'http://127.0.0.1:3000/', ws: false, changeOrigin: true, pathRewrite: { ['^' + process.env.VUE_APP_BASE_API]: '' } } }, }

```

引入 request.js ,设置请求 url 、请求方式,页面引用。

```javascript // api.js => home.js import request from 'utils/request'

export function getBann() { return request({ url: '/api/getBann', method: 'get' }) }

// 引用页面 import { getBann } from "api/home"

getBann() .then((res) => {...}) .catch((err) => {...})

```

BetterScroll、vue-awesome-swiper

项目涉及第三方组件主要是 BetterScroll vue-awesome-swiper BetterScroll 也是公共组件, components 新建 BetterScroll 文件夹,详细步骤参考 better-scroll swiper 也是较为公共的组件, components 新建 Swiper SwiperSlide vue-awesome-swiper 版本造成的坑比较多,主要由于 vue-awesome-swiper swiper 的版本不适应造成,建议使用 4.1.1 5.2.0 ,详细步骤参考 vue-awesome-swiper

页面

首页

目前基础架子基本搭建完成, vuex 状态管理部分暂不考虑,实际用到的时候自然带入。首页组件已含有 NavBar ,调整首页目录结构,组件命名尽量语义化,后期维护非常方便。

```javascript ├── home │ ├── index.vue │ ├── components │ │ ├── RecommendView.vue │ │ ├── FeatureView.vue │ │ ├── CardList.vue │ │ ├── CardListItem.vue

```

首页组件树结构,浏览器安装 devtools 工具非常直观。

```javascript ▼

```

NavBar 默认 fiexed 定位屏幕顶部,会导致遮住 better-scroll home 使用伪元素 before 规避。且 better-scroll 外层 wrapper 需要指定高度,尽量加上相对定位。

```javascript // styles -> index.less .m-home::before{ content: ''; display: block; height: 44px; width: 100%; }

// home -> index.vue .scroll { height: calc(100vh - 93px); overflow: hidden; position: relative; }

```

轮播图获取等数据接口,页面调用都需要 api 文件夹文件声明接口再引入。

```javascript // api -> home.js export function getRecom() { return request(...) }

// home -> index.vue import { getBann } from "api/home"

```

IndexBar 也是公共组件, components 新建 IndexBar ,组件参数传递数组,存在高亮切换和点击事件的抛出,同时含默认高亮,则将 IndexBar 封装 v-model 形式。 props 增加组件可复用性,不仅仅只依赖于 data 内数据 label-value 对形式,传递 props 可依赖多种形式。 model props.data 是封装自定义组件 v-model 必备,具体步骤参考官方 v-model index-bar-item 点击调用 change ,实现 v-model

```javascript // IndexBar -> index.vue

...

export default { props: { data: { type: Array, default: () => [], }, value: {}, props: { type: Object, default: () => ({ label: "label", value: "value", }), }, }, model: { value: "value", event: "change", }, methods: { itemClick(item) { item[this.props.value] !== this.value && this.$emit("change", item[this.props.value]) }, } }

// home -> index.vue

data:{ indexBars: [ { label: "流行", value: "0" } ... ], currentBar: "0" }

```

列表数据接口,传递参数包括点击 currentType pageNum pageSize 。图片异步加载必然导致 better-scroll 高度计算失误,每张图片加载完毕都要重新计算高度才合理,故 CardListItem 内图片 load 完毕需要抛出给首页,再调用 scroll 组件内 refresh 方法。首页与 CardListItem 组件之间的关系薄弱,或者说没有关系,组件间事件通信可采用 EventBus 事件总线的方式。

```javascript // mian.js Vue.prototype.$bus = new Vue()

// CardListItem 发出 onLoad(){ this.$bus.$emit('imageLoad') }

// home -> index.vue 监听 this.$bus.$on("imageLoad", () => { this.$refs.scroll.refresh() })

```

但是对于图片较多的列表,会导致调用 refresh 方法频繁,需要添加防抖函数。 utils index.js timer 作为了闭包函数 debounce 的私有变量,首页引入函数 debounce

```javascript export function debounce(func, delay = 20) { var timer = null return function(...arg) { if (timer) clearTimeout(timer)

timer = setTimeout(() => {
  func.apply(this, arg)
}, delay)

} }

```

当组件 scroll 实例完全创建完毕才有必要生成防抖函数,实例未创建完毕 $ref.scroll.refresh 不存在,生成的防抖函数实际也不生效,短路运算 && 更加保证 refresh 非函数则不执行。如此 fresh 就是一个保存有私有变量 timer 的防抖函数,图片加载小于 20ms 只执行最后一次。

```javascript // home -> index.vue

onLoad() { this.refresh = debounce(this.$refs.scroll.refresh, 20) }

mounted() { this.$bus.$on("imageLoad", () => { this.refresh && this.refresh() }) }

```

indexBar 吸顶,通过使 better-scroll 下的 InddexBar fixed 定位不可取, better-scroll 使用 translate 会导致内部定位元素非理想状态,解决办法最好是 NavBar 同级再添加组件 IndexBar fixed 定位, scroll 未到吸顶距离隐藏,吸顶距离则显示。 showTop 用于返回顶部,滚动距离高于一屏则显示返回顶部按钮。

```javascript // home -> index.vue scroll({ y }) { this.$nextTick(() => { this.showSticky = this.$refs.indexBar && -y > this.$refs.indexBar.$el.offsetTop })

this.showTop = -y > document.body.clientHeight }

```

上拉加载、下拉刷新、 IndexBar 切换,下拉重新调用接口,上拉当前 pageNum++ ,再获取数据, list 数据使用 concat 拼接,或者使用 push(...array) 方式, indexBar 切换重新获取数据, scroll 滚动至 IndexBar 位置。

```javascript this.$nextTick(() => { this.showSticky && this.$refs.scroll.scrollTo(0, -this.$refs.indexBar.$el.offsetTop, 0) })

```

首页需要 keep-active 缓存,保存页面状态。

```javascript // App.vue

```

详情

路由 routes.js 新增详情路由。

```javascript // router -> routes.js { path: '/detail/:id', name: 'detail', meta: { title: '详情' }, component: () => import('views/detail') }

// home -> index.vue this.$router.push({ path: /detail/${id} })

```

目录结构。

```javascript ├── Detail │ ├── index.vue │ ├── components │ │ ├── GoodsInfo.vue │ │ ├── StoreInfo.vue │ │ ├── ClothList.vue │ │ ├── ParamsInfo.vue │ │ ├── CommentList.vue │ │ ├── RecommendList.vue │ │ ├── NavBar.vue │ │ ├── SubmitBar.vue

```

组件树结构。

```javascript ▼

```

组件大致同首页一致, NavBar 差别较大, NavBar 对公共组件的 NavBar 进行封装,组件自定义 v-model ,抛出 change 事件,点击实现类似锚点的功能,同时伴随高亮。大致原理点击获取元素的 value 值, value 值查询 navbars 对应的 refName ,获取对应组件的 offsetTop 实现锚点。

```javascript navbars: [ { label: "商品", value: "0", refName: "swiper" } ]

this.$refs.scroll.scrollTo(0, -this.$refs[refName].$el.offsetTop)

```

scroll 滚动过程中高亮伴随切换,在 scroll 事件中获取滚动距离,遍历 navbars 设置 currentBar 的值,同时 v-model 双向绑定 currentBar ,从而实现滚动高亮。

```javascript this.navbars.forEach((el) => { if (this.$refs[el.refName] && -y >= this.$refs[el.refName].$el.offsetTop) { this.currentBar = el.value } })

```

添加购物车需要 vuex 状态管理,需要用到的部分实质只有购物车的商品列表,故使用 vuex 显得大材小用,况且不用 vuex 也能实现迷你版状态管理。为了保留与 vuex 一致性, store 下新增 index.js vuex.js vuex.js 声明 Store 类,构造函数默认观察 state 数据。

```javascript import Vue from "vue"

class Store { constructor({ state, mutations }) { Object.assign(this, { state: Vue.observable(state || {}), mutations, }) } commit(type, arg) { this.mutations type } }

export default { Store }

```

index.js 与一般状态管理基本一致。

```javascript import Vuex from './vuex'

export default new Vuex.Store({ state: { goods: [] }, mutations: { ADD_GOODS(state, arg) {...}, ALL_CHECKED(state, val) {...} } })

```

页面实现 this.$store 方式调用还要将导出实例放置原型上,至此迷你版 vuex 调用方式与 vuex 趋于一致, actions gutters 暂时用不上。

```javascript import store from "./store"

Vue.prototype.$store = store

```

添加购物车按钮点击,调用 mutations 方法。

```javascript this.$store.commit("ADD_GOODS", {...})

```

详情页面点击不同首页商品,只会请求同一商品,原因 keep-active 缓存了当前详情页,不会再次触发 created ,调整 App.vue

```javascript

```

此时详情页 Tabbar 还存在,类比 keep-active ,组件传值 exclude

```javascript // layout -> Tabbar.vue export default { props: { exclude: String, }, computed: { show() { const excludes = this.exclude.split(",")

  return !excludes.includes(this.$route.name)
}

} }

// App.vue

```

Message 消息提示组件封装,根据开源组件库 element-ui ,封装一个简单版的 Message components 下新建 Message ,新建 main.vue index.js main.vue 内部 mounted 之后,固定延时关闭 Message ,同时执行关闭回调。

```javascript

{{ message }}

export default { data() { return { visible: true, message: "", duration: 2000, onClose: null } }, mounted() { setTimeout(() => { this.visible = false

  this.onClose && this.onClose()
}, this.duration)

} }

```

index.js 内部引入 Vue ,同时引入组件 Message ,创建组件构造器,通过 new 构造器创建组件实例, $mount 挂载当前实例同时渲染为真实 DOM ,再追加至 body 内部,对外抛出 install 方法。

```javascript import Vue from "vue"

import main from "./main.vue"

const MessageConstructor = Vue.extend(main)

const Message = function(options) { if (typeof options === "string") { options = { message: options } }

const instance = new MessageConstructor({ data: options })

instance.$mount()

document.body.appendChild(instance.$el) }

export default { install() { Vue.prototype.$message = Message } }

```

main.js 引入组件, Vue.use() 调用内部 install 方法, Message 被置于 Vue.prototype 上。

```javascript // mian.js import Message from "components/Message" Vue.use(Message)

// detail -> index.vue this.$message("商品添加成功!")

```

购物车

购物车页面商品多,存在滚动情况,使用 better-scroll ,页面列表依赖 store state

```javascript computed: { data() { return this.$store.state.goods } }

```

目录结构。

```javascript ├── cart │ ├── index.vue │ ├── components │ │ ├── GoodsList.vue │ │ ├── TotalBar.vue

```

组件树结构。

```javascript ▼

```

CheckButton 即公共选中按钮, components 下新建 CheckButton ,内部实现 v-model ,内部通过切换背景色实现选中和取消,且内部点击事件阻止冒泡。可能存在当外部调用 CheckButton 时,带有 CheckButton 的整个卡片点击则 CheckButton 取消或者选中,此时修改 v-model 绑定值即可。但是当点击 CheckButton 时,由于本身 CheckButton 被点击时会切换,加上事件冒泡,外层卡片也会触发点击事件,再次修改 v-model 值,出现预期之外的结果,最好的办法就是阻止事件的冒泡。

```javascript @click.stop="$emit('change', !value)"

```

TotalBar 内部计算属性依赖 store state ,根据 state 商品数量动态计算价格、总量。全选按钮点击商品全部选中,再次点击全部取消。全选点击则调用 store mutations 遍历修改商品 checked 属性。但是点击 CheckButton ,由于内部冒泡的阻止,触发不了外部点击事件。此时伪元素 after 就又能派上用场了,定位一个空盒子在全选按钮上,点击事件的触发元素一直是这个 after 伪元素。

```javascript .check { position: relative;

&::after {
  content: "";
  display: block;
  position: absolute;
  left: 0;
  right: 0;
 top: 0;
 bottom: 0;

} }

```

由于 keep-active 的缓存机制,导致列表无法下拉,主要由于初始情况 scroll 计算高度错误导致。解决办法一,添加 activated 事件,页面活动时,调用组件内部 refresh 事件更新高度。

```javascript activated(){ this.$nextTick(()=>{ this.$refs.scroll.refresh() }) }

```

解决办法二, keep-active 不缓存 cart 页面。

```javascript

```

分类

目录结构。

```javascript ├── category │ ├── index.vue │ ├── components │ │ ├── CatesList.vue

```

组件树结构。

```javascript ▼

```

个人信息

目录结构。

```javascript ├── profile │ ├── index.vue │ ├── components │ │ ├── UserInfo.vue │ │ ├── CountInfo.vue │ │ ├── OptionList.vue

```

组件树结构。

```javascript ▼

```

优化部分

图片懒加载

首页商品懒加载, assets 文件夹添加懒加载填充图。

```javascript // 安装 cnpm i vue-lazyload --save

// main.js import VueLazyload from "vue-lazyload" Vue.use(VueLazyload, { loading: require('assets/placeholder.png') })

```

移动端点击

移动端 300ms 点击。

```javascript // 安装 cnpm i fastclick --save

// main.js import FastClick from 'fastclick' FastClick.attach(document.body)

```

px 转换 vw

px vw 视口单位,相关插件 postcss-px-to-viewport ,根目录需要新建 postcss.config.js 配置文件,相关配置参数官方文档很详尽了,唯一需要注意的就是 px 单位避免存在于行内/内联样式, minPixelValue 最小转换数值一般为 1 ,可能有部分边框需要 1px 显示。

```javascript // 安装 cnpm install postcss-px-to-viewport --save-dev

// postcss.config.js module.exports = { plugins: { 'postcss-px-to-viewport': { unitToConvert: 'px', viewportWidth: 375, unitPrecision: 6, propList: ['*'], viewportUnit: 'vw', fontViewportUnit: 'vw', selectorBlackList: [], minPixelValue: 1, replace: true, exclude: undefined, include: undefined, landscapeUnit: 'vw' } } }

```

windows nginx 部署

nginx 选择 Stable version 稳定版 nginx/Windows-x.xx.x ,下载压缩包解压,根目录执行命令启动 nginx

```javascript // 查看 nginx 版本号 nginx -v

// 启动 start nginx

// 强制停止或关闭 nginx nginx -s stop

// 正常停止或关闭 nginx (处理完所有请求后再停止服务) nginx -s quit

// 修改配置后重新加载 nginx -s reload

// 测试配置文件是否正确 nginx -t

```

浏览器输入 http://localhost/ ,正常访问为 Welcome to nginx! nginx 默认访问 html/index.html ,可修改配置文件 conf/nginx.conf 更改默认路径,运行重新加载命令。

```javascript ├── dist │ ├── index.html │ ├── ... ├── html │ ├── index.html │ ├── 50x.html ...

location / { root dist; index index.html index.htm; }

```

后记

项目基本思路均梳理大半,部分思路可能未提及,项目 Github 开放,可以克隆或者下载压缩包,仓库内存稍大,大约 464M ,压缩包下载 1 分钟左右,原因主要由于脱离网络接口,数据保存本地导致,详细情况开头已细致说明。整个项目非常适用新手练手,服务端数据服务只需要 npm run serve 即可开启。

由于 express 动态获取本机内网 ip ,所以完全可以手机访问 cli-service 启动的 Network 地址,实现手机浏览器也可预览的效果。

参考文献

  • 基于Spring Boot的电子商城设计与实现(哈尔滨工业大学·李晨)
  • 基于B/S的网上购物系统的设计与实现(厦门大学·荆飞)
  • 手机销售网站设计与实现(电子科技大学·杨俊升)
  • 网上购物模拟系统(吉林大学·郭秋野)
  • OTO电子商务系统设计与实现(吉林大学·李艳)
  • 网上交易系统的设计与实现(厦门大学·杨云)
  • 基于.NET的在线购物平台研究与实现(东北师范大学·刘雪娇)
  • 翼百公司网上购物系统设计与实现(大连理工大学·赵斌)
  • 基于JAVA WEB的虚拟数字图书电子商务平台设计与实现(吉林大学·霍剑峰)
  • 基于J2EE的爽购购物网站设计与实现(黑龙江大学·孟祥龙)
  • 基于Spring Boot的电子商城设计与实现(哈尔滨工业大学·李晨)
  • 基于Spring Boot的电子商城设计与实现(哈尔滨工业大学·李晨)
  • 翼百公司网上购物系统设计与实现(大连理工大学·赵斌)
  • 基于JSP的网上购物系统的实现(山东大学·武珺)
  • 基于Asp.net的B2C电子商务系统设计与实现(重庆大学·李俊)

本文内容包括但不限于文字、数据、图表及超链接等)均来源于该信息及资料的相关主题。发布者:源码客栈 ,原文地址:https://bishedaima.com/yuanma/35818.html

相关推荐

发表回复

登录后才能评论