fix(plugins): show tenant domain and move store prices into a tab

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-08 11:01:00 +03:30
co-authored by Cursor
parent 2a9e8ea023
commit ca923c7d9b
3 changed files with 90 additions and 21 deletions
+35 -2
View File
@@ -7,6 +7,13 @@ import api from '@/api/http'
import MoneyInput from '@/components/MoneyInput.vue'
import { useAuthStore } from '@/stores/auth'
withDefaults(
defineProps<{
embedded?: boolean
}>(),
{ embedded: false },
)
type PriceRow = {
durationDays: number
enabled: boolean
@@ -109,8 +116,11 @@ onMounted(load)
</script>
<template>
<div class="page-shell">
<div style="display: flex; align-items: flex-start; gap: 8px; margin-bottom: 16px">
<div :class="embedded ? 'catalog-embed' : 'page-shell'">
<div
v-if="!embedded"
style="display: flex; align-items: flex-start; gap: 8px; margin-bottom: 16px"
>
<a-button type="text" @click="router.push({ name: 'plugin-panel' })">
<ArrowRightOutlined />
</a-button>
@@ -122,6 +132,14 @@ onMounted(load)
<PlusOutlined /> تنظیم جدید
</a-button>
</div>
<div v-else class="catalog-embed-bar">
<p class="catalog-hint">
همهٔ کلیدهای تنظیمات خانگی سوپرادمین. مدت خاموش در ادمین ماژول دیده نمی‌شود.
</p>
<a-button v-if="canWrite" type="primary" size="small" @click="showCreate = true">
<PlusOutlined /> تنظیم جدید
</a-button>
</div>
<a-spin :spinning="loading">
<div class="catalog-grid">
@@ -193,6 +211,21 @@ onMounted(load)
</template>
<style scoped>
.catalog-embed-bar {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
margin-bottom: 12px;
}
.catalog-hint {
margin: 0;
flex: 1;
min-width: 12rem;
color: #64748b;
font-size: 0.85rem;
}
.catalog-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr));
+54 -16
View File
@@ -1,10 +1,11 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { computed, onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { message } from 'ant-design-vue'
import { ArrowRightOutlined, AppstoreAddOutlined, SearchOutlined } from '@ant-design/icons-vue'
import api from '@/api/http'
import { useAuthStore } from '@/stores/auth'
import PluginCatalogPage from '@/pages/PluginCatalogPage.vue'
type ExpiryLevel = 'healthy' | 'warning' | 'critical' | 'unknown' | 'none'
@@ -33,7 +34,10 @@ type RequestRow = {
createdAt: string
}
type MainTab = 'khanegi' | 'hamkar' | 'requests' | 'catalog'
const router = useRouter()
const route = useRoute()
const auth = useAuthStore()
const canWrite = computed(() => auth.can('plugins:write'))
const storeBusyId = ref<string | null>(null)
@@ -41,7 +45,7 @@ const loadingList = ref(false)
const tenants = ref<TenantRow[]>([])
const q = ref('')
const toneFilter = ref<'healthy' | 'warning' | 'critical' | 'unknown' | null>(null)
const mainTab = ref<'khanegi' | 'hamkar' | 'requests'>('khanegi')
const mainTab = ref<MainTab>('khanegi')
const requests = ref<RequestRow[]>([])
const loadingRequests = ref(false)
const actingId = ref<string | null>(null)
@@ -160,11 +164,29 @@ async function reject(row: RequestRow) {
}
}
function setTab(tab: 'khanegi' | 'hamkar' | 'requests') {
function setTab(tab: MainTab) {
mainTab.value = tab
const query = tab === 'khanegi' ? {} : { tab }
void router.replace({ query })
if (tab === 'requests') void loadRequests()
}
function tabFromQuery(raw: unknown): MainTab {
if (raw === 'hamkar' || raw === 'requests' || raw === 'catalog') return raw
return 'khanegi'
}
watch(
() => route.query.tab,
(tab) => {
const next = tabFromQuery(tab)
if (mainTab.value === next) return
mainTab.value = next
if (next === 'requests') void loadRequests()
},
{ immediate: true },
)
onMounted(() => {
loadTenants()
})
@@ -189,9 +211,10 @@ onMounted(() => {
<button type="button" :class="{ active: mainTab === 'khanegi' }" @click="setTab('khanegi')">خانگی</button>
<button type="button" :class="{ active: mainTab === 'hamkar' }" @click="setTab('hamkar')">همکار</button>
<button type="button" :class="{ active: mainTab === 'requests' }" @click="setTab('requests')">درخواست‌ها</button>
<button type="button" :class="{ active: mainTab === 'catalog' }" @click="setTab('catalog')">قیمت فروشگاه</button>
</div>
<div v-if="mainTab !== 'requests'" class="toolbar">
<div v-if="mainTab === 'khanegi' || mainTab === 'hamkar'" class="toolbar">
<a-input
v-model:value="q"
allow-clear
@@ -200,12 +223,9 @@ onMounted(() => {
>
<template #prefix><SearchOutlined /></template>
</a-input>
<a-button v-if="mainTab === 'khanegi'" type="primary" @click="router.push({ name: 'plugin-catalog' })">
قیمت فروشگاه
</a-button>
</div>
<div v-if="mainTab !== 'requests'" class="legend" role="group" aria-label="فیلتر وضعیت افزونه">
<div v-if="mainTab === 'khanegi' || mainTab === 'hamkar'" class="legend" role="group" aria-label="فیلتر وضعیت افزونه">
<button
type="button"
class="chip all"
@@ -292,6 +312,11 @@ onMounted(() => {
</a-spin>
</section>
<section v-else-if="mainTab === 'catalog'" class="sms-selector">
<div class="sms-side-head">قیمت فروشگاه خانگی</div>
<PluginCatalogPage embedded />
</section>
<section v-else class="sms-selector">
<div class="sms-side-head"><AppstoreAddOutlined /> نسخه‌ها</div>
<a-spin :spinning="loadingList">
@@ -332,14 +357,13 @@ onMounted(() => {
:class="toneClass(t.summary?.expiryLevel)"
@click="openTenant(t.id)"
>
<div
v-if="canWrite"
class="sms-toggle"
:class="{ off: !t.pluginStoreEnabled }"
@click.stop
>
<span class="sms-toggle-label">فروشگاه افزونه</span>
<div class="sms-toggle" :class="{ off: !t.pluginStoreEnabled }" @click.stop>
<div class="sms-toggle-text">
<span class="sms-toggle-label">فروشگاه افزونه</span>
<small class="sms-toggle-domain" dir="ltr">{{ t.domain }}</small>
</div>
<a-switch
v-if="canWrite"
:checked="t.pluginStoreEnabled === true"
:loading="storeBusyId === t.id"
checked-children="روشن"
@@ -488,6 +512,20 @@ onMounted(() => {
color: #334155;
white-space: nowrap;
}
.sms-toggle-text {
display: grid;
gap: 2px;
min-width: 0;
flex: 1;
}
.sms-toggle-domain {
font-size: 0.7rem;
font-weight: 600;
color: #64748b;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tenant-card strong { font-size: 0.95rem; }
.tenant-card span,
+1 -3
View File
@@ -61,9 +61,7 @@ const router = createRouter({
},
{
path: 'plugins/catalog',
name: 'plugin-catalog',
component: () => import('@/pages/PluginCatalogPage.vue'),
meta: { permissions: ['plugins:read'] },
redirect: { name: 'plugin-panel', query: { tab: 'catalog' } },
},
{
path: 'plugins/:tenantId',