Давайте разберемся, как же нам построить классный сервис для ВКонтакте. Представим, что вы продавец и решили сделать сервис, чтобы продавать через нашу площадку и взаимодействовать с VK API.
В этом случае, нам понадобится 3 примочки от VK:
- VK Apps, который умеет отправить нативное уведомление вашему пользователю
- VK UI, чтобы не мучиться с первым дизайном, нам ведь надо проверить нашу идею
- VK API, чтобы связать ваше сообщество и иметь реалтайм данные о товаре
Н
Начнем подготовку
Мы уже когда-то писали, как начать использовать наш сборщик для VK Apps, но там был простой пример с двумя экранами.
Давайте копнем немного глубже, но возьмем все тот же пример.
sgustun-nb:Desktop s.gustun$ npx @vkontakte/create-vk-app ecomerce-app
npx: установлен 209 в 7.43s
🎬 Creating project...
⏱ Installing project dependencies — it might take a few minutes..
✅ Dependencies installed
⏱ Copying VK App source files..
🖼 Assets directory and file copied
✌️ VK App Boilerplate is ready to start in ecomerce-app folder.
🧐 Check README.MD for brief instructrions.
💻 Happy Coding!
sgustun-nb:Desktop s.gustun$
После перейдем в папку
cd ecomerce-app
Отлично, мы получили первую версию нашего приложения, давайте теперь ее изменим.
Пока что уберем нашего Персика и оставим только панель Home, с которой немного поработаем.
Изменим ее до такого состояния:
import React from 'react';
import PropTypes from 'prop-types';
import {Panel, PanelHeader} from '@vkontakte/vkui';
const Home = ({id, go, fetchedUser}) => (
<Panel id={id}>
<PanelHeader>Товары</PanelHeader>
</Panel>
);
Home.propTypes = {
id: PropTypes.string.isRequired,
go: PropTypes.func.isRequired,
fetchedUser: PropTypes.shape({
photo_200: PropTypes.string,
first_name: PropTypes.string,
last_name: PropTypes.string,
city: PropTypes.shape({
title: PropTypes.string,
}),
}),
};
export default Home;
А теперь запустим наш сервис.
npm start
Надеюсь, что у вас вылезла ошибка и вы быстро с ней разобрались.
На нашем экране пустовато, давайте добавим список из заранее собранных товаров, но лучше если мы заглянем в наше API и посмотрим в каком формате приходят товары сообществ:
Отлично, вот наш формат:
"items": [
{
"id": 250409,
"owner_id": -124527492,
"title": "Persik",
"description": "A majestic furball who adores to sleep, to purr, and to play with a computer mouse.",
"price": {
"amount": "100000",
"currency": {
"id": 643,
"name": "RUB"
},
"text": "1,000 rub."
},
"category": {
"id": 1001,
"name": "Cats",
"section": {
"id": 10,
"name": "Pets"
}
},
"date": 1467722904,
"thumb_photo": "https://pp.vk.me/...6f3/SQ607FYCmy4.jpg",
"availability": 0
},
{
"id": 250407,
"owner_id": -124527492,
"title": "Spotty",
"description": "A tail wagging champion, true friend and never-failing warmer.",
"price": {
"amount": "100000",
"currency": {
"id": 643,
"name": "RUB"
},
"text": "1,000 rub."
},
"category": {
"id": 1000,
"name": "Dogs",
"section": {
"id": 10,
"name": "Pets"
}
},
"date": 1467722851,
"thumb_photo": "https://pp.vk.me/...6e5/1OWGz65-8vw.jpg",
"availability": 0
},
{
"id": 250396,
"owner_id": -124527492,
"title": "First market item",
"description": "Description text",
"price": {
"amount": "10000",
"currency": {
"id": 643,
"name": "RUB"
},
"text": "100 rub."
},
"category": {
"id": 1,
"name": "Women's Clothing",
"section": {
"id": 0,
"name": "Fashion"
}
},
"date": 1467721947,
"thumb_photo": "https://pp.vk.me/...ae0/7lXUEbCwYYM.jpg",
"availability": 0
}
]
Давайте изменим нашу панель, добавим список товаров и сделаем из этой панели полноценный компонент.
App.js
...
this.state = {
items: [
{
"id": 250409,
"owner_id": -124527492,
"title": "Persik",
"description": "A majestic furball who adores to sleep, to purr, and to play with a computer mouse.",
"price": {
"amount": "100000",
"currency": {
"id": 643,
"name": "RUB"
},
"text": "1,000 rub."
},
"category": {
"id": 1001,
"name": "Cats",
"section": {
"id": 10,
"name": "Pets"
}
},
"date": 1467722904,
"thumb_photo": "https://pp.vk.me/c631229/v631229852/3b6f3/SQ607FYCmy4.jpg",
"availability": 0
},
{
"id": 250407,
"owner_id": -124527492,
"title": "Spotty",
"description": "A tail wagging champion, true friend and never-failing warmer.",
"price": {
"amount": "100000",
"currency": {
"id": 643,
"name": "RUB"
},
"text": "1,000 rub."
},
"category": {
"id": 1000,
"name": "Dogs",
"section": {
"id": 10,
"name": "Pets"
}
},
"date": 1467722851,
"thumb_photo": "https://pp.vk.me/c631229/v631229852/3b6e5/1OWGz65-8vw.jpg",
"availability": 0
},
{
"id": 250396,
"owner_id": -124527492,
"title": "First market item",
"description": "Description text",
"price": {
"amount": "10000",
"currency": {
"id": 643,
"name": "RUB"
},
"text": "100 rub."
},
"category": {
"id": 1,
"name": "Women's Clothing",
"section": {
"id": 0,
"name": "Fashion"
}
},
"date": 1467721947,
"thumb_photo": "https://pp.vk.me/c633819/v633819852/37ae0/7lXUEbCwYYM.jpg",
"availability": 0
}
]
};
...
<Home id="home" items={this.state.items} fetchedUser={this.state.fetchedUser} go={this.go}>
...
Home.js
class Home extends React.Component {
constructor(props) {
super(props);
}
render() {
let {id} = this.props
return (
<Panel id={id}>
<PanelHeader>Товары</PanelHeader>
<Group>
<List>
{
this.props.items.map((item, index) => (
<Cell
key={index}
before={
<img
style={{
width: 40,
height: 40,
margin: 10
}}
src={item.thumb_photo}
/>
}
multiline
description={item.description}
>
{item.title}, {item.price.amount} {item.price.currency.name}
</Cell>
))
}
</List>
</Group>
</Panel>
);
}
}
Home.propTypes = {
id: PropTypes.string.isRequired,
go: PropTypes.func.isRequired,
fetchedUser: PropTypes.shape({
photo_200: PropTypes.string,
first_name: PropTypes.string,
last_name: PropTypes.string,
city: PropTypes.shape({
title: PropTypes.string,
}),
}),
};
export default Home;
Ну вот, уже выглядит приятнее.
Но нам не хватает запроса реальных данных.
Начнем с того, что нам надо получить ключ доступа, чтобы мы могли делать к некоторым методам API запросы.
В документации есть строчка об этом.
connect.send('VKWebAppGetAuthToken', {'app_id': 6906999, 'scope': 'friends,status'});
Давайте изменим наш файл App и добавим в него запрос на получении токена пользователя.
componentDidMount() {
connect.subscribe((e) => {
switch (e.detail.type) {
case 'VKWebAppGetUserInfoResult':
this.setState({fetchedUser: e.detail.data});
break;
case 'VKWebAppAccessTokenReceived':
this.setState({authToken: e.detail.data.access_token});
break;
default:
console.log(e.detail.type);
}
});
connect.send('VKWebAppGetUserInfo', {});
connect.send('VKWebAppGetAuthToken', {'app_id': 6906999, 'scope': 'market'});
}
Отлично, как мы можем теперь с помощью нашего токена получать товары сообщества?
Давайте напишем вот такую функцию, только перед этим еще добавим пакет для запросов:
npm install fetch-jsonp
И создадим функцию:
getItems() {
const ownerId = 124527492
let api = `https://api.vk.com/method/market.get?v=5.52&access_token=${this.state.authToken}&owner_id=-${ownerId}`
fetchJsonp(api)
.then(res => res.json())
.then(data => data.response.item)
.catch(e => [])
}
Я как всегда надеюсь, что у вас возникла ошибка и вы знаете как ее обойти.
Не забудьте, описать поведение, когда у вас нуль товаров.
Давайте посмотрим, что получилось у нас.
render() {
let {
id, items
} = this.props
return (
<Panel id={id}>
<PanelHeader>Товары</PanelHeader>
<Group>
<List>
{
items.map((item, index) => (
<Cell
key={index}
before={
<img
style={{
width: 40,
height: 40,
margin: 10
}}
src={item.thumb_photo}
/>
}
multiline
description={item.description}
>
{item.title}, {item.price.amount} {item.price.currency.name}
</Cell>
))
}
{
items.length == 0 &&
<Div>
Хм, но мы не нашли товаров
</Div>
}
</List>
</Group>
</Panel>
);
}
Так же посмотрим, что у нас стало с нашим компонентом App.
import React from 'react';
import connect from '@vkontakte/vkui-connect';
import {View} from '@vkontakte/vkui';
import '@vkontakte/vkui/dist/vkui.css';
import Home from './panels/Home';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
activePanel: 'home',
fetchedUser: null,
authToken: null
};
}
componentDidMount() {
connect.subscribe((e) => {
switch (e.detail.type) {
case 'VKWebAppGetUserInfoResult':
this.setState({fetchedUser: e.detail.data});
break;
case 'VKWebAppAccessTokenReceived':
this.setState({authToken: e.detail.data.access_token});
this.getItems()
break;
default:
console.log(e.detail.type);
}
});
connect.send('VKWebAppGetUserInfo', {});
connect.send('VKWebAppGetAuthToken', {'app_id': 6396978, 'scope': 'market'});
}
go = (e) => {
this.setState({activePanel: e.currentTarget.dataset.to})
};
getItems() {
const ownerId = 124527492
let api = `https://api.vk.com/method/market.get?v=5.52&access_token=${this.state.authToken}&owner_id=-${ownerId}`
fetchJsonp(api)
.then(res => res.json())
.then(data => data.response.item)
.catch(e => [])
}
render() {
return (
<View activePanel={this.state.activePanel}>
<Home id="home" items={this.state.items} authToken={this.state.authToken} fetchedUser={this.state.fetchedUser} go={this.go}/>
</View>
);
}
}
export default App;
Для вас останется небольшое задание:
- попробуйте изменить картинку у товара, а так же сделайте заглушку для нее, когда она не смогла загрузиться
- не забывайте, что иногда пользователь может не подтвердить разрешение на получение своего токена, тоже попробуйте это описать
На этом мы закончим, а в следующий раз, сделаем полноценную страничку товара, с остальными особенностями нашего API и UI.
Если вы готовы, поделиться своими решениями заданий, то не стесняйтесь и отправляйте их нам, лучшие мы добавим в следующий выпуск!
Ссылка на репозиторий: https://github.com/Gaserd/ecomerce-app
Ссылка на рабочее приложение: https://vk.com/app6906999