mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-21 18:06:19 +00:00
refactor(frontend): extract shared OrderTable component
- Create OrderTable.vue with shared cell rendering (ID, order number, amount, payment method, status badge, created time) - Accepts showUser prop to conditionally show user_email column - Actions column uses scoped slot for view-specific buttons - UserOrdersView and AdminOrdersView both use OrderTable - Removes duplicated DataTable cell templates from both views chore: bump version to 0.1.108.144
This commit is contained in:
@@ -1 +1 @@
|
||||
0.1.108.143
|
||||
0.1.108.144
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<DataTable :columns="columns" :data="orders" :loading="loading">
|
||||
<template #cell-id="{ value }">
|
||||
<span class="font-mono text-sm">#{{ value }}</span>
|
||||
</template>
|
||||
<template #cell-out_trade_no="{ value }">
|
||||
<span class="text-sm text-gray-900 dark:text-white">{{ value }}</span>
|
||||
</template>
|
||||
<template v-if="showUser" #cell-user_email="{ value, row }">
|
||||
<div class="text-sm">
|
||||
<span class="text-gray-900 dark:text-white">{{ value || row.user_name || '#' + row.user_id }}</span>
|
||||
<span v-if="row.user_notes" class="ml-1 text-xs text-gray-400">({{ row.user_notes }})</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell-amount="{ value, row }">
|
||||
<div class="text-sm">
|
||||
<span class="font-medium text-gray-900 dark:text-white">${{ value.toFixed(2) }}</span>
|
||||
<span v-if="row.pay_amount !== value" class="ml-1 text-xs text-gray-500">(${{ row.pay_amount.toFixed(2) }})</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell-payment_type="{ value }">
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">{{ t('payment.methods.' + value, value) }}</span>
|
||||
</template>
|
||||
<template #cell-status="{ value }">
|
||||
<OrderStatusBadge :status="value" />
|
||||
</template>
|
||||
<template #cell-created_at="{ value }">
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">{{ formatDate(value) }}</span>
|
||||
</template>
|
||||
<template #cell-actions="{ row }">
|
||||
<slot name="actions" :row="row" />
|
||||
</template>
|
||||
</DataTable>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { PaymentOrder } from '@/types/payment'
|
||||
import type { Column } from '@/components/common/types'
|
||||
import DataTable from '@/components/common/DataTable.vue'
|
||||
import OrderStatusBadge from '@/components/payment/OrderStatusBadge.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
orders: PaymentOrder[]
|
||||
loading: boolean
|
||||
showUser?: boolean
|
||||
}>()
|
||||
|
||||
function formatDate(dateStr: string) { return new Date(dateStr).toLocaleString() }
|
||||
|
||||
const columns = computed((): Column[] => {
|
||||
const cols: Column[] = [
|
||||
{ key: 'id', label: t('payment.orders.orderId') },
|
||||
{ key: 'out_trade_no', label: t('payment.orders.orderNo') },
|
||||
]
|
||||
if (props.showUser) {
|
||||
cols.push({ key: 'user_email', label: t('payment.admin.colUser') })
|
||||
}
|
||||
cols.push(
|
||||
{ key: 'amount', label: t('payment.orders.amount') },
|
||||
{ key: 'payment_type', label: t('payment.orders.paymentMethod') },
|
||||
{ key: 'status', label: t('payment.orders.status') },
|
||||
{ key: 'created_at', label: t('payment.orders.createdAt') },
|
||||
{ key: 'actions', label: t('common.actions') },
|
||||
)
|
||||
return cols
|
||||
})
|
||||
</script>
|
||||
@@ -19,33 +19,8 @@
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<DataTable :columns="orderColumns" :data="orders" :loading="ordersLoading">
|
||||
<template #cell-id="{ value }"><span class="font-mono text-sm">#{{ value }}</span></template>
|
||||
<template #cell-out_trade_no="{ value }">
|
||||
<span class="text-sm text-gray-900 dark:text-white">{{ value }}</span>
|
||||
</template>
|
||||
<template #cell-user_email="{ value, row }">
|
||||
<div class="text-sm">
|
||||
<span class="text-gray-900 dark:text-white">{{ value || row.user_name || '#' + row.user_id }}</span>
|
||||
<span v-if="row.user_notes" class="ml-1 text-xs text-gray-400">({{ row.user_notes }})</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell-amount="{ value, row }">
|
||||
<div class="text-sm">
|
||||
<span class="font-medium text-gray-900 dark:text-white">${{ value.toFixed(2) }}</span>
|
||||
<span v-if="row.pay_amount !== value" class="ml-1 text-xs text-gray-500">({{ t('payment.orders.payAmount') }}: ${{ row.pay_amount.toFixed(2) }})</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell-status="{ value }">
|
||||
<OrderStatusBadge :status="value" />
|
||||
</template>
|
||||
<template #cell-payment_type="{ value }">
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">{{ t('payment.methods.' + value, value) }}</span>
|
||||
</template>
|
||||
<template #cell-created_at="{ value }">
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">{{ formatDateTime(value) }}</span>
|
||||
</template>
|
||||
<template #cell-actions="{ row }">
|
||||
<OrderTable :orders="orders" :loading="ordersLoading" show-user>
|
||||
<template #actions="{ row }">
|
||||
<div class="flex items-center gap-1">
|
||||
<button @click="showOrderDetail(row)" class="inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-dark-600">
|
||||
<Icon name="eye" size="sm" />
|
||||
@@ -76,7 +51,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</DataTable>
|
||||
</OrderTable>
|
||||
<Pagination v-if="orderPagination.total > 0" :page="orderPagination.page" :total="orderPagination.total" :page-size="orderPagination.page_size" @update:page="handleOrderPageChange" @update:pageSize="handleOrderPageSizeChange" />
|
||||
</div>
|
||||
|
||||
@@ -143,15 +118,14 @@ import { useAppStore } from '@/stores/app'
|
||||
import { adminPaymentAPI } from '@/api/admin/payment'
|
||||
import { extractApiErrorMessage } from '@/utils/apiError'
|
||||
import type { PaymentOrder } from '@/types/payment'
|
||||
import type { Column } from '@/components/common/types'
|
||||
import AppLayout from '@/components/layout/AppLayout.vue'
|
||||
import DataTable from '@/components/common/DataTable.vue'
|
||||
import Pagination from '@/components/common/Pagination.vue'
|
||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||
import Select from '@/components/common/Select.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import AdminRefundDialog from '@/components/admin/payment/AdminRefundDialog.vue'
|
||||
import OrderStatusBadge from '@/components/payment/OrderStatusBadge.vue'
|
||||
import OrderTable from '@/components/payment/OrderTable.vue'
|
||||
|
||||
interface AuditLog {
|
||||
id: number
|
||||
@@ -225,12 +199,6 @@ const orderTypeFilterOptions = computed(() => [
|
||||
{ value: 'subscription', label: t('payment.admin.subscriptionOrder') },
|
||||
])
|
||||
|
||||
const orderColumns = computed((): Column[] => [
|
||||
{ key: 'id', label: 'ID' }, { key: 'out_trade_no', label: t('payment.orders.orderNo') }, { key: 'user_email', label: t('payment.admin.colUser') }, { key: 'amount', label: t('payment.orders.amount') },
|
||||
{ key: 'payment_type', label: t('payment.orders.paymentMethod') }, { key: 'status', label: t('payment.orders.status') },
|
||||
{ key: 'created_at', label: t('payment.orders.createdAt') }, { key: 'actions', label: t('common.actions') },
|
||||
])
|
||||
|
||||
async function showOrderDetail(order: PaymentOrder) {
|
||||
selectedOrder.value = order
|
||||
orderAuditLogs.value = []
|
||||
|
||||
@@ -15,29 +15,8 @@
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<DataTable :columns="columns" :data="orders" :loading="loading">
|
||||
<template #cell-id="{ value }">
|
||||
<span class="font-mono text-sm">#{{ value }}</span>
|
||||
</template>
|
||||
<template #cell-out_trade_no="{ value }">
|
||||
<span class="text-sm text-gray-900 dark:text-white">{{ value }}</span>
|
||||
</template>
|
||||
<template #cell-amount="{ value, row }">
|
||||
<div class="text-sm">
|
||||
<span class="font-medium text-gray-900 dark:text-white">${{ value.toFixed(2) }}</span>
|
||||
<span v-if="row.pay_amount !== value" class="ml-1 text-xs text-gray-500">(${{ row.pay_amount.toFixed(2) }})</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell-payment_type="{ value }">
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">{{ t('payment.methods.' + value, value) }}</span>
|
||||
</template>
|
||||
<template #cell-status="{ value }">
|
||||
<OrderStatusBadge :status="value" />
|
||||
</template>
|
||||
<template #cell-created_at="{ value }">
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">{{ formatDate(value) }}</span>
|
||||
</template>
|
||||
<template #cell-actions="{ row }">
|
||||
<OrderTable :orders="orders" :loading="loading">
|
||||
<template #actions="{ row }">
|
||||
<div class="flex items-center gap-2">
|
||||
<button v-if="row.status === 'PENDING'" @click="handleCancel(row.id)" class="inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-yellow-600 hover:bg-yellow-50 dark:text-yellow-400 dark:hover:bg-yellow-900/20">
|
||||
<Icon name="x" size="sm" />
|
||||
@@ -49,7 +28,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</DataTable>
|
||||
</OrderTable>
|
||||
|
||||
<!-- Pagination -->
|
||||
<Pagination
|
||||
@@ -109,14 +88,12 @@ import { useAppStore } from '@/stores'
|
||||
import { paymentAPI } from '@/api/payment'
|
||||
import { extractApiErrorMessage } from '@/utils/apiError'
|
||||
import type { PaymentOrder } from '@/types/payment'
|
||||
import type { Column } from '@/components/common/types'
|
||||
import AppLayout from '@/components/layout/AppLayout.vue'
|
||||
import DataTable from '@/components/common/DataTable.vue'
|
||||
import Pagination from '@/components/common/Pagination.vue'
|
||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||
import Select from '@/components/common/Select.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import OrderStatusBadge from '@/components/payment/OrderStatusBadge.vue'
|
||||
import OrderTable from '@/components/payment/OrderTable.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
@@ -139,18 +116,6 @@ const statusFilters = computed(() => [
|
||||
{ value: 'REFUNDED', label: t('payment.status.refunded') },
|
||||
])
|
||||
|
||||
const columns = computed((): Column[] => [
|
||||
{ key: 'id', label: t('payment.orders.orderId') },
|
||||
{ key: 'out_trade_no', label: t('payment.orders.orderNo') },
|
||||
{ key: 'amount', label: t('payment.orders.amount') },
|
||||
{ key: 'payment_type', label: t('payment.orders.paymentMethod') },
|
||||
{ key: 'status', label: t('payment.orders.status') },
|
||||
{ key: 'created_at', label: t('payment.orders.createdAt') },
|
||||
{ key: 'actions', label: t('common.actions') },
|
||||
])
|
||||
|
||||
function formatDate(dateStr: string) { return new Date(dateStr).toLocaleString() }
|
||||
|
||||
async function fetchOrders() {
|
||||
loading.value = true
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user