에오니아 5와 Vue 입문
Movie Vue
을 클릭하십시오응용 프로그램 다운로드 -
Download
소개하다.
자바스크립트는 과거에는 웹 페이지를 더욱 동적으로 만드는 방법일 뿐이었다. 지금은 서버 코드를 작성하고 데스크톱과 모바일 응용 프로그램까지 만들 수 있지만, 지금은 자바스크립트가 크게 발전했다.후자는 우리가 오늘 탐색할 내용으로 Ionic
과 Vue.js
을 사용한다.
본 강좌에서 우리는 Ionic
을 어떻게 사용하는지 중점적으로 배우고 당신이 Vue
개념의 기본 지식을 가지고 있다고 가정할 것입니다.만약 당신이 Vue
을 배우고 싶다면 이 강좌를 읽으세요. Getting started with Vue and the GitHub API
, 여기서 저는 Vue
의 모든 기본 개념을 복습했습니다.
무엇이 이온입니까?
에오니아 팀:
Ionic is an open source mobile UI toolkit for building high quality, cross-platform native and web app experiences.
에오니아는 모바일 플랫폼에서 집처럼 보이는 UI 구성 요소를 제공할 것이며, 그 기능은 거의 모든 본체 응용 프로그램과 다를 것이 없다.
Ionic을 사용하면 Angular, React, Vue 또는 vanilla JavaScript 중에서 선택할 수 있습니다.그러나 모든 구성 요소가 모든 프레임에 적용되는 것은 아니므로 시작하기 전에 사용하고자 하는 모든 구성 요소를 보고 프레임에 적용되는지 확인하십시오.
Ionic을 사용하면 카메라에 접근하거나 모바일 서비스의 위치를 사용할 수 있습니다. 그러나 이 서비스는 기업판의 일부분입니다.
우리는 무엇을 짓고 있습니까?
현재 방영 중인 영화, 인기 영화, 최고급 영화, 곧 발매될 새 영화의 목록을 볼 수 있는 모바일 앱을 개발할 것이다.
애플리케이션 기능:
Ionic is an open source mobile UI toolkit for building high quality, cross-platform native and web app experiences.
TMDb api
을 사용하고, 저희 위치를 얻으려면 ip-api
을 사용하겠습니다.시작하기 전에
TMDb api
의 API 키가 필요하므로 이 키를 가져와야 합니다.최종 결과
설치 프로그램
ionic cli 설치
npm install -g @ionic/cli
ionic 응용 프로그램 시작
측면 메뉴를 사용하여 프로그램을 시작하고 선택한 프레임을 Vue로 설정합니다.
ionic start MovieVue sidemenu --type vue
현장 급유
이 명령을 실행하면 코드를 작성할 때 변경 사항을 실시간으로 볼 수 있는 실시간 서버를 시작합니다.
ionic serve
페이지를 볼 때, 브라우저의 개발 도구를 사용하여 장치를 모바일 장치로 설정하면 실제 휴대전화의 외관을 더욱 진실하게 이해할 수 있습니다.
비밀 번호
서비스를 할 때, 당신의 응용 프로그램은 이렇게
대부분의 코드 변경 사항은 ./src
폴더에 저장됩니다.강좌가 한 단계에서 다음 단계로 넘어가면, 나는 전체 코드를 보여주고, 이전에 변경된 줄에 주석을 추가할 것이다.그래서 만약 네가 순서대로 점진적이라면, 너는 이 노선들을 바꿀 수 있다
반찬 정리
우리가 해야 할 첫 번째 일은 반찬을 정리하는 것이다.우리는 사이드 메뉴의 네 부분만 있으면 모든 라벨을 제거할 수 있다../src/App.vue
을 변경해야 합니다.
템플릿과 스크립트만 수정하면 스타일이 변하지 않습니다.
표시할 내용과 일치하도록 탭을 다시 명명해야 합니다.아이콘도 좀 안 맞아서 고치면서 하자.
거푸집
<template>
<IonApp>
<IonSplitPane content-id="main-content">
<ion-menu content-id="main-content" type="overlay">
<ion-content>
<ion-list id="inbox-list">
<!-- Change name to something more appropriate -->
<ion-list-header>Movie Vue</ion-list-header>
<ion-note>Discover movies</ion-note>
<ion-menu-toggle auto-hide="false" v-for="(p, i) in appPages" :key="i">
<ion-item @click="selectedIndex = i" router-direction="root"
:router-link="p.url" lines="none" detail="false" class="hydrated"
:class="{ selected: selectedIndex === i }">
<ion-icon slot="start" :ios="p.iosIcon" :md="p.mdIcon"></ion-icon>
<ion-label>{{ p.title }}</ion-label>
</ion-item>
</ion-menu-toggle>
</ion-list>
<!-- Remove Labels -->
</ion-content>
</ion-menu>
<ion-router-outlet id="main-content"></ion-router-outlet>
</IonSplitPane>
</IonApp>
</template>
각본
import { IonApp, IonContent, IonIcon, IonItem, IonLabel, IonList, IonListHeader,
IonMenu, IonMenuToggle, IonNote, IonRouterOutlet, IonSplitPane } from '@ionic/vue';
import { defineComponent, ref } from 'vue';
import { useRoute } from 'vue-router';
// Update the icons
import { heartOutline, heartSharp, flashOutline, flashSharp,
diamondOutline, diamondSharp, rocketOutline, rocketSharp } from 'ionicons/icons';
export default defineComponent({
name: 'App',
components: {
IonApp,
IonContent,
IonIcon,
IonItem,
IonLabel,
IonList,
IonListHeader,
IonMenu,
IonMenuToggle,
IonNote,
IonRouterOutlet,
IonSplitPane,
},
setup() {
const selectedIndex = ref(0);
// Rename the tabs and update the icons
const appPages = [
{
title: 'Now Playing',
url: '/folder/Now Playing',
iosIcon: flashOutline,
mdIcon: flashSharp
},
{
title: 'Popular',
url: '/folder/Popular',
iosIcon: heartOutline,
mdIcon: heartSharp
},
{
title: 'Top Rated',
url: '/folder/Top Rated',
iosIcon: diamondOutline,
mdIcon: diamondSharp
},
{
title: 'Upcoming',
url: '/folder/Upcoming',
iosIcon: rocketOutline,
mdIcon: rocketSharp
}
];
// Remove Labels
const path = window.location.pathname.split('folder/')[1];
if (path !== undefined) {
selectedIndex.value = appPages
.findIndex(page => page.title.toLowerCase() === path.toLowerCase());
}
const route = useRoute();
return {
selectedIndex,
appPages,
// Update the icons
heartOutline,
heartSharp,
flashOutline,
flashSharp,
diamondOutline,
diamondSharp,
rocketOutline,
rocketSharp,
isSelected: (url: string) => url === route.path ? 'selected' : ''
}
}
});
기본 경로를 변경하려면 .src/router/index.ts
의 스크립트를 업데이트해야 합니다.이것은 시작할 때 우리가 원하는 페이지를 열 수 있도록 합니다.
import { createRouter, createWebHistory } from '@ionic/vue-router';
import { RouteRecordRaw } from 'vue-router';
const routes: Array<RouteRecordRaw> = [
{
path: '',
// Change the default route
redirect: '/folder/Now Playing'
},
{
path: '/folder/:id',
component: () => import ('../views/Folder.vue')
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
이런 변화 이후에는 마땅히 이와 같아야 한다
기본 사용자 인터페이스
Google 메인 사용자 인터페이스에서 모든 영화 정보를 포함하는 카드 구성 요소를 사용할 것입니다.
새 파일 생성 ./src/MovieCard.vue
<template>
<ion-card>
<!-- Movie poster at the top of the card -->
<img :src="url" />
<ion-card-header>
<!-- Show the movie title, Average votes and description -->
<ion-card-title>{{ movie.title }}</ion-card-title>
<ion-card-subtitle
>Rating: {{ movie.vote_average }}
<ion-icon :icon="star"></ion-icon
></ion-card-subtitle>
</ion-card-header>
<ion-card-content>
{{ movie.overview }}
</ion-card-content>
</ion-card>
</template>
<script>
// Remove typescript
import {
IonCard,
IonCardContent,
IonCardSubtitle,
IonCardTitle,
IonIcon,
} from "@ionic/vue";
import { star } from "ionicons/icons";
import { defineComponent } from "vue";
export default defineComponent({
components: {
IonCard,
IonCardContent,
IonCardSubtitle,
IonCardTitle,
IonIcon,
},
// Movie props from the parent component
props: ["movie"],
setup() {
return { star };
},
data() {
return {
// Use a placeholder in case there is no poster
url:
this.movie.backdrop_path != null
? "https://image.tmdb.org/t/p/original/" +
this.movie.backdrop_path
: "./assets/placeholder.jpg",
};
},
});
</script>
영화 카드를 전시하다
이제 각 탭의 영화 목록을 보려면 TMDb
API를 조회해야 합니다.우리는 이 일을 완성하기 위해 axios가 필요하기 때문에, 우리는 계속하기 전에 그것을 설치할 것이다.
npm i axios
이 부분에서 모든 스타일을 삭제할 수 있습니다.그 밖에 우리는 여기서 typescript를 사용하지 않을 것이다. 왜냐하면 나는 그것을 무한 스크롤과 함께 일할 수 없기 때문이다. 우리는 이 강좌의 뒤에서 연구할 것이다../src/Folder.vue
년
<template>
<ion-page>
<ion-header :translucent="true">
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button color="primary"></ion-menu-button>
</ion-buttons>
<ion-title>{{ $route.params.id }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true">
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">{{ $route.params.id }}</ion-title>
</ion-toolbar>
</ion-header>
<div id="container">
<!-- Loop over each movie -->
<div v-for="movie in movies" :key="movie.id">
<MovieCard v-bind:movie="movie"></MovieCard>
</div>
</div>
</ion-content>
</ion-page>
</template>
<script>
// Remove typescript
import { IonButtons, IonContent, IonHeader, IonMenuButton, IonPage,
IonTitle, IonToolbar } from '@ionic/vue';
import { ref } from "vue";
// Install Axios and import the Movie card component we just made
import MovieCard from "./MovieCard.vue";
import axios from "axios";
export default {
name: 'Folder',
components: {
IonButtons,
IonContent,
IonHeader,
IonMenuButton,
IonPage,
IonTitle,
IonToolbar,
// Add the MovieCard component
MovieCard
},
data() {
return {
movies: ref([]),
// Page to fetch
pageNumber: 1,
// Total number of pages present
maxPages: 1,
// Get the endpoint from the route parameter
endpoint: this.$route.params.id
.toLowerCase()
.split(" ")
.join("_"),
// Which Country the user is in
country: "",
};
},
methods: {
async fetch(pageNumber) {
// Get Movies corresponding to which tab is open, Now playing, Upcoming, etc
const movies = await axios.get(
"https://api.themoviedb.org/3/movie/" +
this.endpoint +
"?api_key=<Your API KEY here>&language=en-US&page=" +
pageNumber +
"®ion=" +
this.country
);
// Populate movie list
this.movies = movies.data.results;
// Increase page counter by 1
this.pageNumber = movies.data.page + 1;
// Get total number of pages in response
this.maxPages = movies.data.total_pages;
}
},
mounted() {
// Fetch movies when mounted
this.fetch(this.pageNumber);
},
watch: {
$route(to, from) {
// Trigger when the route changes. i.e. when user switches tabs
this.endpoint = this.$route.params.id
.toLowerCase()
.split(" ")
.join("_");
this.pageNumber = 1;
this.maxPages = 1;
// Fetch movies when route changes
this.fetch(this.pageNumber);
}
}
}
</script>
<style scoped>
/* Remove styles */
</style>
이러한 변경을 수행한 후에는 다음을 수행해야 합니다.
무한 스크롤
현재 우리는 이미 기본적인 사용자 인터페이스를 구축했기 때문에 우리는 생활의 질 개선에 전념할 수 있다.TMDb
api를 조회하면, 모든 종류의 영화 총수의 서브집합을 얻을 수 있습니다.그것은 단지 한 페이지이기 때문이다.영화 목록은 몇 페이지로 나뉘는데, 이것은 우리에게 매우 좋다. 왜냐하면 한 번에 이렇게 많은 데이터를 불러오는 데 시간이 오래 걸리기 때문이다.
그러나 사용자는 영화 목록에 다른 내용이 없을 때까지 점차적으로 더 많은 내용을 볼 수 있는 방법이 있어야 한다.이를 위해 무한 스크롤을 설정합니다.
기본적으로 사용자가 지금까지 로드한 컨텐트의 끝에 도달하면 다음 페이지의 요청을 API에 전송하여 목록에 추가합니다.
다행히도, 에오니아 회사에는 이 목적을 위한 부품이 하나 있다../src/Folder.vue
년.
<template>
<ion-page>
<ion-header :translucent="true">
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button color="primary"></ion-menu-button>
</ion-buttons>
<ion-title>{{ $route.params.id }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true">
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">{{ $route.params.id }}</ion-title>
</ion-toolbar>
</ion-header>
<div id="container">
<div v-for="movie in movies" :key="movie.id">
<MovieCard v-bind:movie="movie"></MovieCard>
</div>
</div>
<!-- Add the infinite scroll component and call loadData -->
<ion-infinite-scroll
@ionInfinite="loadData($event)"
threshold="100px"
id="infinite-scroll"
:disabled="isDisabled">
<ion-infinite-scroll-content
loading-spinner="bubbles"
loading-text="Loading more movies...">
</ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-content>
</ion-page>
</template>
<script>
// Import the components
import { IonButtons, IonContent, IonHeader, IonMenuButton, IonPage, IonTitle, IonToolbar,
IonInfiniteScroll, IonInfiniteScrollContent, } from '@ionic/vue';
import { ref } from "vue";
import MovieCard from "./MovieCard.vue";
import axios from "axios";
export default {
name: 'Folder',
components: {
IonButtons,
IonContent,
IonHeader,
IonMenuButton,
IonPage,
IonTitle,
IonToolbar,
MovieCard,
// Add the infinite scroll components
IonInfiniteScroll,
IonInfiniteScrollContent,
},
data() {
return {
movies: ref([]),
pageNumber: 1,
maxPages: 1,
endpoint: this.$route.params.id
.toLowerCase()
.split(" ")
.join("_"),
country: "",
};
},
methods: {
async fetch(pageNumber) {
const movies = await axios.get(
"https://api.themoviedb.org/3/movie/" +
this.endpoint +
"?api_key=<Your API KEY here>&language=en-US&page=" +
pageNumber +
"®ion=" +
this.country
);
this.movies = movies.data.results;
this.pageNumber = movies.data.page + 1;
this.maxPages = movies.data.total_pages;
},
async pushData(pageNumber) {
// Get the next page
const movies = await axios.get(
"https://api.themoviedb.org/3/movie/" +
this.endpoint +
"?api_key=<Your API KEY here>&language=en-US&page=" +
pageNumber +
"®ion=" +
this.country
);
// Add movies to current list
this.movies = this.movies.concat(movies.data.results);
this.pageNumber = movies.data.page + 1;
this.maxPages = movies.data.total_pages;
},
async loadData(ev) {
// Load the new data once we reach the end of the page
const res = await this.pushData(this.pageNumber);
console.log("Loaded data");
console.log(res);
ev.target.complete();
// Once the last page has been fetched, we'll disable infinite loading
if (this.pageNumber >= this.maxPages) {
ev.target.disabled = true;
}
},
},
mounted() {
this.fetch(this.pageNumber);
},
watch: {
$route(to, from) {
// Trigger when the route changes. i.e. when user switches tabs
this.endpoint = this.$route.params.id
.toLowerCase()
.split(" ")
.join("_");
this.pageNumber = 1;
this.maxPages = 1;
// Fetch movies when route changes
this.fetch(this.pageNumber);
}
}
}
</script>
<style scoped>
</style>
네가 이런 변화를 한 후에 너는 반드시 이런 물건을 보게 될 것이다
아래로 미끄러져 다시 불러오기
대부분의 모바일 프로그램의 또 다른 흔한 기능은 아래로 미끄러질 때 내용을 새로 고칠 수 있다는 것이다.이것은 업데이트 내용을 얻는 간단하고 직관적인 제스처이기 때문에 매우 유용하다.
에오니아에게도 우리가 이 문제를 해결할 수 있는 부품이 있다는 것을 몰랐단 말인가!./src/Folder.vue
년.
<template>
<ion-page>
<ion-header :translucent="true">
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button color="primary"></ion-menu-button>
</ion-buttons>
<ion-title>{{ $route.params.id }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true">
<!-- Add refresher component -->
<ion-refresher slot="fixed" @ionRefresh="doRefresh($event)">
<ion-refresher-content></ion-refresher-content>
</ion-refresher>
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">{{ $route.params.id }}</ion-title>
</ion-toolbar>
</ion-header>
<div id="container">
<div v-for="movie in movies" :key="movie.id">
<MovieCard v-bind:movie="movie"></MovieCard>
</div>
</div>
<ion-infinite-scroll
@ionInfinite="loadData($event)"
threshold="100px"
id="infinite-scroll"
:disabled="isDisabled">
<ion-infinite-scroll-content
loading-spinner="bubbles"
loading-text="Loading more movies...">
</ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-content>
</ion-page>
</template>
<script>
// Import the components
import { IonButtons, IonContent, IonHeader, IonMenuButton, IonPage, IonTitle, IonToolbar,
IonInfiniteScroll, IonInfiniteScrollContent, IonRefresher, IonRefresherContent, } from '@ionic/vue';
import { ref } from "vue";
import MovieCard from "./MovieCard.vue";
import axios from "axios";
export default {
name: 'Folder',
components: {
IonButtons,
IonContent,
IonHeader,
IonMenuButton,
IonPage,
IonTitle,
IonToolbar,
MovieCard,
IonInfiniteScroll,
IonInfiniteScrollContent,
// Add the refresher components
IonRefresher,
IonRefresherContent,
},
data() {
return {
movies: ref([]),
pageNumber: 1,
maxPages: 1,
endpoint: this.$route.params.id
.toLowerCase()
.split(" ")
.join("_"),
country: "",
};
},
methods: {
async fetch(pageNumber) {
const movies = await axios.get(
"https://api.themoviedb.org/3/movie/" +
this.endpoint +
"?api_key=<Your API KEY here>&language=en-US&page=" +
pageNumber +
"®ion=" +
this.country
);
this.movies = movies.data.results;
this.pageNumber = movies.data.page + 1;
this.maxPages = movies.data.total_pages;
},
async pushData(pageNumber) {
const movies = await axios.get(
"https://api.themoviedb.org/3/movie/" +
this.endpoint +
"?api_key=<Your API KEY here>&language=en-US&page=" +
pageNumber +
"®ion=" +
this.country
);
this.movies = this.movies.concat(movies.data.results);
this.pageNumber = movies.data.page + 1;
this.maxPages = movies.data.total_pages;
},
async loadData(ev) {
const res = await this.pushData(this.pageNumber);
console.log("Loaded data");
console.log(res);
console.log(this.pageNumber);
ev.target.complete();
if (this.pageNumber >= this.maxPages) {
ev.target.disabled = true;
}
},
async doRefresh(event) {
// Get the movies from the first page again
const res = await this.fetch(1);
console.log(res);
event.target.complete();
},
},
mounted() {
this.fetch(this.pageNumber);
},
watch: {
$route(to, from) {
this.endpoint = this.$route.params.id
.toLowerCase()
.split(" ")
.join("_");
this.pageNumber = 1;
this.maxPages = 1;
this.fetch(this.pageNumber);
}
}
}
</script>
<style scoped>
</style>
네가 위에서 아래로 당길 때, 너는 아래의 그림을 볼 수 있을 것이다.게시할 때 페이지의 내용이 새로 고쳐져야 합니다.
탭 변경 시 맨 위로 스크롤
만약 당신이 지금 응용 프로그램과 상호작용을 하고 있다면, 아마도 이미 뭔가를 알아차렸을 것이다.예를 들어 Popular
을 아래로 스크롤한 다음 다른 탭으로 전환합니다. 예를 들어 Upcoming
을 스크롤 바가 같은 위치에 있습니다.이것은 이상한 사용자 체험을 가져올 것이다. 이상적인 상황에서 우리는 옵션카드를 전환할 때 자동으로 맨 위로 굴러가기를 바란다. 그러면 우리는 페이지의 무작위 위치가 아니라 처음부터 영화 목록을 볼 수 있기를 바란다../src/Folder.vue
년.
<template>
<ion-page>
<ion-header :translucent="true">
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button color="primary"></ion-menu-button>
</ion-buttons>
<ion-title>{{ $route.params.id }}</ion-title>
</ion-toolbar>
</ion-header>
<!-- Enable scroll events and create a ref -->
<ion-content :fullscreen="true" scrollEvents ref="content">
<ion-refresher slot="fixed" @ionRefresh="doRefresh($event)">
<ion-refresher-content></ion-refresher-content>
</ion-refresher>
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">{{ $route.params.id }}</ion-title>
</ion-toolbar>
</ion-header>
<div id="container">
<div v-for="movie in movies" :key="movie.id">
<MovieCard v-bind:movie="movie"></MovieCard>
</div>
</div>
<ion-infinite-scroll
@ionInfinite="loadData($event)"
threshold="100px"
id="infinite-scroll"
:disabled="isDisabled">
<ion-infinite-scroll-content
loading-spinner="bubbles"
loading-text="Loading more movies...">
</ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-content>
</ion-page>
</template>
<script>
import { IonButtons, IonContent, IonHeader, IonMenuButton, IonPage, IonTitle, IonToolbar,
IonInfiniteScroll, IonInfiniteScrollContent, IonRefresher, IonRefresherContent, } from '@ionic/vue';
import { ref } from "vue";
import MovieCard from "./MovieCard.vue";
import axios from "axios";
export default {
name: 'Folder',
components: {
IonButtons,
IonContent,
IonHeader,
IonMenuButton,
IonPage,
IonTitle,
IonToolbar,
MovieCard,
IonInfiniteScroll,
IonInfiniteScrollContent,
IonRefresher,
IonRefresherContent,
},
data() {
return {
movies: ref([]),
pageNumber: 1,
maxPages: 1,
endpoint: this.$route.params.id
.toLowerCase()
.split(" ")
.join("_"),
country: "",
};
},
methods: {
async fetch(pageNumber) {
const movies = await axios.get(
"https://api.themoviedb.org/3/movie/" +
this.endpoint +
"?api_key=<Your API KEY here>&language=en-US&page=" +
pageNumber +
"®ion=" +
this.country
);
this.movies = movies.data.results;
this.pageNumber = movies.data.page + 1;
this.maxPages = movies.data.total_pages;
},
async pushData(pageNumber) {
const movies = await axios.get(
"https://api.themoviedb.org/3/movie/" +
this.endpoint +
"?api_key=<Your API KEY here>&language=en-US&page=" +
pageNumber +
"®ion=" +
this.country
);
this.movies = this.movies.concat(movies.data.results);
this.pageNumber = movies.data.page + 1;
this.maxPages = movies.data.total_pages;
},
async loadData(ev) {
const res = await this.pushData(this.pageNumber);
console.log("Loaded data");
console.log(res);
console.log(this.pageNumber);
ev.target.complete();
if (this.pageNumber >= this.maxPages) {
ev.target.disabled = true;
}
},
async doRefresh(event) {
const res = await this.fetch(1);
console.log(res);
event.target.complete();
},
},
mounted() {
this.fetch(this.pageNumber);
},
watch: {
$route(to, from) {
this.endpoint = this.$route.params.id
.toLowerCase()
.split(" ")
.join("_");
this.pageNumber = 1;
this.maxPages = 1;
this.fetch(this.pageNumber);
// Scroll to top when the tab changes
this.scrollToTop();
}
},
setup() {
// Get ref to content
const content = ref();
// Add function to scroll to top
return {
content,
scrollToTop: () => content.value.$el.scrollToTop(),
};
},
}
</script>
<style scoped>
</style>
결론
만약 지금까지 당신이 계속 관심을 가지고 있다면, 축하합니다. 당신은 이미 성공적으로 Ionic 응용 프로그램을 만들었습니다."그런데 잠깐만, 이거 Android
에서 운행해야 되는 거 아니야?",나는 네가 말하는 것을 들었다.네가 옳다. 우리는 이미 우리의 인터넷 브라우저에서 휴대전화를 위한 앱을 실행했다. 이 앱을 진정한 안드로이드 휴대전화에 설치하려면 몇 가지 절차가 필요하다.
이 절차는 이 자습서 Building and deploying Ionic apps
의 다음 섹션에서 설명합니다.
이 강좌의 전체 코드를 원하시면 여기를 클릭하십시오: MovieVue
Reference
이 문제에 관하여(에오니아 5와 Vue 입문), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/aveeksaha/getting-started-with-ionic-5-and-vue-20lj
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
npm install -g @ionic/cli
ionic start MovieVue sidemenu --type vue
ionic serve
서비스를 할 때, 당신의 응용 프로그램은 이렇게
대부분의 코드 변경 사항은
./src
폴더에 저장됩니다.강좌가 한 단계에서 다음 단계로 넘어가면, 나는 전체 코드를 보여주고, 이전에 변경된 줄에 주석을 추가할 것이다.그래서 만약 네가 순서대로 점진적이라면, 너는 이 노선들을 바꿀 수 있다반찬 정리
우리가 해야 할 첫 번째 일은 반찬을 정리하는 것이다.우리는 사이드 메뉴의 네 부분만 있으면 모든 라벨을 제거할 수 있다.
./src/App.vue
을 변경해야 합니다.템플릿과 스크립트만 수정하면 스타일이 변하지 않습니다.
표시할 내용과 일치하도록 탭을 다시 명명해야 합니다.아이콘도 좀 안 맞아서 고치면서 하자.
거푸집
<template>
<IonApp>
<IonSplitPane content-id="main-content">
<ion-menu content-id="main-content" type="overlay">
<ion-content>
<ion-list id="inbox-list">
<!-- Change name to something more appropriate -->
<ion-list-header>Movie Vue</ion-list-header>
<ion-note>Discover movies</ion-note>
<ion-menu-toggle auto-hide="false" v-for="(p, i) in appPages" :key="i">
<ion-item @click="selectedIndex = i" router-direction="root"
:router-link="p.url" lines="none" detail="false" class="hydrated"
:class="{ selected: selectedIndex === i }">
<ion-icon slot="start" :ios="p.iosIcon" :md="p.mdIcon"></ion-icon>
<ion-label>{{ p.title }}</ion-label>
</ion-item>
</ion-menu-toggle>
</ion-list>
<!-- Remove Labels -->
</ion-content>
</ion-menu>
<ion-router-outlet id="main-content"></ion-router-outlet>
</IonSplitPane>
</IonApp>
</template>
각본import { IonApp, IonContent, IonIcon, IonItem, IonLabel, IonList, IonListHeader,
IonMenu, IonMenuToggle, IonNote, IonRouterOutlet, IonSplitPane } from '@ionic/vue';
import { defineComponent, ref } from 'vue';
import { useRoute } from 'vue-router';
// Update the icons
import { heartOutline, heartSharp, flashOutline, flashSharp,
diamondOutline, diamondSharp, rocketOutline, rocketSharp } from 'ionicons/icons';
export default defineComponent({
name: 'App',
components: {
IonApp,
IonContent,
IonIcon,
IonItem,
IonLabel,
IonList,
IonListHeader,
IonMenu,
IonMenuToggle,
IonNote,
IonRouterOutlet,
IonSplitPane,
},
setup() {
const selectedIndex = ref(0);
// Rename the tabs and update the icons
const appPages = [
{
title: 'Now Playing',
url: '/folder/Now Playing',
iosIcon: flashOutline,
mdIcon: flashSharp
},
{
title: 'Popular',
url: '/folder/Popular',
iosIcon: heartOutline,
mdIcon: heartSharp
},
{
title: 'Top Rated',
url: '/folder/Top Rated',
iosIcon: diamondOutline,
mdIcon: diamondSharp
},
{
title: 'Upcoming',
url: '/folder/Upcoming',
iosIcon: rocketOutline,
mdIcon: rocketSharp
}
];
// Remove Labels
const path = window.location.pathname.split('folder/')[1];
if (path !== undefined) {
selectedIndex.value = appPages
.findIndex(page => page.title.toLowerCase() === path.toLowerCase());
}
const route = useRoute();
return {
selectedIndex,
appPages,
// Update the icons
heartOutline,
heartSharp,
flashOutline,
flashSharp,
diamondOutline,
diamondSharp,
rocketOutline,
rocketSharp,
isSelected: (url: string) => url === route.path ? 'selected' : ''
}
}
});
기본 경로를 변경하려면 .src/router/index.ts
의 스크립트를 업데이트해야 합니다.이것은 시작할 때 우리가 원하는 페이지를 열 수 있도록 합니다.import { createRouter, createWebHistory } from '@ionic/vue-router';
import { RouteRecordRaw } from 'vue-router';
const routes: Array<RouteRecordRaw> = [
{
path: '',
// Change the default route
redirect: '/folder/Now Playing'
},
{
path: '/folder/:id',
component: () => import ('../views/Folder.vue')
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
이런 변화 이후에는 마땅히 이와 같아야 한다기본 사용자 인터페이스
Google 메인 사용자 인터페이스에서 모든 영화 정보를 포함하는 카드 구성 요소를 사용할 것입니다.
새 파일 생성
./src/MovieCard.vue
<template>
<ion-card>
<!-- Movie poster at the top of the card -->
<img :src="url" />
<ion-card-header>
<!-- Show the movie title, Average votes and description -->
<ion-card-title>{{ movie.title }}</ion-card-title>
<ion-card-subtitle
>Rating: {{ movie.vote_average }}
<ion-icon :icon="star"></ion-icon
></ion-card-subtitle>
</ion-card-header>
<ion-card-content>
{{ movie.overview }}
</ion-card-content>
</ion-card>
</template>
<script>
// Remove typescript
import {
IonCard,
IonCardContent,
IonCardSubtitle,
IonCardTitle,
IonIcon,
} from "@ionic/vue";
import { star } from "ionicons/icons";
import { defineComponent } from "vue";
export default defineComponent({
components: {
IonCard,
IonCardContent,
IonCardSubtitle,
IonCardTitle,
IonIcon,
},
// Movie props from the parent component
props: ["movie"],
setup() {
return { star };
},
data() {
return {
// Use a placeholder in case there is no poster
url:
this.movie.backdrop_path != null
? "https://image.tmdb.org/t/p/original/" +
this.movie.backdrop_path
: "./assets/placeholder.jpg",
};
},
});
</script>
영화 카드를 전시하다
이제 각 탭의 영화 목록을 보려면
TMDb
API를 조회해야 합니다.우리는 이 일을 완성하기 위해 axios가 필요하기 때문에, 우리는 계속하기 전에 그것을 설치할 것이다.npm i axios
이 부분에서 모든 스타일을 삭제할 수 있습니다.그 밖에 우리는 여기서 typescript를 사용하지 않을 것이다. 왜냐하면 나는 그것을 무한 스크롤과 함께 일할 수 없기 때문이다. 우리는 이 강좌의 뒤에서 연구할 것이다../src/Folder.vue
년<template>
<ion-page>
<ion-header :translucent="true">
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button color="primary"></ion-menu-button>
</ion-buttons>
<ion-title>{{ $route.params.id }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true">
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">{{ $route.params.id }}</ion-title>
</ion-toolbar>
</ion-header>
<div id="container">
<!-- Loop over each movie -->
<div v-for="movie in movies" :key="movie.id">
<MovieCard v-bind:movie="movie"></MovieCard>
</div>
</div>
</ion-content>
</ion-page>
</template>
<script>
// Remove typescript
import { IonButtons, IonContent, IonHeader, IonMenuButton, IonPage,
IonTitle, IonToolbar } from '@ionic/vue';
import { ref } from "vue";
// Install Axios and import the Movie card component we just made
import MovieCard from "./MovieCard.vue";
import axios from "axios";
export default {
name: 'Folder',
components: {
IonButtons,
IonContent,
IonHeader,
IonMenuButton,
IonPage,
IonTitle,
IonToolbar,
// Add the MovieCard component
MovieCard
},
data() {
return {
movies: ref([]),
// Page to fetch
pageNumber: 1,
// Total number of pages present
maxPages: 1,
// Get the endpoint from the route parameter
endpoint: this.$route.params.id
.toLowerCase()
.split(" ")
.join("_"),
// Which Country the user is in
country: "",
};
},
methods: {
async fetch(pageNumber) {
// Get Movies corresponding to which tab is open, Now playing, Upcoming, etc
const movies = await axios.get(
"https://api.themoviedb.org/3/movie/" +
this.endpoint +
"?api_key=<Your API KEY here>&language=en-US&page=" +
pageNumber +
"®ion=" +
this.country
);
// Populate movie list
this.movies = movies.data.results;
// Increase page counter by 1
this.pageNumber = movies.data.page + 1;
// Get total number of pages in response
this.maxPages = movies.data.total_pages;
}
},
mounted() {
// Fetch movies when mounted
this.fetch(this.pageNumber);
},
watch: {
$route(to, from) {
// Trigger when the route changes. i.e. when user switches tabs
this.endpoint = this.$route.params.id
.toLowerCase()
.split(" ")
.join("_");
this.pageNumber = 1;
this.maxPages = 1;
// Fetch movies when route changes
this.fetch(this.pageNumber);
}
}
}
</script>
<style scoped>
/* Remove styles */
</style>
이러한 변경을 수행한 후에는 다음을 수행해야 합니다.무한 스크롤
현재 우리는 이미 기본적인 사용자 인터페이스를 구축했기 때문에 우리는 생활의 질 개선에 전념할 수 있다.
TMDb
api를 조회하면, 모든 종류의 영화 총수의 서브집합을 얻을 수 있습니다.그것은 단지 한 페이지이기 때문이다.영화 목록은 몇 페이지로 나뉘는데, 이것은 우리에게 매우 좋다. 왜냐하면 한 번에 이렇게 많은 데이터를 불러오는 데 시간이 오래 걸리기 때문이다.그러나 사용자는 영화 목록에 다른 내용이 없을 때까지 점차적으로 더 많은 내용을 볼 수 있는 방법이 있어야 한다.이를 위해 무한 스크롤을 설정합니다.
기본적으로 사용자가 지금까지 로드한 컨텐트의 끝에 도달하면 다음 페이지의 요청을 API에 전송하여 목록에 추가합니다.
다행히도, 에오니아 회사에는 이 목적을 위한 부품이 하나 있다.
./src/Folder.vue
년.<template>
<ion-page>
<ion-header :translucent="true">
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button color="primary"></ion-menu-button>
</ion-buttons>
<ion-title>{{ $route.params.id }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true">
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">{{ $route.params.id }}</ion-title>
</ion-toolbar>
</ion-header>
<div id="container">
<div v-for="movie in movies" :key="movie.id">
<MovieCard v-bind:movie="movie"></MovieCard>
</div>
</div>
<!-- Add the infinite scroll component and call loadData -->
<ion-infinite-scroll
@ionInfinite="loadData($event)"
threshold="100px"
id="infinite-scroll"
:disabled="isDisabled">
<ion-infinite-scroll-content
loading-spinner="bubbles"
loading-text="Loading more movies...">
</ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-content>
</ion-page>
</template>
<script>
// Import the components
import { IonButtons, IonContent, IonHeader, IonMenuButton, IonPage, IonTitle, IonToolbar,
IonInfiniteScroll, IonInfiniteScrollContent, } from '@ionic/vue';
import { ref } from "vue";
import MovieCard from "./MovieCard.vue";
import axios from "axios";
export default {
name: 'Folder',
components: {
IonButtons,
IonContent,
IonHeader,
IonMenuButton,
IonPage,
IonTitle,
IonToolbar,
MovieCard,
// Add the infinite scroll components
IonInfiniteScroll,
IonInfiniteScrollContent,
},
data() {
return {
movies: ref([]),
pageNumber: 1,
maxPages: 1,
endpoint: this.$route.params.id
.toLowerCase()
.split(" ")
.join("_"),
country: "",
};
},
methods: {
async fetch(pageNumber) {
const movies = await axios.get(
"https://api.themoviedb.org/3/movie/" +
this.endpoint +
"?api_key=<Your API KEY here>&language=en-US&page=" +
pageNumber +
"®ion=" +
this.country
);
this.movies = movies.data.results;
this.pageNumber = movies.data.page + 1;
this.maxPages = movies.data.total_pages;
},
async pushData(pageNumber) {
// Get the next page
const movies = await axios.get(
"https://api.themoviedb.org/3/movie/" +
this.endpoint +
"?api_key=<Your API KEY here>&language=en-US&page=" +
pageNumber +
"®ion=" +
this.country
);
// Add movies to current list
this.movies = this.movies.concat(movies.data.results);
this.pageNumber = movies.data.page + 1;
this.maxPages = movies.data.total_pages;
},
async loadData(ev) {
// Load the new data once we reach the end of the page
const res = await this.pushData(this.pageNumber);
console.log("Loaded data");
console.log(res);
ev.target.complete();
// Once the last page has been fetched, we'll disable infinite loading
if (this.pageNumber >= this.maxPages) {
ev.target.disabled = true;
}
},
},
mounted() {
this.fetch(this.pageNumber);
},
watch: {
$route(to, from) {
// Trigger when the route changes. i.e. when user switches tabs
this.endpoint = this.$route.params.id
.toLowerCase()
.split(" ")
.join("_");
this.pageNumber = 1;
this.maxPages = 1;
// Fetch movies when route changes
this.fetch(this.pageNumber);
}
}
}
</script>
<style scoped>
</style>
네가 이런 변화를 한 후에 너는 반드시 이런 물건을 보게 될 것이다아래로 미끄러져 다시 불러오기
대부분의 모바일 프로그램의 또 다른 흔한 기능은 아래로 미끄러질 때 내용을 새로 고칠 수 있다는 것이다.이것은 업데이트 내용을 얻는 간단하고 직관적인 제스처이기 때문에 매우 유용하다.
에오니아에게도 우리가 이 문제를 해결할 수 있는 부품이 있다는 것을 몰랐단 말인가!
./src/Folder.vue
년.<template>
<ion-page>
<ion-header :translucent="true">
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button color="primary"></ion-menu-button>
</ion-buttons>
<ion-title>{{ $route.params.id }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true">
<!-- Add refresher component -->
<ion-refresher slot="fixed" @ionRefresh="doRefresh($event)">
<ion-refresher-content></ion-refresher-content>
</ion-refresher>
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">{{ $route.params.id }}</ion-title>
</ion-toolbar>
</ion-header>
<div id="container">
<div v-for="movie in movies" :key="movie.id">
<MovieCard v-bind:movie="movie"></MovieCard>
</div>
</div>
<ion-infinite-scroll
@ionInfinite="loadData($event)"
threshold="100px"
id="infinite-scroll"
:disabled="isDisabled">
<ion-infinite-scroll-content
loading-spinner="bubbles"
loading-text="Loading more movies...">
</ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-content>
</ion-page>
</template>
<script>
// Import the components
import { IonButtons, IonContent, IonHeader, IonMenuButton, IonPage, IonTitle, IonToolbar,
IonInfiniteScroll, IonInfiniteScrollContent, IonRefresher, IonRefresherContent, } from '@ionic/vue';
import { ref } from "vue";
import MovieCard from "./MovieCard.vue";
import axios from "axios";
export default {
name: 'Folder',
components: {
IonButtons,
IonContent,
IonHeader,
IonMenuButton,
IonPage,
IonTitle,
IonToolbar,
MovieCard,
IonInfiniteScroll,
IonInfiniteScrollContent,
// Add the refresher components
IonRefresher,
IonRefresherContent,
},
data() {
return {
movies: ref([]),
pageNumber: 1,
maxPages: 1,
endpoint: this.$route.params.id
.toLowerCase()
.split(" ")
.join("_"),
country: "",
};
},
methods: {
async fetch(pageNumber) {
const movies = await axios.get(
"https://api.themoviedb.org/3/movie/" +
this.endpoint +
"?api_key=<Your API KEY here>&language=en-US&page=" +
pageNumber +
"®ion=" +
this.country
);
this.movies = movies.data.results;
this.pageNumber = movies.data.page + 1;
this.maxPages = movies.data.total_pages;
},
async pushData(pageNumber) {
const movies = await axios.get(
"https://api.themoviedb.org/3/movie/" +
this.endpoint +
"?api_key=<Your API KEY here>&language=en-US&page=" +
pageNumber +
"®ion=" +
this.country
);
this.movies = this.movies.concat(movies.data.results);
this.pageNumber = movies.data.page + 1;
this.maxPages = movies.data.total_pages;
},
async loadData(ev) {
const res = await this.pushData(this.pageNumber);
console.log("Loaded data");
console.log(res);
console.log(this.pageNumber);
ev.target.complete();
if (this.pageNumber >= this.maxPages) {
ev.target.disabled = true;
}
},
async doRefresh(event) {
// Get the movies from the first page again
const res = await this.fetch(1);
console.log(res);
event.target.complete();
},
},
mounted() {
this.fetch(this.pageNumber);
},
watch: {
$route(to, from) {
this.endpoint = this.$route.params.id
.toLowerCase()
.split(" ")
.join("_");
this.pageNumber = 1;
this.maxPages = 1;
this.fetch(this.pageNumber);
}
}
}
</script>
<style scoped>
</style>
네가 위에서 아래로 당길 때, 너는 아래의 그림을 볼 수 있을 것이다.게시할 때 페이지의 내용이 새로 고쳐져야 합니다.탭 변경 시 맨 위로 스크롤
만약 당신이 지금 응용 프로그램과 상호작용을 하고 있다면, 아마도 이미 뭔가를 알아차렸을 것이다.예를 들어
Popular
을 아래로 스크롤한 다음 다른 탭으로 전환합니다. 예를 들어 Upcoming
을 스크롤 바가 같은 위치에 있습니다.이것은 이상한 사용자 체험을 가져올 것이다. 이상적인 상황에서 우리는 옵션카드를 전환할 때 자동으로 맨 위로 굴러가기를 바란다. 그러면 우리는 페이지의 무작위 위치가 아니라 처음부터 영화 목록을 볼 수 있기를 바란다../src/Folder.vue
년.<template>
<ion-page>
<ion-header :translucent="true">
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button color="primary"></ion-menu-button>
</ion-buttons>
<ion-title>{{ $route.params.id }}</ion-title>
</ion-toolbar>
</ion-header>
<!-- Enable scroll events and create a ref -->
<ion-content :fullscreen="true" scrollEvents ref="content">
<ion-refresher slot="fixed" @ionRefresh="doRefresh($event)">
<ion-refresher-content></ion-refresher-content>
</ion-refresher>
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">{{ $route.params.id }}</ion-title>
</ion-toolbar>
</ion-header>
<div id="container">
<div v-for="movie in movies" :key="movie.id">
<MovieCard v-bind:movie="movie"></MovieCard>
</div>
</div>
<ion-infinite-scroll
@ionInfinite="loadData($event)"
threshold="100px"
id="infinite-scroll"
:disabled="isDisabled">
<ion-infinite-scroll-content
loading-spinner="bubbles"
loading-text="Loading more movies...">
</ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-content>
</ion-page>
</template>
<script>
import { IonButtons, IonContent, IonHeader, IonMenuButton, IonPage, IonTitle, IonToolbar,
IonInfiniteScroll, IonInfiniteScrollContent, IonRefresher, IonRefresherContent, } from '@ionic/vue';
import { ref } from "vue";
import MovieCard from "./MovieCard.vue";
import axios from "axios";
export default {
name: 'Folder',
components: {
IonButtons,
IonContent,
IonHeader,
IonMenuButton,
IonPage,
IonTitle,
IonToolbar,
MovieCard,
IonInfiniteScroll,
IonInfiniteScrollContent,
IonRefresher,
IonRefresherContent,
},
data() {
return {
movies: ref([]),
pageNumber: 1,
maxPages: 1,
endpoint: this.$route.params.id
.toLowerCase()
.split(" ")
.join("_"),
country: "",
};
},
methods: {
async fetch(pageNumber) {
const movies = await axios.get(
"https://api.themoviedb.org/3/movie/" +
this.endpoint +
"?api_key=<Your API KEY here>&language=en-US&page=" +
pageNumber +
"®ion=" +
this.country
);
this.movies = movies.data.results;
this.pageNumber = movies.data.page + 1;
this.maxPages = movies.data.total_pages;
},
async pushData(pageNumber) {
const movies = await axios.get(
"https://api.themoviedb.org/3/movie/" +
this.endpoint +
"?api_key=<Your API KEY here>&language=en-US&page=" +
pageNumber +
"®ion=" +
this.country
);
this.movies = this.movies.concat(movies.data.results);
this.pageNumber = movies.data.page + 1;
this.maxPages = movies.data.total_pages;
},
async loadData(ev) {
const res = await this.pushData(this.pageNumber);
console.log("Loaded data");
console.log(res);
console.log(this.pageNumber);
ev.target.complete();
if (this.pageNumber >= this.maxPages) {
ev.target.disabled = true;
}
},
async doRefresh(event) {
const res = await this.fetch(1);
console.log(res);
event.target.complete();
},
},
mounted() {
this.fetch(this.pageNumber);
},
watch: {
$route(to, from) {
this.endpoint = this.$route.params.id
.toLowerCase()
.split(" ")
.join("_");
this.pageNumber = 1;
this.maxPages = 1;
this.fetch(this.pageNumber);
// Scroll to top when the tab changes
this.scrollToTop();
}
},
setup() {
// Get ref to content
const content = ref();
// Add function to scroll to top
return {
content,
scrollToTop: () => content.value.$el.scrollToTop(),
};
},
}
</script>
<style scoped>
</style>
결론
만약 지금까지 당신이 계속 관심을 가지고 있다면, 축하합니다. 당신은 이미 성공적으로 Ionic 응용 프로그램을 만들었습니다."그런데 잠깐만, 이거 Android
에서 운행해야 되는 거 아니야?",나는 네가 말하는 것을 들었다.네가 옳다. 우리는 이미 우리의 인터넷 브라우저에서 휴대전화를 위한 앱을 실행했다. 이 앱을 진정한 안드로이드 휴대전화에 설치하려면 몇 가지 절차가 필요하다.
이 절차는 이 자습서 Building and deploying Ionic apps
의 다음 섹션에서 설명합니다.
이 강좌의 전체 코드를 원하시면 여기를 클릭하십시오: MovieVue
Reference
이 문제에 관하여(에오니아 5와 Vue 입문), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/aveeksaha/getting-started-with-ionic-5-and-vue-20lj
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
Reference
이 문제에 관하여(에오니아 5와 Vue 입문), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/aveeksaha/getting-started-with-ionic-5-and-vue-20lj텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)