redesign some pages of store components
This commit is contained in:
parent
f3517d55d6
commit
3d6f27ef80
|
@ -285,7 +285,6 @@ class PluginController extends AbstractController
|
||||||
foreach ($plugins as $plugin) {
|
foreach ($plugins as $plugin) {
|
||||||
$plugin->setDateExpire($jdate->jdate('Y/n/d', $plugin->getDateExpire()));
|
$plugin->setDateExpire($jdate->jdate('Y/n/d', $plugin->getDateExpire()));
|
||||||
$plugin->setDateSubmit($jdate->jdate('Y/n/d', $plugin->getDateSubmit()));
|
$plugin->setDateSubmit($jdate->jdate('Y/n/d', $plugin->getDateSubmit()));
|
||||||
$plugin->setPrice(number_format($plugin->getPrice()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->json($plugins);
|
return $this->json($plugins);
|
||||||
|
|
|
@ -1,83 +1,343 @@
|
||||||
<script>
|
<script setup>
|
||||||
import {defineComponent, ref} from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import axios from "axios";
|
import axios from "axios"
|
||||||
|
|
||||||
export default defineComponent({
|
const searchValue = ref('')
|
||||||
name: "plugin-my",
|
const loading = ref(true)
|
||||||
data: ()=>{return{
|
const items = ref([])
|
||||||
searchValue: '',
|
const selectedFilter = ref('all') // 'all', 'success', 'failed'
|
||||||
loading : ref(true),
|
|
||||||
items:[],
|
const headers = [
|
||||||
headers: [
|
{ title: "افزونه", key: "des", sortable: true },
|
||||||
{ text: "افزونه", value: "des"},
|
{ title: "تاریخ ایجاد", key: "dateSubmit", sortable: true },
|
||||||
{ text: "تاریخ ایجاد", value: "dateSubmit", sortable: true},
|
{ title: "تاریخ اعتبار", key: "dateExpire", sortable: true },
|
||||||
{ text: "تاریخ اعتبار", value: "dateExpire", sortable: true},
|
{ title: "وضعیت", key: "status", sortable: true },
|
||||||
{ text: "وضعیت", value: "status", sortable: true},
|
{ title: "قیمت (تومان)", key: "price", sortable: true, align: 'end' },
|
||||||
{ text: "قیمت (تومان)", value: "price", sortable: true},
|
|
||||||
]
|
]
|
||||||
}},
|
|
||||||
methods:{
|
// Computed properties for filtered data
|
||||||
loadData(){
|
const filteredItems = computed(() => {
|
||||||
|
let filtered = items.value
|
||||||
|
|
||||||
|
// Filter by status
|
||||||
|
if (selectedFilter.value === 'success') {
|
||||||
|
filtered = filtered.filter(item => item.status == 100)
|
||||||
|
} else if (selectedFilter.value === 'failed') {
|
||||||
|
filtered = filtered.filter(item => item.status != 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by search
|
||||||
|
if (searchValue.value) {
|
||||||
|
filtered = filtered.filter(item =>
|
||||||
|
item.des?.toLowerCase().includes(searchValue.value.toLowerCase()) ||
|
||||||
|
item.dateSubmit?.includes(searchValue.value) ||
|
||||||
|
item.dateExpire?.includes(searchValue.value) ||
|
||||||
|
item.price?.toString().includes(searchValue.value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
})
|
||||||
|
|
||||||
|
// Computed properties for summary counts
|
||||||
|
const totalCount = computed(() => items.value.length)
|
||||||
|
const successCount = computed(() => items.value.filter(item => item.status == 100).length)
|
||||||
|
const failedCount = computed(() => items.value.filter(item => item.status != 100).length)
|
||||||
|
|
||||||
|
const loadData = () => {
|
||||||
|
loading.value = true
|
||||||
axios.post('/api/plugin/get/paids').then((response) => {
|
axios.post('/api/plugin/get/paids').then((response) => {
|
||||||
this.items = response.data;
|
items.value = response.data
|
||||||
this.loading = false;
|
loading.value = false
|
||||||
|
}).catch(() => {
|
||||||
|
loading.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
|
||||||
created() {
|
const getStatusColor = (status) => {
|
||||||
this.loadData()
|
return status == 100 ? 'success' : 'error'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getStatusText = (status) => {
|
||||||
|
return status == 100 ? 'پرداخت شده' : 'پرداخت نشده'
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatPrice = (price) => {
|
||||||
|
return new Intl.NumberFormat('fa-IR').format(price) + ' تومان'
|
||||||
|
}
|
||||||
|
|
||||||
|
const setFilter = (filter) => {
|
||||||
|
selectedFilter.value = filter
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="block block-content-full">
|
<!-- Toolbar -->
|
||||||
<div id="fixed-header" class="block-header block-header-default bg-gray-light" >
|
<v-toolbar title="تاریخچه پرداختها" flat color="toolbar">
|
||||||
<h3 class="block-title text-primary-dark">
|
<template v-slot:prepend>
|
||||||
<i class="fa fa-cog"></i>
|
<v-btn icon @click="$router.back()" class="me-2 d-none d-md-flex" variant="text">
|
||||||
تاریخچه پرداختها
|
<v-icon>mdi-arrow-right</v-icon>
|
||||||
</h3>
|
</v-btn>
|
||||||
<div class="block-options">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="block-content pb-3">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12 col-md-12 m-0 p-0">
|
|
||||||
<div class="mb-1">
|
|
||||||
<div class="input-group input-group-sm">
|
|
||||||
<span class="input-group-text"><i class="fa fa-search"></i></span>
|
|
||||||
<input v-model="searchValue" class="form-control" type="text" placeholder="جست و جو ...">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<EasyDataTable table-class-name="customize-table"
|
|
||||||
multi-sort
|
|
||||||
show-index
|
|
||||||
alternating
|
|
||||||
|
|
||||||
:search-value="searchValue"
|
|
||||||
:headers="headers"
|
|
||||||
:items="items"
|
|
||||||
theme-color="#1d90ff"
|
|
||||||
header-text-direction="center"
|
|
||||||
body-text-direction="center"
|
|
||||||
rowsPerPageMessage="تعداد سطر"
|
|
||||||
emptyMessage="اطلاعاتی برای نمایش وجود ندارد"
|
|
||||||
rowsOfPageSeparatorMessage="از"
|
|
||||||
:loading="loading"
|
|
||||||
>
|
|
||||||
|
|
||||||
<template #item-status="{ status }">
|
|
||||||
<span v-show="status == 100" class="text-success">پرداخت شده</span>
|
|
||||||
<span v-show="status != 100" class="text-danger">پرداخت نشده</span>
|
|
||||||
</template>
|
</template>
|
||||||
</EasyDataTable>
|
<template v-slot:append>
|
||||||
</div>
|
<v-btn icon variant="text" @click="loadData" :loading="loading">
|
||||||
</div>
|
<v-icon>mdi-refresh</v-icon>
|
||||||
</div>
|
</v-btn>
|
||||||
</div>
|
</template>
|
||||||
|
</v-toolbar>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<v-container fluid class="pa-4">
|
||||||
|
<!-- Summary Cards as Filters -->
|
||||||
|
<v-row class="mb-4">
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-card
|
||||||
|
class="summary-card"
|
||||||
|
elevation="2"
|
||||||
|
rounded="lg"
|
||||||
|
:class="{ 'selected-filter': selectedFilter === 'all' }"
|
||||||
|
@click="setFilter('all')"
|
||||||
|
style="cursor: pointer;"
|
||||||
|
>
|
||||||
|
<v-card-item>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-avatar color="primary" size="48">
|
||||||
|
<v-icon size="24" color="white">mdi-receipt</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
</template>
|
||||||
|
<v-card-title class="text-h6">کل پرداختها</v-card-title>
|
||||||
|
<v-card-subtitle>{{ totalCount }} مورد</v-card-subtitle>
|
||||||
|
</v-card-item>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-card
|
||||||
|
class="summary-card"
|
||||||
|
elevation="2"
|
||||||
|
rounded="lg"
|
||||||
|
:class="{ 'selected-filter': selectedFilter === 'success' }"
|
||||||
|
@click="setFilter('success')"
|
||||||
|
style="cursor: pointer;"
|
||||||
|
>
|
||||||
|
<v-card-item>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-avatar color="success" size="48">
|
||||||
|
<v-icon size="24" color="white">mdi-check-circle</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
</template>
|
||||||
|
<v-card-title class="text-h6">پرداختهای موفق</v-card-title>
|
||||||
|
<v-card-subtitle>{{ successCount }} مورد</v-card-subtitle>
|
||||||
|
</v-card-item>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-card
|
||||||
|
class="summary-card"
|
||||||
|
elevation="2"
|
||||||
|
rounded="lg"
|
||||||
|
:class="{ 'selected-filter': selectedFilter === 'failed' }"
|
||||||
|
@click="setFilter('failed')"
|
||||||
|
style="cursor: pointer;"
|
||||||
|
>
|
||||||
|
<v-card-item>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-avatar color="error" size="48">
|
||||||
|
<v-icon size="24" color="white">mdi-close-circle</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
</template>
|
||||||
|
<v-card-title class="text-h6">پرداختهای ناموفق</v-card-title>
|
||||||
|
<v-card-subtitle>{{ failedCount }} مورد</v-card-subtitle>
|
||||||
|
</v-card-item>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<!-- Search Bar - Full Width -->
|
||||||
|
<v-row class="mb-4">
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field
|
||||||
|
v-model="searchValue"
|
||||||
|
prepend-inner-icon="mdi-magnify"
|
||||||
|
placeholder="جستجو در افزونهها، تاریخها یا قیمتها..."
|
||||||
|
variant="outlined"
|
||||||
|
density="comfortable"
|
||||||
|
hide-details
|
||||||
|
clearable
|
||||||
|
full-width
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<!-- Data Table -->
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-card elevation="2" rounded="lg">
|
||||||
|
<v-data-table
|
||||||
|
:headers="headers"
|
||||||
|
:items="filteredItems"
|
||||||
|
:loading="loading"
|
||||||
|
hover
|
||||||
|
class="elevation-0"
|
||||||
|
:items-per-page="10"
|
||||||
|
:items-per-page-options="[5, 10, 25, 50]"
|
||||||
|
locale="fa"
|
||||||
|
>
|
||||||
|
<!-- Custom Status Column -->
|
||||||
|
<template v-slot:item.status="{ item }">
|
||||||
|
<v-chip
|
||||||
|
:color="getStatusColor(item.status)"
|
||||||
|
size="small"
|
||||||
|
variant="flat"
|
||||||
|
class="font-weight-medium"
|
||||||
|
>
|
||||||
|
<v-icon start size="small">
|
||||||
|
{{ item.status == 100 ? 'mdi-check-circle' : 'mdi-close-circle' }}
|
||||||
|
</v-icon>
|
||||||
|
{{ getStatusText(item.status) }}
|
||||||
|
</v-chip>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Custom Price Column -->
|
||||||
|
<template v-slot:item.price="{ item }">
|
||||||
|
<span class="font-weight-medium text-primary">
|
||||||
|
{{ formatPrice(item.price) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Custom Date Columns -->
|
||||||
|
<template v-slot:item.dateSubmit="{ item }">
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
<span class="text-body-2 font-weight-medium">{{ item.dateSubmit }}</span>
|
||||||
|
<span class="text-caption text-grey">تاریخ ایجاد</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item.dateExpire="{ item }">
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
<span class="text-body-2 font-weight-medium">{{ item.dateExpire }}</span>
|
||||||
|
<span class="text-caption text-grey">تاریخ اعتبار</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Empty State -->
|
||||||
|
<template v-slot:no-data>
|
||||||
|
<div class="text-center pa-8">
|
||||||
|
<v-icon size="64" color="grey-lighten-1" class="mb-4">mdi-receipt-text</v-icon>
|
||||||
|
<h3 class="text-h6 text-grey-darken-1 mb-2">هیچ پرداختی یافت نشد</h3>
|
||||||
|
<p class="text-body-2 text-grey">
|
||||||
|
{{ selectedFilter !== 'all' ? 'با فیلتر انتخاب شده هیچ پرداختی یافت نشد.' : 'در حال حاضر هیچ پرداختی در سیستم ثبت نشده است.' }}
|
||||||
|
</p>
|
||||||
|
<v-btn
|
||||||
|
v-if="selectedFilter !== 'all'"
|
||||||
|
color="primary"
|
||||||
|
variant="outlined"
|
||||||
|
class="mt-4"
|
||||||
|
@click="setFilter('all')"
|
||||||
|
>
|
||||||
|
<v-icon start>mdi-filter-remove</v-icon>
|
||||||
|
حذف فیلتر
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Loading State -->
|
||||||
|
<template v-slot:loading>
|
||||||
|
<v-skeleton-loader type="table-row@10" />
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.summary-card {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: 1px solid rgba(var(--v-theme-outline), 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card.selected-filter {
|
||||||
|
border-color: rgba(var(--v-theme-primary), 0.5);
|
||||||
|
background-color: rgba(var(--v-theme-primary), 0.05);
|
||||||
|
box-shadow: 0 4px 12px rgba(var(--v-theme-primary), 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card.selected-filter:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 16px rgba(var(--v-theme-primary), 0.3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom table styles */
|
||||||
|
:deep(.v-data-table) {
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-data-table__wrapper) {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-data-table-header) {
|
||||||
|
background-color: rgba(var(--v-theme-primary), 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-data-table-header th) {
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(var(--v-theme-on-surface), 0.87);
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-data-table__tr:hover) {
|
||||||
|
background-color: rgba(var(--v-theme-primary), 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Center align all table cells */
|
||||||
|
:deep(.v-data-table td) {
|
||||||
|
text-align: center !important;
|
||||||
|
vertical-align: middle !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-data-table th) {
|
||||||
|
text-align: center !important;
|
||||||
|
vertical-align: middle !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Center align specific content types */
|
||||||
|
:deep(.v-data-table .v-chip) {
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-data-table .d-flex) {
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-data-table .d-flex.flex-column) {
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.summary-card .v-card-item {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-data-table) {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,68 +1,230 @@
|
||||||
<script>
|
<script setup>
|
||||||
import {defineComponent} from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import axios from "axios";
|
import axios from "axios"
|
||||||
|
|
||||||
export default defineComponent({
|
const plugins = ref([])
|
||||||
name: "plugin-my",
|
const loading = ref(true)
|
||||||
data: ()=>{return {
|
|
||||||
plugins:{}
|
const loadData = () => {
|
||||||
}},
|
loading.value = true
|
||||||
methods:{
|
|
||||||
loadData(){
|
|
||||||
axios.post('/api/plugin/get/actives').then((response) => {
|
axios.post('/api/plugin/get/actives').then((response) => {
|
||||||
this.plugins = response.data;
|
plugins.value = response.data
|
||||||
|
loading.value = false
|
||||||
|
}).catch(() => {
|
||||||
|
loading.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
|
||||||
created() {
|
onMounted(() => {
|
||||||
this.loadData();
|
loadData()
|
||||||
}
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="block block-content-full">
|
<!-- Toolbar -->
|
||||||
<div id="fixed-header" class="block-header block-header-default bg-gray-light" >
|
<v-toolbar title="افزونههای فعال" flat color="toolbar">
|
||||||
<h3 class="block-title text-primary-dark">
|
<template v-slot:prepend>
|
||||||
<i class="fa fa-cog"></i>
|
<v-btn icon @click="$router.back()" class="me-2 d-none d-md-flex" variant="text">
|
||||||
افزونههای فعال
|
<v-icon>mdi-arrow-right</v-icon>
|
||||||
</h3>
|
</v-btn>
|
||||||
<div class="block-options">
|
</template>
|
||||||
</div>
|
<template v-slot:append>
|
||||||
</div>
|
<v-btn icon variant="text" @click="loadData" :loading="loading">
|
||||||
<div class="block-content pb-3">
|
<v-icon>mdi-refresh</v-icon>
|
||||||
<div class="container-fluid">
|
</v-btn>
|
||||||
<div class="row">
|
</template>
|
||||||
<div class="col-12">
|
</v-toolbar>
|
||||||
<div class="bg-body-extra-light">
|
|
||||||
<table class="table table-striped table-hover table-borderless table-vcenter">
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="plugin in plugins">
|
|
||||||
<td>
|
|
||||||
<div class="d-sm-flex">
|
|
||||||
<div class="ms-sm-2 me-sm-4 py-3">
|
|
||||||
<a class="item item-rounded bg-body-dark text-dark fs-2 mb-2 mx-auto" href="javascript:void(0)">
|
|
||||||
<i class="fa fa-fw fa-plug-circle-plus"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="py-3">
|
|
||||||
<RouterLink :to="'/acc/plugins/' + plugin.name + '/intro'" class="link-fx h4 mb-1 d-inline-block text-dark"> {{plugin.des}} </RouterLink>
|
|
||||||
<div class="fs-sm fw-semibold text-muted mb-2"> تاریخ پایان اعتبار : {{plugin.dateExpire}} </div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<v-container fluid class="pa-4">
|
||||||
|
<!-- Empty State -->
|
||||||
|
<v-row v-if="!loading && plugins.length === 0">
|
||||||
|
<v-col cols="12" class="text-center">
|
||||||
|
<v-card class="pa-8" variant="outlined">
|
||||||
|
<v-icon size="64" color="grey-lighten-1" class="mb-4">mdi-power-plug-off</v-icon>
|
||||||
|
<h3 class="text-h6 text-grey-darken-1 mb-2">هیچ افزونه فعالی یافت نشد</h3>
|
||||||
|
<p class="text-body-2 text-grey">در حال حاضر هیچ افزونهای در سیستم شما فعال نیست.</p>
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
variant="outlined"
|
||||||
|
class="mt-4"
|
||||||
|
to="/acc/store/plugin-world"
|
||||||
|
>
|
||||||
|
<v-icon start>mdi-store</v-icon>
|
||||||
|
مشاهده افزونهها
|
||||||
|
</v-btn>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<!-- Plugins Grid -->
|
||||||
|
<v-row v-else>
|
||||||
|
<v-col
|
||||||
|
v-for="plugin in plugins"
|
||||||
|
:key="plugin.name"
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
lg="4"
|
||||||
|
xl="3"
|
||||||
|
>
|
||||||
|
<v-card
|
||||||
|
class="plugin-card mb-4"
|
||||||
|
elevation="2"
|
||||||
|
rounded="lg"
|
||||||
|
hover
|
||||||
|
:to="'/acc/plugins/' + plugin.name + '/intro'"
|
||||||
|
>
|
||||||
|
<!-- Plugin Header -->
|
||||||
|
<v-card-item class="pb-0">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-avatar
|
||||||
|
color="primary"
|
||||||
|
size="48"
|
||||||
|
class="me-3"
|
||||||
|
>
|
||||||
|
<v-icon size="24" color="white">mdi-power-plug</v-icon>
|
||||||
|
</v-avatar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<v-card-title class="text-h6 font-weight-bold">
|
||||||
|
{{ plugin.des }}
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<template v-slot:append>
|
||||||
|
<v-chip
|
||||||
|
color="success"
|
||||||
|
size="small"
|
||||||
|
variant="flat"
|
||||||
|
class="ms-2"
|
||||||
|
>
|
||||||
|
<v-icon start size="small">mdi-check-circle</v-icon>
|
||||||
|
فعال
|
||||||
|
</v-chip>
|
||||||
|
</template>
|
||||||
|
</v-card-item>
|
||||||
|
|
||||||
|
<!-- Plugin Content -->
|
||||||
|
<v-card-text class="pt-0">
|
||||||
|
<div class="d-flex align-center justify-space-between mb-3">
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<v-icon color="warning" size="small" class="me-2">mdi-clock-outline</v-icon>
|
||||||
|
<span class="text-body-2 text-grey-darken-1">تاریخ پایان اعتبار:</span>
|
||||||
|
</div>
|
||||||
|
<v-chip
|
||||||
|
:color="getExpiryColor(plugin.dateExpire)"
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
>
|
||||||
|
{{ plugin.dateExpire }}
|
||||||
|
</v-chip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Plugin Features -->
|
||||||
|
<v-row class="text-center py-2 bg-grey-lighten-5 rounded-lg">
|
||||||
|
<v-col cols="6" class="border-e">
|
||||||
|
<v-icon color="primary" size="small" class="me-1">mdi-shield-check</v-icon>
|
||||||
|
<span class="text-caption">امن</span>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6">
|
||||||
|
<v-icon color="primary" size="small" class="me-1">mdi-update</v-icon>
|
||||||
|
<span class="text-caption">بهروزرسانی</span>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<!-- Plugin Actions -->
|
||||||
|
<v-card-actions class="pt-0">
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
variant="flat"
|
||||||
|
block
|
||||||
|
:to="'/acc/plugins/' + plugin.name + '/intro'"
|
||||||
|
>
|
||||||
|
<v-icon start>mdi-book-open-page-variant</v-icon>
|
||||||
|
مشاهده جزئیات
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<!-- Loading State -->
|
||||||
|
<v-row v-if="loading">
|
||||||
|
<v-col cols="12" md="6" lg="4" xl="3" v-for="i in 6" :key="i">
|
||||||
|
<v-card class="mb-4" elevation="2" rounded="lg">
|
||||||
|
<v-card-item>
|
||||||
|
<v-skeleton-loader type="avatar" class="me-3"></v-skeleton-loader>
|
||||||
|
<v-skeleton-loader type="text" width="60%"></v-skeleton-loader>
|
||||||
|
</v-card-item>
|
||||||
|
<v-card-text>
|
||||||
|
<v-skeleton-loader type="text" width="100%"></v-skeleton-loader>
|
||||||
|
<v-skeleton-loader type="text" width="80%"></v-skeleton-loader>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-skeleton-loader type="button" width="100%"></v-skeleton-loader>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Helper function to determine expiry color
|
||||||
|
const getExpiryColor = (dateExpire) => {
|
||||||
|
if (!dateExpire) return 'grey'
|
||||||
|
|
||||||
|
const today = new Date()
|
||||||
|
const expiryDate = new Date(dateExpire)
|
||||||
|
const daysUntilExpiry = Math.ceil((expiryDate - today) / (1000 * 60 * 60 * 24))
|
||||||
|
|
||||||
|
if (daysUntilExpiry < 0) return 'error'
|
||||||
|
if (daysUntilExpiry <= 7) return 'warning'
|
||||||
|
if (daysUntilExpiry <= 30) return 'orange'
|
||||||
|
return 'success'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.plugin-card {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: 1px solid rgba(var(--v-theme-outline), 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1) !important;
|
||||||
|
border-color: rgba(var(--v-theme-primary), 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-card .v-card-item {
|
||||||
|
padding: 16px 16px 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-card .v-card-text {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-card .v-card-actions {
|
||||||
|
padding: 0 16px 16px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-e {
|
||||||
|
border-right: 1px solid rgba(var(--v-theme-outline), 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.plugin-card .v-card-item {
|
||||||
|
padding: 12px 12px 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-card .v-card-text {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-card .v-card-actions {
|
||||||
|
padding: 0 12px 12px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
Loading…
Reference in a new issue