創(chuàng)新互聯(lián)10年專注成都高端網(wǎng)站建設(shè)按需網(wǎng)站策劃服務(wù),為客戶提供專業(yè)的成都網(wǎng)站制作,成都網(wǎng)頁(yè)設(shè)計(jì),成都網(wǎng)站設(shè)計(jì)服務(wù);創(chuàng)新互聯(lián)服務(wù)內(nèi)容包含成都網(wǎng)站建設(shè),微信小程序開發(fā),軟件開發(fā),網(wǎng)絡(luò)營(yíng)銷推廣,網(wǎng)絡(luò)運(yùn)營(yíng)服務(wù)及企業(yè)形象設(shè)計(jì);創(chuàng)新互聯(lián)擁有眾多專業(yè)的高端網(wǎng)站制作開發(fā)團(tuán)隊(duì),資深的高端網(wǎng)頁(yè)設(shè)計(jì)團(tuán)隊(duì)及經(jīng)驗(yàn)豐富的架構(gòu)師高端網(wǎng)站策劃團(tuán)隊(duì);我們始終堅(jiān)持從客戶的角度出發(fā),為客戶量身訂造網(wǎng)絡(luò)營(yíng)銷方案,解決網(wǎng)絡(luò)營(yíng)銷疑問(wèn)。
一、企業(yè)項(xiàng)目開發(fā)流程
產(chǎn)品提需求
交互設(shè)計(jì)出原型設(shè)計(jì)
視覺設(shè)計(jì)出UI設(shè)計(jì)圖
前端開發(fā)出頁(yè)面模板
server端存取數(shù)據(jù)庫(kù)
驗(yàn)收測(cè)試
二、為什么要使用vue: https://cn.vuejs.org/v2/guide/comparison.html
5個(gè)前端,4個(gè)會(huì)vue,1個(gè)會(huì)react,那么你該如何選擇
客戶要求使用vue
...
三、如何選擇腳手架
自己搭建腳手架 webpack
使用現(xiàn)成的腳手架 https://cli.vuejs.org/zh/
vue-cli 基于webpack 3
@vue/cli 基于webpack 4
假設(shè)電腦中裝的時(shí)@vue/cli腳手架,但是想用vue-cli的模板,可以如下安裝指令
cnpm install -g @vue/cli
cnpm install -g @vue/cli-init
四、創(chuàng)建項(xiàng)目
@vue/cli
第一種創(chuàng)建方式: vue create mynewapp
第二種創(chuàng)建方式: vue ui
第三種創(chuàng)建法師: vue init webpack myapp
五、開始項(xiàng)目配置
1、如果做的移動(dòng)端,那么需要考慮300ms延時(shí)以及點(diǎn)擊穿透的問(wèn)題,甚至是部分android手機(jī)不支持promise的解決辦法,在index.html中引入如下代碼,如果做的是pc端,忽略此步驟
// 避免移動(dòng)端真機(jī)運(yùn)行雙擊屏幕會(huì)放大
2、修改目錄結(jié)構(gòu)
src
api
assets
components
lib
router
store
views
App.vue
main.js
3、修改App.vue結(jié)構(gòu)
cnpm i node-sass sass-loader -D
export default {
name: 'App'
}
@import '~@/lib/reset.scss';
html, body, .container, .detailContent {
@include rect(100%, 100%); // width: 100%; height: 100%;
}
.container, .detailContent {
@include flexbox(); // display: flex
@include flex-direction(column); // flex-direction:column
.box {
@include flex();
@include rect(100%, auto);
@include flexbox();
@include flex-direction(column);
.header {
@include rect(100%, 0.44rem);
@include background-color(#f66);
}
.content {
@include flex(); // flex: 1;
@include rect(100%, auto);
@include overflow(auto);
}
}
.footer {
@include rect(100%, 0.5rem);
@include background-color(#efefef);
@include flexbox();
a {
@include flex();
@include rect(auto, 100%);
@include flexbox();
@include justify-content(); // justify-content: center;
@include align-items(); // align-items: center;
@include text-color(#333);
&.active {
@include text-color(#f66);
}
}
}
}
4、依據(jù)結(jié)構(gòu)設(shè)計(jì)頁(yè)面
views/home/index.vue
views/kind/index.vue
views/cart/index.vue
views/user/index.vue
以home為例
export default {
}
5、配置路由
router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import routes from './routes'
Vue.use(Router)
export default new Router({
routes
})
router/routes.js ----- 命名視圖+命令路由+路由的懶加載+路由重定向
// 如果一個(gè)頁(yè)面不需要底部,那么就不要傳footer,比如kind無(wú)需底部
const routes = [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'home',
components: {
default: () => import('@/views/home'),
footer: () => import('@/components/Footer')
}
},
{
path: '/kind',
name: 'kind',
components: {
default: () => import('@/views/kind'),
footer: () => import('@/components/Footer')
}
},
{
path: '/cart',
name: 'cart',
components: {
default: () => import('@/views/cart'),
footer: () => import('@/components/Footer')
}
},
{
path: '/user',
name: 'user',
components: {
default: () => import('@/views/user'),
footer: () => import('@/components/Footer')
}
}
]
export default routes
修改App.vue ---- 命名視圖(多視圖路由)default footer
6、底部點(diǎn)擊切換路由
components/Footer.vue,需要在App.vue中修改布局樣式
首頁(yè)
分類
購(gòu)物車
我的
export default {
}
7、編寫頁(yè)面
PC: element-ui https://element.eleme.io/
iview https://www.iviewui.com/
移動(dòng)端: mint-ui http://mint-ui.github.io/
vant https://youzan.github.io/vant/
mint-ui 為例
cnpm i mint-ui -S
cnpm install babel-plugin-component -D
修改.babelrc文件
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
],
"plugins": ["transform-vue-jsx", "transform-runtime",["component", [
{
"libraryName": "mint-ui",
"style": true
}
]]],
"env": {
"test": {
"presets": ["env", "stage-2"],
"plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
}
}
}
7.1 main.js入口文件處引入mintui
import Vue from 'vue'
import App from './App'
import router from './router'
import MintUI from 'mint-ui'
Vue.config.productionTip = false
Vue.use(MintUI)
7.2 封裝了banner.vue和prolist.vue組件
banner.vue組件中使用了UI庫(kù) ---- 輪播圖默認(rèn)占據(jù)整個(gè)高度,提前設(shè)置好一個(gè)父容器
import Vue from 'vue'
import { Swipe, SwipeItem } from 'mint-ui'
Vue.use(Swipe, SwipeItem)
export default {
}
.banner {
height: 150px;
}
prolist.vue
肖生客的救贖
export default {
}
home/index.vue中引用組件
import Banner from '@/components/Banner'
import Prolist from '@/components/Prolist'
export default {
components: {
Banner,
Prolist
}
}
.banner {
height: 150px;
}
8、數(shù)據(jù)請(qǐng)求
cnpm i axios -S
8.1 添加mock數(shù)據(jù)功能 ----- 開發(fā)前期 ---- 后端沒有接口時(shí)這樣用
cnpm i mockjs -D
api/mock.js
// 引入mockjs
const Mock = require('mockjs')
const Random = Mock.Random
const doubandata = function () {
let articles = []
for (let i = 0; i < 10; i++) {
let newArticleObject = {
title: Random.csentence(5, 30),
thumbnail_pic_s: Random.dataImage('300x250', 'mock的圖片'),
author_name: Random.cname(),
date: Random.date() + ' ' + Random.time()
}
articles.push(newArticleObject)
}
return articles
}
// Mock.mock( url, post/get , 返回的數(shù)據(jù));
Mock.mock('/douban', 'get', doubandata)
api/index.js
import axios from 'axios'
import { Indicator } from 'mint-ui'
const baseUrl = process.env.NODE_ENV === 'development' ? '' : 'https://www.daxunxun.com'
console.log(baseUrl)
// 添加請(qǐng)求攔截器
axios.interceptors.request.use(function (config) {
// 在發(fā)送請(qǐng)求之前做些什么
Indicator.open()
return config
}, function (error) {
// 對(duì)請(qǐng)求錯(cuò)誤做些什么
Indicator.close()
return Promise.reject(error)
})
// 添加響應(yīng)攔截器
axios.interceptors.response.use(function (response) {
// 對(duì)響應(yīng)數(shù)據(jù)做點(diǎn)什么
Indicator.close()
return response
}, function (error) {
// 對(duì)響應(yīng)錯(cuò)誤做點(diǎn)什么
Indicator.close()
return Promise.reject(error)
})
const api = {
requestGet (url) {
return new Promise((resolve, reject) => {
axios.get(baseUrl + url)
.then(data => resolve(data.data))
.catch(err => reject(err))
})
},
requestPost (url, params) {
return new Promise((resolve, reject) => {
axios.post(baseUrl + url, params)
.then(data => resolve(data.data))
.catch(err => reject(err))
})
}
}
export default api
main.js處引入mock,項(xiàng)目上線以及由接口時(shí)則刪掉即可
import Vue from 'vue'
import App from './App'
import router from './router'
import MintUI from 'mint-ui'
import '@/api/mock'
8.2 假設(shè)后端已經(jīng)有了接口,但是可能會(huì)存在跨域問(wèn)題,如果有跨域問(wèn)題,開發(fā)時(shí)需要使用到反向代理
刪掉main.js出的mock
config/index.js處配置反向代理
proxyTable: {
'/daxun': {
target: 'https://www.daxunxun.com/',
changeOrigin: true,
pathRewrite: {
'^/daxun': ''
}
},
},
修改api/index.js的 baseUrl地址
const baseUrl = process.env.NODE_ENV === 'development' ? '/daxun' : 'https://www.daxunxun.com'
重啟服務(wù)器,查看效果,預(yù)期一致
9、數(shù)據(jù)處理
本組件內(nèi)部處理 data
狀態(tài)管理器處理
data處理方式
home/index.vue
import Banner from '@/components/Banner'
import Prolist from '@/components/Prolist'
import api from '@/api'
export default {
data () {
return {
bannerdata: [],
prolist: []
}
},
components: {
Banner,
Prolist
},
mounted () {
api.requestGet('/douban').then(data => {
console.log(data)
this.prolist = data
})
}
}
.banner {
height: 150px;
}
prolist.vue
{{ item.title }}
export default {
props: {
prolist: Array
}
}
mock.js修改了模擬地址,以后切換更加簡(jiǎn)單
Mock.mock('/daxun/douban', 'get', doubandata)
以后切換mock和開發(fā)服務(wù)器只需要添加和刪除main.js中的mock字段即可
10、狀態(tài)管理器
cnpm i vuex -S
創(chuàng)建store/index.js,store/home.js,store/kind.js
index.js
import Vue from 'vue'
import VueX from 'vuex'
import home from './home'
import kind from './kind'
Vue.use(VueX)
const store = new VueX.Store({
modules: {
home,
kind
}
})
export default store
kind.js
export default {
state: {},
getters: {},
actions: {},
mutations: {}
}
home.js
import api from '@/api'
const store = {
state: {
bannerdata: [1, 2, 3],
prolist: []
},
getters: {
prolistLength (state) {
return state.prolist.length
}
},
actions: {
getprolist ({ commit }) { // 參數(shù)的解構(gòu)賦值 context
api.requestGet('/douban')
.then(data => {
console.log(data)
commit('changeprolist', data) // context.commit('changeprolist', data)
}).catch(err => console.log(err))
}
},
mutations: {
changebannerdata (state, data) {
state.bannerdata = data
},
changeprolist (state, data) {
state.prolist = data
}
}
}
export default store
home/index.vue 通過(guò)mapState輔助函數(shù)可以直接獲取狀態(tài)管理器中的值,通過(guò)dispatch 觸發(fā)異步的actions
{{ bannerdata }}
import Banner from '@/components/Banner'
import Prolist from '@/components/Prolist'
import { mapState } from 'vuex'
export default {
computed: {
...mapState({
bannerdata: (state) => state.home.bannerdata,
prolist: (state) => state.home.prolist
})
},
components: {
Banner,
Prolist
},
mounted () {
this.$store.dispatch('getprolist') // dispatch 一個(gè)action(異步操作)
}
}
.banner {
height: 150px;
}
使用mapActions的等價(jià)寫法
{{ bannerdata }}
import Banner from '@/components/Banner'
import Prolist from '@/components/Prolist'
import { mapState, mapActions } from 'vuex'
export default {
computed: {
...mapState({
bannerdata: (state) => state.home.bannerdata,
prolist: (state) => state.home.prolist
})
},
components: {
Banner,
Prolist
},
methods: {
...mapActions(['getprolist']) // 生成一個(gè)同名的函數(shù) function getprolsit () {this.$store.dispatch('getprolist')}
},
mounted () {
this.getprolist()
}
}
.banner {
height: 150px;
}
11、列表進(jìn)入詳情
編寫詳情頁(yè)面 detail/index.vue,一定要記得修改App.vue中的樣式
export default {
}
修改routes.js
{
path: '/detail/:id',
name: 'detail',
components: {
default: () => import('@/views/detail')
}
}
聲明式跳轉(zhuǎn)
prolist.vue
{{ item.title }}
編程時(shí)跳轉(zhuǎn)
{{ item.title }}
methods: {
goDetail (item) {
// this.$router.push('/detail/' + item.id)
this.$router.push({
name: 'detail',
params: {id: item.id}
})
}
}
詳情頁(yè)面可以通過(guò) this.$route.params.id 拿到傳遞過(guò)來(lái)的數(shù)據(jù)
12、頁(yè)面切換效果
App.vue使用transition包裹router-view
export default {
name: 'App'
}
@import '~@/lib/reset.scss';
html, body, .container {
@include rect(100%, 100%); // width: 100%; height: 100%;
}
.container {
max-width: 640px;
margin: 0 auto;
box-shadow: 0 0 2px #ccc;
@include flexbox(); // display: flex
@include flex-direction(column); // flex-direction:column
.box {
@include flex();
@include rect(100%, auto);
@include flexbox();
@include flex-direction(column);
.header {
@include rect(100%, 0.44rem);
@include background-color(#f66);
}
.content {
@include flex(); // flex: 1;
@include rect(100%, auto);
@include overflow(auto);
}
}
.footer {
@include rect(100%, 0.5rem);
@include background-color(#efefef);
ul {
@include rect(100%, 100%);
@include flexbox();
li {
@include flex();
@include rect(auto, 100%);
@include flexbox();
@include justify-content(); // justify-content: center;
@include align-items(); // align-items: center;
@include text-color(#333);
&.router-link-exact-active,.router-link-active{
@include text-color(#f66);
}
}
}
}
}
.slide-enter {
transform: translateX(100%);
}
.slide-enter-active {
transition: all .3s;
}
.slide-enter-to {
transform: translateX(0%);
}
.slide-leave {
transform: translateX(0%);
}
.slide-leave-active {
transition: all 0s;
}
.slide-leave-to {
transform: translateX(-100%);
}
13、下拉刷新以及上拉加載功能
以分類為例
import Vue from 'vue'
import { Loadmore } from 'mint-ui'
import api from '@/api'
Vue.use(Loadmore)
export default {
data () {
return {
kindlist: [],
allLoaded: false, // 所有的數(shù)據(jù)是否已經(jīng)加載完畢
pageCode: 1 // 頁(yè)碼
}
},
mounted () { // 請(qǐng)求一次數(shù)據(jù)
api.requestGet('/douban')
.then(data => {
this.kindlist = data
})
},
methods: {
loadTop () { // 下啦刷新函數(shù) --- 請(qǐng)求了第一頁(yè)的數(shù)據(jù)
api.requestGet('/douban')
.then(data => {
this.kindlist = data // 替換數(shù)據(jù)
this.pageCode = 1 // 刷新完畢,頁(yè)碼歸1
this.allLoaded = false // 刷新完畢,表示可以繼續(xù)加載下一頁(yè)
this.$refs.loadmore.onTopLoaded() // 更新列表
})
},
loadBottom () {
api.requestGet('/douban?count=20&start=' + this.pageCode * 20)
.then(data => {
if (data.length === 0) { // 沒有數(shù)據(jù)的條件
this.allLoaded = true// 若數(shù)據(jù)已全部獲取完畢
}
this.pageCode += 1 // 頁(yè)碼加一,下一次請(qǐng)求數(shù)據(jù)時(shí)用
this.kindlist = [...this.kindlist, ...data] //組合數(shù)據(jù)
this.$refs.loadmore.onBottomLoaded() // 更新列表
})
}
}
}
.kindlist {
li {
height: 40px;
border-bottom: 1px solid #ccc;
line-height: 40px;
}
}
如果想要結(jié)合vuex實(shí)現(xiàn)
kind/index.vue
import Vue from 'vue'
import { Loadmore } from 'mint-ui'
import { mapState } from 'vuex'
Vue.use(Loadmore)
export default {
data () {
return {
allLoaded: false,
pageCode: 1
}
},
computed: {
...mapState({
kindlist: (state) => state.kind.kindlist
})
},
mounted () {
this.$store.dispatch('getkindlist')
},
methods: {
loadTop () {
this.$store.dispatch('loadTop').then(() => {
this.pageCode = 1
this.allLoaded = false
this.$refs.loadmore.onTopLoaded()
})
},
loadBottom () {
this.$store.dispatch('loadBottom', { pageCode: this.pageCode }).then(data => {
if (data.length === 0) {
this.allLoaded = true
} else {
this.pageCode += 1
}
this.$refs.loadmore.onBottomLoaded()
})
}
}
}
.kindlist {
li {
height: 40px;
border-bottom: 1px solid #ccc;
line-height: 40px;
}
}
store/kind.js
import api from '@/api'
export default {
state: {
kindlist: []
},
getters: {},
actions: {
getkindlist ({ commit }) {
api.requestGet('/douban')
.then(data => {
commit('changekindlist', data)
})
},
loadTop ({ commit }) {
return new Promise((resolve, reject) => {
api.requestGet('/douban')
.then(data => {
commit('changekindlist', data)
resolve()
})
})
},
loadBottom ({ commit, state }, params) {
console.log(params)
return new Promise((resolve, reject) => {
api.requestGet('/douban?count=20&start=' + params.pageCode * 20)
.then(data => {
console.log('bottom', data)
const arr = [...state.kindlist, ...data]
console.log('arr', arr)
commit('changekindlist', arr)
resolve(data)
})
})
}
},
mutations: {
changekindlist (state, data) {
state.kindlist = data
}
}
}
14、回到頂部
components/BackTop.vue
返回頂部
export default {
methods: {
backtop () {
console.log('1')
document.getElementById('content').scrollIntoView()
}
}
}
.backtop {
position:fixed;
right:10px;bottom:60px;
}
使用時(shí)可以給需要的地方添加一個(gè)
15、購(gòu)物車業(yè)務(wù)邏輯
{{ item.name }}
{{ item.num }} ¥{{ item.price }} 小計(jì): {{ item.num * item.price}}
export default {
data () {
return {
allChecked: false,
cartlist: [
{
id: 1,
name: '蘋果',
price: 4.8,
num: 2,
flag: false
},
{
id: 2,
name: '香蕉',
price: 3,
num: 5,
flag: false
},
{
id: 3,
name: '榴蓮',
price: 29.8,
num: 1,
flag: false
}
]
}
},
methods: {
test () {
if (this.allChecked) {
this.cartlist.map(item => {
item.flag = true
})
} else {
this.cartlist.map(item => {
item.flag = false
})
}
},
fn (item) {
if (!item.flag) {
this.allChecked = false
} else {
let bool = true
this.cartlist.map(item => {
if (!item.flag) {
bool = false
}
})
this.allChecked = bool
}
}
},
computed: {
totalNum () {
let num = 0
this.cartlist.map(item => {
item.flag ? num += item.num : num += 0
})
return num
},
totalprice () {
let price = 0
this.cartlist.map(item => {
item.flag ? price += item.num * item.price : price += 0
})
return price.toFixed(2)
}
},
watch: {
allChecked (newVal) {
}
}
}
16、注冊(cè)功能 --- 計(jì)算屬性
import Vue from 'vue'
import { Field, Button } from 'mint-ui'
import api from '@/api'
Vue.use(Field, Button)
export default {
data () {
return {
username: '17733203950',
password: '123456',
msg: '發(fā)送驗(yàn)證碼',
time: 10,
sendState: false,
code: '',
adminCode: ''
}
},
computed: {
usernameState () {
if (this.username === '') {
return ''
} else if (/^[1][3,4,5,6,7,8,9][0-9]{9}$/.test(this.username)) {
return 'success'
} else {
return 'error'
}
},
passwordState () {
if (this.password === '') {
return ''
} else if (this.password.length > 5) {
return 'success'
} else {
return 'error'
}
},
codeState () {
if (this.code === '') {
return ''
} else if (this.code === this.adminCode) {
return 'success'
} else {
return 'error'
}
},
disabledtype () {
if (this.usernameState === 'success' && this.passwordState === 'success' && this.codeState === 'success') {
return false
}
},
type () {
if (this.usernameState === 'success' && this.passwordState === 'success' && this.codeState === 'success') {
return 'primary'
} else {
return 'default'
}
}
},
methods: {
getCode () {
api.requestGet('/users/sendCode?tel=' + this.username)
.then(data => {
if (data === 0) {
console.log('驗(yàn)證碼發(fā)送失敗')
} else if (data === 1) {
console.log('手機(jī)號(hào)已經(jīng)注冊(cè)過(guò)')
} else {
console.log(data)
this.adminCode = data.code
}
})
},
sendCode () {
console.log('發(fā)送短信驗(yàn)證碼')
this.sendState = true
this.getCode()
var timer = setInterval(() => {
this.msg = this.time + '后重新發(fā)送'
this.time--
if (this.time === -1) {
this.msg = '發(fā)送驗(yàn)證碼'
this.sendState = false
this.time = 10
clearInterval(timer)
}
}, 1000)
},
register () {
api.requestPost('/users/register', {
username: this.username,
password: this.password
}).then(data => {
if (data === 0) {
console.log('注冊(cè)失敗')
} else if (data === 1) {
console.log('注冊(cè)成功')
} else {
console.log('用戶名已注冊(cè)')
}
})
}
}
}
登錄
import Vue from 'vue'
import { Field, Button } from 'mint-ui'
import api from '@/api'
Vue.use(Field, Button)
export default {
data () {
return {
username: '17733203950',
password: '123456'
}
},
computed: {
usernameState () {
if (this.username === '') {
return ''
} else if (/^[1][3,4,5,6,7,8,9][0-9]{9}$/.test(this.username)) {
return 'success'
} else {
return 'error'
}
},
passwordState () {
if (this.password === '') {
return ''
} else if (this.password.length > 5) {
return 'success'
} else {
return 'error'
}
},
disabledtype () {
if (this.usernameState === 'success' && this.passwordState === 'success') {
return false
}
},
type () {
if (this.usernameState === 'success' && this.passwordState === 'success') {
return 'primary'
} else {
return 'default'
}
}
},
methods: {
login () {
api.requestPost('/users/login', {
username: this.username,
password: this.password
}).then(data => {
if (data === 0) {
console.log('登錄失敗')
} else if (data === 1) {
console.log('登錄成功')
// 登錄成功,還可以返回token信息,把它保存到本地
// 以后請(qǐng)求數(shù)據(jù)時(shí),把token攜帶過(guò)去
localStorage.setItem('isLogin', 'ok')
} else if (data === 2) {
console.log('用戶未注冊(cè)')
} else {
console.log('密碼錯(cuò)誤')
}
})
}
}
}