模板基於 vue-cli4 和 Vant-ui 搭建,進行大型 H5 項目開發最佳實踐方案,讓咱們來一探究竟javascript
模板地址 動動你可愛的小手點亮一顆 star
css
本項目已經爲你生成了一個完整的開發框架,下面是整個項目的目錄結構。html
├── .github # git log
├── plop-templates # 基本模板
├── public # 靜態資源
│ │── favicon.ico # favicon圖標
│ └── index.html # html模板
├── src # 源代碼
│ ├── assets # 靜態資源
│ ├── components # 全局公用組件
│ ├── constants # 常量
│ ├── core # 分層
│ ├── enum # 枚舉
│ ├── filters # 全局 filter
│ ├── icons # 項目全部 svg icons
│ ├── lang # 國際化 language
│ ├── layout # 全局 layout
│ ├── router # 路由
│ ├── store # 全局 store 管理
│ ├── styles # 全局樣式
│ ├── utils # 全局公用方法
│ ├── pages # pages 全部頁面
│ ├── pwa # 漸進式Web應用
│ ├── App.vue # 入口頁面
│ ├── main.js # 入口文件 加載組件 初始化等
│ └── permission.js # 權限管理
├── tests # 測試
├── .editorconfig # 代碼風格
├── .env.xxx # 環境變量配置
├── .eslintrc.js # eslint 配置項
├── .sentryclirc.js # 前端異常日誌監控配置
├── .babel.config # babel 配置
├── plopfile.js # 基本模板配置
├── vue.config.js # vue-cli 配置
├── postcss.config.js # postcss 配置
└── package.json # package.json
...
複製代碼
# 克隆項目
git clone https://github.com/push-over/vue-h5-template.git
# 進入項目目錄
cd vue-h5-template
# 安裝依賴
npm install
# 建議不要用 cnpm 安裝 會有各類詭異的bug 能夠經過以下操做解決 npm 下載速度慢的問題
npm install --registry=https://registry.npm.taobao.org
# 本地開發 啓動項目
npm start
複製代碼
TIP前端
強烈建議不要用直接使用 cnpm 安裝,會有各類詭異的 bug,能夠經過從新指定 registry 來解決 npm 安裝速度慢的問題。若仍是不行,可以使用 yarn 替代
npm
。vueWindows 用戶若安裝不成功,很大機率是
node-sass
安裝失敗,解決方案。java另外由於
node-sass
是依賴python
環境的,若是你以前沒有安裝和配置過的話,須要自行查看一下相關安裝教程。node
啓動完成後會自動打開瀏覽器訪問 [http://localhost:9000, 你看到下面的頁面就表明操做成功了。python
接下來你能夠修改代碼進行業務開發了,本項目內建了典型業務模板、模擬數據、狀態管理、國際化、全局路由等等各類實用的功能來輔助開發android
# 項目打包
npm run build:xxx
# 自動建立
npm run new
# 規範Git提交
npm run git-cz
# 生成CHANGELOG
npm run genlog
複製代碼
目前前端的一個開發趨勢是以搭建單頁應用 (SPA) 爲主的。當應用體系比較大,或者你的應用業務邏輯足夠複雜的時候,會遇到各類各樣的問題,咱們隨便提兩點:webpack
產品須要多人協做時,每一個人的代碼風格和對業務的理解不一樣,致使業務邏輯分佈雜亂無章
對產品的理解停留在頁面驅動層面,致使實現的技術模型與實際業務模型出入較大,當業務需求變更時,技術模型很容易被摧毀
...
針對上面所遇到的問題,咱們如下面這張架構圖作講解:
其中 視圖層/View 是你們接觸最多的,想必你們都很瞭解,就不在這裏介紹了,重點介紹其餘幾個層的含義:
Services 層是用來對底層技術進行操做的,例如封裝 AJAX
請求,操做瀏覽器 Cookie
、LocaStorage
、IndexedDB
,操做 Native
提供的能力(如調用攝像頭等),以及創建 Websocket
與後端進行交互等。
Axios 封裝
.....
export default async function(options) {
const { url } = options
const requestOptions = Object.assign({}, options)
try {
const { data, data: { errno, errmsg }} = await instance.request(requestOptions)
if (errno) {
errorReport(url, errmsg, requestOptions, data)
throw new Error(errmsg)
}
return data
} catch (err) {
errorReport(url, err, requestOptions)
throw err
}
}
複製代碼
IndexedDB
...
export class DBRequest {
instance
static getInstance() {
if (!this.instance) {
this.instance = new DBRequest()
}
return this.instance
}
async create(options = {}) {
const { name, data } = options
const db = await indexDB(name)
return await db.add(name, data)
}
...
}
複製代碼
.......
實體 Entity 是領域驅動設計的核心概念,它是領域服務的載體,它定義了業務中某個個體的屬性和方法。區分一個對象是不是實體,主要是看他是否有惟一的標誌符(例如 id)
經過上面的代碼能夠看到,這裏主要是以實體自己的屬性以及派生屬性爲主,固然實體自己也能夠具備方法,用於實現屬於實體自身的業務邏輯。
並非全部的實體都應該按上面那樣封裝成一個類,若是某個實體自己業務邏輯很簡單,就沒有必要進行封裝,例如本模板中的 Test
只是作個演示。
Interactors 層是負責處理業務邏輯的層,主要是由業務用例組成
import { Request } from '@/utils/request'
import { CARDS } from '@/constants/api/test'
class TestHttpInteractor {
service
constructor(service) {
this.service = service
}
async getTest() {
try {
const options = { url: CARDS }
return await this.service.get(options)
} catch (error) {
throw error
}
}
async createTest(data) {
try {
const optons = { url: CARDS, data }
await this.service.post(optons)
} catch (error) {
throw error
}
}
...
}
const testHttpInteractor = new TestHttpInteractor(Request.getInstance())
export default testHttpInteractor
複製代碼
經過上面的代碼能夠看到,Sevices 層提供的類的實例主要是經過 Interactors 層的類的構造函數獲取到,這樣就能夠達到兩層之間解耦,實現快速切換 service 的目的了,固然這個和依賴注入 DI 仍是有些差距的,不過已經知足了咱們的需求。
另外 Interactors 層還能夠獲取 Entities 層提供的實體類,將實體類提供的與實體強相關的業務邏輯和 Interactors 層的業務邏輯融合到一塊兒提供給 View 層,例如:
固然這種分層架構並非銀彈,其主要適用的場景是:實體關係複雜,而交互相對模式化,例如企業軟件領域。相反實體關係簡單而交互複雜多變就不適合這種分層架構了。
而後須要明確的是,架構和項目文件結構並非等同的,文件結構是你從視覺上分離應用程序各部分的方式,而架構是從概念上分離應用程序的方式。你能夠在很好地保持相同架構的同時,選擇不一樣的文件結構方式。沒有完美的文件結構,所以請根據項目的不一樣選擇適合你的文件結構。
頁面總體佈局是一個產品最外層的框架結構, 這裏使用了 vue-router 路由嵌套, 因此通常狀況下,你增長或者修改頁面只會影響 app-main
這個主體區域。其它配置在 layout
中的內容如:底部導航都是不會隨着你主體頁面變化而變化的。
/foo /bar
+------------------+ +-----------------+
| layout | | layout |
| +--------------+ | | +-------------+ |
| | foo.vue | | +------------> | | bar.vue | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
複製代碼
這裏在 app-main
外部包了一層 keep-alive
主要是爲了緩存 的,如不須要可自行去除。
在樣式開發過程當中,有兩個問題比較突出:
好在 vue 爲咱們提供了 scoped 能夠很方便的解決上述問題。 它顧名思義給 css 加了一個域的概念。
/* 編譯前 */
.example {
color: red;
}
/* 編譯後 */
.example[_v-f3f3eg9] {
color: red;
}
複製代碼
只要加上 style scoped
這樣 css 就只會做用在當前組件內了。
TIP
使用 scoped 後,父組件的樣式將不會滲透到子組件中。不過一個子組件的根節點會同時受其父組件的 scoped CSS 和子組件的 scoped CSS 的影響。這樣設計是爲了讓父組件能夠從佈局的角度出發,調整其子組件根元素的樣式。
vue-h5-template 全部全局樣式都在 @/src/styles
目錄下設置
├── styles
│ ├── _animation # 按鈕樣式
│ ├── index.scss # 全局通用樣式
│ ├── _mixin.scss # 全局mixin
│ ├── _transition.scss # 過渡效果
│ └── variables.scss # 全局變量
複製代碼
在 vue-h5-template
中,一個完整的前端 UI 交互到服務端處理流程是這樣的:
其中,@/src/utils/request.js
是基於 Server目錄的 http 的二次封裝,便於統一處理 POST,GET 等請求方式。具體能夠參看項目代碼。
...
export class Request {
instance
static getInstance() {
if (!this.instance) {
this.instance = new Request()
}
return this.instance
}
async post(options = {}) {
const { data } = await service({
method: 'post',
...options
})
return data
}
...
}
複製代碼
定義接口地址統一管理 src/constants/api/test.js
export const CARDS = '/admin/cards'
複製代碼
請求方法 src/core/interactors/test-interactor.js
async getTest() {
try {
const options = { url: CARDS }
return await this.service.get(options)
} catch (error) {
throw error
}
}
複製代碼
請求方式 src/utils/request.js
async get(options = {}) {
const { data } = await service({
method: 'get',
...options
})
return data
}
複製代碼
TIP
目錄結構不要糾結,我的習慣而定
頁面使用 src/pages/test/index.vue
# 生命週期
async created() {
if (this.id) {
await this.handleGetTest()
}
}
# 請求
async handleGetTest() {
try {
const test = await testInteractor.getTest(this.id)
this.addressInfo = Object.assign({}, test)
} catch (error) {
console.log(error)
}
}
複製代碼
可能你們會以爲很繁瑣,這麼多文件容易搞混,重複編寫代碼等等,不要着急,本模板配置了自動生成文件,上述除了視圖/View層這塊須要你手動去編寫代碼,其餘的咱們都會去一鍵生成,接下來咱們就來說講使用方法。
在開發過程當中,不管咱們添加頁面也好仍是添加組件等等。都須要不停地新建 .vue
文件(或者其餘框架或者html/js/css文件)
以Vue項目爲例, 咱們新建一個component 或 view 的時候,須要新建一個.vue文件,而後寫 <template>、<script>、<style>
。最後寫咱們的業務代碼。若是使用 class 風格來寫 Vue 還須要在每一個頁面都引入 Vue 和Component
既然這種重複性的工做,並且並無實際的操做難度,咱們是學不到任何東西的,那有沒有什麼辦法能夠告別手敲呢, 在這裏給你們介紹一個插件 plop,它的使用方式比較簡單,在這裏我不作過多介紹了,你們能夠查閱文檔,或者直接拉取本模板進行查閱
本項目中我一個配置了5項,他們分別表明着什麼呢?
module.exports = function(plop) {
plop.setGenerator('page', pageGenerator) // Page
plop.setGenerator('component', componentGenerator) // 組件
plop.setGenerator('store', storeGenerator) // vuex
plop.setGenerator('interactor', interactorGenerator) // 業務邏輯
plop.setGenerator('db-interactor', dbInteractorGenerator) //db業務邏輯
}
複製代碼
TIP
建立模板指令是 npm run new,記得屬於目錄或文件名稱哦
# 移動端控制檯 開(yes) | 關(no)
VCONSOLE=no
# 標題
VUE_APP_TITLE=CHINA-GOODS-H5
# 端口號
DEVSERVERPORT=9000
# 錯誤監控平臺
SENTRY_ENABLED=yes
SENTRY_DSN='https://b84aa04e3def4784a471f8032dc62fd4@sentry.io/3619515'
SENTRY_PLUGIN_ENABLED=no
複製代碼
export const TITLE = '' // 標題
export const TOKEN_KEY = '' // token-key
export const LANGUAGE_KEY = 'language' // 國際化
...
複製代碼
無論是多人合做仍是我的項目,代碼規範都是很重要的。這樣作不只能夠很大程度地避免基本語法錯誤,也保證了代碼的可讀性。
ESLint 全部的配置文件都在 .eslintrc.js 中。 本項目基本規範是依託於 vue 官方的 eslint 規則 eslint-config-vue 作了少量的修改。你們能夠按照本身的需求進行定製化配置。
代碼風格在 .editorconfig,你們能夠按照我的喜歡個性化修改。
若是你沒有在本項目 Icon 中找到須要的圖標,能夠到 iconfont.cn 上選擇並生成本身的業務圖標庫,再進行使用。或者其它 svg 圖標網站,下載 svg 並放到文件夾之中就能夠了。
首先,搜索並找到你須要的圖標,將它採集到你的購物車裏,在購物車裏,你能夠將選中的圖標添加到項目中(沒有的話,新建一個),後續生成的資源/代碼都是以項目爲維度的。
如今本項目支持和推薦單獨導出 svg 的引入使用方式。下載方式以下圖:
下載完成以後將下載好的 .svg 文件放入 @/icons/svg
文件夾下以後就會自動導入。
已經配置指令,只須要執行相關指令就好:
npm run svgo
複製代碼
<svg-icon icon-class="password" /> // icon-class 爲 icon 的名字
複製代碼
本項目集合了國際化 i18n 方案。經過 vue-i18n而實現。
因爲本項目 ui 框架使用了 Vant UI
,因此國際化的同時也要將其國際化。同時將當前 lang
語言存在 cookie
之中,爲了下次打開頁面能記住上次的語言設置。
export const VueVantLocales = (lang = getLocale()) => {
switch (lang) {
case 'zh':
Locale.use('zh-CN', vantZhLocale)
break
case 'en':
Locale.use('en-US', vantEnLocale)
break
}
}
export default new VueI18n({
locale: getLocale(),
fallbackLocale: getLocale(),
messages
})
複製代碼
Html 中使用:
// $t 是 vue-i18n 提供的全局方法
$t('heoll')
複製代碼
Js 中使用:
const options = [
{
value: '1',
label: this.$t('i18nView.one')
},
{
value: '2',
label: this.$t('i18nView.two')
}
]
複製代碼
因爲本模板是H5開發模板,因此必定要有樣式適配啦。對此目前主流方案有 vw 和 rem,咱們來使用一個工具來幫助咱們完成屏幕的適配,postcss-px-to-viewport,安裝以後咱們只須要在 postcss.config.js
配置便可,具體配置說明還請查閱文檔。
在調試方面,本項目使用 vconsole
做爲手機端調試面板,功能至關於打開 PC 控制檯,能夠很方便地查看 Console, Network, Element、Storage 等關鍵調試信息。
對別的錯誤監控平臺也不太瞭解,只記得當時在寫 PHP
的時候有用過 sentry
,因此本項目中就配置了它做爲錯誤監控平臺。同時使用了這個大佬的插件 編譯時自動在 try catch 中添加錯誤上報函數的 babel 插件,相關配置在 .sentryclirc
這個文件中,具體相關配置在你建立時就會給出提示。還不清楚的請查閱 配置sentry
[defaults]
url=https://sentry.io
org=組織名
project=項目名
[auth]
token=token
複製代碼
代碼提交記錄是一個很好的代碼修改日誌。規範的代碼提交記錄,無論在平時代碼開發維護過程當中,仍是在定位 bug 或者回退版原本說都是極爲重要。
npm run git-cz
複製代碼
1. Select the type of change that you're committing 選擇您要提交的更改類型 2. What is the scope of this change (e.g. component or file name): (press enter to skip) 更改的範圍是什麼(例如,組件或文件名):(按Enter跳過) 3. Write a short, imperative tense description of the change (max 61 chars) 撰寫簡短的命令式時態描述(最多61個字符) 4. Provide a longer description of the change: (press enter to skip) 提供更改的詳細說明:(按Enter跳過) 5. Are there any breaking changes? 有重大變化嗎? 6. Does this change affect any open issues? 此更改會影響任何未解決的問題嗎? - feat 新功能 - fix Bug 修復 - docs 文檔更新 - style 代碼的格式,標點符號的更新 - refactor 代碼重構 - perf 性能優化 - test 測試更新 - build 構建系統或者包依賴更新 - ci CI 配置,腳本文件等更新 - chore 非 src 或者 測試文件的更新 - revert commit 回退 複製代碼
也已經配置相關指令:
npm run genlog
複製代碼
就會出現相似與這種的文件格式:
### Features
* 國際化 攔截器 完善模板 ([379b452](https://10.6.30.204/front/china-goods-h5/commits/379b4522f9c0c0a1c282281af68c92c1cca10858))
* 飛入購物車 ([f1a5f2d](https://10.6.30.204/front/china-goods-h5/commits/f1a5f2db2fe6e67e3b5801c26563ba9f089e470b))
複製代碼
PWA是Progressive Web App的英文縮寫, 翻譯過來就是漸進式加強WEB應用, 是Google 在2016年提出的概念,2017年落地的web技術。目的就是在移動端利用提供的標準化框架,在網頁應用中實現和原生應用相近的用戶體驗的漸進式網頁應用。
vue 最新腳手架中集成了 pwa
的插件,將 pwa
的實現變得更加的簡單,只須要在 vue.config.js
文件中配置 pwa
屬性就能夠自動生成對應的 service-worker.js
配置文件,在這不作過多介紹。
# PWA
pwa: {
name: VUE_APP_TITLE,
workboxPluginMode: 'InjectManifest',
workboxOptions: {
swSrc: resolve('src/pwa/service-worker.js')
}
}
# 別名
configureWebpack: {
name: VUE_APP_TITLE,
resolve: {
alias: {
'@': resolve('src')
}
}
},
# 在樣式引入時,對於變量的引入,須要在每一個文件裏都要引入一遍,爲了不每次使用時都須要單獨引入一遍的問題,採用了style-resources-loader
pluginOptions: {
'style-resources-loader': {
preProcessor: 'scss',
patterns: [
resolve('src/styles/_variables.scss'),
resolve('src/styles/_mixins.scss')
]
}
},
# vconsole
config.plugin('VConsolePlugin')
.use(new VConsolePlugin({
filter: [],
enable: DEV && VCONSOLE === 'yes'
}))
.end()
# 引入 lodash 在頁面能夠直接使用 _.isString() ...
config.plugin('ProvidePlugin')
.use(new webpack.ProvidePlugin({
_: 'lodash'
}))
.end()
# 設置 svg-sprite-loader
config.module
.rule('svg')
.exclude.add(resolve('src/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
# 提交 map 文件
config.plugin('sentryPlugin')
.use(new SentryPlugin({
release: version,
include: path.join(__dirname, './dist/static/js'),
urlPrefix: '~/vue-h5-template/statis/js',
ignore: ['node_modules']
}))
.end()
複製代碼
vconsole-webpack-plugin
@sentry/webpack-plugin
lodash-webpack-plugin
...
項目中還作了一些細節處理,詳情請拉取項目查看,謝謝觀看。以爲好能夠給顆 star 模板地址 ,會持續更新,有問題能夠留言或加我微信 gao1336650475