Files
interview-demo-code/frontend/admin/src/components/TopLead.vue
Nikita Kiselev f329bfa9d9
Some checks failed
Telegram Mini App Shop Builder / Compute version metadata (push) Has been cancelled
Telegram Mini App Shop Builder / Run Frontend tests (push) Has been cancelled
Telegram Mini App Shop Builder / Run Backend tests (push) Has been cancelled
Telegram Mini App Shop Builder / Run PHP_CodeSniffer (push) Has been cancelled
Telegram Mini App Shop Builder / Build module. (push) Has been cancelled
Telegram Mini App Shop Builder / release (push) Has been cancelled
Squashed commit message
2026-03-11 23:00:23 +03:00

230 lines
8.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="tw:bg-surface-0 tw:dark:bg-surface-950 tw:px-6 tw:py-8 tw:md:px-12 tw:lg:px-20">
<div class="tw:flex tw:items-center tw:flex-col tw:lg:flex-row tw:lg:justify-between">
<div class="tw:flex tw:items-start tw:flex-col tw:lg:flex-row tw:gap-8">
<OcImagePicker v-model="settings.items.app.app_icon" class="tw:w-[6.42rem] tw:h-[6.42rem]"/>
<div class="tw:flex tw:flex-col tw:gap-4">
<div class="tw:flex tw:items-center">
<span class="tw:text-surface-900 tw:dark:text-surface-0 tw:font-bold tw:text-3xl">
{{ settings.items.app.app_name }}
</span>
<a
v-if="tgMe?.result?.first_name"
:href="`https://t.me/${tgMe?.result?.username}`"
class="tw:ml-2 tw:text-surface-900 tw:dark:text-surface-0 tw:text-xl">
@{{ tgMe?.result?.first_name }}
</a>
</div>
<div class="tw:flex tw:items-center tw:flex-wrap tw:gap-8">
<div>
<span
v-tooltip.top="'Общее количество заказов, сделанное через AcmeShop за всё время.'"
class="tw:text-surface-500 tw:dark:text-surface-300"
>
Количество заказов
</span>
<div
class="tw:text-surface-700 tw:dark:text-surface-100 tw:mt-1 tw:text-sm tw:font-semibold"
>
{{ stats.items.orders_count ?? '-' }}
</div>
</div>
<div>
<span
v-tooltip.top="'Итоговая сумма заказов, сделанных через AcmeShop за всё время.'"
class="tw:text-surface-500 tw:dark:text-surface-300"
>Общая сумма</span>
<div
class="tw:text-surface-700 tw:dark:text-surface-100 tw:mt-1 tw:text-sm tw:font-semibold">
{{ rub(stats.items.orders_total_amount ?? 0) }}
</div>
</div>
<div>
<span
v-tooltip.top="'Общее количество уникальных Telegram-посетителей, взаимодействовавших с магазином за всё время включая тех, кто просто заходил посмотреть, без оформления заказа.'"
class="tw:text-surface-500 tw:dark:text-surface-300">Кол-во посетителей</span>
<div
class="tw:text-surface-700 tw:dark:text-surface-100 tw:mt-1 tw:text-sm tw:font-semibold">
<RouterLink to="/customers">{{ stats.items.customers_count ?? 0 }}</RouterLink>
</div>
</div>
<div>
<span
v-tooltip.top="'Текущий статус магазина'"
class="tw:text-surface-500 tw:dark:text-surface-300">Статус магазина</span>
<div
class="tw:text-surface-700 tw:dark:text-surface-100 tw:mt-1 tw:text-sm tw:font-semibold">
<div v-if="settings.items.app.app_enabled" class="tw:flex tw:items-center">
<div class="tw:h-2 tw:w-2 tw:rounded-full tw:bg-green-400 tw:flex tw:mr-2">
<span
class="tw:inline-flex tw:h-full tw:w-full tw:animate-ping tw:rounded-full tw:bg-green-400 tw:opacity-75"></span>
</div>
<div>Online</div>
</div>
<div v-else
class="tw:text-surface-700 tw:dark:text-surface-100 tw:mt-1 tw:text-sm tw:font-semibold">
<div class="tw:flex tw:items-center">
<div class="tw:h-2 tw:w-2 tw:rounded-full tw:bg-red-400 tw:flex tw:mr-2">
<span
class="tw:inline-flex tw:h-full tw:w-full tw:animate-ping tw:rounded-full tw:bg-red-400 tw:opacity-75"></span>
</div>
<div>Offline</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tw:mt-6 tw:lg:mt-0 tw:flex tw:items-center tw:gap-4">
<ButtonGroup>
<ResetCacheBtn/>
<Button
icon="fa fa-list"
v-tooltip.top="'Журнал событий'"
@click="showLogsDrawer = true"
/>
<Button
icon="fa fa-info-circle"
v-tooltip.top="'Системная информация'"
@click="showSystemInfoDrawer = true"
/>
</ButtonGroup>
<ButtonGroup>
<Button
icon="fa fa-play"
v-tooltip.top="(tgMe?.result?.has_main_web_app !== true) ? 'Вы не привязали Telegram Mini App к боту.' : 'Открыть Telegram магазин'"
as="a"
target="_blank"
:href="`https://t.me/${tgMe?.result?.username}?startapp`"
/>
<Button
icon="fa fa-book"
v-tooltip.top="'Документация по модулю AcmeShop'"
as="a"
target="_blank"
href="https://acme-inc.github.io/docs/"
/>
<Button
icon="fa fa-group"
v-tooltip.top="'Официальная Telegram группа модуля AcmeShop'"
as="a"
target="_blank"
href="https://t.me/ocstore3"
/>
</ButtonGroup>
</div>
</div>
<Drawer
v-model:visible="showLogsDrawer"
header="Журнал событий"
position="right"
:baseZIndex="1000"
class="tw:!w-full tw:md:!w-1/2"
>
<LogsViewer/>
</Drawer>
<Drawer
v-model:visible="showSystemInfoDrawer"
header="Системная информация"
position="right"
:baseZIndex="1000"
class="tw:!w-full tw:md:!w-1/2"
>
<div class="tw:flex tw:flex-col tw:gap-4 tw:h-full">
<div class="tw:flex tw:justify-end">
<Button
label="Скопировать"
icon="fa fa-copy"
@click="copySystemInfo"
:disabled="!systemInfo"
/>
</div>
<Textarea
v-model="systemInfo"
readonly
class="tw:w-full tw:h-full tw:font-mono tw:text-sm"
style="font-family: monospace;"
/>
</div>
</Drawer>
</div>
</template>
<script setup>
import {useSettingsStore} from "@/stores/settings.js";
import {useStatsStore} from "@/stores/stats.js";
import {onMounted, ref, watch} from "vue";
import OcImagePicker from "@/components/OcImagePicker.vue";
import {apiGet} from "@/utils/http.js";
import ResetCacheBtn from "@/components/Form/ResetCacheBtn.vue";
import {Button, ButtonGroup, Drawer} from "primevue";
import Textarea from 'primevue/textarea';
import {rub} from "@/utils/helpers.js";
import LogsViewer from "@/components/LogsViewer.vue";
import {useToast} from "primevue/usetoast";
const settings = useSettingsStore();
const stats = useStatsStore();
const toast = useToast();
const tgMe = ref(null);
const showLogsDrawer = ref(false);
const showSystemInfoDrawer = ref(false);
const systemInfo = ref('');
const fetchSystemInfo = async () => {
try {
const response = await apiGet('getSystemInfo');
if (response.success) {
systemInfo.value = response.data;
} else {
systemInfo.value = 'Ошибка при получении системной информации: ' + (response.error || 'Unknown error');
}
} catch (error) {
systemInfo.value = 'Ошибка при получении системной информации: ' + (error.message || 'Unknown error');
}
};
const copySystemInfo = async () => {
if (!systemInfo.value) {
return;
}
try {
await navigator.clipboard.writeText(systemInfo.value);
toast.add({
severity: 'success',
summary: 'Скопировано',
detail: 'Системная информация скопирована в буфер обмена',
life: 3000
});
} catch (error) {
toast.add({
severity: 'error',
summary: 'Ошибка',
detail: 'Не удалось скопировать информацию',
life: 3000
});
}
};
watch(showSystemInfoDrawer, (newValue) => {
if (newValue && !systemInfo.value) {
fetchSystemInfo();
}
});
onMounted(async () => {
await stats.fetchStats();
const response = await apiGet('tgGetMe');
tgMe.value = response.data;
});
</script>
<style scoped lang="scss">
</style>