Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Events init #9

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
35 changes: 29 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
UiScrollbar,
UiTag,
UiTransition,
CrmYandexMap
CrmYandexMap,
CrmPlacement
```

Более подробно познакомиться с этими компонентами можно на [витрине](https://design.retailcrm.tech/omnica-vue3/index.html).
Expand Down Expand Up @@ -61,6 +62,13 @@

## API Ядра в карточке заказа

```javascript
api.getPlacement
```
Возвращает информацию о placement (места встраивания компонентов приложения в интерфейс CRM).
Метод принимает 2 параметра - имя плейсмента и название скоупа.
Возвращает массив объектов вида {id, context}, где id - идентификатор точки монтирования, context - специфическая информация для текущего placement.

```javascript
api.getCustomerEmail
```
Expand Down Expand Up @@ -104,6 +112,15 @@ api.parseDeliveryAddress

Стили должны быть модульными

## Монтирование компонентов в интерфейсе хостового приложения
Для монтирования компонентов используется компонент ```CrmPlacement```. В него следует обернуть компоненты приложения, которые должны быть смонтированы в конкретной точке хостового приложения.
Компонент имеет обязательный входной параметр ```placementId``` - идентификатор точки монтирования.

Для того, чтобы получить список доступных точек монтирования, нужно вызвать метод ```api.getPlacement(placementName, scopeName)```, который вернет массив объектов вида {id, context}, где
id - идентификатор точки монтирования, context - объект с информацией, специфичной для данной точки.

Например, для плеймента ```customer-phone```, контекст будет вида ```{ phone: string }```.

## В удаленном приложении нужно описать используемые компоненты
```typescript
const CrmYandexMap = defineRemoteComponent('CrmYandexMap', [
Expand All @@ -121,7 +138,6 @@ export { UiButton, UiModalWindow, CrmYandexMap }
```javascript
[{
entrypoint: 'extension-url',
placement: 'delivery-address',
scope: 'order-card',
stylesheet: 'stylesheet-url',
uuid: '1'
Expand Down Expand Up @@ -174,14 +190,20 @@ make zip-archive

# Пример удаленного приложения на основе @omnicajs/vue-remote

## Описание примера
## Описание примеров

### 1. Адрес доставки на карте

Пример представляет собой модальное окно с интерактивной картой от Яндекса. Маркер можно перетаскивать и тем самым изменять адрес.
Используется JavaScript API Яндекс Карт и Геокодер версии 3.0. При первоначальной отрисовке позиционирование маркера происходит
исходя из адреса в поле "Адрес", далее при перетаскивании маркера адрес выбирается исходя из текущих координат маркера, выбирается
ближайший адрес к текущим координатам. При нажатии кнопки "Выбрать", происходит заполнение или обновление адреса в поле
"Адрес".

### 2. Кнопки перехода в мессенджеры

Пример позволяет добавить рядом с номером телефона клиента на странице заказа и клиента кнопки перехода в мессенджеры по этому номеру.

## Запуск

### Начальное развертывание
Expand Down Expand Up @@ -210,12 +232,13 @@ make zip-archive
```javascript
extensionsInit([{
entrypoint: 'http://localhost:3000/extension/62aa8145-ed53-4862-b28f-f1bc6b36a3a3',
placement: 'delivery-address',
scope: 'order-card',
scope: ['order-card', 'customer-card'],
stylesheet: 'http://localhost:3000/extension/62aa8145-ed53-4862-b28f-f1bc6b36a3a3/stylesheet',
uuid: '1'
}])
```

В секции "Отгрузка и доставка" под полем "Адрес" должна появиться кнопка "На карте". При клике на кнопку открывается модальное окно с картой.
На странице заказа в секции "Отгрузка и доставка" под полем "Адрес" должна появиться кнопка "На карте". При клике на кнопку открывается модальное окно с картой.
Для работы карты нужен api ключ.

Рядом с номером телефона клиента должны появиться кнопки перехода в мессенджеры.
3 changes: 3 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ const CrmYandexMap = defineRemoteComponent('CrmYandexMap', [
'change': (address: string) => void,
})

const CrmPlacement = defineRemoteComponent('CrmPlacement')

export {
UiButton,
UiModalWindow,
CrmYandexMap,
CrmPlacement,
}
51 changes: 51 additions & 0 deletions src/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
interface IEventData {
detail: {
id: string,
name: string
}
}

class HostEvents {
private _listeners

constructor () {
this._listeners = new Map()
}

addListener (eventName: string, listener: () => void) {
const eventListeners = this._listeners.get(eventName)

if (typeof listener !== 'function') {
return
}

if (eventListeners) {
eventListeners.push(listener)
return
}

this._listeners.set(eventName, [listener])
}

removeListener (eventName: string, listener: () => void) {
const listeners = this._listeners.get(eventName)
const listenerIndex = listeners.indexOf(listener)

if (listenerIndex > -1) {
listeners.splice(listenerIndex, 1)
}
}

call (eventName: string, eventData: IEventData) {
(this._listeners.get(eventName) || [])
.forEach((listener: (eventData: IEventData) => void) => listener(eventData))
}

clear () {
Array.from(this._listeners.keys()).forEach(key => {
this._listeners.delete(key)
})
}
}

export const hostEvents = new HostEvents()
21 changes: 18 additions & 3 deletions src/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import {
createRemoteRenderer,
} from '@omnicajs/vue-remote/remote'

import VExtension from '@/extension/VExtension.vue'
import VApp from '@/extension/VApp.vue'

import { hostEvents } from './events'

const endpoint = createEndpoint(fromInsideIframe())

Expand All @@ -21,13 +23,16 @@ const createApp = async (channel, component, props) => {
'UiModalWindow',
'UiModalWindowSurface',
'CrmYandexMap',
'CrmPlacement',
],
})

await remoteRoot.mount()

const app = createRemoteRenderer(remoteRoot).createApp(component, props)

app.provide('hostEventListener', hostEvents.addListener.bind(hostEvents))

app.mount(remoteRoot)

return app
Expand All @@ -36,15 +41,17 @@ const createApp = async (channel, component, props) => {
let onRelease = () => {}

endpoint.expose({
async run (channel, api) {
async run (channel, api, scopes) {
retain(channel)
retain(api)

const app = await createApp(channel, VExtension, {
const app = await createApp(channel, VApp, {
api,
scopes,
})

onRelease = () => {
hostEvents.clear()
release(channel)
release(api)

Expand All @@ -55,4 +62,12 @@ endpoint.expose({
release () {
onRelease()
},

/**
* Метод будет вызван при наступлении события в хостовом приложении
* Например, при добавлении нового плейсмента
*/
onEvent (eventName, eventData) {
hostEvents.call(eventName, eventData)
},
})
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
/>

<template #footer>
<UiButton @click="api.setDeliveryAddress(address); opened = false">
<UiButton @click="setAddress">
Выбрать
</UiButton>
</template>
Expand All @@ -46,10 +46,10 @@ import { ref, onMounted } from 'vue'

const props = defineProps({
api: {
type: Object as PropType<{
type: Object as PropType<Partial<{
setDeliveryAddress (value: string): void;
getDeliveryAddress (): Promise<string | null>;
}>,
}>>,
required: true,
},
})
Expand All @@ -59,8 +59,18 @@ const apiKey = 'dd51f938-0693-457d-ae62-6d50fa668d0a'
const opened = ref(false)
const address = ref('')

const setAddress = () => {
if (props.api?.setDeliveryAddress) {
props.api.setDeliveryAddress(address.value)
}

opened.value = false
}

onMounted(async () => {
address.value = await props.api.getDeliveryAddress() ?? ''
if (props.api?.getDeliveryAddress) {
address.value = await props.api.getDeliveryAddress() ?? ''
}
})
</script>

Expand Down
99 changes: 99 additions & 0 deletions src/extension/CustomerPhoneExtension.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<template>
<ul
:class="{
[$style['list']]: true,
[$style['list_order']]: isOrderCard
}"
>
<li :class="$style['list__item']">
<a :href="links.whatsapp" target="_blank">
<svg viewBox="0 0 60 60">
<path
style="fill:#2CB742;"
d="M0,58l4.988-14.963C2.457,38.78,1,33.812,1,28.5C1,12.76,13.76,0,29.5,0S58,12.76,58,28.5 S45.24,57,29.5,57c-4.789,0-9.299-1.187-13.26-3.273L0,58z"
/>
<path
style="fill:#FFFFFF;"
d="M47.683,37.985c-1.316-2.487-6.169-5.331-6.169-5.331c-1.098-0.626-2.423-0.696-3.049,0.42
c0,0-1.577,1.891-1.978,2.163c-1.832,1.241-3.529,1.193-5.242-0.52l-3.981-3.981l-3.981-3.981c-1.713-1.713-1.761-3.41-0.52-5.242
c0.272-0.401,2.163-1.978,2.163-1.978c1.116-0.627,1.046-1.951,0.42-3.049c0,0-2.844-4.853-5.331-6.169
c-1.058-0.56-2.357-0.364-3.203,0.482l-1.758,1.758c-5.577,5.577-2.831,11.873,2.746,17.45l5.097,5.097l5.097,5.097
c5.577,5.577,11.873,8.323,17.45,2.746l1.758-1.758C48.048,40.341,48.243,39.042,47.683,37.985z"
/>
</svg>
</a>
</li>
<li :class="$style['list__item']">
<a :href="links.telegram" target="_blank">
<svg viewBox="0 0 30 30" fill="none">
<circle cx="16" cy="16" r="14" fill="#007DBB" />
<path
d="M22.9866 10.2088C23.1112 9.40332 22.3454 8.76755 21.6292 9.082L7.36482 15.3448C6.85123 15.5703 6.8888 16.3483 7.42147 16.5179L10.3631 17.4547C10.9246 17.6335 11.5325 17.541 12.0228 17.2023L18.655 12.6203C18.855 12.4821 19.073 12.7665 18.9021 12.9426L14.1281 17.8646C13.665 18.3421 13.7569 19.1512 14.314 19.5005L19.659 22.8523C20.2585 23.2282 21.0297 22.8506 21.1418 22.1261L22.9866 10.2088Z"
fill="white"
/>
</svg>
</a>
</li>
</ul>
</template>

<script lang="ts" setup>
import { computed } from 'vue'
import type { PropType } from 'vue'

const SCOPE = {
ORDER_CARD: 'order-card',
CUSTOMER_CARD: 'customer-card',
}

const props = defineProps({
scopes: {
type: Array as PropType<string[]>,
default: () => {},
},

phone: {
type: String,
default: null,
},
})

// Пример стилизации в зависимости от scope
const isOrderCard = computed(() => {
return props.scopes.includes(SCOPE.ORDER_CARD)
})

const links = computed(() => {
return {
whatsapp: `https://wa.me/${props.phone}`,
telegram: `https://t.me/${props.phone}`,
}
})
</script>

<style lang="less" module>
.list {
display: inline-flex;
align-items: center;
gap: 4px;
margin: 0;
transform: translateY(5px);

&__item {
list-style: none;
width: 18px;
height: 18px;
}

&_order {
margin-top: 8px;
margin-bottom: 12px;
transform: none;

.list__item {
width: 24px;
height: 24px;
}
}
}
</style>
Loading