資料庫應用開發(實作)
===
<xiaoding.edu@gmail.com>
## 內容綱要
* 介紹Web前端介紹
* 前置知識
* 快速體驗Vue.js開發
* Vue.js的學習路線規劃建議
* 使用vue-cli創建 Vue.js專案
* Vue開發環境搭建
* Vue目錄結構與檔案分析
* Vue 組件化
* 打包&部署應用
* Vue Router (路由)
* Vue+ElementUI
* Vue實體生命週期 (Instance Lifecycle Hooks)
* (20191219)接後端php程式做新增改查(CRUD)
## 介紹Web前端介紹
#### 目前流行前端框架:
1. Angular (Google)
2. React (Facebook)
3. **Vue.js (尤雨溪) - 本課使用**
* 實現單頁面框架(SPA)
* 組件化開發模式
#### HTML、CSS及JavaScript的框架,提供字體排印、表單、按鈕、導航及其他各種元件及Javascript擴充套件:
1. Bootstrap
2. Material
3. **Element UI - 本課使用**
4. Quasar Framework
5. Vux (針對Mobile)
6. Mint UI
## 前置知識
* HTML語法 (不了解就課後了解)
* CSS語法 (同上)
* JavaScript語法 (同上)
* 需要安裝Node.js環境,並花些時間了解這個生態系
* https://nodejs.org/en/download/
---
#### 為什麼要學習流行框架
* 提高開發效率,開發歷程的演進: 原生JS -> jQuery之類的庫 -> 前端模板引擎 -> Angular.js / React / Vue.js
* 業務邏輯往往佔了主要業務邏輯的80%時間
* 人無我有,人有我優
* 一個核心的概念,就是讓用戶不再操作DOM元素,解放了用戶的雙手,讓工程師可以更多的時間去關注業務邏輯
* DOM是什麼?傳送門(https://ithelp.ithome.com.tw/articles/10202689)
* 幫助我們減少不必要的DOM操作,提高渲染效率
* 提供雙向數據綁定的概念,通過框架提供的指令,coder只需要關係業務邏輯,而不用關心DOM是如何渲染的
* Vue.js是前端的主流框架之一,和Angular.js React.js一起,為前端三大主流框架
* 沒有良好的基礎學不好Vue,但Vue的優點是,就算你基礎不行,也能勉強用
---
## 快速體驗Vue.js開發
* 參考網址: https://vuejs.org/v2/guide/
* 創建一個vue.html (練習用)
* 體驗v-if, v-else, v-else-if, v-for, data biding
```javascript=
<!DOCTYPE html PUBLIC "-//IETF//DTD HTML 2.0//EN">
<HTML>
<HEAD>
<TITLE>
A Small Hello
</TITLE>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</HEAD>
<BODY>
<div id="app">
{{ message }}
<span v-if="obj.str === 'A'">Now you see AA</span>
<span v-else-if="str === 'B'">Now you see BB</span>
<span v-else>Now you see CC</span>
<ol>
<li v-for="t in todos">
{{ t }}
</li>
</ol>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
todos: ['todo1', 'todo2'],
seen: false,
num: 3,
str: 'A',
obj: {
str: 'A'
},
message: 'Hello Vue!1213131321' + new Date().toLocaleString()
}
})
</script>
</BODY>
</HTML>
```
---
## Vue.js的學習路線規劃建議
* HTML/CSS/JS基礎 (沒有良好的基礎學不好Vue,但Vue的優點是,就算你基礎不行,也能勉強用)
* HTML常用語法 (div, span, img..)
* CSS基礎語法
* Scoped CSS (CSS作用域: https://vue-loader-v14.vuejs.org/zh-cn/features/scoped-css.html)
* JS基礎語法 (https://pjchender.blogspot.com/2016/06/javascriptfunction-constructornew.html)
* new
* this
* ES6語法
* Object.defineProperty
* class
* 設計模式
* MVC模式
* eventbus發佈訂閱
* mixin 混入
* prototype原型
* extends繼承
* 依賴注入
* Vue API (建議分別寫四篇文章整理和學習,因為太多了)
* 組件
* data
* props
* methods
* watch
* computed
* template
* render
* 鉤子 (hook)
* beforeCreate
* created
* beforeMount
* mounted
* beforeUpdate
* updated
* beforeDestroy
* destroyed
* 模板語法
* 條件 (v-if/ v-else / v-else-if)
* 迴圈 (v-for)
* 事件 (v-on/ v-once)
* 屬性 (v-bind)
* 特殊 (v-model)
* 其他 (v-text / v-html / v-show / v-pre / v-cloak)
* 過度動畫
* transition
* transition-group
* Webpack配置
* vue-loader (最重要的一個loader)
* @vue-cli (用來創建專案)
* sass-loader / less-loader / stylus-loader
* babel-loader
* ts loader
* eslint (非常煩的東西,可以訓練自己變成程式碼潔癖)
* Vue全家桶 (如果學完這一步,基本上就是一個Vue的掌握者了)
* Vuex
* state
* getter
* mutation
* action
* module
* Vue Router
* hash 模式
* history 模式
* 導航守護 (路由跳轉前驗證登入)
* 懶加載 (組件拆分)
* Axios
* 攔截器
* RESTFul
* Jest/ Mocha
* 單元測試
* mock / stub
* PWA
* service worker
* HTTPS
* UI框架 (掌握完這一步,作什麼頁面都會超快,生產力會非常高)
* Element
* Ant Design Vue
* iview
* cube-ui
* Vant
* Vux
* Vue 3.0
* TypeScript
* React Hooks
* Proxy API
* Reactive 風格
* 函數式程式
* 高級用法 (這邊才涉及到演算法和資料結構)
* 虛擬DOM
* Diff演算法
* 模板編譯
---
## 使用vue-cli創建 Vue.js專案
#### 相依環境:
* 安裝node.js環境
* https://nodejs.org/en/
* 安裝yarn指令工具 (比node.js原生的npm指令有效率)
* 透過npm來安裝:
* npm install -g yarn
* 或在官網下載安裝懶人包
* https://yarnpkg.com/en/docs/install#windows-stable
* Yarn 是 Facebook 發佈的一款 JavaScript 套件管理工具,其功能與 npm 相同,但 npm 最為人詬病的是安裝速度很慢、安裝相依套件沒有順序性,而 Yarn 不但解決了這些問題,還加入了方便的 Cache 機制使套件均只要下載一次,多個專案若使用相同套件時不必重新下載。官方也表示 Yarn 是快速、安全、可靠的,且能支援多系統。
---
## vue開發環境搭建
#### 編輯器推薦
* vscode: https://code.visualstudio.com/
* atmo: https://atom.io/
#### 安裝vue-cli指令工具 (第二代) - 本課使用
```
# 使用 npm 安裝
npm install --global vue-cli
```
#### 使用vue-cli創建專案, cd到對應的目錄名稱,
```
vue init webpack vue-demo #創建專案, 名稱為vue-demo
```
接下來會看到專案建立引導流程,確認專案的環境,以便產生器製作
```
? Project name vue-demo? Y #專案的名稱
? Project description A Vue.js project? Y #專案的描述
? Author chunting.d? Y #專案的所有者 (通常是你自己)
? Vue build standalone? Y
? Install vue-router? Yes #vue 的 route 意即專案路由
? Use ESLint to lint your code? No #規範 coding style 的工具, 神煩
? Set up unit tests? Yes #是否設定單元測試
? Pick a test runner jest #使用單元測試的工具,這裡選 jest
? Setup e2e tests with Nightwatch? Yes #e2e 測試的工具
? Should we run `npm install` for you after the project has been created? (recommended) npm #幾種 install 的方式,這裡是用預設的 npm
vue-cli · Generated "vue-demo".
```
* 注意:基本上都是YES (enter), 但對初學者建議Use ESLint to lint your code?這邊選NO
* ESLint是程式碼檢查器 (非常嚴格),讓你多一個空白或少一個空白都會報錯,先把語法檢察器關掉,便於學習 (但型專案建議使用)
* 看到Project initialization finished!就代表成功結束
接下來繼續操作:
```
cd vue-demo #cd到對應的目錄名稱
npm install #安裝相依套件, 若有安裝yarn, 也可以用yarn install
npm run dev #啟動hot reload開發模式
```
> 注意vue-demo可請自行改成喜歡的專案名稱
---
## Vue目錄結構與檔案分析
參考下列幾個頁面
* vue專案總結之資料夾結構配置詳解[https://codertw.com/%E5%89%8D%E7%AB%AF%E9%96%8B%E7%99%BC/226385/]
* 比較重要的部分
* node_modules資料夾: 專案的依賴庫
* src 資料夾: 我們主要的開發程式都會在這邊,元件的增加修改等都在這個資料夾裡操作
* build && config 資料夾裡面是 webpack 相關的設定檔,這邊就不做太多的介紹
* package.json:使用 npm 管理套件會一併出現的檔案,裡面記錄著在這個專案中所使用的套件,以及 npm 的執行指令
* main.js && App.vue
* main.js是整個 Vue 專案的最源頭,裡面引入了 Vue 並創建了 Vue 實例,這個 Vue 實例有一個引入的 components: App。
* App.vue 它是一個 component,可能有人很疑惑怎麼有 .vue 這個副檔名,因為專案中有使用 webpack 裡面有使用一個套件 vue-loader,有了這個套件 webpack 才能夠將 .vue 檔解析成 js。
* components
* components 這個資料夾就是規劃放置 Vue component 的地方,像初始專案中已經有一個 HelloWorld.vue component。
* assets
* assets是放置靜態檔案的地方,像是圖片之類的,必須放在這個資料夾內才能夠在程式部分被引用。
* router
* 因為我們執行安裝的時候有選擇要裝 vue-router 所以才會有這個資料夾,那這邊先不多作介紹,後面會詳細介紹 vue-router。
另外使用`npm run dev`開啟了本地 server 後,vue-cli 有支援 hot reload,所以我們再開發的過程中修改後只要儲存了網頁會自動 reload,不需要再按 f5 或 cmd R 來重整。
---
## Vue 組件化
* 善用 CRM 學習法
* Copy(抄) Run (運作) Modify (改)
* Vue的模板中 (template)只能有一個根組件
* 為什麼要組件化:
* 提高開發效率
* 方便重複使用
* 簡化調試步驟
* 提升整個項目的可維護性
* 便於協同開發

#### 版本一: component
範例: App.vue
```javascript=
<template>
<div>
<div class=row>
<Cell/>
<Cell/>
<Cell/>
</div>
<div class=row>
<Cell/>
<Cell/>
<Cell/>
</div>
<div class=row>
<Cell/>
<Cell/>
<Cell/>
</div>
</div>
</template>
<script>
import Cell from '@/components/Cell'
export default {
name: 'App',
components: {
Cell
},
data () {
return {
}
}
}
</script>
<style>
.row {
display: flex;
}
</style>
```
範例: Cell.vue
```javascript=
<template>
<div class="cell" @click="clicked = true">
<div v-if="clicked">O</div>
<div v-else-if="!clicked"></div>
</div>
</template>
<script>
export default {
name: 'Cell',
data () {
return {
clicked: false
}
}
}
</script>
<style>
.cell {
border: 1px black solid;
width: 100px;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
font-size: 80px;
}
</style>
```
#### 版本二: component props (從外部導入符號與增加事件回乎)
App.vue
```javascript=
<template>
<div>
<div class="row">
<Cell :symbol="curSymbol" v-on:update="onUpdate"/>
<Cell :symbol="curSymbol" v-on:update="onUpdate"/>
<Cell :symbol="curSymbol" v-on:update="onUpdate"/>
</div>
<div class="row">
<Cell :symbol="curSymbol" v-on:update="onUpdate"/>
<Cell :symbol="curSymbol" v-on:update="onUpdate"/>
<Cell :symbol="curSymbol" v-on:update="onUpdate"/>
</div>
<div class="row">
<Cell :symbol="curSymbol" v-on:update="onUpdate"/>
<Cell :symbol="curSymbol" v-on:update="onUpdate"/>
<Cell :symbol="curSymbol" v-on:update="onUpdate"/>
</div>
</div>
</template>
<script>
import Cell from '@/components/Cell'
export default {
name: 'App',
components: {
Cell
},
data () {
return {
curSymbol: 'O',
clicked: false
}
},
methods: {
onUpdate () {
if (this.curSymbol === 'O') {
this.curSymbol = 'X'
} else {
this.curSymbol = 'O'
}
console.log('onUpdate')
}
}
}
</script>
<style>
.row {
display: flex;
}
</style>
```
Cell.vue
```javascript=
<template>
<div class="cell" @click="onClick">
<div v-if="!clicked" class="red"></div>
<div v-else-if="clicked" class="red">{{ symbol }}</div>
</div>
</template>
<script>
export default {
name: 'Cell',
props: {
symbol: {
type: String,
required: true
}
},
data () {
return {
clicked: false
}
},
methods: {
onClick () {
this.clicked = true
this.$emit('update')
console.log('onClick')
}
}
}
</script>
<style>
.cell {
border: 1px black solid;
width: 100px;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
font-size: 80px;
}
</style>
```
#### 版本三: component 增加步數資訊與事件參數
App.vue
```javascript=
<template>
<div>
<div class="row">
<Cell :step="step" v-on:update="onUpdate(0, $event)"/>
<Cell :step="step" v-on:update="onUpdate(1, $event)"/>
<Cell :step="step" v-on:update="onUpdate(2, $event)"/>
</div>
<div class="row">
<Cell :step="step" v-on:update="onUpdate(3, $event)"/>
<Cell :step="step" v-on:update="onUpdate(4, $event)"/>
<Cell :step="step" v-on:update="onUpdate(5, $event)"/>
</div>
<div class="row">
<Cell :step="step" v-on:update="onUpdate(6, $event)"/>
<Cell :step="step" v-on:update="onUpdate(7, $event)"/>
<Cell :step="step" v-on:update="onUpdate(8, $event)"/>
</div>
</div>
</template>
<script>
import Cell from '@/components/Cell'
export default {
name: 'App',
components: {
Cell
},
data () {
return {
step: 0,
clicked: false
}
},
methods: {
onUpdate (i, symbol) {
console.log(`${i}, 格被點擊,內容是${symbol}`)
this.step++
console.log(symbol)
}
}
}
</script>
<style>
.row {
display: flex;
}
</style>
```
Cell.vue
```javascript=
<template>
<div>
<div class="cell" @click="onClick">
<div v-if="!clicked" class="red"></div>
<div v-else-if="clicked" class="red">{{ symbol }}</div>
</div>
{{step}}
</div>
</template>
<script>
export default {
name: 'Cell',
props: {
step: {
type: Number
}
},
data () {
return {
symbol: '',
clicked: false
}
},
methods: {
onClick () {
if (this.symbol !== '') { //已經被點了
return;
}
this.clicked = true
this.symbol = this.step % 2 === 0 ? 'X' : 'O'
this.$emit('update', this.symbol)
//console.log('onClick')
}
}
}
</script>
<style scoped>
.cell {
border: 1px black solid;
width: 100px;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
font-size: 80px;
}
</style>
```
#### 版本四: 增加判斷輸贏
App.vue
```javascript=
<template>
<div>
<div class="row">
<Cell :step="step" v-on:update="onUpdate(0, $event)"/>
<Cell :step="step" v-on:update="onUpdate(1, $event)"/>
<Cell :step="step" v-on:update="onUpdate(2, $event)"/>
</div>
<div class="row">
<Cell :step="step" v-on:update="onUpdate(3, $event)"/>
<Cell :step="step" v-on:update="onUpdate(4, $event)"/>
<Cell :step="step" v-on:update="onUpdate(5, $event)"/>
</div>
<div class="row">
<Cell :step="step" v-on:update="onUpdate(6, $event)"/>
<Cell :step="step" v-on:update="onUpdate(7, $event)"/>
<Cell :step="step" v-on:update="onUpdate(8, $event)"/>
</div>
{{map}}
{{finished}}
</div>
</template>
<script>
import Cell from '@/components/Cell'
export default {
name: 'App',
components: {
Cell
},
data () {
return {
step: 0,
clicked: false,
map: [
[null, null, null],
[null, null, null],
[null, null, null]
],
finished: false
}
},
methods: {
onUpdate (i, symbol) {
console.log(`${i}, 格被點擊,內容是${symbol}`)
// 0 map[0][0]
// 1 map[0][1]
// 2 map[0][2]
// 3 map[1][0]
// 4 map[1][1]
// 5 map[1][2]
// 6 map[2][0]
// 7 map[2][1]
// 8 map[2][2]
this.map[Math.floor(i/3)][i%3] = symbol
this.step++
console.log(symbol)
let t = this.isFinish()
if (t !== '') {
console.log(`${t}獲勝`)
}
},
isFinish () {
const map = this.map
// 判斷橫的
for (let i = 0; i < 2; i++) {
if (map[i][0] !== null && map[i][0] === map[i][1] && map[i][1] === map[i][2]) {
this.finished = true
return map[i][0]
}
}
// 判斷直的
for (let i = 0; i < 2; i++) {
if (map[0][i] !== null && map[0][i] === map[1][i] && map[1][i] === map[2][i]) {
this.finished = true
return map[0][i]
}
}
// 左上到右下
if (map[0][0] !== null && map[0][0] === map[1][1] && map[1][1] === map[2][2]){
this.finished = true
return map[0][0]
}
// 右上到左下
if (map[0][2] !== null && map[0][2] === map[1][1] && map[1][1] === map[2][0]){
this.finished = true
return map[0][2]
}
return ''
}
}
}
</script>
<style>
.row {
display: flex;
}
</style>
```
知識點:
* 監聽事件,在Cell裡面透過$emit告訴APP 被點擊了
* 在Cell中要定義Props,並在APP中透過v-bind 告訴Cell目前的n,Cell就得宣告一個
* 指令: 在Vue中提供了一些對於頁面+數據更為方便的輸出(e.g v-xxx)
小結:
* 組件化就是可以組裝的東西
* 組件化使得大問題變成小問題,小問題很好解決
* 大事化小,小事化了
訊息傳遞
* 父子組件可單向傳遞訊息
* 非父子組件訊息不共享,除非借用外力
* 外力有很多: window, context, eventbus,vuex
語法
* 不用死記,勤看文件和Google,看了忘,忘了看
* 記得自己寫篇筆記總結
Vue是對前端知識的匯總
* 你在用Vue的時候很容易發現自己的短版
* 發現短版時,你應該去補基礎
---
## 打包&部署應用
開發完到一個段落後,可以透過 npm run build將整個專案<打包>
```shell
npm run build
```
接著會在專案目錄中產生dist資料夾

將資料夾內的index.html和static資料夾直接複製到網頁伺服器的網頁根目錄(部屬)
---
## Vue Router (路由)
參考:https://router.vuejs.org/zh/
* Router 路徑與組件佈局
* router-view使用方式
* 如何使用router-link 組件
* 導航程式化: router.push(), router.replace(), router.go()
* HTML5 History 模式
* 參數傳遞
* 父子組件 (嵌套路由)
#### 路由分成前端路由跟後端路由
* 後端路由:
* 定義:通過用戶請求的url導航到具體的html頁面;每跳轉到不同的URL,都是重新訪問服務端,然後服務端返回頁面,頁面也可以是服務端獲取數據,然後和模板組合,返回HTML,也可以是直接返回模板HTML,然後由前端js再去請求數據,使用前端模板和數據進行組合,生成想要的HTML
* 前端路由
* 在單頁面應用,大部分頁面結構不變,只改變部分內容的使用
* 從性能和用戶體驗的層面來比較的話,後端路由每次訪問一個新頁面的時候都要向服務器發送請求,然後服務器再響應請求,這個過程肯定會有延遲。而前端路由在訪問一個新頁面的時候僅僅是變換了一下路徑而已,沒有了網絡延遲,對於用戶體驗來說會有相當大的提升
* Vue Router 是前端路由
首先確認在main.js當中,有將router導入至Vue實例參數中
```javascript=
import router from './router'
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
```
在App.vue中增加router-view和router-link
App.vue
```javascript=
<template>
<div>
<div>
<router-link to="/">Go to HelloWorld</router-link>
<router-link to="/chess">Go to Chess</router-link>
<router-link :to="{path: '/'}">Go to HelloWorld</router-link>
<router-link :to="{name: 'Chess'}">Go to Chess</router-link>
</div>
<!-- 轉跳後所載入的 component 最後會顯示在此 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
components: {
},
data () {
return {
}
},
methods: {
}
}
</script>
<style>
.row {
display: flex;
}
</style>
```
router/index.js
```javascript=
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Chess from '@/components/Chess'
Vue.use(Router)
/*
* router-link 就像<a href="/chess">chess</a>
* :to 裡面是物件形式,描述要轉跳的目的與需要帶的參數目的
* 可以用 path 或 name
*/
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
},
{
path: '/chess',
name: 'Chess',
component: Chess
}
]
})
```
-->
#### 將App.vue原本棋盤內容複製到新的Component Chess.vue
Chess.vue
```javascript=
<template>
<div>
<div class="row">
<Cell :step="step" v-on:update="onUpdate(0, $event)"/>
<Cell :step="step" v-on:update="onUpdate(1, $event)"/>
<Cell :step="step" v-on:update="onUpdate(2, $event)"/>
</div>
<div class="row">
<Cell :step="step" v-on:update="onUpdate(3, $event)"/>
<Cell :step="step" v-on:update="onUpdate(4, $event)"/>
<Cell :step="step" v-on:update="onUpdate(5, $event)"/>
</div>
<div class="row">
<Cell :step="step" v-on:update="onUpdate(6, $event)"/>
<Cell :step="step" v-on:update="onUpdate(7, $event)"/>
<Cell :step="step" v-on:update="onUpdate(8, $event)"/>
</div>
{{map}}
{{finished}}
</div>
</template>
<script>
import Cell from '@/components/Cell'
export default {
name: 'App',
components: {
Cell
},
data () {
return {
step: 0,
clicked: false,
map: [
[null, null, null],
[null, null, null],
[null, null, null]
],
finished: false
}
},
methods: {
onUpdate (i, symbol) {
console.log(`${i}, 格被點擊,內容是${symbol}`)
this.map[Math.floor(i/3)][i%3] = symbol
this.step++
console.log(symbol)
let t = this.isFinish()
if (t !== '') {
console.log(`${t}獲勝`)
}
},
isFinish () {
const map = this.map
// 判斷橫的
for (let i = 0; i < 2; i++) {
if (map[i][0] !== null && map[i][0] === map[i][1] && map[i][1] === map[i][2]) {
this.finished = true
return map[i][0]
}
}
// 判斷直的
for (let i = 0; i < 2; i++) {
if (map[0][i] !== null && map[0][i] === map[1][i] && map[1][i] === map[2][i]) {
this.finished = true
return map[0][i]
}
}
// 左上到右下
if (map[0][0] !== null && map[0][0] === map[1][1] && map[1][1] === map[2][2]){
this.finished = true
return map[0][0]
}
// 右上到左下
if (map[0][2] !== null && map[0][2] === map[1][1] && map[1][1] === map[2][0]){
this.finished = true
return map[0][2]
}
return ''
}
}
}
</script>
<style>
.row {
display: flex;
}
</style>
```

---
#### 導航程式化: router.push(), router.replace(), router.go()
#### 以下程式為範例
```javascript=
// 使用 $router.push
this.$router.push({ name: 'Chess'})
// 使用 $router.replace
this.$router.replace({ name: 'Chess'})
// 可使用name 或著path的方式去切換
this.$router.replace({ path: '/chess'})
this.$router.replace({ name: 'HelloWorld'})
//各自分別的參數傳遞方法, path搭配query, name搭配params
this.$router.replace({ name: 'Chess', params: { userId: 123 }})
this.$router.replace({path: '/chess', query:{userId: '123'}})
// 接收資料的components
console.log(this.$route.params.userId) // 使用 name+params
console.log(this.$route.query.userId) // 使用 path+query
```
延伸閱讀:https://www.cnblogs.com/jin-zhe/p/10313012.html
---
## Vue實體生命週期 (Instance Lifecycle Hooks)
Vue 除了提供雙向綁定將資料跟樣板綁在一起之外,其中也執行了一連串工作,包含在原始資料加料、建立Vue Instance、編譯樣板、綁定資料等,隨著資料新增修改,週而復始直到刪除,稱為實體生命週期(Lifecycle)。

其中如圖白底紅字的部分,可以提供客製化的空間。
```javascript=
<script>
export default {
name: 'TodoUpdate',
data () {
return {
}
},
beforeCreate () {
// vue instance 被 constructor 建立前
console.log('[TodoUpdate] beforeCreate')
},
created () {
// vue instance 被 constructor 建立後,在這裡完成 data binding
console.log('[TodoUpdate] created')
},
beforeMount () {
// 綁定 DOM 之前
console.log('[TodoUpdate] beforeMount')
},
mounted () {
// 綁定 DOM 之後
console.log('[TodoUpdate] mounted')
},
beforeUpdate () {
// 資料更新,但尚未更新 DOM
console.log('[TodoUpdate] beforeUpdate')
},
updated () {
// 因資料更新,而更新 DOM
console.log('[TodoUpdate] updated')
},
beforeDestroy () {
// 移除 vue instance 之前
console.log('[TodoUpdate] beforeDestroy')
},
destroyed () {
// 移除 vue instance 之後
console.log('[TodoUpdate] destroyed')
}
}
</script>
```
#### 使用timer的範例,在created時啟動timer, beforeDestory時移除timer
```javascript=
data () {
return {
timerA: null
}
},
created () {
this.timerA = setInterval(( () => console.log("Hello!") ), 1000);
},
beforeDestroy () {
if (this.timerA !== null) {
clearTimeout(this.timerA);
}
}
```
---
## Vue+ElementUI
```shell=
# 安裝element ui
npm install element-ui --save
```
> --save 是 npm 安裝指令中的參數之一,目的是安裝某個套件,並幫我加入到 package.json 檔案中,如果後面又串上 -dev 則會特別加在 devDependencies 區域中,否則會放在 dependencies 區域中。
```javascript=
// 在main.js增加下列幾行,把 element ui 載入進來
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import locale from 'element-ui/lib/locale/lang/zh-TW'
Vue.use(Element, { locale })
```
---
參考資料:https://kuro.tw/posts/2017/06/07/%E5%A6%82%E4%BD%95%E5%9C%A8-Vue-CLI-%E5%BB%BA%E7%AB%8B%E7%9A%84%E9%96%8B%E7%99%BC%E7%92%B0%E5%A2%83%E5%91%BC%E5%8F%AB%E8%B7%A8%E5%9F%9F%E9%81%A0%E7%AB%AF-RESTful-APIs/
https://medium.com/@moojing/vue-js-node-js-openapi-%E5%B8%B6%E4%BD%A0%E4%B8%80%E6%AC%A1%E4%BA%86%E8%A7%A3cors%E8%B7%A8%E5%9F%9F%E8%AB%8B%E6%B1%82-b37cd926551f
---
#### Element UI Table
```shell=
# 安裝axios
npm install axios --save
```
axios 基本使用 & Config
https://ithelp.ithome.com.tw/articles/10212120
```javascript=
// main.js增加
import axios from 'axios';
Vue.prototype.$https = axios;
```
增加 TableView.vue
```javascript=
<template>
<div>
<el-table
:data="tableData"
style="width: 100%;">
<el-table-column
prop="seq"
label="序列"
width="180">
</el-table-column>
<el-table-column
prop="行政區"
label="行政區"
width="180">
</el-table-column>
<el-table-column
prop="臨時停車處所"
label="臨時停車處所"
width="180">
</el-table-column>
<el-table-column
prop="可提供小型車停車位"
label="可提供小型車停車位"
width="180">
</el-table-column>
<el-table-column
prop="地址"
label="地址">
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
name: 'TableView',
components: {
},
data () {
return {
tableData: []
}
},
created () {
var url = "https://api.kcg.gov.tw/api/service/get/897e552a-2887-4f6f-a6ee-709f7fbe0ee3";
let self = this
this.$https.get(url)
.then(function (response) {
console.log(response)
self.tableData = response.data.data
// seq: 33, 行政區: "阿蓮區", 臨時停車處所: "阿蓮國小", 可提供小型車停車位: "30", 地址: "民族路163號"}
})
.catch(function (error) {
console.log(error)
});
},
mounted () {
},
beforeDestroy () {
},
methods: {
}
}
</script>
<style>
</style>
```
增加 TableView
router/index.js
```javascript=
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import TableView from '@/components/TableView'
import Chess from '@/components/Chess'
Vue.use(Router)
/*
* router-link 就像<a href="/chess">chess</a>
* :to 裡面是物件形式,描述要轉跳的目的與需要帶的參數目的
* 可以用 path 或 name
* 要注意path和name 不要重複
*/
export default new Router({
routes: [
{
path: '/chess',
name: 'Chess',
component: Chess
},
{
path: '/tableview',
name: 'TableView',
component: TableView
},
{
path: '/*',
name: 'HelloWorld',
component: HelloWorld
}
]
})
```
參考資料:
https://data.kcg.gov.tw/dataset/kcgoa-00000036-808/resource/deb6eacc-1d07-46be-a1b1-9c71ac2f5932
---
## 串接後端php程式做新增改查(CRUD) - 20191219
vue安裝
npm install axios --save
npm install element-ui --save
```javascript=
// 在main.js增加下列幾行,把 element ui 載入進來
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import locale from 'element-ui/lib/locale/lang/zh-TW'
Vue.use(Element, { locale })
```
```javascript=
// main.js增加
import axios from 'axios';
Vue.prototype.$https = axios;
```
以下範例使用misdb.sql做為練習
api/config/database.php
```php=
<?php
class Database{
private $db_host = 'localhost';
private $db_name = 'misdb';
private $db_username = 'root';
private $db_password = '';
public function dbConnection() {
try {
// 連結資料庫
$conn = new PDO('mysql:host='.$this->db_host.';port=3307'.';dbname='.$this->db_name,$this->db_username,$this->db_password);
$conn->exec("set names utf8");
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $conn;
}
catch(PDOException $e){
echo "Connection error ".$e->getMessage();
exit;
}
}
}
?>
```
port=3307是mariaDB的port
port=3306是mysql的port
讀取misdb範例資料中的product表格
api/v1/product/read.php
```php=
<?php
// SET Response header
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: access");
header("Access-Control-Allow-Methods: GET");
header("Access-Control-Allow-Credentials: true");
header("Content-Type: application/json; charset=UTF-8");
// INCLUDING DATABASE AND MAKING OBJECT
require '../../config/database.php';
$db_connection = new Database();
$conn = $db_connection->dbConnection();
// MAKE SQL QUERY
// IF GET PRODUCTS ID, THEN SHOW PRODUCTS BY ID OTHERWISE SHOW ALL PRODUCTS
$ProdID = isset($_GET['ProdID']) ? $_GET['ProdID'] : 'all_products';
// 如果沒有ProdID參數, 則將所有產品查找出來
$sql = ($ProdID != 'all_products') ? "SELECT * FROM `product` WHERE ProdID='$ProdID'" : "SELECT * FROM `product`";
$stmt = $conn->prepare($sql);
$stmt->execute();
//CHECK WHETHER THERE IS ANY PRODUCT IN OUR DATABASE
if($stmt->rowCount() > 0){
// CREATE PRODUCTS ARRAY
$products_array = [];
while($row = $stmt->fetch(PDO::FETCH_ASSOC)){
$product_data = [
'ProdName' => $row['ProdName'],
'ProdID' => $row['ProdID'],
'UnitPrice' => $row['UnitPrice'],
'Cost' => $row['Cost']
];
// PUSH PRODUCT DATA IN OUR $products_array ARRAY
array_push($products_array, $product_data);
}
//SHOW PRODUCT/PRODUCTS IN JSON FORMAT
echo json_encode(['message'=>'ok', 'results' => $products_array]);
}
else{
//IF THER IS NO PRODUCT IN OUR DATABASE
echo json_encode(['message'=>'No product found']);
}
?>
```
ProductList.vue
```javascript=
<template>
<div>
<el-table
:data="tableData"
style="width: 100%;">
<el-table-column
prop="ProdName"
label="產品名稱"
width="180">
</el-table-column>
<el-table-column
prop="ProdID"
label="產品編號"
width="180">
</el-table-column>
<el-table-column
prop="UnitPrice"
label="單價"
width="180">
</el-table-column>
<el-table-column
prop="Cost"
label="成本">
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
name: 'TableView',
components: {
},
data () {
return {
tableData: []
}
},
created () {
var url = "http://localhost/api/v1/product/read.php";
let self = this
this.$https.get(url)
.then(function (response) {
console.log(response)
self.tableData = response.data.results
// ProdName: "EnhanceIDE VL BUS"
// ProdID: "EIDE2RP"
// UnitPrice: "1560"
// Cost: "990"
// seq: 33, 行政區: "阿蓮區", 臨時停車處所: "阿蓮國小", 可提供小型車停車位: "30", 地址: "民族路163號"}
})
.catch(function (error) {
console.log(error)
});
},
mounted () {
},
beforeDestroy () {
},
methods: {
}
}
</script>
<style>
</style>
```
---
## 串接後端php程式做新增改查(CRUD) - 20191226
接續實作新增產品的功能:
* 排版
* 增加 Container容器
* 新增產品
* 增加一個新增按鈕
* 使用Dialog對話框
* 將相關數據的內容可以配置在欄位當中
* 點選按鈕上傳或是取消
* 設計create.php 接收資料 並寫進至資料庫
* 新增完成後,將新的資料放進表格當中
* 問題點
* CORS問題(https://stackoverflow.com/questions/8719276/cross-origin-request-headerscors-with-php-headers)
* AXIOS POST 範例(https://segmentfault.com/a/1190000015261229)
* PHP insert資料 (https://stackoverflow.com/questions/39756812/pdo-insert-statement-with-variables)
#### 試組合並修改下列程式碼
解決CORS跨網域存取並接收來自axios所送出的json訊息
```php=
// create.php
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, DELETE, PUT, PATCH, OPTIONS');
header('Access-Control-Allow-Headers: token, Content-Type');
header('Access-Control-Max-Age: 1728000');
header('Content-Length: 0');
header('Content-Type: text/plain');
die();
}
header('Access-Control-Allow-Origin: *');
header('Content-Type: application/json');
// INCLUDING DATABASE AND MAKING OBJECT
include_once '../../config/database.php';
$db_connection = new Database();
$conn = $db_connection->dbConnection();
// get posted data json_decode
$data = json_decode(file_get_contents("php://input"));
```
判斷post json資料是否完整
```php=
// create.php
// make sure data is not empty
if (
!empty($data->column1) &&
!empty($data->column2) &&
!empty($data->column3) &&
!empty($data->column4)
) {
$sql = "INSERT INTO `product` (ProdName, ProdID, UnitPrice, Cost)
VALUES (?,?,?,?)";
$stmt = $conn->prepare($sql);
$stmt->execute([$data->column1, $data->column2, $data->column3, $data->column4]);
// set response code - 201 created
http_response_code(201);
// tell the user
echo json_encode(['message'=>'ok', 'results' => "Product was created."]);
}
// tell the user data is incomplete
else {
// set response code - 400 bad request
http_response_code(400);
echo json_encode(['message'=>'error', 'results' => "Unable to create product. Data is incomplete.", 'data' => $data]);
}
```
新增dialogVisible 和 editForm欄位
```javascript=
data () {
return {
dialogVisible: false,
tableData: [],
editForm: {
ProdName: '',
ProdID: '',
UnitPrice: 0,
Cost: 0
}
}
}
```
axios POST
```javascript=
onCreateProductClick () {
let self = this
let url = "http://localhost/api/v1/product/????????"
console.log(self.editForm)
this.$https.post(url, {
ProdName: self.editForm.ProdName,
ProdID: self.editForm.ProdID,
UnitPrice: self.editForm.UnitPrice,
Cost: self.editForm.Cost
})
.then(function (response) {
console.log(response)
// self.tableData = response.data.results
})
.catch(function (error) {
console.log(error)
});
}
```
```htmlmixed=
<el-dialog
title="提示"
width="50%"
:visible.sync="dialogVisible">
<span>新增產品資料(以下需修改)</span>
<el-form label-position="left" label-width="80px" :model="form">
<el-form-item label="產品名稱">
<el-input v-model="form.ProdName"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="onCreateProductClick">確 定</el-button>
</span>
</el-dialog>
```
參考:
https://www.codeofaninja.com/2017/02/create-simple-rest-api-in-php.html
https://kknews.cc/zh-tw/code/z92a3jg.html
---
## 串接後端php程式做新增改查(CRUD) - 20190102
表格內容中增加 el-table-column ,增加編輯和刪除按鈕,並分別對應handleEdit 和 handleDelete methods
```javascript=
<el-table-column label="操作">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleEdit(scope.$index, scope.row)">編輯</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">刪除</el-button>
</template>
</el-table-column>
```
handleEdit 與 handleDelete 方法,透過index參數,搭配tableData取得需要修改的資料欄位,並將該資料放置到editForm參數內
以下內容放置在 methods中
```javascript=
handleEdit(index, row) {
let self = this
let product = self.tableData[index]
self.editForm = {
ProdName: product.ProdName,
ProdID: product.ProdID,
UnitPrice: product.UnitPrice,
Cost: product.Cost
}
self.editAction = 'update'
self.dialogVisible = true
},
handleDelete(index, row) {
let self = this
let product = self.tableData[index]
self.editForm = {
ProdName: product.ProdName,
ProdID: product.ProdID,
UnitPrice: product.UnitPrice,
Cost: product.Cost
}
self.editAction = 'delete'
self.$confirm(`確認要刪除? 產品編號:${product.ProdID}`)
.then(_ => {
self.deleteProduct()
})
.catch(_ => {})
}
```
以及追加一個變數editAction來控制送出按鈕的行為(區分新增與更新功能)
以下內容放置在 methods中
```javascript=
onConfirm () {
let self = this
if (self.editAction === 'create') {
self.createProduct()
} else if (self.editAction === 'update') {
self.updateProduct()
}
}
```
更新後的dialog
以下內容放置在 <template>標籤中
```htmlmixed=
<el-dialog
title="產品資料"
width="80%"
:visible.sync="dialogVisible">
<el-form label-position="left" label-width="80px" :model="editForm">
<el-form-item label="產品名稱">
<el-input v-model="editForm.ProdName"></el-input>
</el-form-item>
<el-form-item label="產品編號">
<el-input v-model="editForm.ProdID"></el-input>
</el-form-item>
<el-form-item label="價格">
<el-input v-model="editForm.UnitPrice"></el-input>
</el-form-item>
<el-form-item label="成本">
<el-input v-model="editForm.Cost"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="onConfirm">確 定</el-button>
</span>
</el-dialog>
```
另外新增update.php 與 delete.php 分別讓vue這邊可以透過axios搭配post的方式進行API呼叫
```javascript=
updateProduct () {
let self = this
let url = "http://localhost/api/v1/product/update.php"
},
deleteProduct () {
let self = this
let url = "http://localhost/api/v1/product/delete.php"
}
```
更新資料 update.php的參考內容
```php=
<?php
// create.php
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, DELETE, PUT, PATCH, OPTIONS');
header('Access-Control-Allow-Headers: token, Content-Type');
header('Access-Control-Max-Age: 1728000');
header('Content-Length: 0');
header('Content-Type: text/plain');
die();
}
header('Access-Control-Allow-Origin: *');
header('Content-Type: application/json');
// INCLUDING DATABASE AND MAKING OBJECT
include_once '../../config/database.php';
$db_connection = new Database();
$conn = $db_connection->dbConnection();
// get posted data json_decode
$data = json_decode(file_get_contents("php://input"));
// create.php
// make sure data is not empty
// TODO: 修改column
if (
!empty($data->column1) &&
!empty($data->column2) &&
!empty($data->column3) &&
!empty($data->column4)
) {
$sql = "UPDATE `product` SET ProdName=?, UnitPrice=?, Cost=? WHERE ProdID=?";
$stmt = $conn->prepare($sql);
// TODO: 注意column的順序 (ProdID在結尾)
$stmt->execute([$data->column1, $data->column2, $data->column3, $data->column4]);
// set response code - 201 created
http_response_code(201);
// tell the user
echo json_encode(['message'=>'ok', 'results' => "Product was created."]);
}
// tell the user data is incomplete
else {
// set response code - 400 bad request
http_response_code(400);
echo json_encode(['message'=>'error', 'results' => "Unable to create product. Data is incomplete.", 'data' => $data]);
}
?>
```
```php=
<?php
// create.php
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, DELETE, PUT, PATCH, OPTIONS');
header('Access-Control-Allow-Headers: token, Content-Type');
header('Access-Control-Max-Age: 1728000');
header('Content-Length: 0');
header('Content-Type: text/plain');
die();
}
header('Access-Control-Allow-Origin: *');
header('Content-Type: application/json');
// INCLUDING DATABASE AND MAKING OBJECT
include_once '../../config/database.php';
$db_connection = new Database();
$conn = $db_connection->dbConnection();
// get posted data json_decode
$data = json_decode(file_get_contents("php://input"));
// create.php
// make sure data is not empty
// TODO: 修改column
if (!empty($data->column)) {
$sql = "DELETE FROM `product` WHERE ProdID=?";
$stmt = $conn->prepare($sql);
// TODO: 修改column
$stmt->execute([$data->column]);
// set response code - 201 created
http_response_code(201);
// tell the user
echo json_encode(['message'=>'ok', 'results' => "Product was created."]);
}
// tell the user data is incomplete
else {
// set response code - 400 bad request
http_response_code(400);
echo json_encode(['message'=>'error', 'results' => "Unable to create product. Data is incomplete.", 'data' => $data]);
}
?>
```
---
部屬到fs.mis.kuas.edu.tw上的方法
例如: http://fs.mis.kuas.edu.tw/~你的學號/檔案資料夾/index.html#/
(記得修改學號跟資料夾 不要用中文,然後要跟網址跟目錄對應)
修改vue 專案下的 config/index.js
```javascript=
build: {
// Template for index.html
index: path.resolve(__dirname, '../dist/index.html'),
// Paths
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/~你的學號/檔案資料夾/',
/**
* Source Maps
*/
productionSourceMap: true,
// https://webpack.js.org/configuration/devtool/#production
devtool: '#source-map',
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
}
```
1. 修改完成後 記得要重新使用 npm run build 並將dist/下的檔案覆蓋 伺服器上的檔案!
2. PHP也要丟上去,並且注意database.php連線的參數要改
---