REALWORLD項目
介紹
- GitHub倉庫:https://github.com/wang1xiang/realworld-nuxtJS
- 在線示例:http://106.75.190.29:3000
- 接口文檔:https://github.com/gothinkster/realworld/tree/master/api
- 頁面模板:https://github.com/gothinkster/realworld-starter-kit/blob/master/FRONTEND_INSTRUCTIONS.md
創(chuàng)建項目
// 在nuxt-js-demo創(chuàng)建realworld-nuxtJS分支
git checkout -b realworld-nuxtJS
# 生成 package.json 文件
npm init -y
# 安裝 nuxt 依賴
npm install nuxt
在 package.json 中添加啟動腳本:
"scripts": {
"dev": "nuxt"
}
創(chuàng)建pages/index.vue
<template>
<div>
<h1>Home Page</h1>
</div>
</template>
<script>
export default {
name: 'HomePage'
}
</script>
<style>
</style>
啟動項目:
npm run dev
此時在瀏覽器訪問http://localhost:3000/ 測試肆良。
導(dǎo)入樣式文件
在項目根目錄創(chuàng)建app.html頁面杀饵,導(dǎo)入nuxt.js默認的html
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head {{ HEAD_ATTRS }}>
{{ HEAD }}
<!-- 引入樣式文件 -->
<!-- Import Ionicon icons & Google Fonts our Bootstrap theme relies on -->
<link
rel="stylesheet" type="text/css">
<link href="http://fonts.googleapis.com/css?
family=Titillium+Web:700|Source+Serif+Pro:400,700|Merriweather+Sans:400,700|Sour
ce+Sans+Pro:400,300,600,700,300italic,400italic,600italic,700italic"
rel="stylesheet" type="text/css">
<!-- Import the custom Bootstrap 4 theme from our hosted CDN -->
<!-- <link rel="stylesheet" > -->
<link rel="stylesheet" href="/index.css">
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
</body>
</html>
創(chuàng)建nuxt.config.js文件
module.exports = {
router: {
linkActiveClass: 'active', // 處理導(dǎo)航鏈接高亮
// 自定義路由規(guī)則
extendRoutes (routes, resolve) {
// 清空基于pages目錄默認生成的路由表規(guī)則
routes.splice(0)
routes.push(...[
{
path: '/',
component: resolve(__dirname, 'pages/layout/'),
children: [
{
path: '', // 為空代表默認子路由
name: 'home',
component: resolve(__dirname, 'pages/home/'),
}, {
path: '/login',
name: 'login',
component: resolve(__dirname, 'pages/login/'),
}, {
path: '/register',
name: 'register',
component: resolve(__dirname, 'pages/login/'),
}, {
path: '/profile/:username',
name: 'profile',
component: resolve(__dirname, 'pages/profile/'),
}, {
path: '/settings',
name: 'settings',
component: resolve(__dirname, 'pages/settings/'),
}, {
path: '/editor',
name: 'editor',
component: resolve(__dirname, 'pages/editor/'),
}, {
path: '/article/:slug',
name: 'article',
component: resolve(__dirname, 'pages/article/'),
}
]
}
])
}
}
}
導(dǎo)入以下對應(yīng)頁面 從[頁面模板](https://github.com/gothinkster/realworld-starter-kit/blob/master/FRONTEND_INS TRUCTIONS.md)導(dǎo)入
layout作為頁面根路由物舒,由頭部header慈省、子路由<nuxt-child/>和底部footer組成
引入axios 封裝請求模塊
-
安裝axios
npm i axios
-
創(chuàng)建utils/request.js
import axios from 'axios' const request = axios.create({ baseURL: 'https://conduit.productionready.io/' }) export default request
登陸注冊模塊
-
添加api/user.js文件,封裝登陸注冊方法
import { request } from '@/plugins/request' // 用戶登錄 export const login = data => { return request({ method: 'POST', url: '/api/users/login', data }) } // 用戶注冊 export const register = data => { return request({ method: 'POST', url: '/api/users', data }) }
在login組件中根據(jù)路由調(diào)用登陸或注冊接口
-
為了防止刷新頁面數(shù)據(jù)丟失么介,我們需要把數(shù)據(jù)持久化把登陸狀態(tài)存到Cookie中
// login/index.vue // 登陸成功后撬碟,為了防止刷新頁面數(shù)據(jù)丟失伯顶,我們需要把數(shù)據(jù)持久化 把登陸狀態(tài)存到Cookie中 // 瀏覽器刷新cookie數(shù)據(jù)不會消失 Cookie.set('user', data.user) // store/index.js // 要使用cookie中的數(shù)據(jù)初始化vuex中的數(shù)據(jù) 保持登陸狀態(tài) export const actions = { // nuxtServerInit 是一個nuxt提供的特殊的 action 方法 // 這個 action 會在服務(wù)端渲染期間自動調(diào)用,且僅在服務(wù)端中運行 // 作用:初始化容器數(shù)據(jù)鳖眼,以及需要傳遞數(shù)據(jù)給客戶端使用的數(shù)據(jù) // commit提交mutations的方法 req服務(wù)端渲染期間的請求對象 nuxtServerInit ({ commit }, { req }) { // console.log('nuxtServerInit') let user = null // 服務(wù)端代碼 // 如果請求頭中有 Cookie if (req.headers.cookie) { // 使用 cookieparser 把 cookie 字符串轉(zhuǎn)為 JavaScript 對象 // 接口中會自動將本地存儲的cookie數(shù)據(jù)發(fā)送到服務(wù)端 const parsed = cookieparser.parse(req.headers.cookie) // try...catch...防止cookie的數(shù)據(jù)格式不對 try { user = JSON.parse(parsed.user) } catch (err) { // No valid cookie found } } // 提交 mutation 修改 state 狀態(tài) commit('setUser', user) } }
-
使用中間件處理頁面訪問權(quán)限
創(chuàng)建middleware文件夾黑毅,添加authenticated.js和notAuthenticated.js文件,用于控制頁面權(quán)限
在login/index.vue添加notAuthenticated中間件具帮,用于控制已登錄時跳轉(zhuǎn)到首頁博肋,其他路由頁面添加authenticated中間件用于驗證是否登錄
布局組件layout
在layout中根據(jù)vuex中的user低斋,判斷是否登陸狀態(tài),從而header中設(shè)置不同的選項
首頁模塊home
封裝article.js請求
獲取文章列表getArticles匪凡,應(yīng)該在服務(wù)端渲染膊畴,所以放入asyncData中
根據(jù)獲取到的數(shù)據(jù)格式渲染頁面
處理分頁參數(shù) limit和offset
-
點擊分頁時改變路由query參數(shù),默認情況不會調(diào)用asyncData方法病游,通過watchQuery監(jiān)聽query改變
watchQuery: ['page']
獲取文章標(biāo)簽列表并展示
-
優(yōu)化并行異步任務(wù)唇跨,使用Promise.all()實現(xiàn)獲取tags和articles
// 并發(fā)執(zhí)行 const [ articleRes, tagRes ] = await Promise.all([ loadArticles({ limit, offset: (page - 1) * limit, // 數(shù)據(jù)偏移量 頁數(shù) * 大小 tag }), getTags() ]) const { data: { articles, articlesCount } } = articleRes const { data: { tags } } = tagRes
-
處理標(biāo)簽列表鏈接和數(shù)據(jù),標(biāo)簽中通過改變query去重新加載數(shù)據(jù)
watchQuery: ['page', 'tag'],
-
處理標(biāo)簽高亮及鏈接衬衬,通過監(jiān)聽tab的變化买猖,改變標(biāo)簽的高亮及是否展示tag
watchQuery: ['page', 'tag', 'tab']
-
請求Your Feed標(biāo)簽數(shù)據(jù),getYourFeedArticles滋尉,需要設(shè)置請求頭
export const getYourFeedArticles = params => { return request({ method: 'GET', url: '/api/articles/feed', params, headers: { // 添加用戶身份玉控,數(shù)據(jù)格式:Token空格Token數(shù)據(jù) Authorization: `Token eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6NDgxMTYsInVzZXJuYW1lIjoibHB6OTk5IiwiZXhwIjoxNTk3NzQxNTA4fQ.2yO8Fss4hYnvsIN2UYHsutQ1hmYqSSAA-UrIRnP4DOY` } }) }
-
使用axios攔截器同一設(shè)置用戶Token
如果在utils/request.js中直接設(shè)置請求攔截器,不能像Vue那樣直接通過
import store from '@/store'
拿到store對象狮惜,因為nuxt中的state是一個函數(shù)高诺;所以需要將請求request.js添加到plugins中創(chuàng)建plugins文件夾,將request.js移入plugins中
-
在nuxt.config.js中引入,~代表根目錄
// 注冊插件 plugins: [ '~/plugins/request.js', ]
plugins中默認導(dǎo)出一個上下文對象context碾篡,包含一下內(nèi)容
屬性 | 類型 | 可用 | 描述 |
---|---|---|---|
app | vue根實例 | 客戶端 & 服務(wù)端 | 包含所有插件的根實例虱而。例如:想使用axios,可以通過context.app.$axios獲取 |
isClient | Boolean | 客戶端 & 服務(wù)端 | 是否來自客戶端渲染开泽,廢棄牡拇,請使用process.client |
isServer | Boolean | 客戶端 & 服務(wù)端 | 是否來自服務(wù)端渲染,廢棄穆律,請使用process.server |
isStatic | Boolean | 客戶端 & 服務(wù)端 | 是否通過nuxt generate |
isDev | Boolean | 客戶端 & 服務(wù)端 | 是否開發(fā)模式惠呼,在生產(chǎn)壞境的數(shù)據(jù)緩存中用到 |
isHMR | Boolean | 客戶端 & 服務(wù)端 | 是否通過模塊熱替換嵌戈,僅在客戶端以dev模式 |
route | 路由 | 客戶端 & 服務(wù)端 | 路由實例 |
store | vuex數(shù)據(jù) | 客戶端 & 服務(wù)端 | Vuex.sttore實例 |
env | l Object | 客戶端 & 服務(wù)端 | nuxt.config.js中的環(huán)境變量 |
params | Object | 客戶端 & 服務(wù)端 | route.params的別名 |
query | Object | 客戶端 & 服務(wù)端 | route.query的別名 |
req | http.Request | 服務(wù)端 | Node.js API的Request對象废恋。如果nuxt以中間件形式使用的話岁忘,這個對象就根據(jù)你所使用的框架(個人理解為頁面)而定翅敌。nuxt generate 不可用 |
res | http.Reponse | 服務(wù)端 | Node.js API的Reponse對象椎侠。如果nuxt以中間件形式使用的話闰靴,這個對象就根據(jù)你所使用的框架(個人理解為頁面)而定角溃。nuxt generate 不可用 |
redirect | Function | 服務(wù)端 | 用于重定向另一個路由杠愧,狀態(tài)碼在服務(wù)端被使用利朵,默認302 redirect([status,]path[,query]) |
error | Function | 客戶端 & 服務(wù)端 | 前往錯誤頁面律想,error(parmas),params包含statusCode和message字段 |
nuxtState | Object | 客戶端 | nuxt狀態(tài) |
beforeNuxtRender(fn) | Function | 服務(wù)端 | 更新NUXT在客戶端呈現(xiàn)的變量,具體了解請看官網(wǎng) |
-
設(shè)置axios請求攔截器,獲取store中的user對象绍弟,設(shè)置token
// 通過插件機制獲取到上下文對象 // export default (context) export default ({ store }) => { // console.log(context) // 可以在攔截器中做公共業(yè)務(wù)處理 如設(shè)置token request.interceptors.request.use(function (config) { /** * 需要拿到vuex中的user對象 * import store from '@/store' * 因為store都是通過export按需導(dǎo)出 * 需要按需加載import { state } from '@/store' * 此時拿到的state是一個函數(shù) 需要調(diào)用一下此函數(shù) * 這樣拿到的數(shù)據(jù)永遠是null * 不同于客戶端渲染 所以需要放入到plugins中 */ const { user } = store.state if (user && user.token) { config.headers.Authorization = `Token ${user.token}` } // 返回 config 請求配置對象 return config }, function (error) { // 如果請求失敗(此時請求還沒有發(fā)出去)就會進入這里 // Do something with request error return Promise.reject(error) }) }
-
處理文章發(fā)布時間格式化
使用dayjs技即,比moment更輕量,封裝全局過濾器(在plugins中創(chuàng)建)
-
文章點贊
asyncData中請求數(shù)據(jù)后樟遣,設(shè)置文章點贊的disabled狀態(tài)
articles.forEach(article => article.favoriteDisabled = false)
文章點贊或取消點贊時而叼,需要在請求時處理點贊按鈕狀態(tài)
文章詳情
通過getArticle獲取文章詳情身笤,需要在服務(wù)端渲染,請求放入asyncData中
使用markdown-it將markdown格式轉(zhuǎn)為HTML葵陵,使用v-html渲染頁面
展示文章作者相關(guān)信息液荸,封裝組件
article-meta.vue
,傳入article文章詳情進行渲染-
設(shè)置頁面meta優(yōu)化SEO脱篙,nuxt使用vue-meta更新應(yīng)用的頭部標(biāo)簽Head和html屬性娇钱,可以在nuxt.config.js中定義,也可以設(shè)置個性化特定頁面的meta標(biāo)簽
// 設(shè)置頁面title和description對SEO非常有用 head () { return { title: this.title, meta: [ { hid: 'description', name: 'description', content: 'My custom description' } ] } }
通過客戶端渲染展示評論列表
發(fā)布文章
添加成功后調(diào)用createArticle創(chuàng)建文章绊困,并跳轉(zhuǎn)到文章詳情頁
用戶中心
加載用戶信息文搂,修改并提交
修改成功后和登陸頁一樣做持久化處理,并跳轉(zhuǎn)到個人中心
退出登陸直接將容器和Coolie中的user設(shè)置為null秤朗,跳轉(zhuǎn)回首頁
個人中心
通過服務(wù)端渲染獲取文章列表和個人信息
處理Edit Profile Setting
和Follow Eric Simons
按鈕顯示與否
通過監(jiān)聽tab的變化加載文章數(shù)據(jù)
點贊和取消點贊和首頁相同
如果是當(dāng)前用戶點擊Edit Profile Setting進入設(shè)置
發(fā)布部署-打包
Nuxt.js 提供了一系列常用的 命令,用于開發(fā)或發(fā)布部署
命令 | 描述 |
---|---|
nuxt | 啟動一個熱加載的Web服務(wù)器(開發(fā)模式) localhost:3000煤蹭。 |
nuxt build | 利用webpack編譯應(yīng)用,壓縮JS和CSS資源(發(fā)布用)取视。 |
nuxt start | 以生產(chǎn)模式啟動一個Web服務(wù)器 (需要先執(zhí)行 nuxt build )疯兼。 |
nuxt generate | 編譯應(yīng)用,并依據(jù)路由配置生成對應(yīng)的HTML文件 (用于靜態(tài)站點的部署)贫途。 |
部署 Nuxt.js 服務(wù)端渲染的應(yīng)用不能直接使用 nuxt 命令,而應(yīng)該先進行編譯構(gòu)建nuxt build待侵,然后再啟動 Nuxt 服務(wù)nuxt start
常見的命令有:
- --config-file 或 -c : 指定 nuxt.config.js 的文件路徑丢早。
- --spa 或 -s : 禁用服務(wù)器端渲染,使用SPA模式
- --unix-socket 或 -n : 指定UNIX Socket的路徑秧倾。
最簡單的部署方式
-
配置Host + Port
在nuxt.config.js中添加
// 生產(chǎn)環(huán)境服務(wù)器 server: { host: '0.0.0.0', // 默認localhost 如果配置到生產(chǎn)環(huán)境時要設(shè)置為0.0.0.0怨酝,監(jiān)聽所有網(wǎng)卡地址,不然無法訪問 port: 3000 },
壓縮發(fā)布包
.nuxt目錄和static目錄以及nuxt配置文件那先;package.json和package-lock.json是因為需要在服務(wù)端安裝第三方包
壓縮這五個文件到一個壓縮包
-
把發(fā)布包傳到服務(wù)端
在vs Code終端中鏈接遠程服務(wù)器
ssh root@106.75.190.29
將壓縮包上傳到服務(wù)器對應(yīng)地址
scp ./xxx.zip root@106.75.190.29:/root/realworld-nuxtjs
解壓
unzip 解壓-
安裝依賴
-
在服務(wù)器上安裝 nvm 參考: https://github.com/nvm-sh/nvm
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash
-
重啟ssh終端后, 查看 nvm 版本
nvm --version
-
安裝 Node.js lts 長期支持版
nvm install --lts
-
安裝依賴
cnpm i
-
-
啟動服務(wù)
npm run dev
啟動服務(wù)
使用PM2啟動Node服務(wù)
在服務(wù)器上是通過npm run start
命令啟動web服務(wù)农猬,最終執(zhí)行的是nodeJs下的相關(guān)腳本啟動。此時如果退出命令行售淡,服務(wù)就會被關(guān)閉斤葱,導(dǎo)致訪問不到,此時就需要讓服務(wù)在后臺運行揖闸。
如果使用了 Koa/Express 等 Node.js Web 開發(fā)框架揍堕,并使用了 Nuxt 作為中間件,可以自定義 Web 服 務(wù)器的啟動入口:
命令 | 描述 |
---|---|
NODE_ENV=development nodemon server/index.js | 啟動一個熱加載的自定義 Web 服務(wù)器(開發(fā)模 式)汤纸。 |
NODE_ENV=production node server/index.js | 以生產(chǎn)模式啟動一個自定義 Web 服務(wù)器 (需要先執(zhí)行 nuxt build )衩茸。 |
自動化部署
-
傳統(tǒng)部署
麻煩,手動構(gòu)建并發(fā)布
-
現(xiàn)代化部署方式(CI/CD)
本地將更新代碼推送到遠程倉庫
-
將用戶更新通知給持續(xù)集成/持續(xù)部署服務(wù)
- 拉取最新代碼到服務(wù)當(dāng)中
- 編譯構(gòu)建
- 打包生成release
- 將release部署到服務(wù)器
image-20210321162548934.png
CI/CD服務(wù)
- Jenkins
- Gitlab CI
- GitHub Actions
- Travis CI
- Circle CI
- ...
使用GitHub Action實現(xiàn)自動部署
配置GItHub Access Token
生成:https://github.com/settings/tokens
github settings --> Developer settings --> Personal access tokens --> Generate new token
Note --> Select scopes 全選repo贮泞,對此倉庫操作權(quán)限楞慈,生成
復(fù)制token(只顯示一次)
配置到項目的Secrets中:https://
指定倉庫 settings --> Secrets --> New Secret創(chuàng)建
Name填寫和腳本中的名稱要一致幔烛,value填入剛生成的token
配置GitHub Actions執(zhí)行腳本
在項目根目錄創(chuàng)建.github/workflows目錄
-
在workflows目錄中創(chuàng)建main.yml文件
name: Publish And Deploy Demo on: push: tags: - 'v*' jobs: build-and-deploy: runs-on: ubuntu-latest steps: # 下載源碼 - name: Checkout uses: actions/checkout@master # 打包構(gòu)建 - name: Build uses: actions/setup-node@master - run: npm install - run: npm run build - run: tar -zcvf release.tgz .nuxt static nuxt.config.js package.json package-lock.json pm2.config.json # 發(fā)布 Release - name: Create Release id: create_release uses: actions/create-release@master env: GITHUB_TOKEN: ${{ secrets.TOKEN }} with: tag_name: ${{ github.ref }} release_name: Release ${{ github.ref }} draft: false prerelease: false # 上傳構(gòu)建結(jié)果到 Release - name: Upload Release Asset id: upload-release-asset uses: actions/upload-release-asset@master env: GITHUB_TOKEN: ${{ secrets.TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ./release.tgz asset_name: release.tgz asset_content_type: application/x-tgz # 部署到服務(wù)器 - name: Deploy uses: appleboy/ssh-action@master with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} password: ${{ secrets.PASSWORD }} port: ${{ secrets.PORT }} script: | cd /root/realworld-nuxtjs wget https://github.com/lipengzhou/realworld-nuxtjs/releases/latest/download/release.tgz -O release.tgz tar zxvf release.tgz npm install --production pm2 reload pm2.config.json
-
修改配置
添加secrets參數(shù)到github對應(yīng)倉庫
-
配置PM2配置文件
/** * pm2配置文件 * 使用pm2啟動服務(wù) 名稱RealWorld * 腳本是npm 參數(shù)是start * 相當(dāng)于執(zhí)行npm start命令 */ { "apps": [ { "name": "RealWorld", "script": "npm", "args": "start" } ] }
-
提交更新
git add . git tag v0.1.0 git push origin v0.1.0
-
查看自動部署狀態(tài)
Actions頁簽 --> 發(fā)布部署-測試 --> Public And Deploy Demo --> build-and-deploy
訪問網(wǎng)站
提交更新...