Squashed commit message
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
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
This commit is contained in:
229
frontend/admin/src/components/TopLead.vue
Normal file
229
frontend/admin/src/components/TopLead.vue
Normal file
@@ -0,0 +1,229 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user