vue
Imitated Millet mall – small workshop actual combat record
This is a vue bucket project imitating Xiaomi Mall, click preview
Project environment introduction:
- System:
macos
- Package management tools:
yarn
Node
:v12.4.0
Pages and features the project will complete:
- Login page -> Encapsulate form validation method
- Home -> Achieve forward and backward routing animation
- Category page -> Use third party lazy load components
- Details -> Encapsulation
popup
component - Shopping cart – >
vue
A list of animation
Some animations should be added to the project to enrich the interaction
General knowledge involved in the project:
vue 3.x
Latest Use of scaffoldingwebstorm
Use trickswebpack
Configuration optimizationvue
Generic component encapsulationvw
Mobile terminal adaptation and tricking practicejsDOC
To write comments for utility functionsmockjs
Perform data simulation- Package and deploy to
github page
. And so on
In the process of writing code, I will pay attention to my code specification and the readability of naming, and I will also learn and record in this process. Let’s start this exciting journey together.
Quick start
We can quickly get the project up and running, packaged and published with the following commands:
Git clone [email protected]: wangkaiwd/xiaomi - shop. Git CD xiaomi - shop # start projects yarn start # package yarn yarn build # analysis package files Build: Analysis # Deploy to github Page YARN deployCopy the code
The directory structure of the project is as follows:
Mime-shop ├─.Browserslistrc ├─.env.analysis // Vue CLI Environment Variables File ├─.gitignore ├─ Readme.md ├─ Babel.config.js ├─ The deploy. Sh / / project deployment script ├ ─ package. The json ├ ─ postcss. Config. Js ├ ─ public │ ├ ─ the favicon. Ico │ ├ ─ img │ │ └ ─ the ICONS │ ├ ─ Index.html │ ├ ─ the manifest. Json │ └ ─ robots. TXT ├ ─ screenshots / / project screenshots │ ├ ─ calc - SCSS. PNG │ ├ ─ icon - the font - link. PNG │ └ ─ Icon - the font - prefix. PNG ├ ─ SRC │ ├ ─ MiApp. Vue │ ├ ─ API / / interface API │ │ └ ─ index. The js │ ├ ─ assets/resources/static │ │ ├ ─ img │ │ └ ─ Styles │ ├─ Bass Exercises - Dialog │ ├─ footerNav │ ├─ Bass Exercises - Icon │ ├─ Layout │ ├─ number │ │ ├ ─ popup │ │ ├ ─ skeleton │ │ ├ ─ toast │ │ └ ─ topHeader │ ├ ─ config/configuration/projects │ │ └ ─ navConfig. Js │ ├ ─ helpers / / help function │ │ ├─ Anti-Flag. Js │ │ ├─ Anti-Flag. Js │ │ ├─ Anti-Flag │ │ └ ─ the validator. Js │ ├ ─ HTTP / / axios related encapsulation │ │ ├ ─ axiosConfig. Js │ │ └ ─ request. Js │ ├ ─ main. Js / / entry documents │ ├ ─ RegisterServiceWorker. Js │ ├ ─ the router / / routing configuration │ │ ├ ─ lazyLoading. Js │ │ └ ─ the router. The js │ ├ ─ store / / vuex │ │ └ ─ store. Js │ └ ─ views / / project page │ ├ ─ category │ ├ ─ the detail │ ├ ─ example │ ├ ─ home │ ├ ─ homeCategory │ ├ ─ login │ ├ ─ mime │ ├ ─ search │ └─ shopCart ├─ vue.config.js // webpack setup ├─ damn.lockCopy the code
Project creation
Here we use the vue CLI provided by VUE to initialize the project:
yarn global add @vue/cli
vue create xiaomi-shop
Copy the code
If we find that we have already installed the Vue CLI, to ensure that the CLI tool is the latest version, we can upgrade the version:
yarn global upgrade @vue/cli
Copy the code
After that, you can choose the required modules and tools according to the PROMPTS of cli tools for development. The author uses the following options: Babel+Router(mode: Hash)+Vuex+Sass/SCSS(with Dart-sass)
Dart-sass is used here because Node-sass always has problems during the download and installation process
configurationwebpack
Next we configure webpack in vue.config.js, my configuration code is here: portal
The configuration file does the following things:
- Shut down
eslint
- Set global variables to facilitate the packaging of different environments
- Configuring a Path Alias
- Configuration file extension
- Automatic global import
css
- Set up the
favicon
Icon path - Remove the packaged
console.log
- through
HardSourceWebpackPlugin
Cache packaging intermediate steps to improve performance - open
gzip
- use
autodll-webpack-plugin
Package third-party modules and files that are not frequently changed in advance to improve the packaging speed
There is also a community summary of a detailed configuration file for vue.config.js: Portal
The HardSourceWebpackPlugin and autodll-webpack-plugin are highlighted here. After using these two plug-ins in the project, the first package speed didn’t increase much, but the second package saved nearly 80% of the package time. Try this if you’re having trouble packing (it’s easy to configure in the React project).
Add the corresponding shortcut to package.json when you are done:
"scripts": {
"start": "vue-cli-service serve",
"build": "vue-cli-service build",
"build:analysis": "vue-cli-service build --mode analysis",
"deploy": "sh ./deploy.sh"
},
Copy the code
webstorm
Practical skills
We can provide WebStorm with the WebPack configuration file, so that WebStorm can recognize the configuration of path aliases and suffixes, which greatly facilitates WebStorm to automatically import our path completion and code.
Vue’s webpack.config.js is here, and it dynamically recognizes the configuration in vue. Config.js:
If we’re building our project using react-create-app and don’t want to use eject, we can write a fake webpack.config.js file specifically for WebStorm to recognize:
// This is not a real WebPack configuration file, but is used to make WebPack recognize the corresponding configuration
const path = require('path');
module.exports = {
resolve: {
alias: {
The '@': path.resolve(__dirname, './src')}}};Copy the code
In the project we disabled the ESLint plugin and used WebStorm to control our code style. Once configured, we just formatted it:
Here our JavaScript code partitions follow the default standard code style and are set to end each line with a semicolon
In code style, you can also set the code style of CSS, HTML, SASS and other files. You can study it yourself.
Here are a few more shortcuts that I find particularly useful:
I use a MAC
shift+F6
: You can rename variables and change where variables are used, which greatly facilitates code refactoringctrl+B
: When not using the mouse, you can jump to a function or variable definition using the keyboardoption+enter
: pops up a code prompt window, especially useful when importing dependencies automaticallyctrl+[ / ctrl+]
: can jump to the point before or after we operate the code to make it passctrl+B
Jumping to the definition and back again is surprisingly quick
Install third-party project dependencies
We also used some of the best third-party plugins in the community:
vue-awesome-swiper
:vue
Version of theswiper
Plugins, all supportedswiper
In theapi
vue-lazyload
:vue
Image lazy loading pluginaxios
: support toPromise
Is sent in the form ofhttp
request- Nprogress: implements the header loading progress bar
- VConsole: mobile page development tool
VConsole is only used in the development environment:
if (process.env.NODE_ENV === 'development') {
const VConsole = require('vconsole');
const vConsole = new VConsole();
}
Copy the code
There has always been a saying in programming: Don’t duplicate the wheel. Especially in the work, the development pays more attention to efficiency, the use of some excellent third-party plug-ins and third-party component library can better assist our work, we should carry out secondary packaging on the original components to improve the development efficiency.
But when it comes to learning, we can still improve our personal strength by lifting the wheels. Just because we don’t object to repeating the wheel doesn’t mean we don’t have the ability to do it.
Adaptation scheme
The project uses VW unit for mobile end adaptation to be compatible with different models.
First we need to install the following dependencies:
yarn add cssnano cssnano-preset-advanced postcss-aspect-ratio-mini postcss-cssnext postcss-import postcss-px-to-viewport postcss-url postcss-viewport-units postcss-write-svg -DCopy the code
Then add the following configuration to postcss.config.js:
module.exports = {
plugins: {
'postcss-import': {},
'postcss-url': {},
'postcss-aspect-ratio-mini': {},
'postcss-write-svg': {
'utf8': false
},
'postcss-cssnext': {},
// document address: https://github.com/evrone/postcss-px-to-viewport/blob/master/README_CN.md
'postcss-px-to-viewport': {
'viewportWidth': 375.'unitPrecision': 5.'selectorBlackList': [
'.ignore'.'.hairlines'].'mediaQuery': false
},
'postcss-viewport-units': {
/ / filter when using pseudo elements covered plug-in generated content in the command line of warning:https://github.com/didi/cube-ui/issues/296
filterRule: rule= > rule.nodes.findIndex(i= > i.prop === 'content') = = =- 1
},
'cssnano': {
'preset': 'advanced'.'autoprefixer': false.'postcss-zindex': false}}};Copy the code
The important thing to note here is that the viewport configuration item, which we set to 375, in the real world the UI designer will give us a double graph, which is 750. If you want a configuration item, you can refer to the documentation: Portal
Guidelines on pit
In the process of using VW adaptation scheme, the following two problems were probably encountered:
- Add using pseudo-elements
content
Property is prompted on the command lineerror
- Set up the
style
Unable to convert tovw
}}}}}}}}}}}}}}}}}}}}}}}}}}}
'postcss-viewport-units': {
/ / filter when using pseudo elements covered plug-in generated content in the command line of warning:https://github.com/didi/cube-ui/issues/296
filterRule: rule= > rule.nodes.findIndex(i= > i.prop === 'content') = = =- 1
}
Copy the code
The problem with style conversion vw is that we simply wrote a js method to help us with the conversion:
export const vw = (number) = > {
const htmlWidth = document.documentElement.offsetWidth;
return number * (100 / htmlWidth);
};
Copy the code
In this way, we simply solved some minor problems encountered in the current development.
Universal component design
For common components, since they are introduced in many places globally, we use webpack’s require.context method to automatically register the global component for ease of use. I put it in a separate JS file to execute:
// autoRegister.js
import Vue from 'vue';
// Components that do not require automatic registration
const blackList = ['MuiToast'];
const requireComponent = require.context('components'.true, /Mui[A-Z]\w+\.vue$/);
requireComponent.keys().forEach(filename= > {
const componentConfig = requireComponent(filename);
const start = filename.lastIndexOf('/') + 1;
const end = filename.lastIndexOf('. ');
const componentName = filename.slice(start, end);
if (blackList.includes(filename)) {return; }// Globally register components
Vue.component(
componentName,
// If the component option is exported via 'export default',
// Then '.default 'is preferred,
// Otherwise fall back to the root of the use module.
componentConfig.default || componentConfig
);
});
Copy the code
Of course, we need to define a naming convention here: component names must start with Mui and follow the rules for camel naming
Based on project requirements, I implemented the following common components:
layout
Layout components (MuiLayout,MuiHeder,MuiFooter,MuiAside,MuiContent
)icon
Font Icon Component (MuiIcon
)popup
Pop-up component (MuiPopup
)dialog
Dialog box component (MuiDialog
)toast
Global prompt (MuiToast
)number
Product Add button (MuiNumber
)
Here we mainly talk about the implementation process of icon and Toast components. You can see the source code for the implementation process of other components.
icon
component
Icon icon is used very frequently in the project, so IT is necessary for me to create a unified package for easy use.
The icon icon used in the project was obtained from the iconfont website: portal. Here we use symbol to implement, can support multi-color ICONS, can also use font size, color to adjust the style.
First of all, we need to select our icon in the icon library, and then we can make simple Settings for the project of our icon:
We then select the icon of type Symbol and copy the address to pubic/index.html.
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport"
content="Width =device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>Millet mall</title>
<script src="//at.alicdn.com/t/font_1253950_whicd7mh5w.js"></script>
</head>
<body>
<noscript>
<strong>We're sorry but vue-cli-demo doesn't work properly without JavaScript enabled. Please enable it to
continue.</strong>
</noscript>
<div id="app"></div>
<! -- built files will be auto injected -->
</body>
</html>
Copy the code
After the preparation, we created the MuiIcon file and added the following code:
<template>
<svg
class="mui-icon"
aria-hidden="true"
>
<use xlink:href="#icon-xxx"></use>
</svg>
</template>
<script>
export default {
name: 'MiIcon',
};
</script>
<style lang="scss" scoped>
.mui-icon {
display: inline-block;
width: 1em; height: 1em;
vertical-align: top;
fill: currentColor;
overflow: hidden;
}
</style>
Copy the code
CSS is not covered here
The XXX in the code needs to be replaced with the name of the corresponding icon during use. We dynamically set the icon name by passing a name attribute to the icon component. Since the project icon is prefixed with mi, the following changes will be made:
<template>
<svg
class="mui-icon"
aria-hidden="true"
>
<use :xlink:href="`#mi-${name}`"></use>
</svg>
</template>
<script>
export default {
name: 'MiIcon',
props: {
name: { type: String, required: true }
}
};
</script>
Copy the code
Thus we have implemented a basic Icon component that can be used in projects like this:
<mui-icon name="logo"></mui-icon>
Copy the code
In our daily projects, we also encounter the following requirements:
- The mouse moving
icon
Icon, icon rotation - Click on the
icon
Jump to a page
It is not possible to add properties and methods to the Icon component one by one. Here we use some of the less commonly used apis in Vue:
v-on
andv-bind
Bound object: The properties of the object are distributed to the current node$attrs
: can get not inprops
Attributes defined in$listens
: Gets none in the parent scope.native
The decoratorv-on
Event listenerinheritAttrs
: Can let notprops
Properties added to theicon
On the root node of the component
<template> <svg class="mui-icon" aria-hidden="true" v-bind="$attrs" v-on="$listeners" > <use :xlink:href="`#mi-${name}`"></use> </svg> </template> <script> export default { name: 'MiIcon', inheritAttrs: False, // The default value is true, whether to display an attribute passed to the root node that was not received by props: {name: {type: String, required: true}}; </script>Copy the code
Once written in this way, the Icon component can accept any of the events and properties that SVG natively supports.
React will encounter similar requirements, and react will not help us merge classes. So the idea in React is as follows:
- The individual
class
Manually concatenate multiple class name format (Vue
This is done for us.) - through
. restProps
Extend the remaining attributes to the corresponding node
toast
component
Toast is not used in the same way as the other components. It is registered globally using vue.use. When we use the Vue. Use method, we pass in an install method that takes the Vue instance and configuration options as arguments.
export default {
install (Vue,options) {
}
};
Copy the code
A quick glance at the source code shows that the above install method is also executed when vue. use is executed.
In the VUE community, we often see examples of calling components directly from functions on vUE instances:
this.$toast('This is a toast');
this.$toast({ message: 'Loading... '.type: 'loading'.mask: true })
Copy the code
This way of calling is because we bind the corresponding method to the vue prototype, which can then be accessed directly on the vue instance object. Combining what we said above, the code looks something like this:
export default {
install (Vue) {
Vue.prototype.$toast = (options) = > {
// doSomeThing}; }};Copy the code
This way we can add the $toast method to the Vue prototype via vue. use, which is easy to call directly from the component.
At this point, we have roughly determined how our component will be called, and we have designed the parameters as follows:
message
: Prompt messagemask
: Whether there is a mask layertype
: Prompt type, when passed inloading
, the loading status can be displayedicon
: Prompts font icon displayduration
: Displays the event in milliseconds. The event is not automatically closed when 0 is passed in
Post my implementation code (excluding CSS):
<template> <transition name="fade"> <div class="mui-toast" v-if="visible"> <div class="mui-toast-content" :class="{hasIcon}"> <div class="mui-toast-icon" v-if="hasIcon"> <mui-icon class="mui-toast-icon-loading" v-if="isLoading" name="loading"></mui-icon> <mui-icon v-else :name="icon"></mui-icon> </div> {{message}} </div> <div class="mui-toast-mask" v-if="mask"></div> </div> </transition> </template> <script> export default { name: 'MuiToast', props: { message: { type: String, }, mask: { type: Boolean, default: false }, type: { type: String, validator (value) { return ['default', 'loading'].includes(value); }, default: 'default' }, icon: { type: String }, duration: { type: Number, default: 3000 } }, data () { return { visible: false }; }, computed: { isLoading () { return this.type === 'loading'; }, hasIcon () { return this.isLoading || this.icon; } }, mounted () { this.visible = true; this.autoClose(); }, methods: { closeToast () { this.visible = false; this.$nextTick(() => { this.$el.remove(); this.$destroy(); }); }, autoClose () { if (this.duration === 0 || this.type === 'loading') {return; } setTimeout(() => { this.closeToast(); }, this.duration); }}}; </script>Copy the code
The idea is to define visible: False in data first, and then set Visible: True after the component is mounted, so that the transition component can animate the component when it appears and is destroyed.
Note that if we animate children of the root element, we need to specify the transition time explicitly, otherwise the animation will not work
The document address
After the component is created, instead of calling it directly, we use some OF vue’s apis to dynamically generate the component and render the content into the body:
export default {
install (Vue) {
Vue.prototype.$toast = (options) = > {
// Pass the Toast component configuration item for 'vue.extend' to generate the constructor
const componentClass = Vue.extend(Toast);
// Create 'toastInstance' dynamically through the constructor
const toastInstance = new componentClass({
// Pass parameters via propsData
propsData: options,
});
// If no render node is specified for $mount, the component can be inserted into the document via the native DOM API
toastInstance.$mount();
document.body.appendChild(toastInstance.$el); }; }};Copy the code
For dynamically creating vUE components and rendering them into a page, see this article:
At this point, a basic Toast component is roughly complete
After the test, I probably found the following problems:
- Click repeatedly to create the component
- Unable to close the component outside the component, causing
loading
Can’t shut down - Provides simplified invocation methods:
this.$toast(message)
, without passing in complex configuration items, easy to use
Here we receive the generated component instance through an external variable and remove the old instance and DOM structure from the page with each creation. After creating a component from a function, it returns a function to close the component, which we can call directly:
import Toast from './MuiToast';
let toastInstance = null;
export default {
install (Vue) {
Vue.prototype.$toast = (options) = > {
// If the component already exists, destroy it and recreate it
if (toastInstance) { // You can call the methods in the component directly from the instance
toastInstance.closeToast();
}
const componentClass = Vue.extend(Toast);
if (typeof options === 'string') {
options = { message: options };
}
toastInstance = new componentClass({
propsData: options,
});
toastInstance.$mount();
document.body.appendChild(toastInstance.$el);
// Returns the close function after the component is called
returntoastInstance.closeToast; }; }};Copy the code
The effects used in the project are as follows:
Knowledge anecdotal stories
In the writing process of the project, I gained more knowledge about the use of import and export in ES6.
Here is a question to test friends, interested please leave a comment below.
Create 3 new files in the project SRC directory: a.js,b.js and c.js, where A.js is the entry file (that is, the first one to execute). The code in each file is as follows:
// a.js
console.log('a.js');
import './b.js'
// b.js
console.log('b.js');
import './c.js'
// c.js
console.log('c.js');
import './a.js'
Copy the code
What is the final output? Anyway, this is a subversion of the author’s perception
Resources: Load implementation of Module
conclusion
It took about two months to write and summarize this project. The author shared what he saw and learned, hoping it would be helpful to everyone.
Open source is not easy, we hope you can give a start to encourage developers in the community willing to share to create better games.
Source code address: Xiaomi -shop
Another vUE project: VUE + Element background management system. When VUE is combined with Element UI, a different spark will be generated.