1 Project Overview
1.1 introduction
The project name mogugou is similar to general shopping WebApp, including home page, classification, shopping cart, personal center and details.
The project is based on VUE, VUE-Router and VUE-CLI3. The relevant part of API request adopts AXIOS. The data part is not from the server, but starts relevant data service locally based on Express. The first reason is that the network interface updates quickly, the data changes greatly, and the dependence is high; the second reason is that the project itself is not big. Based on the project, the local service has high flexibility, and the code can run depending on installation. Therefore, it is finally considered to crawl the relevant interface data of Express and save it locally.
Vuex is not used for state management. It is unnecessary to use vuEX only in a small part. It is necessary for the project to be large. Vuex can be partially realized based on vue. Observable. Image loading part asynchronous update DOM adopts event bus for component communication.
The third-party open source components of the project include the Better-Scroll scroll plug-in, vuE-awes-swiper and swiper rotation components, normalize. CSS initialization styles, vue-lazyLoad lazy loading, and fastCLI for mobile terminal click300ms delay Ck.
The project difficulty is not high, suitable for beginners to practice, this article is only the practice of componentized packaging and directory configuration related records.
1.2 Previewing Addresses
Mushrooms and purchasing
Figure 1.3
1.4 File and Directory Configuration
├ ─ ─ public │ ├ ─ ─ the favicon. Ico │ ├ ─ ─ index. The HTML ├ ─ ─ server │ ├ ─ ─static│ │ ├ ─ ─ image │ ├ ─ ─ app. Js │ ├ ─ ─ the js │ ├ ─ ─ the router. The js ├ ─ ─ the SRC │ ├ ─ ─ API │ │ ├ ─ ─ home. Js │ │ ├ ─ ─ category. Js │ ├ ─ ─ Assets │ ├─ Iconfont │ ├─ img │ ├─ placeholder. PNG │ ├─ Components │ BetterScroll │ ├─ CheckButton │ │ ├ ─ ─ IndexBar │ │ ├ ─ ─ Message │ │ │ ├ ─ ─ the Message. The vue │ │ │ ├ ─ ─ index. The js │ │ ├ ─ ─ Navbar │ │ ├ ─ ─ Swiper │ │ ├ ─ ─ SwiperSlide │ │ ├ ─ ─ Tabbar │ │ ├ ─ ─ TabbarItem │ ├ ─ ─ layout │ │ ├ ─ ─ Tabbar │ ├ ─ ─ the router │ │ ├ ─ ─ index. The js │ │ ├ ─ ─ Routes. Js │ ├ ─ ─ store │ │ ├ ─ ─ index. The js │ │ ├ ─ ─ vuex. Js │ ├ ─ ─ styles │ │ ├ ─ ─ index. The less │ ├ ─ ─ utils │ │ ├ ─ ─ index. The js │ │ ├ ─ ─ request. Js │ ├ ─ ─ views │ │ ├ ─ ─ home │ │ ├ ─ ─ the category │ │ ├ ─ ─ cart │ │ ├ ─ ─ profile │ │ ├ ─ ─ the detail │ ├ ─ ─ App. Vue │ ├ ─ ─ └.js │ ├─.env.Development │ ├─ package.json │ ├─ readme.md │ ├─ Vue.config.jsCopy the code
2 the initialization
2.1 Scaffold initialization
Initial empty scaffolding VUE-CLI3 only configure Babel, Router, and CSS pre-processors (less). Delete irrelevant parts of other services. The folder part is created step by step according to requirements.
2.2 Tabbar Components and routes
At present, the normal operation of the project is blank. First, build the part related to routes, extract the static data of routes, add routes.js in the same directory to export static data, and import static data of index.js.
// 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')}... ]Copy the code
When the project starts, routing path loading errors will occur, and folder alias needs to be configured. Empty scaffolding does not contain vue.config.js, which needs to be manually added. Path SRC /views Change alias views. Other aliases will be used later.
// vue.config.js
const path = require('path');
function resolve(dir) {
return path.join(__dirname, dir)
}
module.exports = {
chainWebpack: (config) = > {
config.resolve.alias
.set(The '@', resolve('src'))
.set('views', resolve('src/views'))... }}Copy the code
After the folder alias is configured, there are no files in the lazy loading path. The views home folder is added, under which index.vue is added, the same for other files, restart and run.
At this time, the project is still blank, but the index.vue under home has been redirected. Next, wrap Tabbar, Tabbar is relatively common, create Tabbar and TabbarItem under Components, add index.vue under folder. The Tabbar is typically 49px high for comfort and is positioned at the bottom of the screen, ranking above other components. The TabbarItem introduces router-link internally, the component receives parameters referring to the vuant-UI and has some modifications, and is highlighted with the current routes.path parameter and calculation properties.
// Tabbar -> index.vue
<div class="tabbar">
<slot />
</div>
// TabbarItem -> index.vue
<div class="tabbar-item">
<router-link :to="to" tag="div">
<div
class="tabbar-item-icon"
:style="{ color: to === path ? activeColor : inactiveColor }"
>
<slot name="icon" />
</div>
<div
class="tabbar-item-text"
:style="{ color: to === path ? activeColor : inactiveColor }"
>
<slot name="text" />
</div>
</router-link>
</div>
export default {
props: {
to: String.activeColor: String.inactiveColor: String
},
computed: {
path() {
return this.$route.path
}
}
}
Copy the code
The common component Tabbar has been wrapped, but the project-related Tabbar has not been wrapped or referenced. Since the project is relevant Tabbar has a page layout about the project. Therefore, a new folder named Layout is added under SRC. There are not too many related layout components.
// layout -> Tabbar.vue
<m-tabbar>
<m-tabbar-item to="/home" active-color="#ff8198" inactive-color="# 555555">
<i slot="icon" class="iconfont icon-home"></i>
<span slot="text">Home page</span>
</m-tabbar-item>. </m-tabbar>import Tabbar from "components/Tabbar";
import TabbarItem from "components/TabbarItem";
export default {
components: {
MTabbar: Tabbar,
MTabbarItem: TabbarItem,
},
Copy the code
Use iconFont as the icon. Select an appropriate Tabbar icon from the official website, download the package, and decompress it. Create the iconfont folder under assets and import all the decompressed files. Demo_index. HTML explains how to use font ICONS in detail, and iconfont. CSS needs to be introduced manually. Iconfont is also a font, which ultimately boils down to CSS styles. Create a styles folder under SRC, create index.less, place a common initialization style in index. js, and finally import index.less.
├ ─ ─ iconfont │ ├ ─ ─ demo_index. HTML │ ├ ─ ─ demo. CSS │ ├ ─ ─ iconfont. CSS...// index.less
@import '~assets/iconfont/iconfont.css';
// main.js
import 'styles/index.less'
Copy the code
Tabbar business components are packaged, app. vue is introduced, and at the bottom of Tabbar display screen under project operation, routing jump and URL update occur by clicking Tabbar.
// App.vue
<div id="app">
<tabbar />
<router-view />
</div>
import Tabbar from "layout/Tabbar"
export default {
components: { Tabbar }
}
Copy the code
2.3 Style, page title, icon initialization
Redirect to the home page, find body margin, install normalize.css, mian. Js import.
/ / installation
cnpm i normalize.css --save
// main.js
import 'normalize.css'
Copy the code
Styles folder index.less initializes HTML, body, #app heights and removes app. vue styles.
html,
body,
#app {
height: 100%;
}
Copy the code
Add navigation guard router.beforeEach to initialize the page title, but the target route to does not contain meta. Change routes.js, and similarly run the page title switch correctly. Replace public under favicon.ico, project refresh display icon.
// router -> index.js
router.beforeEach((to, from, next) = > {
document.title = to.meta.title
next()
})
// router -> routes.js
{
path: '/home'.name: 'home'.meta: {
title: 'home'
},
component: () = > import('views/home')},Copy the code
2.4 NavBar
NavBar is also a general general component, components new NavBar, component open slot, general height of 44px is the most comfortable, routing page, details are used, component value background-color. Home page introduction, page introduction of components in order to follow the introduction of public components, custom components, public JS, custom JS.
3 Data Service
3.1 Express
At present, the project can realize the route jump, but the relevant API and data have not been prepared. Network interface is often updated and data is unstable. Express and Superagent are used to crawl and save interface data. Crawler refers to other articles. Split roughly the back-end interfaces needed for the project, including home page rotation map, features, recommendations, details, lists, categories, etc. Create a serve folder for the project. Image Saves the data image.
├ ─ ─ serve │ ├ ─ ─static│ │ ├ ─ ─ image │ ├ ─ ─ app. Js │ ├ ─ ─ the js │ ├ ─ ─ the router, jsCopy the code
App.js start the data service, open the static static folder, map /static to serve/static.
app.use('/static/', express.static('./serve/static/'))
Copy the code
Db. Js local database, baseURL is the LOCAL LAN IP, which is convenient for mobile terminal to access the local data, but also convenient for debugging. Before the project, ipconfig was used to input the IP address manually, which was tedious. As the data service had no actual relevance to the project once started, there was no need to modify the IP address again. Therefore, the OS module is used to dynamically obtain the LOCAL LAN IP. Of course, if the PC fails to access the picture in this way, there is a large probability that the dynamic IP acquisition part is wrong. Note the relevant code and modify the IP in the previous way.
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}`)}Copy the code
As there are not many business-related interfaces in router.js back-end routing, there is no need for router to be classified, and there are no POST-related requests, so there is no need to install body-Parser.
const db = require('./db')
router.get('/api/getBann'.function (req, res) {
res.send({
message: "success".result: db.banner,
status: "0".success: true})})...Copy the code
Package. json configures the quick start command.
/ / installation
cnpm i nodemon --save-dev
scripts: {
serve: "nodemon serve/app.js"
}
Copy the code
3.2 AXIOS and API encapsulation
The project uses axios third-party plug-ins, and the installation steps refer to AXIos.
├ ─ ─ the SRC │ ├ ─ ─ API │ │ ├ ─ ─ home. Js │ ├ ─ ─ utils │ │ ├ ─ ─ request. Js ├ ─ ─ the env. Development ├ ─ ─ vue. Config. JsCopy the code
BaseURL is separated from request.js and placed in the utils utility class functions folder using environment variables.
const server = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000
})
Copy the code
Development and production urls are usually inconsistent, usually to configure environment variables, the root directory to create. Env. development file, later need to add. Env. production to configure product environment variables.
VUE_APP_BASE_API = '/api'
Copy the code
A cross-domain error occurs when a project attempts to access express requests. The server can set the cross-domain portion, or the project can set the proxy.
// 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: {[A '^' + process.env.VUE_APP_BASE_API]: ' '}}}},Copy the code
Request. js is introduced to set the request URL, request mode and page reference.
// api.js => home.js
import request from 'utils/request'
export function getBann() {
return request({
url: '/api/getBann'.method: 'get'})}// Reference page
import { getBann } from "api/home"
getBann()
.then((res) = >{... }) .catch((err) = >{... })Copy the code
4 BetterScroll, vue – awesome – swiper
This project involves third-party components such as BetterScroll and vue-awesome-swiper. BetterScroll is also a public component. Components Create a BetterScroll folder. Swiper is also a common component. Components New swiper, SwiperSlide, vue-awesome-swiper version causes more pits, mainly because vue-awesome-swiper version does not adapt to swiper. 4.1.1 and 5.2.0 are recommended. For details, see vue-awes-swiper.
5 pages
5.1 the home page
At present, the basic shelf has been basically built. The vuEX state management part is not considered for the time being and will be brought in when it is used in practice. Home component already contains NavBar, adjust home directory structure, component naming semantic as far as possible, late maintenance is very convenient.
├── Home │ ├── Vue │ ├─ 03/03/2004 │ ├─ Vue │ ├── 03/03/2004 │ ├─ CardListItem.vueCopy the code
Home component tree structure, browser installation devTools is very intuitive.
< font style = "text-autoel-system.html" > < font style = "text-modify: none; <CardListItem>Copy the code
NavBar defaults to fiexed positioning at the top of the screen, which will cause the better Scroll to be covered, and home uses the pseudo-element before to avoid. In addition, the height of the outer better-scroll wrapper should be specified, and relative positioning should be added as far as possible.
// 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;
}
Copy the code
Data interface, such as data acquisition, page call need API folder file declaration interface reintroduction.
// api -> home.js
export function getRecom() {
return request(...)
}
// home -> index.vue
import { getBann } from "api/home"
Copy the code
IndexBar is also a common component. Components create an IndexBar, pass an array of component parameters, throw highlighting and click events, and wrap IndexBar in V-Model form with default highlighting. Props increases component reusability by relying on more than just the label-value pair form of the data inside the data. Transfer props can rely on multiple forms. Model and props. Data are required for encapsulating the v-Model of a customized component. For details, refer to the official V-Model and index-bar-item.
// IndexBar -> index.vue
<div
class="index-bar-item"
:class="{ active: value === item[props.value] }"
@click="itemClick(item)"
v-for="item in data"
:key="item[props.value]">... </div>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
<index-bar
:data="indexBars"
v-model="currentBar"
@change="onChange"
/>
data:{
indexBars: [{label: "Pop".value: "0"}... ] .currentBar: "0"
}
Copy the code
List data interface, pass parameters including currentType, pageNum, pageSize. Asynchronous image loading will inevitably lead to errors in the calculation of the better Scroll height. It is reasonable to recalculate the height of each image after loading. Therefore, the image load in CardListItem needs to be thrown to the home page and then call the refresh method in the Scroll component. There is weak or no relationship between the home page and CardListItem components, and event communication between components can take the form of an EventBus.
// mian.js
Vue.prototype.$bus = new Vue()
/ / CardListItem
onLoad(){
this.$bus.$emit('imageLoad')}// home -> index.vue
this.$bus.$on("imageLoad".() = > {
this.$refs.scroll.refresh()
});
Copy the code
However, for a list with a large number of images, the refresh method is called frequently, and the anti-shake function needs to be added. In index.js, timer is used as a private variable for the closure debounce.
export function debounce(func, delay = 20) {
var timer = null
return function (. arg) {
if (timer) clearTimeout(timer)
timer = setTimeout(() = > {
func.apply(this, arg)
}, delay)
}
}
Copy the code
If $ref.scroll. Refresh does not exist before the component scroll instance is created, the generated function will not take effect, and the short-circuit operation will ensure that refresh will not be executed if it is not a function. Fresh is thus an anti-shake function that holds a private timer variable and only executes the last time the image loads less than 20ms.
// home -> index.vue
<scroll @load='onLoad'>
onLoad() {
this.refresh = debounce(this.$refs.scroll.refresh, 20);
}
mounted() {
this.$bus.$on("imageLoad".() = > {
this.refresh && this.refresh(); })}Copy the code
Fixed positioning of InddexBar in better Scroll is not desirable by making the InddexBar fixed positioning in better Scroll. The use of Translate in better Scroll will result in non-ideal internal positioning elements. The best solution is to add the component IndexBar fixed position at the same level as NavBar, and hide the distance from scroll to the top, while display the distance from the top. ShowTop is used to return to the top, and the back to the top button is displayed if the scroll distance is higher than one screen.
// home -> index.vue
scroll({ y }) {
this.$nextTick(() = > {
this.showSticky =
this.$refs.indexBar && -y > this.$refs.indexBar.$el.offsetTop
})
this.showTop = -y > document.body.clientHeight
}
Copy the code
Pull-up load, pull-down refresh, IndexBar toggle, pull-down re-invoke interface, pull-up current pageNum++, and fetch data, concat concatenation of list data, or push(… Array) to retrieve data and scroll to the indexBar position.
this.$nextTick(() = > {
this.showSticky &&
this.$refs.scroll.scrollTo(0, -this.$refs.indexBar.$el.offsetTop, 0)})Copy the code
The home page needs keep-active cache to store the page status.
// App.vue
<keep-alive>
<router-view />
</keep-alive>
Copy the code
5.2 details
Routes. js Added a detailed route.
// router -> routes.js
{
path: '/detail/:id'.name: 'detail'.meta: {
title: 'details'
},
component: () = > import('views/detail')}// home -> index.vue
this.$router.push({ path: `/detail/${id}` })
Copy the code
Directory structure.
├ ─ ─ the Detail │ ├ ─ ─ index. The vue │ ├ ─ ─ components │ │ ├ ─ ─ GoodsInfo. Vue │ │ ├ ─ ─ StoreInfo. Vue │ │ ├ ─ ─ ClothList. Vue │ │ ├ ─ ─ Vue │ ├── ├.vue │ ├─.vue │ ├─.vue │ ├─.vue │ ├─.vue │ ├─.vue │ ├─.vueCopy the code
Component tree structure.
<Detail> <NavBar> ▼<BetterScroll> ▼<Swiper> <SwiperSlide> <StoreInfo> <ClothList> <ParamsInfo> <CommentList> <RecommendList> <SubmitBar>Copy the code
NavBar encapsulates the NavBar of a common component. The component customizes the V-Model, throws the change event, and clicks to achieve the function of an anchor point, along with highlighting. Click to obtain the value of the element, query the refName corresponding to navbars for the value, and obtain the offsetTop implementation anchor point of the corresponding component.
navbars: [
{
label: "Goods".value: "0".refName: "swiper"}]this.$refs.scroll.scrollTo(0, -this.$refs[refName].$el.offsetTop)
Copy the code
Highlight during Scroll with the switch, the scroll distance is obtained in scroll event, the value of currentBar is set through NavBars, and the V-Model is bidirectional binding currentBar, so as to achieve scroll highlight.
this.navbars.forEach((el) = > {
if (this.$refs[el.refName] &&
-y >= this.$refs[el.refName].$el.offsetTop
) {
this.currentBar = el.value; }})Copy the code
Adding a shopping cart requires VUex state management. The only part of vuex that needs to be used is the list of items in the shopping cart. Therefore, using Vuex is overqualified and mini version state management can be achieved without vuex. In order to maintain consistency with vuex, index.js and vuex.js are added under Store. Vuex. js declares store classes, and the constructor observes state data by default.
import Vue from 'vue'
class Store {
constructor({ state, mutations }) {
Object.assign(this, {
state: Vue.observable(state || {}),
mutations,
})
}
commit(type, arg) {
this.mutations[type](this.state, arg)
}
}
export default { Store }
Copy the code
Index.js is basically the same as general state management.
import Vuex from './vuex'
export default new Vuex.Store({
state: {
goods: []},mutations: {
ADD_GOODS(state, arg){... },ALL_CHECKED(state, val){... }}})Copy the code
Page implementation of this.$store calls also need to place the exported instance on the prototype, so far vuex mini version calls tend to be consistent with vuex, actions, gutters are not used for the time being.
import store from './store'
Vue.prototype.$store = store
Copy the code
Add a shopping cart button click, call the mutations method.
this.$store.commit("ADD_GOODS", {... })Copy the code
The detail page will only request the same item if it clicks on a different item on the home page. The reason is that keep-active caches the current detail page and will not trigger creation again to adjust app.vue.
<keep-alive exclude="detail">
Copy the code
In this case, Tabbar is still in place, which is similar to keep-active and exclude.
// layout -> Tabbar.vue
export default {
props: {
exclude: String,},computed: {
show() {
const excludes = this.exclude.split(",");
return! excludes.includes(this.$route.name); }}}// App.vue
<tabbar exclude="detail" />
Copy the code
/ / Add main.vue, index.js, main.vue inside mounted. / / Add main.vue inside mounted. Fixed delay closing Message while executing the closing callback.
<transition name="fade" v-if="visible">
<div class="message">{{ message }}</div>
</transition>
export default {
data() {
return {
visible: true.message: "".duration: 2000.onClose: null}},mounted() {
setTimeout(() = > {
this.visible = false
this.onClose && this.onClose()
}, this.duration); }}Copy the code
Index.js introduces Vue internally, introduces component Message, creates component constructor, creates component instance via new constructor, mounts current instance and renders as real DOM, appends to body, throws install method externally.
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
}
}
Copy the code
Main.js imports the component, vue.use () calls the internal install method, and Message is placed on vue.Prototype.
// mian.js
import Message from 'components/Message'
Vue.use(Message)
// detail -> index.vue
this.$message('Product added successfully! ')
Copy the code
5.3 the shopping cart
There are many items on the shopping cart page, so the better Scroll is used and the page list depends on the state in store.
computed: {
data() {
return this.$store.state.goods
}
}
Copy the code
Directory structure.
├── Heavy Metal Metal Metal Metal Metal Metal Metal Metal Metal Metal Metal Metal Metal Metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metalCopy the code
Component tree structure.
<BetterScroll> <GoodsList> ▼<TotalBar> <CheckButton>Copy the code
CheckButton is a public CheckButton, new CheckButton under components, internal v-model, internal by switching background color to select and cancel, and internal click events to prevent bubbling. There may be external call CheckButton, the whole card with CheckButton click CheckButton cancel or select, then modify the V-model binding value. But when the CheckButton is clicked, the CheckButton itself will switch, plus the event bubble, the outer card will also trigger the click event, modify the V-Model value again, appear unexpected results, the best way is to prevent the event bubble.
@click.stop="$emit('change', ! value)"
Copy the code
The internal calculation attribute of TotalBar depends on the state in store, and dynamically calculates the price and total amount according to the quantity of state goods. Select all button click all selected goods, click again all cancel. Click on all calls store go through modify the product checked properties. Clicking the CheckButton, however, will not trigger the external click event due to internal bubbling. This is where the after element comes in handy again, positioning an empty box on the all button, and the trigger element for the click event is always the after element.
.check {
position: relative;
&::after {
content: "";
display: block;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0; }}Copy the code
Because of the keep-active caching mechanism, the list cannot be pulled down, mainly because the initial scroll height calculation is incorrect. Solution 1: Add the Activated event, which calls the component’s internal Refresh event to update the height when the page is active.
activated(){
this.$nextTick(() = >{
this.$refs.scroll.refresh()
})
}
Copy the code
Solution 2: Keep-active does not cache cart pages.
<keep-alive exclude="detail,cart">
Copy the code
5.4 classification
Directory structure.
├── Category │ ├─ Vue │ ├─ Components │ ├─ VueCopy the code
Component tree structure.
▼ < Category > < NavBar > < CatesList >Copy the code
5.5 Personal Information
Directory structure.
├─ Profile │ ├─ Index. Vue │ ├─ Index. Vue │ ├─ Index. Vue │ ├─ IndexCopy the code
Component tree structure.
<Profile> <NavBar> <UserInfo> <CountInfo> <OptionList>Copy the code
6 optimization Part
6.1 Lazy loading of images
Home page goods lazy loading, assets folder add lazy loading filling diagram.
/ / installation
cnpm i vue-lazyload --save
// main.js
import VueLazyload from "vue-lazyload";
Vue.use(VueLazyload, {
loading: require('assets/placeholder.png')});Copy the code
6.2 Mobile Click
Mobile terminal 300ms click.
/ / installation
cnpm i fastclick --save
// main.js
import FastClick from 'fastclick';
FastClick.attach(document.body);
Copy the code
6.3 Convert PX to VW
Postcss-px-to-viewport = postcss.config.js; postcss.config.js; MinPixelValue The minimum conversion value is usually 1, and some borders may need to be displayed at 1px.
/ / installation
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: [The '*'].viewportUnit: 'vw'.fontViewportUnit: 'vw'.selectorBlackList: [].minPixelValue: 1.replace: true.exclude: undefined.include: undefined.landscapeUnit: 'vw'}}}Copy the code
7 Windows Nginx deployment
Nginx Select Stable version nginx/ windows-x.xx. x, download the compressed package, decompress it, and run commands in the root directory to start nginx.
// Check the nginx version number
nginx -v
/ / start
start nginx
// Forcibly stop or close nginx
nginx -s stop
// Stop or close nginx normally (stop service after all requests are processed)
nginx -s quit
// Modify the configuration and reload
nginx -s reload
// Test whether the configuration file is correct
nginx -t
Copy the code
Enter http://localhost/ to Welcome to nginx! , nginx default access to HTML /index.html, you can modify the configuration file conf/nginx.conf change the default path, run the reload command.
├── Dist │ ├─ index.html │ ├─... ├─ HTML │ ├─ Index.html │ ├─ Index.html... location / { root dist; index index.html index.htm; }Copy the code
8 afterword.
The project Gitee is open, and the compressed package can be cloned or downloaded. The warehouse has a slightly large memory (about 464M), and the compressed package can be downloaded in about 1 minute. The main reason is that the data is saved locally due to the separation from the network interface. The whole project is very suitable for beginners to practice, the server data service only need NPM Run serve can be started.
Because the Express dynamically obtains the local Intranet IP address, mobile phones can access the Network address of the CLI-service startup, enabling the preview effect even on mobile browsers.
9 Update Logs
9.1 the 20-11-13″
In the picture storage project, it takes too long to download or clone for the first time. Express also obtains the LOCAL LAN IP to achieve mobile access, which makes the project seem redundant. If you had a graph bed and Express was responsible for returning different image addresses, the problem would be solved to a fundamental degree. So use Gitee, manually build a chart bed, understand the principle without PicGo online can also be achieved.
The essence is to open up a Gitee public repository and submit image files. Click the raw data to get the original URL of the image. The root directory of the image is usually https://gitee.com/ user name/warehouse name /raw/master, and the rest is the path of the image in the project.
Delete the original project dynamically obtain LAN IP, adjust baseURL, app.js static file close, delete static folder. Image details recommended random number generation may have the same situation optimization.
9.2 the 20-11-30 22:00
The project can be accessed and previewed by the cloud server, but it is not necessary to practice small projects. Gitee opens the static webpage preview function, which can adjust part of the code implementation. The ajax part is removed, and the API does not introduce the request tool functions, but directly introduce db. Js under serve, and combine router.js and API functions.
Vue. Config. js added publicPath, because static webpage preview history mode refresh error 404, router model deleted the history mode, use the default hash mode. Since only the gitee preview function is added, the code is not submitted, and the original code logic is not affected.