Initial SuperAdmin web panel (Vue 3)
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { ArrowRightOutlined, PlusOutlined, SearchOutlined } from '@ant-design/icons-vue'
|
||||
import api from '@/api/http'
|
||||
|
||||
type Tenant = {
|
||||
id: string
|
||||
product: 'hamkar' | 'khanegi'
|
||||
slug: string
|
||||
nameFa: string
|
||||
domain: string
|
||||
avatarUrl?: string | null
|
||||
primaryColor?: string | null
|
||||
isActive: boolean
|
||||
nodeServer: string
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const rows = ref<Tenant[]>([])
|
||||
const q = ref('')
|
||||
|
||||
const product = computed(() => {
|
||||
const p = String(route.params.product || '')
|
||||
return p === 'khanegi' ? 'khanegi' : 'hamkar'
|
||||
})
|
||||
|
||||
const title = computed(() => (product.value === 'hamkar' ? 'همکار' : 'خانگی'))
|
||||
const subtitle = computed(() =>
|
||||
product.value === 'hamkar'
|
||||
? 'نسخههای فروشی پروژه همکار'
|
||||
: 'نسخههای فروشی پروژه خانگی (Modular)',
|
||||
)
|
||||
|
||||
const filtered = computed(() => {
|
||||
const term = q.value.trim().toLowerCase()
|
||||
if (!term) return rows.value
|
||||
return rows.value.filter(
|
||||
(t) =>
|
||||
t.nameFa.toLowerCase().includes(term) ||
|
||||
t.domain.toLowerCase().includes(term) ||
|
||||
t.slug.toLowerCase().includes(term),
|
||||
)
|
||||
})
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const { data } = await api.get('/tenants', { params: { product: product.value } })
|
||||
rows.value = data
|
||||
} catch (e: any) {
|
||||
message.error(e?.response?.data?.message || 'خطا در دریافت نسخهها')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function addVersion() {
|
||||
router.push({ name: 'version-create', query: { product: product.value } })
|
||||
}
|
||||
|
||||
function edit(id: string) {
|
||||
router.push({ name: 'version-edit', params: { id } })
|
||||
}
|
||||
|
||||
function remove(record: Tenant) {
|
||||
Modal.confirm({
|
||||
title: `حذف نسخه «${record.nameFa}»؟`,
|
||||
content: 'فقط از رجیستری سوپرادمین حذف میشود؛ سرور مشتری را پاک نمیکند.',
|
||||
okText: 'حذف',
|
||||
okType: 'danger',
|
||||
cancelText: 'انصراف',
|
||||
async onOk() {
|
||||
await api.delete(`/tenants/${record.id}`)
|
||||
message.success('حذف شد')
|
||||
await load()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
watch(product, load, { immediate: true })
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-shell">
|
||||
<div style="display: flex; align-items: flex-start; gap: 8px; margin-bottom: 4px">
|
||||
<a-button type="text" @click="router.push({ name: 'home' })">
|
||||
<ArrowRightOutlined />
|
||||
</a-button>
|
||||
<div style="flex: 1">
|
||||
<h1 class="page-title">{{ title }}</h1>
|
||||
<p class="page-sub">{{ subtitle }} · {{ filtered.length }} مورد</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toolbar">
|
||||
<a-input
|
||||
v-model:value="q"
|
||||
allow-clear
|
||||
placeholder="جستجو نام، دامنه یا slug"
|
||||
style="flex: 1; min-width: 180px"
|
||||
>
|
||||
<template #prefix><SearchOutlined /></template>
|
||||
</a-input>
|
||||
<a-button type="primary" @click="addVersion">
|
||||
<PlusOutlined /> نسخه جدید
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<div v-if="filtered.length" class="version-list">
|
||||
<article v-for="item in filtered" :key="item.id" class="version-card">
|
||||
<div class="version-card-head">
|
||||
<img
|
||||
v-if="item.avatarUrl"
|
||||
class="version-avatar"
|
||||
:src="item.avatarUrl"
|
||||
:alt="item.nameFa"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="version-avatar fallback"
|
||||
:style="{ background: item.primaryColor || '#1d4ed8' }"
|
||||
>
|
||||
{{ item.nameFa.slice(0, 1) }}
|
||||
</div>
|
||||
<div class="version-meta">
|
||||
<h3>{{ item.nameFa }}</h3>
|
||||
<div class="domain">{{ item.domain }}</div>
|
||||
<div style="margin-top: 8px">
|
||||
<a-tag>{{ item.slug }}</a-tag>
|
||||
<a-tag :color="item.isActive ? 'success' : 'default'">
|
||||
{{ item.isActive ? 'فعال' : 'غیرفعال' }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="version-actions">
|
||||
<a-button type="primary" ghost block style="flex: 1" @click="edit(item.id)">
|
||||
مدیریت
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="product === 'khanegi'"
|
||||
type="default"
|
||||
block
|
||||
style="flex: 1"
|
||||
@click="router.push({ name: 'version-ui', params: { id: item.id } })"
|
||||
>
|
||||
ظاهر اپ
|
||||
</a-button>
|
||||
<a-button danger ghost style="flex: 0 0 auto" @click="remove(item)">حذف</a-button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<a-empty v-else description="نسخهای ثبت نشده" style="margin: 32px 0">
|
||||
<a-button type="primary" @click="addVersion">افزودن اولین نسخه</a-button>
|
||||
</a-empty>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user