本文只是本人學習過程的一個記錄,並非什麼很是嚴謹的教程,但願和你們一塊兒共同進步。也但願你們能指出個人問題。適合有必定基礎,志在全棧的前端初學者學習,從點擊按鈕提交ajax到得到服務器response,而後更新頁面,這其中到底發生了什麼?下面咱們就來實現一個小demo,之前後端分離的方式獨立跑通一個簡單的增刪改查流程,邁出全棧第一步。php
用到的一些技術棧html
後端負責提供接口,操做數據庫提供前端所需的數據和狀態。
前端負責調用接口,將數據展現給用戶,並對用戶的一些操做轉發給後端處理。
數據庫固然是負責存儲數據啦,關於數據庫,網上不少教程都是使用mongodb,經過mongoose操做mongdb的確比mysql便捷不少,不過實際工做中仍是使用mysql的多,技術仍是得迴歸實際應用才能體現出價值。前端
本demo使用node建立本地服務器,在localhost就能完成所有流程,並不須要線上服務器。雖然功能很是簡單,可是用的的模塊和工具仍是蠻多的,建議你們把注意力放在從前到後的這個流程上,一些工具和庫的使用我也不詳細介紹了,你們本身google,要成爲全棧這點學習能力仍是要有的。vue
先上github倉庫地址吧node
大體介紹下項目結構,先後端在不一樣的文件夾下面,互不影響。前端使用webpack構建,利用webpack-dev-server開發,前端入口是localhost:8888/dist/index.html。後端使用express框架,利用nodemon自動重啓,主機是localhost:9999。使用webpack-dev-server和express分別建立了兩個服務器,用同一個端口會衝突,so這裏會有跨域問題,不過用devserver能夠輕鬆解決,後面會說到具體解決辦法。若是是線上服務器的話放一個裏面就好了。mysql
首先用vue-cli生成項目模板就好了,用webpack-simple就夠了,改相關配置方便點。webpack
咱們的頁面很簡單,主要有兩個組件list.vue(展現全部數據和相關操做),一個form.vue(新增及修改商品),這麼一個頁面各位前端估計啪啪幾下就搞定了吧,至於你用不用element-ui都無所謂,用的話速度快點顏值高點。你們請無視我項目裏使用的pug(jade)模板、餓了麼主題文件、登陸組件等,這只是爲了方便之後擴展。前端index.html用一個空殼就好了。ios
配置一下前端路由,/admin下有兩個子頁面,list和form,默認爲list(通常默認是個後臺概況頁)git
export default new Router({ routes: [ { path: '/admin', redirect: '/admin/list', name: 'admin', component: Admin, children: [ { path: '/admin/list', name: 'list', component: List, }, { path: '/admin/form', name: 'form', component: Form, },] }, ] });
靜態部分基本完成了,下面來編寫組件中的數據流轉邏輯程序員
列表的數據是從後端來的,因此list組件的created鉤子裏應有一個獲取所有數據的ajax。先不急着上,要用ajax的地方不少,那麼咱們先對ajax方法作一個封裝吧。
// pubulic/func.js import axios from "axios"; export default { ajaxGet (api, cb) { axios.get(api) .then(cb) .catch(err => { console.log(err); }) }, ajaxPost (api, post, cb) { axios.post(api, post) .then(cb) .catch(err => { console.log(err); }) }, }
這裏咱們使用的axios模塊來進行ajax請求,寫法是promise的鏈式操做,封裝一個get和一個post就夠用了。
// pubulic/api.js let host = '/api'; export default { goodsList: host + '/goods-list', goodsDetail: host + '/goods-detail', goodsDelete: host + '/goods-delete', goodsAdd: host + '/goods-add', }
一樣在public文件夾下建立一個api.js把全部的接口信息都寫在一塊兒,方便後續修改。路徑要與後端接口一致。
下面解決跨域問題,配置一下devserver.proxy就能輕鬆搞定,按照下面的配置,路徑以/api開頭的請求就會被node服務器轉發到9999端口,關於webpack的一些東西能夠看看個人另外一篇文章關於webpack的一點當心得
// webpack.config.js // ... devServer: { port: 8888, historyApiFallback: true, stats: 'minimal', // 輸入精簡信息 overlay: true, // 將錯誤顯示在html之上 proxy: { '/api': { target: 'http://localhost:9999', secure: false, changeOrigin: true, } } },
終於要進入組件中寫具體的業務邏輯了,咱們在created裏拿到數據,渲染進表格。雖而後端還沒開始呢,但咱們指望res.data是一個包含全部商品的數組(若是數據大了還要分頁哦),數據以後在後端中處理,實際項目中可使用mock模擬數據。
刪除操做把要刪除的商品id post至指定接口,而後在回調裏判斷返回的狀態,這個status應該是約定好的,我就設爲201是成功好了。後端返回成功後,前端數據中對應的元素也要刪掉,更新視圖。
// list.vue import func from '../../public/func'; import api from '../../public/api'; // ...省略代碼若干行 methods: { // 刪除 handleDelete(row) { func.ajaxPost(api.goodsDelete, {id: row.Id}, res => { if (res.status === 201) { let index = this.tableData.indexOf(row); this.tableData.splice(index, 1); this.$message.success('刪除成功'); } }); }, // 修改 handleEdit (row) { this.$router.push({name: 'form', query: {id: row.Id}}); }, }, created () { func.ajaxGet(api.goodsList, res => { this.tableData = res.data; }); },
list頁的修改操做就是路由跳轉到form頁了,同時把id以query形式傳過去。在form的created鉤子裏判斷,若是有query.id的話就說明是在修改商品,沒有的話就是添加,這樣就能夠複用這個form組件咯。不愛偷懶的程序員不是好程序員。這個修改操做也能夠用vuex把商品數據傳遞過來,不過頁面刷新就沒有了,仍是用ajax穩妥。
// form.vue // ...省略代碼若干行 created () { let id = this.$route.query.id; console.log(id); if (id) { func.ajaxPost(api.goodsDetail, {id}, res => { this.form = res.data; }); } },
其餘的一些操做不具體說了,都挺簡單的,讓咱們進入久違的後端吧。
來到後端第一步就是建立一個數據庫,這裏我用的是phpstudy附帶的,固然你也能夠本身裝,畢竟這個附帶的仍是老舊的5.5版本。sql語句我玩不來啊就用phpstudy附帶的mysqlfront這個gui工具來擼了。建一個叫vue-admin的庫,而後一張goods的表,只有id,name,price,create_time這四個字段,簡單明瞭。
終於玩到node了,首先全局安裝nodemon幫咱們自動重啓,而後裝好express等包,新手不推薦使用express-generator建立項目。看到這裏請你們先去預習一下mysqljs這個模塊。
咱們把數據庫的配置寫在單獨的文件中,抽離配置文件是一個好習慣。而後在控制器中使用mysql.createPool(db)建立鏈接池。
// db.js module.exports = { host: 'localhost', port: 3306, user: 'root', password: 'root', database: 'vue-admin' }; // controls/goods.js let pool = mysql.createPool(db);
下面編寫增刪改查等路由接口,與前端的api.js中的路徑保持一致,get仍是post根據狀況而定,回調函數不寫在這裏寫進控制器goods.js中。在入口文件中use router,這時候咱們的接口路徑就是/api/goods-list
// router.js router.get('/goods-list', goods.getGoodsList); router.post('/goods-detail', goods.getOneGoods); router.post('/goods-add', goods.addGoods); router.post('/goods-delete', goods.deleteGoods); module.exports = router; // app.js let router = require('./routes/router'); app.use('/api', router);
控制器中一樣是增刪改查四個方法,首先咱們把一些可複用的sql語句封裝起來。這是mysqljs中的語法,?就是變量,雙??是表名或字段名,單?則爲value。insert和update就不封裝了,涉及到具體字段,直接寫好了。
// sql.js module.exports = { queryAll: 'SELECT * FROM ??', queryById: 'SELECT * FROM ?? WHERE id=?', del: 'DELETE FROM ?? WHERE id=?', };
控制器裏拿一個方法出來講一下吧,完整的代碼都在github。使用pool.getConnection方法從鏈接池創建鏈接,SELECT * FROM goods獲取goods表中全部數據,res.json將數據以json格式傳給前端。讀取操做完成後調用release()釋放鏈接。rows及前端拿到的res的數據格式你們能夠console看一下,都是數組類型。
// 獲取商品列表 getGoodsList (req, res) { pool.getConnection((err, conn) => { conn.query(sql.queryAll, 'goods', (err, rows) => { if (err) throw err; rows = formatDate(rows); res.json(rows); conn.release(); }); }); }, // formatDate函數利用moment.js將數據庫中的時間戳格式轉化爲年月日的格式 function formatDate(rows) { return rows.map(row => { let date = moment(row.create_time).format('YYYY-MM-DD'); return Object.assign({}, row, {create_time: date}); }); }
寫後端接口的時候還跑去前端提交請求比較蛋疼,這裏推薦你們使用postman這個工具來測試接口,提升效率。postman能夠以chrome插件的形式安裝,十分方便。
後端接口跑通後,先後端協調修改一下,從前端調用接口,到後端從數據庫中讀取數據,最後返回給前端,整個流程至此就跑通了。
咱們只是完成了一個web應用最最基本的功能,新手可能一臉懵逼,大牛可能一臉蔑視,全棧之路還遠着呢。接下來須要去增長登陸等模塊,更復雜的業務邏輯,還有安全方面的考慮,讓程序健壯起來,你們一塊兒加油吧。