From 3371015b967829bda994afe3b4f29b92a0a8dbc4 Mon Sep 17 00:00:00 2001 From: Mahdi Date: Thu, 23 Jul 2026 19:42:07 +0330 Subject: [PATCH] Creating a control panel infrastructure --- src/pages/HomePage.vue | 150 +++++++++++- src/pages/ProductVersionsPage.vue | 8 + src/pages/SmsPanelPage.vue | 193 ++++++++++++++- src/pages/SmsTenantPage.vue | 380 ++++++++++++++++++++++++++++++ src/router/index.ts | 5 + src/theme/tokens.ts | 2 - tsconfig.app.json | 3 +- 7 files changed, 712 insertions(+), 29 deletions(-) create mode 100644 src/pages/SmsTenantPage.vue diff --git a/src/pages/HomePage.vue b/src/pages/HomePage.vue index 4ea073a..40bc05f 100644 --- a/src/pages/HomePage.vue +++ b/src/pages/HomePage.vue @@ -10,25 +10,32 @@ import { import { message } from 'ant-design-vue' import api from '@/api/http' -type Tenant = { id: string; product: 'hamkar' | 'khanegi' } +type BalanceLevel = 'healthy' | 'warning' | 'critical' | 'unknown' + +type SmsSummary = { + balanceRials: number | null + balanceLabel: string + balanceLevel: BalanceLevel +} + +type Tenant = { + id: string + product: 'hamkar' | 'khanegi' + slug?: string + nameFa?: string + sms?: SmsSummary +} const router = useRouter() const loading = ref(true) const tenants = ref([]) +const smsTenants = ref([]) const hamkarCount = computed(() => tenants.value.filter((t) => t.product === 'hamkar').length) const khanegiCount = computed(() => tenants.value.filter((t) => t.product === 'khanegi').length) +const lowestSmsTenants = computed(() => smsTenants.value.slice(0, 5)) const modules = computed(() => [ - { - key: 'sms', - className: 'sms', - title: 'پنل پیامکی', - desc: 'ارسال و مدیریت پیامک مشترک برای همکار و خانگی در یکجا', - meta: 'مرکزی', - icon: MessageOutlined, - to: { name: 'sms-panel' as const }, - }, { key: 'server', className: 'server', @@ -61,8 +68,12 @@ const modules = computed(() => [ async function load() { loading.value = true try { - const { data } = await api.get('/tenants') - tenants.value = data + const [tenantsRes, lowestRes] = await Promise.all([ + api.get('/tenants'), + api.get('/sms/lowest', { params: { limit: 5 } }), + ]) + tenants.value = tenantsRes.data + smsTenants.value = lowestRes.data } catch (e: any) { message.error(e?.response?.data?.message || 'خطا در دریافت آمار') } finally { @@ -70,6 +81,27 @@ async function load() { } } +function openSmsPanel(tenantId?: string) { + if (tenantId) { + router.push({ name: 'sms-tenant', params: { tenantId } }) + return + } + router.push({ name: 'sms-panel' }) +} + +function smsTone(level?: BalanceLevel) { + switch (level) { + case 'healthy': + return 'healthy' + case 'warning': + return 'warning' + case 'critical': + return 'critical' + default: + return 'unknown' + } +} + onMounted(load) @@ -83,6 +115,40 @@ onMounted(load)
+
+
+
+ +
+

پنل پیامکی

+

۵ نسخه با کمترین موجودی برای پیگیری سریع شارژ و وضعیت پیامک

+
+
+ +
+ هنوز نسخه‌ای برای نمایش موجودی ثبت نشده است +
+
+
+
+
+ {{ lowestSmsTenants.length }} نسخه کم‌موجودی + ورود ← +
+
+
+
+ + diff --git a/src/pages/ProductVersionsPage.vue b/src/pages/ProductVersionsPage.vue index 92ef853..d8fc990 100644 --- a/src/pages/ProductVersionsPage.vue +++ b/src/pages/ProductVersionsPage.vue @@ -10,11 +10,14 @@ type Tenant = { product: 'hamkar' | 'khanegi' slug: string nameFa: string + nameEn?: string | null domain: string avatarUrl?: string | null primaryColor?: string | null isActive: boolean nodeServer: string + notes?: string | null + hasAppUiConfig?: boolean } const route = useRoute() @@ -133,9 +136,14 @@ onMounted(load)
{{ item.domain }}
{{ item.slug }} + {{ item.nameEn }} {{ item.isActive ? 'فعال' : 'غیرفعال' }} + + بررسی محصول + + ظاهر اپ
diff --git a/src/pages/SmsPanelPage.vue b/src/pages/SmsPanelPage.vue index 618f513..3fc99bb 100644 --- a/src/pages/SmsPanelPage.vue +++ b/src/pages/SmsPanelPage.vue @@ -1,8 +1,55 @@ + + diff --git a/src/pages/SmsTenantPage.vue b/src/pages/SmsTenantPage.vue new file mode 100644 index 0000000..260a982 --- /dev/null +++ b/src/pages/SmsTenantPage.vue @@ -0,0 +1,380 @@ + + + + + diff --git a/src/router/index.ts b/src/router/index.ts index a7781bc..caacc9f 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -24,6 +24,11 @@ const router = createRouter({ name: 'sms-panel', component: () => import('@/pages/SmsPanelPage.vue'), }, + { + path: 'sms/:tenantId', + name: 'sms-tenant', + component: () => import('@/pages/SmsTenantPage.vue'), + }, { path: 'servers', name: 'server-panel', diff --git a/src/theme/tokens.ts b/src/theme/tokens.ts index f007011..35b012a 100644 --- a/src/theme/tokens.ts +++ b/src/theme/tokens.ts @@ -21,8 +21,6 @@ export const saTheme: ThemeConfig = { }, components: { Button: { - primaryShadow: '0 10px 24px rgba(32, 201, 151, 0.28)', - fontWeight: 600, borderRadius: 14, }, Card: { diff --git a/tsconfig.app.json b/tsconfig.app.json index 4695e65..5afb611 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -2,9 +2,8 @@ "extends": "@vue/tsconfig/tsconfig.dom.json", "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "baseUrl": ".", "paths": { - "@/*": ["src/*"] + "@/*": ["./src/*"] }, "strict": true, "noUnusedLocals": false,