之前的学习,都是使用graphiql或者postman验证后台api.作为全栈工程师,这不能够啊,必须要有前端,必须的
今天学习在vue中使用graphql——vue-apollo.
使用apollo有两个选择:Apollo Boost或者Apollo Client. 这两者的区别是:Apollo Boost更傻瓜化,缺省帮我们做了很多的配置;而Apollo Client需要自己详细的配置.对于我这种二把刀的全栈工程师,还是选择Apollo Boost好了.
安装node.js什么的就不赘述了. 先安装vue cli
1. npm install -g @vue/cli
2. vue create demo
选择缺省模板创建项目就可以了.创建成功后,会有如下输出:
???? Successfully created project demo.
???? Get started with the following commands:
$ cd demo
$ npm run serve
按照提示,我们进入demo,执行npm run serve,成功信息
DONE Compiled successfully in 2702ms9:47:28 AM
<s> [webpack.Progress] 100%
App running at:
- Local: http://localhost:8080/
- Network: http://10.5.92.149:8080/
Note that the development build is not optimized.
To create a production build, run npm run build.
浏览器里可以看到:
准备工作就做好了.
接下来需要增加apollo组件. 执行
npm install --save vue-apollo graphql apollo-boost graphql-tag
在继续前端之前,我们需要后端做点小修改: 因为前端后端都使用8080端口监听,所以我们修改一下后端的端口,改成监听9090.另外,还需要引入新的package "github.com/rs/cors"解决跨域的问题:
// main
func main() {
h := Register()
// 解决跨域的问题
hdl := cors.Default().Handler(h)
http.Handle("/graphql", hdl)
fmt.Println("Now server is running on port 9090")
http.ListenAndServe(":9090", nil)
}
还有,需要改正一个bug, models/article.go里的update()函数,要改成如下:
// Update a article
func UpdateArticle(id int, title, content string) (*Article, error) {
for k, _ := range articleList {
if articleList[k].ID == id {
if title != "" {
articleList[k].Title = title
}
if content != "" {
articleList[k].Content = content
}
return &articleList[k], nil
}
}
return nil, errors.New("Article not found")
}
继续前端的工作. 新建demo/src/utils目录,添加文件apollo.js:
import ApolloClient from 'apollo-boost';
const apolloClient = new ApolloClient({
// 这里指向后端的地址、端口和URL
uri: 'http://127.0.0.1:9090/graphql'
})
export default apolloClient;
新建/demo/src/graphql目录,添加文件article.js:
import gql from 'graphql-tag'
import apolloClient from '../utils/apollo'
// 文章列表
export function getArticles(params) {
return apolloClient.query({
query: gql `{
articles{
id
title
content
}
}`,
variables: params
})
}
// 单篇文章详情
export function getArticle(params) {
return apolloClient.query({
query: gql `query ($id : Int) {
article(id: $id) {
id
title
content
}
}`,
variables: params
})
}
// 添加新文章
export function createArticle(params) {
return apolloClient.mutate({
mutation: gql `mutation ($title: String, $content: String) {
add(title: $title, content: $content){
id
title
content
}
}`,
variables: params
})
}
// 编辑文章
export function editArticle(params) {
return apolloClient.mutate({
mutation: gql `mutation ($id: Int, $title: String, $content: String) {
update(id: $id, title: $title, content: $content){
id
title
content
}
}`,
variables: params
})
}
// 删除文章
export function deleteArticle(params) {
return apolloClient.mutate({
mutation: gql `mutation ($id: Int) {
delete(id: $id){
id
title
content
}
}`,
variables: params
})
}
这里比较难理解的是传入参数的解析,对照一下页面模板传入的参数,多比较就能明白了.
然后在demo/components下新建Article.vue文件,内容如下:
<template>
<div id="article">
<div class="list">
<h1>{{ msg }}</h1>
<ul>
<li v-for="(v, k) of list" :key="k">
文章名称: {{ v.id }}----------------({{ v.title }})
<button @click="getArticle(v.id)">详情</button>
<button @click="deleteArticle(v.id)">删除</button>
</li>
</ul>
</div>
<div v-if="article.id > 0">
<div>文章id:{{ article.id }}</div>
标题:<input v-model="article.title" type="text"><br>
文章内容: <textarea v-model="article.content" name="" id="" cols="30" rows="10"></textarea><br>
<button @click="editArticle">编辑</button><button @click="article={}">取消</button>
</div>
<div class="form">
<h1>添加文章</h1>
标题:<input v-model="formData.title" type="text"><br>
文章内容: <textarea v-model="formData.content" name="" id="" cols="30" rows="10"></textarea><br>
<button @click="createArticle">添加</button>
</div>
</div>
</template>
<script>
import { getArticles,getArticle,createArticle,deleteArticle,editArticle } from '../graphql/article'
export default {
name: 'Article',
props: {
msg: String
},
data() {
return {
list: [],
formData: {
title: '',
content: ''
},
article: {
id: 0,
title: '',
content: ''
}
}
},
methods: {
initData() {
getArticles()
.then(res=>{
console.log('request success')
this.list = res.data.articles
})
.catch(err=>{
console.log(err)
})
},
getArticle(id) {
getArticle({id:id})
.then(res =>{
this.article = res.data.article
})
.catch(err =>{
console.log(err)
})
},
createArticle() {
createArticle(this.formData)
.then(()=>{
this.initData()
})
.catch(err=>{
console.log(err)
})
},
deleteArticle(id) {
deleteArticle({id: id})
.then(() =>{
this.initData()
})
.catch(err=>{
console.log(err)
})
},
editArticle() {
editArticle(this.article)
.then(() =>{
this.initData()
})
.catch(err=>{
console.log(err)
})
}
},
mounted() {
this.initData()
}
}
</script>
<style>
</style>
最后修改demo/App.vue:
<template>
<div id="app">
<!-- <img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/> -->
<Article msg="Article List" />
</div>
</template>
<script>
// import HelloWorld from './components/HelloWorld.vue'
import Article from './components/Article.vue'
export default {
name: 'App',
components: {
// HelloWorld
Article
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
因为页面比较丑,而且增删改后要刷新页面才会显示新的数据.就不截图展示了