Найти в Дзене
Roman Pux

Vk Apps, первый шаг к VK API

Оглавление

Давайте разберемся, как же нам построить классный сервис для ВКонтакте. Представим, что вы продавец и решили сделать сервис, чтобы продавать через нашу площадку и взаимодействовать с 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 и посмотрим в каком формате приходят товары сообществ:

https://vk.com/dev/market.get

Отлично, вот наш формат:

"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

-2

Взято с Vk.com
Взято с Vk.com