概要
- NuxtとGraphQLとAWS(Amplify, appsync, dynamodb, cognito, s3....)を用いてWebアプリチュートリアルPart1
- NuxtとGraphQLとAWS Amplify用いてWebアプリを作ってみたPart2
- NuxtとGraphQLとAWS(Amplify, appsync, dynamodb, cognito, s3....)を用いてWebアプリチュートリアルPart3
- NuxtとGraphQLとAWS(Amplify, appsync, dynamodb, cognito, s3....)を用いてWebアプリチュートリアルPart4
やること
- ブログ一覧表示
- 新規登録
フロント実装編
ブログ一覧表示
まずはデータの取得なしでデザインのみでコードを書いていきます。
<template>
<display />
</template>
<script>
import Display from '../components/readBlog/index'
export default {
components: {
Display,
},
}
</script>
コンポーネントを記述します。
<template>
<v-card class="mx-auto" max-width="344">
<v-img
src="https://cdn.vuetifyjs.com/images/cards/sunshine.jpg"
height="200px"
></v-img>
<v-card-title> Top western road trips </v-card-title>
<v-card-subtitle> 1,000 miles of wonder </v-card-subtitle>
<v-card-actions>
<v-btn color="orange lighten-2" text> Explore </v-btn>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</template>
コンポーネントのコードはvuetifyからのコピペです。
このように表示されます。
続いて、データの取得をしたいところですが、dynamoDBにデータがないので、新規作成機能を作り、そのあと取得することにします。
新規作成機能
最初は、デザインからいきます。
<template>
<CreateBlog />
</template>
<script>
import CreateBlog from '../../components/writeBlog/create'
export default {
components: {
CreateBlog,
},
}
</script>
コンポーネントを適当に作成します
<template>
<v-container>
<v-row>
<v-col col="12">
<input v-model="title" type="text" />
</v-col>
</v-row>
<v-row>
<v-col col="12">ファイル用</v-col>
</v-row>
<v-row>
<v-col col="12"></v-col>
</v-row>
</v-container>
</template>
今、ブログのタイトル、画像の投稿を入れました。
続いて、ブログの内容を書くためにマークダウンに対応したエディタをインストールします。
候補
お好きなのを使ってください。
今回私が使用するプラグインはmavonEditorです。
mavon-editorのインストール
npm install mavon-editor --save
import Vue from 'vue'
import mavonEditor from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
Vue.use(mavonEditor)
次にnuxt.config.js
ファイルにプラグインパスを書きます。
plugins: [
...
{ src: '@/plugins/vue-mavon-editor', ssr: false }
// { src: './plugins/vue-mavon-editor', ssr: false } ドキュメント通りやると私のほうはエラー出たのでこちらを使いました。
],
続いて、mavonEditorの表示です。
<template>
<v-container>
<v-row>
<v-col col="12">
<input v-model="title" type="text" />
</v-col>
</v-row>
<v-row>
<v-col col="12">ファイル用</v-col>
</v-row>
<v-row>
<!-- 追加 -->
<v-col col="12">
<div class="mavonEditor">
<no-ssr>
<mavon-editor
v-model="handbook"
:toolbars="markdownOption"
language="en" // デフォルトは中国語になるので英語に変更
/>
</no-ssr>
</div>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
data() {
return {
markdownOption: {
bold: true, // 粗体
italic: true, // 斜体
header: true, // hello
underline: true, // 下划线
strikethrough: true, // 中划线
mark: true, // 标记
superscript: true, // 上角标
subscript: true, // 下角标
quote: true, // 引用
ol: true, // 有序列表
ul: true, // 无序列表
link: true, // hi
// imagelink: true, // 图片链接
code: true, // code
table: true, // 表格
fullscreen: true, // 全屏编辑
readmodel: true, // 沉浸式阅读
htmlcode: true, // 展示html源码
help: true, // 帮助
/* 1.3.5 */
undo: true, // 上一步
redo: true, // 下一步
trash: true, // 清空
// save: true, // 保存(触发events中的save事件)
/* 1.4.2 */
navigation: true, // 导航目录
/* 2.1.8 */
alignleft: true, // 左对齐
aligncenter: true, // 居中
alignright: true, // 右对齐
/* 2.2.1 */
subfield: true, // 单双栏模式
preview: true, // 预览
},
handbook: '#### how to use mavonEditor in nuxt.js',
}
},
methods: {
// comfirmImage() {},
},
}
</script>
// 追加↓
<style scoped>
.mavonEditor {
width: 100%;
height: 100%;
}
</style>
追加完了です。
こちらにアクセスしてみると画像のように表示されていればOKです!!
http://localhost:3000/write/create
一度レイアウトを整えます。。
タイトル名を表示する場所が背景と同化しているので、、。
// 略
<p>Title</p>
<input class="text" v-model="title" type="text" />
</v-col>
</v-row>
<v-row>
<!-- <v-col col="12"><input type="file" @change="confirmImage" /></v-col>-->
</v-row>
<v-row>
<v-col col="12">
<p>Contents</p>
<div class="mavonEditor">
<no-ssr>
<mavon-editor
v-model="handbook"
:toolbars="markdownOption"
language="en"
/>
</no-ssr>
// 略
<style scoped>
// 略
.text {
color: white;
padding: 0px 10px;
font-size: 30px;
border: 1px solid #424242;
border-radius: 20px;
height: 40px;
width: 100%;
}
以下のように書き換えましょう。。
次に投稿実装と画像のアップロードも一緒にやります。
まずは画像のアップロード機能を実装しましょう。(s3以外)
// 略
<v-row>
<v-col col="12">
<p>Title</p>
<input class="text" v-model="title" type="text" />
</v-col>
</v-row>
// 画像追加用
<v-row>
<v-col col="6">
<input class="image" type="file" @change="confirmImage" />
</v-col>
<v-col col="6">
<span v-if="confirmedImage">
<img class="c-image" :src="confirmedImage" alt />
</span>
</v-col>
</v-row>
<v-row>
<v-col col="12">
<p>Contents</p>
// 略
<script>
export default {
data() {
return {
markdownOption: {
// 略
},
handbook: '#### how to use mavonEditor in nuxt.js',
//追加
file: '',
confirmedImage: '',
message: '',
}
},
methods: {
// 追加
confirmImage(event) {
this.message = ''
this.file = event.target.files[0]
if (!this.file.type.match('image.*')) {
this.message = '画像ファイルを選択して下さい'
this.confirmedImage = ''
return
}
this.createImage(this.file)
},
createImage(file) {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = (e) => {
this.confirmedImage = e.target.result
}
},
},
}
</script>
<style scoped>
// 追加
.image {
height: 150px;
}
.c-image {
width: 300px;
height: 150px;
}
</style>
これで画像アップロード用の機能実装完了です。残りはs3とgraphqlの実装です。
s3の導入
ターミナルで以下のコードを実行
% amplify add storage
? Please select from one of the below mentioned services: Content (Images, audio, video, etc.)
? Please provide a friendly name for your resource that will be used to label this category in the project: blog
? Please provide bucket name: blog
? Who should have access: Auth and guest users
? What kind of access do you want for Authenticated users? create/update, read, delete
? What kind of access do you want for Guest users? read
? Do you want to add a Lambda Trigger for your S3 Bucket? No
以下のコマンドを打ってバックエンドに反映させましょう✌️
% amplify push
これで導入完了です。
本当かよ!ってくらい早くないっすか??
気になる人は簡単にチェックする方法があって、s3のページに行くのもいいですが、
aws-export.jsというファイルを見るとs3
の項目が表示されているはずです。
% amplify status
↑こちらでも確認できます。
投稿機能仕上げ
投稿機能ラストです!!
投稿しましょう。
<template>
<!-- 略 -->
<v-col col="12">
<p>Contents</p>
<div class="mavonEditor">
<no-ssr>
<mavon-editor
v-model="handbook"
:toolbars="markdownOption"
language="en"
/>
</no-ssr>
</div>
</v-col>
</v-row>
<v-row>
<v-col class="btn-box" col="12">
<input class="btn" type="button" @click="submit" value="投稿" />
</v-col>
</v-row>
</v-container>
</template>
<script>
// 追加
import { API, Storage } from 'aws-amplify'
import awsmobile from '../../aws-exports'
import { createBlog } from '../../graphql/mutations'
export default {
data() {
return {
// 略
handbook: '#### how to use mavonEditor in nuxt.js',
imageData: '',
confirmedImage: '',
imageUrl: '',
message: '',
}
},
methods: {
// 略
async submit() {
const filePath = `cuisine/${this.imageData.name}`
const imageUrlDefault =
'https://' +
awsmobile.aws_user_files_s3_bucket +
'.s3-' +
awsmobile.aws_user_files_s3_bucket_region +
'.amazonaws.com/public/'
await Storage.put(filePath, this.imageData).then((result) => {
this.imageUrl = imageUrlDefault + filePath
})
const createBlogInfo = {
title: this.title,
image_url: this.imageUrl,
content: this.handbook,
}
await API.graphql({
query: createBlog,
variables: { input: createBlogInfo },
})
window.location.href = '/'
},
},
}
</script>
<style scoped>
/* 略 */
.btn-box {
text-align: center;
}
.btn {
border: 1px solid #000000;
background-color: white;
color: #424242;
width: 90px;
height: 30px;
border-radius: 10px;
}
</style>
あれ、、、。エラー??
違います。
実はs3バケットを作成した時の権限の話で、s3に書き込みができるのは、認証済みユーザのみ!としたはずです、、。
つまり、ログイン必須
。
<template>
<amplify-authenticator>
<CreateBlog />
<amplify-sign-out></amplify-sign-out>
</amplify-authenticator>
</template>
<script>
import CreateBlog from '../../components/writeBlog/create'
export default {
name: 'Create',
components: {
CreateBlog,
},
}
</script>
amplifyのauth
タグの追加を行いました。
プラスで、name
属性の追加を行いました。どうやらこれが無いとダメっぽいので、、、。
さ、完了です。投稿してみましょう。
投稿が完了すると,トップページへ画面遷移しますが、トップページでは、データの取得を行っていません。なのでデータの取得を行います。
<template>
<div>
<Display v-for="post in posts" :key="post.id" :post="post" />
</div>
</template>
<script>
import { API } from 'aws-amplify'
import Display from '../components/readBlog/index'
import { listBlogs } from '../graphql/queries'
export default {
components: {
Display,
},
data() {
return {
posts: [],
}
},
created() {
this.getBlog()
},
methods: {
async getBlog() {
const posts = await API.graphql({
query: listBlogs,
})
this.posts = posts.data.listBlogs.items
},
},
}
</script>
次にコンポーネント
<template>
<v-card class="mx-auto" max-width="344">
<v-img :src="post.image_url" height="200px"></v-img>
<v-card-title>{{ post.title }}</v-card-title>
<v-card-subtitle>{{ post.createdAt }}</v-card-subtitle>
<v-card-actions>
<v-btn color="orange lighten-2" text>
Explore
</v-btn>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</template>
<script>
export default {
name: 'PostCard',
props: ['post'],
}
</script>
このようにデータ取得できていたら成功です。
デザインが少しおかしいですが、ま、いいでしょう。
次に詳細画面への画面遷移です。
まずは、一覧表示の画面で、詳細へ飛べるようにリンクを貼りましょう
<template>
<v-card class="mx-auto" max-width="344">
<v-img :src="post.image_url" height="200px"></v-img>
<v-card-title>{{ post.title }}</v-card-title>
<v-card-subtitle>{{ post.createdAt }}</v-card-subtitle>
<v-card-actions>
<v-btn color="orange lighten-2" text :to="'/detail/' + post.id">
Explore
</v-btn>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</template>
<script>
export default {
name: 'PostCard',
props: ['post'],
}
</script>
あとは、詳細ページ用のファイルを作成して完了です。
<template>
<DetailCard :key="blog.id" :blog="blog" />
</template>
<script>
import { API } from 'aws-amplify'
import { getBlog } from '../../graphql/queries'
import DetailCard from '../../components/readBlog/detail'
export default {
components: {
DetailCard,
},
data() {
return {
blog: [],
}
},
created() {
this.getCuisineDetail()
},
methods: {
async getCuisineDetail() {
// eslint-disable-next-line no-undef
const detailBlog = await API.graphql({
query: getBlog,
variables: {
id: this.$route.params.id,
},
})
this.blog = detailBlog.data.getBlog
},
},
}
</script>
次はコンポーネント
<template>
<v-container>
<v-row>
<v-col col="12">
<img class="image" :src="blog.image_url" alt="test" />
</v-col>
</v-row>
<v-row>
<v-col col="12">
<h2>{{ blog.title }}</h2>
</v-col>
</v-row>
<v-row>
<v-col col="12">
<div class="mavonEditor">
<no-ssr>
<mavon-editor
v-model="content"
language="ja"
:subfield="false"
defaultOpen="preview"
:toolbars="false"
/>
</no-ssr>
</div>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
name: 'DetailCard',
props: ['blog'],
data() {
return {
content: '',
}
},
created() {
this.data()
},
methods: {
data() {
this.content = this.blog.content
},
},
}
</script>
<style scoped>
.image {
width: 100%;
height: 300px;
}
</style>
ブログ一覧と詳細
以上でパート2終わりです。
ソースコード
part2のソースコードです。