feat(payment): UI improvements for admin payment pages

- Plan table: group column shows GroupBadge with platform color instead
  of raw ID, column header changed to "分组"
- Plan dialog: group info preview uses GroupBadge, removes redundant
  platform/rate fields
- Action buttons: all payment admin tables (plans, orders, providers)
  now use vertically stacked icon+text buttons matching the app style
- Added i18n keys: payment.admin.group, common.view
This commit is contained in:
erio
2026-04-07 11:36:36 +08:00
parent 6c5a2452f2
commit 37292367c9
6 changed files with 49 additions and 25 deletions
@@ -83,37 +83,37 @@
</template>
<template #cell-actions="{ row }">
<div class="flex items-center gap-1">
<div class="flex items-center gap-2">
<button
@click="emit('detail', row)"
class="btn-icon text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
:title="t('common.view')"
class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-gray-50 hover:text-gray-700 dark:hover:bg-gray-800/50 dark:hover:text-gray-300"
>
<Icon name="eye" size="sm" />
<span class="text-xs">{{ t('common.view') }}</span>
</button>
<button
v-if="row.status === 'PENDING'"
@click="emit('cancel', row)"
class="btn-icon text-yellow-500 hover:text-yellow-700"
:title="t('payment.orders.cancel')"
class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-yellow-50 hover:text-yellow-600 dark:hover:bg-yellow-900/20 dark:hover:text-yellow-400"
>
<Icon name="x" size="sm" />
<span class="text-xs">{{ t('payment.orders.cancel') }}</span>
</button>
<button
v-if="row.status === 'FAILED'"
@click="emit('retry', row)"
class="btn-icon text-blue-500 hover:text-blue-700"
:title="t('payment.admin.retry')"
class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-blue-50 hover:text-blue-600 dark:hover:bg-blue-900/20 dark:hover:text-blue-400"
>
<Icon name="refresh" size="sm" />
<span class="text-xs">{{ t('payment.admin.retry') }}</span>
</button>
<button
v-if="canRefund(row)"
@click="emit('refund', row)"
class="btn-icon text-red-500 hover:text-red-700"
:title="t('payment.admin.refund')"
class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 dark:hover:text-red-400"
>
<Icon name="dollar" size="sm" />
<span class="text-xs">{{ t('payment.admin.refund') }}</span>
</button>
</div>
</template>
@@ -46,12 +46,14 @@
<div class="flex items-center gap-4">
<ToggleSwitch :label="t('common.enabled')" :checked="provider.enabled" @toggle="emit('toggleField', 'enabled')" />
<ToggleSwitch :label="t('admin.settings.payment.refundEnabled')" :checked="provider.refund_enabled" @toggle="emit('toggleField', 'refund_enabled')" />
<div class="flex items-center gap-1 border-l border-gray-200 pl-3 dark:border-dark-600">
<button @click="emit('edit')" class="btn-icon text-blue-500 hover:text-blue-700" :title="t('common.edit')">
<div class="flex items-center gap-2 border-l border-gray-200 pl-3 dark:border-dark-600">
<button @click="emit('edit')" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-blue-50 hover:text-blue-600 dark:hover:bg-blue-900/20 dark:hover:text-blue-400">
<Icon name="edit" size="sm" />
<span class="text-xs">{{ t('common.edit') }}</span>
</button>
<button @click="emit('delete')" class="btn-icon text-red-500 hover:text-red-700" :title="t('common.delete')">
<button @click="emit('delete')" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 dark:hover:text-red-400">
<Icon name="trash" size="sm" />
<span class="text-xs">{{ t('common.delete') }}</span>
</button>
</div>
</div>
+2
View File
@@ -306,6 +306,7 @@ export default {
saving: 'Saving...',
selectedCount: '({count} selected)',
refresh: 'Refresh',
view: 'View',
settings: 'Settings',
chooseFile: 'Choose File',
notAvailable: 'N/A',
@@ -5199,6 +5200,7 @@ export default {
forSale: 'For Sale',
onSale: 'On Sale',
offSale: 'Off Sale',
group: 'Group',
groupId: 'Group ID',
features: 'Features',
featuresHint: 'One feature per line',
+2
View File
@@ -306,6 +306,7 @@ export default {
saving: '保存中...',
selectedCount: '(已选 {count} 个)',
refresh: '刷新',
view: '查看',
settings: '设置',
chooseFile: '选择文件',
notAvailable: '不可用',
@@ -5396,6 +5397,7 @@ export default {
forSale: '上架状态',
onSale: '上架',
offSale: '下架',
group: '分组',
groupId: '分组 ID',
features: '功能特性',
featuresHint: '每行一个特性',
@@ -38,11 +38,23 @@
<span class="text-xs text-gray-500 dark:text-gray-400">{{ formatDateTime(value) }}</span>
</template>
<template #cell-actions="{ row }">
<div class="flex items-center gap-1">
<button @click="showOrderDetail(row)" class="btn-icon text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" :title="t('common.view')"><Icon name="eye" size="sm" /></button>
<button v-if="row.status === 'PENDING'" @click="handleCancelOrder(row)" class="btn-icon text-yellow-500 hover:text-yellow-700" :title="t('payment.orders.cancel')"><Icon name="x" size="sm" /></button>
<button v-if="row.status === 'FAILED'" @click="handleRetryOrder(row)" class="btn-icon text-blue-500 hover:text-blue-700" :title="t('payment.admin.retry')"><Icon name="refresh" size="sm" /></button>
<button v-if="canRefund(row)" @click="openRefundDialog(row)" class="btn-icon text-red-500 hover:text-red-700" :title="t('payment.admin.refund')"><Icon name="dollar" size="sm" /></button>
<div class="flex items-center gap-2">
<button @click="showOrderDetail(row)" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-gray-50 hover:text-gray-700 dark:hover:bg-gray-800/50 dark:hover:text-gray-300">
<Icon name="eye" size="sm" />
<span class="text-xs">{{ t('common.view') }}</span>
</button>
<button v-if="row.status === 'PENDING'" @click="handleCancelOrder(row)" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-yellow-50 hover:text-yellow-600 dark:hover:bg-yellow-900/20 dark:hover:text-yellow-400">
<Icon name="x" size="sm" />
<span class="text-xs">{{ t('payment.orders.cancel') }}</span>
</button>
<button v-if="row.status === 'FAILED'" @click="handleRetryOrder(row)" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-blue-50 hover:text-blue-600 dark:hover:bg-blue-900/20 dark:hover:text-blue-400">
<Icon name="refresh" size="sm" />
<span class="text-xs">{{ t('payment.admin.retry') }}</span>
</button>
<button v-if="canRefund(row)" @click="openRefundDialog(row)" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 dark:hover:text-red-400">
<Icon name="dollar" size="sm" />
<span class="text-xs">{{ t('payment.admin.refund') }}</span>
</button>
</div>
</template>
</DataTable>
@@ -49,9 +49,15 @@
</button>
</template>
<template #cell-actions="{ row }">
<div class="flex items-center gap-1">
<button @click="openPlanEdit(row)" class="btn-icon text-blue-500 hover:text-blue-700" :title="t('common.edit')"><Icon name="edit" size="sm" /></button>
<button @click="confirmDeletePlan(row)" class="btn-icon text-red-500 hover:text-red-700" :title="t('common.delete')"><Icon name="trash" size="sm" /></button>
<div class="flex items-center gap-2">
<button @click="openPlanEdit(row)" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-blue-50 hover:text-blue-600 dark:hover:bg-blue-900/20 dark:hover:text-blue-400">
<Icon name="edit" size="sm" />
<span class="text-xs">{{ t('common.edit') }}</span>
</button>
<button @click="confirmDeletePlan(row)" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 dark:hover:text-red-400">
<Icon name="trash" size="sm" />
<span class="text-xs">{{ t('common.delete') }}</span>
</button>
</div>
</template>
</DataTable>
@@ -66,17 +72,17 @@
<input v-model="planForm.name" type="text" class="input" required />
</div>
<div>
<label class="input-label">{{ t('payment.admin.groupId') }}</label>
<label class="input-label">{{ t('payment.admin.group') }}</label>
<Select v-model="planForm.group_id" :options="groupOptions" class="w-full" />
</div>
</div>
<!-- Group Info Preview -->
<div v-if="selectedGroupInfo" class="rounded-lg border border-gray-200 bg-gray-50 p-3 dark:border-dark-600 dark:bg-dark-800">
<p class="mb-2 text-xs font-semibold text-gray-500 dark:text-gray-400">{{ t('payment.admin.groupInfo') }}</p>
<div class="mb-2 flex items-center gap-2">
<GroupBadge :name="selectedGroupInfo.name" :platform="selectedGroupInfo.platform" :rate-multiplier="selectedGroupInfo.rate_multiplier" />
</div>
<div class="grid grid-cols-2 gap-2 text-xs">
<div><span class="text-gray-500">{{ t('payment.admin.platform') }}:</span> <span class="ml-1 font-medium text-gray-700 dark:text-gray-300">{{ selectedGroupInfo.platform }}</span></div>
<div><span class="text-gray-500">{{ t('payment.admin.rateMultiplierLabel') }}:</span> <span class="ml-1 font-medium text-gray-700 dark:text-gray-300">{{ selectedGroupInfo.rate_multiplier }}x</span></div>
<div><span class="text-gray-500">{{ t('payment.admin.dailyLimit') }}:</span> <span class="ml-1 font-medium text-gray-700 dark:text-gray-300">{{ selectedGroupInfo.daily_limit_usd != null ? '$' + selectedGroupInfo.daily_limit_usd : t('payment.admin.unlimited') }}</span></div>
<div><span class="text-gray-500">{{ t('payment.admin.weeklyLimit') }}:</span> <span class="ml-1 font-medium text-gray-700 dark:text-gray-300">{{ selectedGroupInfo.weekly_limit_usd != null ? '$' + selectedGroupInfo.weekly_limit_usd : t('payment.admin.unlimited') }}</span></div>
<div><span class="text-gray-500">{{ t('payment.admin.monthlyLimit') }}:</span> <span class="ml-1 font-medium text-gray-700 dark:text-gray-300">{{ selectedGroupInfo.monthly_limit_usd != null ? '$' + selectedGroupInfo.monthly_limit_usd : t('payment.admin.unlimited') }}</span></div>
@@ -200,7 +206,7 @@ const validityUnitOptions = computed(() => [
const planColumns = computed((): Column[] => [
{ key: 'id', label: 'ID' },
{ key: 'name', label: t('payment.admin.planName') },
{ key: 'group_id', label: t('payment.admin.groupId') },
{ key: 'group_id', label: t('payment.admin.group') },
{ key: 'price', label: t('payment.admin.price') },
{ key: 'validity_days', label: t('payment.admin.validityDays') },
{ key: 'for_sale', label: t('payment.admin.forSale') },