系統設計:
1.使用語言:前端使用vue,并使用axios向后端發送數據。后端使用的是go的gin框架,并使用grom連接數據庫實現數據存儲讀取。
2.設計結構:
最終展示:僅展示添加模塊,其他模塊基本相似
?前端部分
基礎的頁面設計部分使用的是flex布局,這里就不過多的講解了。
由于vue是模塊化的,所以這個頁面可以分為三大模塊
1.就是app.vue這個是最主要的,我們的頁面是通過它來顯示的
2.就是左側的導航欄,它對應四個模塊,根據選擇模塊不同來顯示不同的頁面
3.就是右側的交互部分,四個交互頁面對應四個導航欄的選項
運行端口
運行端口有默認的端口,如果需要自定義端口。我們需要在項目文件中找到vue.config.js文件。然后在里面設置:
原來的文件內容:
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({transpileDependencies: true,})修改后
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({transpileDependencies: true,devServer:{//主要添加這個port: 3000,open: true,}
})
port指定端口,open指定程序運行的時候是否自動打開瀏覽器true為打開
?組件
想要使用組件就需要提前加載組件,也就是導入它(import ...)
加載之后才能掛載(router-view)
我們首先要明確的目標是:1 加載 2對應的組件? ?2 加載 3對應的組件
我們一步一步來看
大致的文件結構如下圖所示,僅做展示,為了方便了解結構
app.vue:
<template>
<div class="father"><div class="one"><h1>blog control center</h1></div><div><index /></div>
</div>
</template><script>
import index from "./views/index.vue"export default {name: "App",components: {index,}
}
</script><style>.father {display: flex;justify-content: center;align-items: center;flex-direction: column;
}
.one{width: 1380px;height: 100px;background: #8e96f1;text-align: center;display: flex;justify-content: center;
}</style>
?App.vue這是整個項目的根組件。其他所有的組件都是基于這個組件展開的
我們主要在這個組件內做了:
- 加載 index組件
- 制作了HTML的頭部區域,也就是顯示blog control center 的部分
- 規定了index的顯示區域(為頭部區域下方
- 給body設置了一個flex布局,讓網頁內容居中顯示
加載組件就是在組件的script部分通過import導入,加載分為全局加載和組件內加載
在上面的代碼中我們使用的是組件內加載,它的特點是,在其組件內加載的組件只能在此組件內使用。而全局加載的特點是只要我們使用全局加載,那么在這個項目內所有組件都可以使用我們加載的組件。
全局加載步驟:首先在main.js里面加載需要用的組件。
import xxx from "相對路徑"? ?xxx就相當于我們給這個組件起的別名
然后我們會看到一個語句:const app = createApp(APP)
然后通過 app.conponent("組件名",組件名)
注意:如果沒有找到const app = createApp(APP)那就是被集成為了createApp(APP).use(router).mount('#app')放在最下方。這時候我們需要把它拆開
我們來看一個簡單的例子:
import index from "./views/index.vue"const app =createApp(App)app.component("index",index)app.use(router).mount('#app')
這就是全局導入的基本步驟了。在這里導入之后就不需要在組件內搞import和components了。直接<index />就可以使用了
這里我們就講解完了,接下來我們來看index的設置
index:
<template>
<div class="body"><div ><div class="son" style="background-color: #42b983"><router-link to="/add" ><h1>添加博文</h1></router-link></div><div class="son" style="background-color: gold"><router-link to="/change"><h1>修改博文</h1></router-link></div><div class="son" style="background-color: pink"><router-link to="/discover"> <h1>查詢博文</h1></router-link></div><div class="son" style="background-color: coral"><router-link to="/del"> <h1>刪除博文</h1></router-link></div></div><div style="width: 1200px;height: 1120px;">
<router-view></router-view></div>
</div>
</template><script>export default {setup() {return{}},}
</script><style scoped>.son{width: 180px;height: 280px;display: flex;justify-content: center;align-items: center;
}
.body{display: flex;flex-direction: row;
}
</style>
這里主要要理解的是router-view和router-link?
router-view是組件要掛載到哪里的入口,也就是說我的router-view放在div里面,我的子組件展示的時候就只能在這個div里面展示。它起到一個占位符的作用,需要與router-link結合使用
router-link就相當于一個連接,這個連接里面設置的路徑是我們在router目錄里面提前設置好的。針對于這個項目我們來看一下router里面index.js的設置
import { createRouter, createWebHashHistory } from 'vue-router'
import index from "../views/index.vue"
import add from "../views/add.vue"
import change from "../views/change.vue"
import discover from "../views/discover.vue"
import del from "../views/del.vue"const routes = [{path: '/index',component: index,},{path: '/add',component: add,},{path: '/change',component: change,},{path: '/discover',component: discover,},{path: '/del',component: del,}
]const router = createRouter({history: createWebHashHistory(),routes
})export default router
路徑的設置主要是創建一個數組,數組存儲對象,對象存儲路徑信息(path和component)path是我們設置的路徑,component是我們導入的組件。由于它是一個.js文件不知道哪些組件被注冊為全局組件。所以需要重新加載一次?
最后我們再創建一個router,吧createRouter([])賦值給它。在這個函數里面有兩個參數。這里暫且略過,因為博主也沒有很理解這個。就暫且按照博主的寫法來吧(嘻嘻)
最后的export default router這個語句的作用是導出一個router對象。這樣問再main里面才可以使用(app.use(router))(注:使用之前需要import導入index.js所在的相對地址哦)
到此,router-view和router-link我們也大致的明白是怎么回事了,簡單來說就是router-link連接顯示的組件會顯示到router-view里面。
然后我們再來看四個不同模塊文件
add.vue:
<script>
import {ref} from "vue";
import axios from "axios"
export default{name:"add",setup(props,context) {let name = ref("")let text = ref("")let rese =ref("")function give(e) {e.preventDefault()axios.post("http://localhost:8081/Add",{name: name.value,text: text.value}).then(res => {console.log(res)alert(res.data.Error)rese.value = res.data.Error})}return{give,name,text,}}}
</script><template><div class="rit"><form method="post" @submit="give" ><table><tr class="a"><td> 請輸入博文名稱: <input name="name" v-model="name" type="text" style="width: 500px"><input type="submit" value="提交" class="submit-button"></td></tr><tr class="b"><td><textarea name="text" v-model="text" /></td></tr></table></form></div>
</template><style scoped>
div{display: flex;align-items: center;justify-content: center;
}
.rit{width: 1200px;height: 1120px;background: #42b882;
}
.a{
width:1200px;height: 100px;
}
.b{width:1200px;height: 1025px;
}
textarea{width:1100px;height:1000px;overflow:auto;
}
</style>
全局css樣式
?這里我們不怎么醬它的css樣式,只簡單了解一下:通常來說,組件之間的css樣式不會互相影響,也就是說我在哪個組件設置的樣式就只能用在哪個組件內。
如果有一個樣式會被運用到很多組件內,我們就可以設置全局css樣式
全局css樣式
設置全局css樣式我們需要單獨寫一個css文件,通常這個文件我們會存放在src/assets目錄下。
導入全局組件同樣需要在main.js里面導入(import "css樣式路徑(相對路徑)")
這樣就可以直接在組件內使用這個文件內的樣式了。
axios 傳遞數據
這里我們傳遞數據使用到了axios作為傳遞的工具。
我們首先導入這個axios,同樣的。既然是導入就會有全局導入和組件內導入
組件內導入很簡單,我們不過多贅述
我們主要來講一下全局導入
在main.js我們首先 import axios from "axios"導入
然后
axios.defaults.baseURL = 'http://localhost:8081'; // 設置默認的 baseURL
這里設置的作用是在組件內使用的時候就不需要寫完整的路徑了,只需要補出后面剩余路徑即可:.post("http://localhost:8081/Add" 就可以寫為.post("/Add"
這樣就會方便很多。
然后app.config.globalProperties.$axios = axios; 這一句的作用類似于起別名。我們在組件內導入的時候使用axios直接使用就可以了。但是全局導入就需要this.$axios了? ?$axios也可以改成別的名字,這個看自己需要。$也可以不寫,但是我們通常會寫上哦
?如果使用全局導入,我們剛才的代碼就需要改為:
setup(props,context) {let name = ref("")let text = ref("")let rese =ref("")function give(e) {e.preventDefault()this.$axios.post("/Add",{name: name.value,text: text.value}).then(res => {console.log(res)alert(res.data.Error)rese.value = res.data.Error})}
this.$axios
這里要著重聲明的是,在vue3的里面這個方法就不是很適用了。因為setup沒有自己的this
我們需要
import {getCurrentInstance} from "vue";事先導入這個
const { proxy } = getCurrentInstance(); // 然后再在setup里面創建這個常量。通過
proxy.$axios進行使用,這里的差異要注意
我們使用this.$axios.post.("url",{json鍵值對數據})向后端發送請求。然后再.then(res=>{接收回應})接收后端返回的回應(一般都是res.data里面有回應的數據)。然后再.catch捕獲錯誤信息,這個在本系統內并沒有使用(不會)
ref變量
這個是vue3里面新出的一個,通過它來創建響應式變量,用于綁定頁面內元素。實現動態的變化。如果要在js所屬的代碼部分訪問它的值是不能直接用變量名的,而是需要使用? 變量名.value? 獲取變量的值。
v-model綁定
它的主要作用是把一個變量綁定到某個元素上,這個變量要是響應式變量。它有一個語法糖,就是先讀取后渲染。把它綜合到了v-model里面。
change.vue:
<script>
import { ref } from "vue";export default {name: "add",setup() {let change_text = ref("");let Blog_body = ref("");let Blog_title = ref("");function change_submit(e) {e.preventDefault();console.log("change_text:", change_text.value); // 檢查值this.$axios.post("/Change",{"Title": change_text.value}).then((response) => {console.log(response);Blog_body.value = response.data.Blog_body;Blog_title.value = response.data.Blog_title;alert("i get the blog");});}function change_out(e){e.preventDefault()alert("數據發送回后端",Blog_title,Blog_body)this.$axios.post("/Changeend", {BlogText: Blog_body.value,BlogName:Blog_title.value,}).then((response) => {alert(response.data.return);})}return {change_submit,change_text,Blog_title,Blog_body,change_out,};},
};
</script><template><div class="rit"><form method="post" @submit="change_submit"><table><tr class="a"><td>請輸入要修改的博文的名稱:<input type="text" style="width: 500px" v-model="change_text" /><input type="submit" value="提交" class="submit-button" /></td></tr></table></form><form @submit="change_out" id="f2"><table><tr class="a"><td>blog_name:<input type="text" style="width: 500px" v-model="Blog_title" /><input type="submit" value="確認提交" class="submit-button"></td></tr><tr class="b"><td><textarea v-model="Blog_body"></textarea></td></tr></table></form></div>
</template><style scoped>
div{display: flex;align-items: center;justify-content: center;
}
.rit{width: 1200px;height: 1120px;background: #fdd600;display: flex;flex-direction: column;justify-content: center;align-items: center;
}.a{width:1200px;height: 50px;text-align: center;
}
.b{width:1200px;height: 1025px;
}
textarea{width:1100px;height:1000px;overflow:auto;
}
</style>
這個里面就沒有什么特別新的內容了,我們簡單理解一下設計概念即可。在change模塊中。主要步驟如下
- 前端輸入blog的name
- 發送給后端,后端返還blog的name和內容? ?這是第一次前后端交流
- 用戶修改,再次返回給后端
- 后端接收,修改數據庫中的數據(是修改不是新建一個新的數據)這是第二次前后端交流
這里著重講一下的是使用了兩個路由,一個路由用于第一次前后端交流,另一個路由用于第二次前后端交流。
其他的部分都是之前講過的了。就不過多贅述了
后面的del.vue和discover.vue模塊都是重復的內容,我們只把代碼貼出來,剩下的就略過了
del.vue
<script>
import {ref} from "vue";export default{name:"add",setup() {let blogs_name = ref("")function del_blog(e){e.preventDefault()console.log("函數被觸發")this.$axios.post("/Delete",{"blog_name":blogs_name.value}).then(res=>{alert("blog 刪除"+res.data.return)console.log(res)})}return {blogs_name,del_blog,}}}
</script><template><div class="rit"><form method="post" @submit="del_blog" ><table><tr class="a"><td> 請輸入要刪除的博文名稱: <input type="text" style="width: 500px" v-model="blogs_name"><input type="submit" value="提交" class="submit-button" ></td></tr></table></form></div>
</template><style scoped>
div{display: flex;align-items: center;justify-content: center;
}
.rit{width: 1200px;height: 1120px;background: #fd7e50;
}
.a{width:1200px;height: 100px;
}textarea{width:1100px;height:1000px;overflow:auto;
}
</style>
discover.vue?
<script>
import {ref} from "vue";export default{name:"add",setup() {let change_text = ref("");let Blog_body = ref("");let Blog_title = ref("");function change_submit(e) {e.preventDefault();console.log("change_text:", change_text.value); // 檢查值this.$axios.post("/Discover",{"Title": change_text.value}).then((response) => {console.log(response);Blog_body.value = response.data.Blog_body;alert("i get the blog");});}return {Blog_body,Blog_title,change_text,change_submit,}}}
</script><template><div class="rit"><form method="post" @submit="change_submit"><table><tr class="a"><td> 請輸入要查詢的博文名稱: <input type="text" style="width: 500px" v-model="change_text"><input type="submit" value="提交" class="submit-button"> </td></tr><tr class="b"><td><textarea v-model="Blog_body"/></td></tr></table></form></div>
</template><style scoped>
div{display: flex;align-items: center;justify-content: center;
}
.rit{width: 1200px;height: 1120px;background: #fdbfca;
}
.a{width:1200px;height: 100px;
}
.b{width:1200px;height: 1025px;
}
textarea{width:1100px;height:1000px;overflow:auto;
}
</style>
路由設置
路由的設置我們一般都在router/index.js文件內設置。這個目錄我們可以手動創建,也可以在項目創建的時候直接選定創建。
跟隨項目一起創建:
?這里有一個Router選項,勾選上就可以。
手動創建:
我們創建對應目錄以及文件,這個時候是不能使用的。我們還需要在main.js里面設置
-
import router from './router'
-
app.use(router)
設置完這兩個就說明我們啟用了路由系統。
具體路由的設置我們在index:對應的部分已經理解過了。這里就簡單贅述一下
路由的設置是設置一個數組,數組存放對象們,對象里面包含兩個參數,一個是path,另一個是component,path設置的是路徑,compoent設置的是路徑對應跳轉哪個界面。
如果有嵌套路徑的存在,就需要再在改對象里面添加一個屬性
children: []
這個里面存放的數據和外部的是一樣的,path,component如果還有嵌套內容就再寫一個children。并且嵌套的路徑不需要再寫 斜杠/? 直接寫路徑就可以了,這個斜杠會自動補充。
前端部分大概就講這一點吧。更加深層的還是需要看更權威的專家的講解或者去看官方文檔。
后端部分
后端我們使用的是go語言的gin框架
大致的架構我們講一下
主要是分為三個模塊,一個是路由模塊,一個是路由對應處理函數模塊,一個是與數據庫的連接模塊。
我們首先來講一下最重要的路由模塊(其實都很重要)
main.go:
這個文件主要負責路由的設置。
package mainimport ("github.com/gin-contrib/cors""github.com/gin-gonic/gin"
)// 同一目錄下,同一包內的.go文件內函數是共享的
func main() {router := gin.Default()router.Use(cors.Default())router.POST("/Add", Addpost) //router.POST("/Change", Change) //這是前端查詢并返回查詢結果的路徑router.POST("/Changeend", Changeend) //這是后端接收前端處理好的結果的路徑router.POST("./Discover", Discover)router.POST("/Delete", Delete)router.Run(":8081") //運行指定端口
}
我們既然要使用gin框架就需要實現導入這個框架。也就是:import ?"github.com/gin-gonic/gin"
,然后我們就可以設置路由了,這里需要注意設置的路由需要和前端對應上,而且要嚴格區分大小寫!!!我們先通過router:= gin.Default()創建了一個默認的gin路由引擎。
然后我們設置了很多條路由,通過? ?.請求方法("路由",對應的處理函數)
最后使用router.Run(":8081")運行。
這里提到了端口,我們前端運行的時候也是需要端口的。雙方端口不能一樣,如果一樣會導致端口占用錯誤,但是端口不一樣雖然不會導致錯誤出現。卻引發了一個新的問題:跨域
跨域問題的解決在前端后端都可以,但是我們還是默認在后端解決跨域問題。
router.Use(cors.Default())這條語句就是解決跨域問題的,它在"github.com/gin-contrib/cors"包下,所以要事先導入這個包。但是這部分博主并不是很精通,就不講了。只能說通過router.Use(cors.Default())語句我們可以設置一些默認的設定解決跨域問題。
然后就是路由后面跟著的處理函數,這個函數我們是寫在與main.go同級目錄下并且都屬于包main。所以可以直接使用不用導入對應的包。
至于為什么函數不加()是因為如果加了()就是直接使用這個函數。這并不是我們需要的。我們需要的是在特定情況下跳轉到對應的處理函數。所以沒有加上()
mysql.go:
package mainimport ("gorm.io/driver/mysql""gorm.io/gorm"
)type blog struct {BlogName string `gorm:"primaryKey type:varchar(255)"`BlogText string `gorm:"not null unique type:text"`
} //創建一個結構體,結構體用于存儲博文和博文名稱func sqlof() *gorm.DB { //這是一個數據庫連接函數,當我們調用它的時候會返回一個數據庫對象。我們用這個對象進行數據的增上改查dsn := "root:123456@tcp(127.0.0.1:3306)/web_of_blog"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {panic(err)}db.AutoMigrate(&blog{})return db
}
這個文件的主要作用是連接數據庫并提供一個函數,函數返回一個數據庫對象。
我們需要創建一個結構體對象,這個對象的字段和數據庫里面的字段一一對應。
dsn是一個字符串,字符串的內容是用戶:密碼@協議(ip,端口)/數據庫名稱
ip使用的是127.0.0.1是說明這是本機的數據庫。
我們使用gorm.Open(mysql.Open(dsn),&gorm.Config{})連接數據庫,它會返會一個對象和錯誤信息。如果連接成功是沒有錯誤信息的,即err==nil
db.AutoMigrate(&blog{})的作用是確認數據庫里有對應的表(結構體名稱+s)如果沒有則自動創建,如果表的結構不同則更新結構。
我們把最后的連接好的對象作為返回值返回給調用者。
controller.go
這個部分可以說是后端的靈魂所在了
package mainimport ("fmt""github.com/gin-gonic/gin"
)var ch = make(chan string, 1)
var db = sqlof() //創建數據庫全局對象
type Article struct { //這個結構體用于接收前端數據博文和博文名稱Name string `json:"name"`Text string `json:"text"`
}
type change_blog_title struct {Title string `json:"title"`
}func Addpost(c *gin.Context) {var article Articleif err := c.ShouldBindJSON(&article); err != nil {c.JSON(400, gin.H{"error": "解析 JSON 失敗"})return}var insert blog //創建一個結構體對象,用于存儲從前端讀取的數據insert.BlogName = article.Nameinsert.BlogText = article.Textif db.Create(&insert).Error != nil { //判斷數據庫插入操作是否成功,成功返回200不成功返回500c.JSON(500, gin.H{"Error": "error",})/*這里返回的是json數據格式,所以前端讀取的時候就需要使用.Error讀取對應的key值*/} else {c.JSON(200, gin.H{"Error": "no error",})}
}// Change /*前端發送一個title作為博文的名字給后端,后端讀取這個數據然后查詢,并返回查詢結果*/
func Change(c *gin.Context) {fmt.Println("change函數被成功調用,說明后端路由設置無誤")var title change_blog_title //這是一個博文的表頭的結構體實例對象var content blog//通過前端發送的數據來把title賦值給后端的title然后再通過db對象進行查詢,并把查詢結果的信息返回給后端err := c.ShouldBindJSON(&title)if err != nil {fmt.Println("error")}fmt.Println("jianche", title.Title)ch <- title.Titledb.Table("blogs").Where("blog_name = ?", title.Title).First(&content) //把查詢到的第一個結果返回給content進行數據綁定c.JSON(200, gin.H{ //返回查詢到的文章和數據"Blog_body": content.BlogText,"Blog_title": content.BlogName,})fmt.Println("數據已返回", content.BlogName, content.BlogText)}
func Changeend(c *gin.Context) { //接收數據并修改,然后返回修改完成還是錯誤blog_name := <-chdate := blog{}if err := c.ShouldBindJSON(&date); err != nil {c.JSON(400, gin.H{"return": "數據寫入錯誤",})}result := db.Table("blogs").Where("blog_name = ?", blog_name).Update("blog_text", date.BlogText)if result.Error != nil {c.JSON(500, gin.H{"return": "更新博文失敗"})return}result = db.Table("blogs").Where("blog_name = ?", blog_name).Update("blog_name", date.BlogName)if result.Error != nil {c.JSON(500, gin.H{"return": "更新博文失敗"})return} else {c.JSON(200, gin.H{"return": "修改成功",})}}func Discover(c *gin.Context) {fmt.Println("discover函數被成功調用,說明后端路由設置無誤")var title change_blog_title //這是一個博文的表頭的結構體實例對象var content blog//通過前端發送的數據來把title賦值給后端的title然后再通過db對象進行查詢,并把查詢結果的信息返回給后端err := c.ShouldBindJSON(&title)if err != nil {fmt.Println("error")}fmt.Println("jianche", title.Title)db.Table("blogs").Where("blog_name = ?", title.Title).First(&content) //把查詢到的第一個結果返回給content進行數據綁定c.JSON(200, gin.H{ //返回查詢到的文章和數據"Blog_body": content.BlogText,})fmt.Println("數據已返回", content.BlogName, content.BlogText)}type DeleteRequest struct {BlogName string `json:"blog_name"`
}func Delete(c *gin.Context) {var request DeleteRequesterr := c.ShouldBindJSON(&request)if err != nil {c.JSON(400, gin.H{"return": "出現錯誤,啦啦啦",})return}blog_name := request.BlogNamefmt.Println("i get it: ", blog_name)resout := db.Where("blog_name = ?", blog_name).Delete(&blog{})if resout.Error != nil {c.JSON(500, gin.H{"return": "error",})} else {c.JSON(200, gin.H{"return": "ok",})}
}
它根據不同路由的設置來執行對應的函數,這一整個文件的核心就是c *gin.Context這個
c *gin.Context
?是 Gin 框架中處理 HTTP 請求的核心對象,它的主要作用包括:
-
獲取請求信息:如請求頭、請求體、查詢參數、路徑參數等。
-
生成響應:如返回 JSON、HTML、文本等。
-
管理中間件:在請求處理過程中傳遞數據。
-
控制流程:如中止請求、重定向等。
我們通過它獲取前端發送的數據,并且返回給前端數據。
我們使用c.ShouldBindJSON(&結構體)把數據綁定到結構體,這需要我們提前創建一個空的結構體對象。對象的字段名稱要與前端通過axios傳遞過來的json數據的key值相同
也就是我前端傳遞數據{"Name":"123","age":18}后端的結構體字段就需要是Name和age默認的類型都是string字符串類型。同樣的,前端獲取后端的回應也是如此。只不過我們前端使用的是
.then(res=>{
我們需要使用res.data.返回json數據的key獲取對應的value
})
綁定完成之后我們就獲取到了前端發送的請求了。
然后就是與數據庫交互的部分了,這部分我們之前在gorm講過
使用gorm連接數據庫
至于為什么后端沒有用mvc架構,是因為代碼量不大,而且模塊也就三個用不上架構。