mirror of
https://github.com/34892002/edgeKey.git
synced 2026-07-01 16:54:20 +08:00
feat: tag组件
This commit is contained in:
40
components/StatusTag.vue
Normal file
40
components/StatusTag.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<span :class="classes">
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
type?: "primary" | "success" | "danger" | "warning" | "default";
|
||||
size?: "sm" | "md" | "lg";
|
||||
variant?: "solid" | "outline" | "pill";
|
||||
}>(), {
|
||||
type: "default",
|
||||
size: "sm",
|
||||
variant: "solid",
|
||||
});
|
||||
|
||||
const colorMap = {
|
||||
primary: { solid: "bg-blue-500 text-white", outline: "border border-blue-500 text-blue-500" },
|
||||
success: { solid: "bg-green-500 text-white", outline: "border border-green-500 text-green-500" },
|
||||
danger: { solid: "bg-red-500 text-white", outline: "border border-red-500 text-red-500" },
|
||||
warning: { solid: "bg-orange-400 text-white", outline: "border border-orange-400 text-orange-400" },
|
||||
default: { solid: "bg-gray-200 text-gray-600", outline: "border border-gray-400 text-gray-500" },
|
||||
};
|
||||
|
||||
const sizeMap = {
|
||||
sm: "text-xs px-2 py-0.5",
|
||||
md: "text-sm px-2.5 py-0.5",
|
||||
lg: "text-base px-3 py-1",
|
||||
};
|
||||
|
||||
const classes = computed(() => {
|
||||
const color = colorMap[props.type][props.variant === "outline" ? "outline" : "solid"];
|
||||
const size = sizeMap[props.size];
|
||||
const radius = props.variant === "pill" ? "rounded-full" : "rounded";
|
||||
return `inline-flex items-center font-medium ${color} ${size} ${radius}`;
|
||||
});
|
||||
</script>
|
||||
@@ -1,5 +1,50 @@
|
||||
# 公共组件文档
|
||||
|
||||
## StatusTag
|
||||
|
||||
状态标签组件,用于展示订单状态、支付状态、发货状态等。
|
||||
|
||||
### Props
|
||||
|
||||
| 属性 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `type` | `"primary" \| "success" \| "danger" \| "warning" \| "default"` | `"default"` | 颜色类型 |
|
||||
| `size` | `"sm" \| "md" \| "lg"` | `"sm"` | 大小 |
|
||||
| `variant` | `"solid" \| "outline" \| "pill"` | `"solid"` | 样式风格 |
|
||||
|
||||
### 颜色对应
|
||||
|
||||
| type | 颜色 | 适用场景 |
|
||||
|------|------|----------|
|
||||
| `primary` | 蓝色 | 主要操作、信息 |
|
||||
| `success` | 绿色 | 已完成、已支付、已发货 |
|
||||
| `danger` | 红色 | 失败、错误 |
|
||||
| `warning` | 橙色 | 待处理、未支付、未发货 |
|
||||
| `default` | 灰色 | 已关闭、中性状态 |
|
||||
|
||||
### 基本用法
|
||||
|
||||
```components/StatusTag.vue#L1-3
|
||||
<StatusTag type="success">已支付</StatusTag>
|
||||
<StatusTag type="warning">待处理</StatusTag>
|
||||
<StatusTag type="danger">发货失败</StatusTag>
|
||||
```
|
||||
|
||||
### 配合 order-status 工具函数
|
||||
|
||||
`lib/utils/order-status.ts` 提供了对应的 type 辅助函数:
|
||||
|
||||
- `getOrderStatusType(status)` — 订单状态 → type
|
||||
- `getPaymentStatusType(status)` — 支付状态 → type
|
||||
- `getDeliveryStatusType(status)` — 发货状态 → type
|
||||
|
||||
```components/StatusTag.vue#L1-5
|
||||
<StatusTag :type="getOrderStatusType(order.status)">
|
||||
{{ getOrderStatusLabel(order.status) }}
|
||||
</StatusTag>
|
||||
```
|
||||
|
||||
|
||||
## SecretInput
|
||||
|
||||
带显示/隐藏切换的密钥输入框,用于密码、API Secret 等敏感字段。
|
||||
|
||||
@@ -52,6 +52,35 @@ export function getPaymentProviderLabel(provider: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getOrderStatusType(status: string): "warning" | "success" | "primary" | "danger" | "default" {
|
||||
switch (status) {
|
||||
case "PENDING": return "warning";
|
||||
case "PAID": return "success";
|
||||
case "DELIVERED": return "success";
|
||||
case "CLOSED": return "default";
|
||||
case "FAILED": return "danger";
|
||||
default: return "default";
|
||||
}
|
||||
}
|
||||
|
||||
export function getPaymentStatusType(status: string): "warning" | "success" | "danger" | "default" {
|
||||
switch (status) {
|
||||
case "UNPAID": return "warning";
|
||||
case "PAID": return "success";
|
||||
case "FAILED": return "danger";
|
||||
default: return "default";
|
||||
}
|
||||
}
|
||||
|
||||
export function getDeliveryStatusType(status: string): "warning" | "success" | "danger" | "default" {
|
||||
switch (status) {
|
||||
case "NOT_DELIVERED": return "warning";
|
||||
case "DELIVERED": return "success";
|
||||
case "FAILED": return "danger";
|
||||
default: return "default";
|
||||
}
|
||||
}
|
||||
|
||||
export function getVerifyStatusLabel(status: string) {
|
||||
switch (status) {
|
||||
case "PENDING":
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<code>{{ value }}</code>
|
||||
</template>
|
||||
<template #status="{ value }">
|
||||
<span class="badge" :class="getStatusBadgeClass(value)">{{ getStatusLabel(value) }}</span>
|
||||
<StatusTag :type="getCardStatusType(value)">{{ getStatusLabel(value) }}</StatusTag>
|
||||
</template>
|
||||
<template #createdAt="{ value }">
|
||||
{{ formatDate(value) }}
|
||||
@@ -106,6 +106,7 @@ import { onImportCards } from "./importCards.telefunc";
|
||||
import { onQueryCards } from "./queryCards.telefunc";
|
||||
import { onDeleteCard } from "./deleteCard.telefunc";
|
||||
import DataTable from "../../../components/DataTable.vue";
|
||||
import StatusTag from "../../../components/StatusTag.vue";
|
||||
import type { Data } from "./+data";
|
||||
|
||||
const { cards, products, overview } = useData<Data>();
|
||||
@@ -141,8 +142,8 @@ function getStatusLabel(status: string) {
|
||||
return ({ UNUSED: "未售出", SOLD: "已售出", LOCKED: "锁定中", INVALID: "已失效" } as Record<string, string>)[status] || status;
|
||||
}
|
||||
|
||||
function getStatusBadgeClass(status: string) {
|
||||
return ({ UNUSED: "badge-success", SOLD: "badge-ghost", LOCKED: "badge-warning", INVALID: "badge-error" } as Record<string, string>)[status] || "badge-ghost";
|
||||
function getCardStatusType(status: string): "success" | "default" | "warning" | "danger" {
|
||||
return ({ UNUSED: "success", SOLD: "default", LOCKED: "warning", INVALID: "danger" } as Record<string, "success" | "default" | "warning" | "danger">)[status] ?? "default";
|
||||
}
|
||||
|
||||
async function fetchPage(page: number) {
|
||||
|
||||
@@ -64,9 +64,9 @@
|
||||
<td>{{ category.slug }}</td>
|
||||
<td>{{ category.sort }}</td>
|
||||
<td>
|
||||
<span class="badge" :class="category.status === 'ACTIVE' ? 'badge-success' : 'badge-ghost'">
|
||||
<StatusTag :type="category.status === 'ACTIVE' ? 'success' : 'default'">
|
||||
{{ category.status === 'ACTIVE' ? '启用' : '停用' }}
|
||||
</span>
|
||||
</StatusTag>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex gap-2">
|
||||
@@ -90,6 +90,7 @@
|
||||
import { normalizeTelefuncError } from "../../../lib/app-error";
|
||||
import { reactive, ref } from "vue";
|
||||
import { useData } from "vike-vue/useData";
|
||||
import StatusTag from "../../../components/StatusTag.vue";
|
||||
import { onDeleteCategory } from "./deleteCategory.telefunc";
|
||||
import { onSaveCategory } from "./saveCategory.telefunc";
|
||||
import { onToggleCategory } from "./toggleCategory.telefunc";
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
<td class="font-mono text-sm">{{ item.id }}</td>
|
||||
<td>{{ item.name || '-' }}</td>
|
||||
<td>
|
||||
<span class="badge badge-outline">{{ getChannelLabel(item.provider) }}</span>
|
||||
<StatusTag variant="outline">{{ getChannelLabel(item.provider) }}</StatusTag>
|
||||
</td>
|
||||
<td>{{ item.fromEmail || '-' }}</td>
|
||||
<td>
|
||||
@@ -128,9 +128,9 @@
|
||||
<span v-else>{{ (item as any).cloudflareBindingName || '-' }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge" :class="item.isEnabled ? 'badge-success' : 'badge-ghost'">
|
||||
<StatusTag :type="item.isEnabled ? 'success' : 'default'">
|
||||
{{ item.isEnabled ? '已激活' : '未激活' }}
|
||||
</span>
|
||||
</StatusTag>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -410,9 +410,9 @@
|
||||
<td class="whitespace-nowrap">{{ configs.find(c => c.provider === log.provider)?.name || '-' }}</td>
|
||||
<td class="whitespace-nowrap">{{ getSceneLabel(log.scene) }}</td>
|
||||
<td>
|
||||
<span class="badge whitespace-nowrap" :class="log.status === 'SUCCESS' ? 'badge-success' : 'badge-error'">
|
||||
<StatusTag class="whitespace-nowrap" :type="log.status === 'SUCCESS' ? 'success' : 'danger'">
|
||||
{{ log.status === 'SUCCESS' ? '成功' : '失败' }}
|
||||
</span>
|
||||
</StatusTag>
|
||||
</td>
|
||||
<td class="whitespace-nowrap">{{ log.toEmail }}</td>
|
||||
<td class="max-w-xs truncate" :title="log.subject">{{ log.subject }}</td>
|
||||
@@ -481,6 +481,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import SecretInput from "../../../components/SecretInput.vue";
|
||||
import StatusTag from "../../../components/StatusTag.vue";
|
||||
import { normalizeTelefuncError } from "../../../lib/app-error";
|
||||
import { reactive, ref, computed } from "vue";
|
||||
import { useData } from "vike-vue/useData";
|
||||
|
||||
@@ -38,9 +38,9 @@
|
||||
<template #paymentProvider="{ value }">{{ getPaymentProviderLabel(value) }}</template>
|
||||
<template #status="{ row }">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span class="badge" :class="orderStatusClass(row.status)">{{ getOrderStatusLabel(row.status) }}</span>
|
||||
<span class="badge" :class="paymentStatusClass(row.paymentStatus)">{{ getPaymentStatusLabel(row.paymentStatus) }}</span>
|
||||
<span class="badge" :class="deliveryStatusClass(row.deliveryStatus)">{{ getDeliveryStatusLabel(row.deliveryStatus) }}</span>
|
||||
<StatusTag :type="getOrderStatusType(row.status)">{{ getOrderStatusLabel(row.status) }}</StatusTag>
|
||||
<StatusTag :type="getPaymentStatusType(row.paymentStatus)">{{ getPaymentStatusLabel(row.paymentStatus) }}</StatusTag>
|
||||
<StatusTag :type="getDeliveryStatusType(row.deliveryStatus)">{{ getDeliveryStatusLabel(row.deliveryStatus) }}</StatusTag>
|
||||
</div>
|
||||
</template>
|
||||
<template #createdAt="{ value }">{{ new Date(value).toLocaleString() }}</template>
|
||||
@@ -57,7 +57,8 @@ import { reactive, ref } from "vue";
|
||||
import { useData } from "vike-vue/useData";
|
||||
import DataTable from "../../../components/DataTable.vue";
|
||||
import { formatCents } from "../../../lib/utils/money";
|
||||
import { getDeliveryStatusLabel, getOrderStatusLabel, getPaymentProviderLabel, getPaymentStatusLabel } from "../../../lib/utils/order-status";
|
||||
import { getDeliveryStatusLabel, getDeliveryStatusType, getOrderStatusLabel, getOrderStatusType, getPaymentProviderLabel, getPaymentStatusLabel, getPaymentStatusType } from "../../../lib/utils/order-status";
|
||||
import StatusTag from "../../../components/StatusTag.vue";
|
||||
import { onQueryOrders } from "./queryOrders.telefunc";
|
||||
import type { Data } from "./+data";
|
||||
|
||||
@@ -98,23 +99,7 @@ async function handleSearch() { await fetchPage(1); }
|
||||
/**
|
||||
* 获取订单状态对应的样式类,采用 badge-soft 提升可读性
|
||||
*/
|
||||
function orderStatusClass(s: string) {
|
||||
return "badge-soft " + ({ PENDING: "badge-warning", PAID: "badge-info", DELIVERED: "badge-success", CLOSED: "badge-ghost", FAILED: "badge-error" }[s] ?? "badge-outline");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支付状态对应的样式类
|
||||
*/
|
||||
function paymentStatusClass(s: string) {
|
||||
return "badge-soft " + ({ UNPAID: "badge-warning", PAID: "badge-success", FAILED: "badge-error" }[s] ?? "badge-outline");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取发货状态对应的样式类
|
||||
*/
|
||||
function deliveryStatusClass(s: string) {
|
||||
return "badge-soft " + ({ NOT_DELIVERED: "badge-warning", DELIVERED: "badge-success", FAILED: "badge-error" }[s] ?? "badge-outline");
|
||||
}
|
||||
|
||||
async function handleReset() {
|
||||
filter.orderNo = "";
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
<p class="text-sm text-base-content/70">订单号:{{ order.orderNo }}</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="badge badge-outline">{{ getOrderStatusLabel(order.status) }}</span>
|
||||
<span class="badge badge-outline">{{ getPaymentStatusLabel(order.paymentStatus) }}</span>
|
||||
<span class="badge badge-outline">{{ getDeliveryStatusLabel(order.deliveryStatus) }}</span>
|
||||
<StatusTag :type="getOrderStatusType(order.status)">{{ getOrderStatusLabel(order.status) }}</StatusTag>
|
||||
<StatusTag :type="getPaymentStatusType(order.paymentStatus)">{{ getPaymentStatusLabel(order.paymentStatus) }}</StatusTag>
|
||||
<StatusTag :type="getDeliveryStatusType(order.deliveryStatus)">{{ getDeliveryStatusLabel(order.deliveryStatus) }}</StatusTag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
@@ -77,11 +77,15 @@ import { useData } from "vike-vue/useData";
|
||||
import { formatCents } from "../../../../lib/utils/money";
|
||||
import {
|
||||
getDeliveryStatusLabel,
|
||||
getDeliveryStatusType,
|
||||
getOrderStatusLabel,
|
||||
getOrderStatusType,
|
||||
getPaymentProviderLabel,
|
||||
getPaymentStatusLabel,
|
||||
getPaymentStatusType,
|
||||
getVerifyStatusLabel,
|
||||
} from "../../../../lib/utils/order-status";
|
||||
import StatusTag from "../../../../components/StatusTag.vue";
|
||||
import { onCloseOrder } from "./closeOrder.telefunc";
|
||||
import { onRedeliver } from "./redeliver.telefunc";
|
||||
import type { Data } from "./+data";
|
||||
|
||||
@@ -36,9 +36,9 @@
|
||||
<td>{{ formatCents(product.price) }}</td>
|
||||
<td>{{ product.minBuy }} - {{ product.maxBuy }}</td>
|
||||
<td>
|
||||
<span class="badge" :class="product.status === 'ACTIVE' ? 'badge-success' : 'badge-ghost'">
|
||||
<StatusTag :type="product.status === 'ACTIVE' ? 'success' : 'default'">
|
||||
{{ product.status === 'ACTIVE' ? '上架' : '下架' }}
|
||||
</span>
|
||||
</StatusTag>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex gap-2">
|
||||
@@ -59,6 +59,7 @@ import { normalizeTelefuncError } from "../../../lib/app-error";
|
||||
import { ref } from "vue";
|
||||
import { useData } from "vike-vue/useData";
|
||||
import { formatCents } from "../../../lib/utils/money";
|
||||
import StatusTag from "../../../components/StatusTag.vue";
|
||||
import { onDeleteProduct } from "./deleteProduct.telefunc";
|
||||
import type { Data } from "./+data";
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
<h1 class="text-2xl font-bold">{{ order.orderNo }}</h1>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<span class="badge badge-outline">{{ getOrderStatusLabel(order.status) }}</span>
|
||||
<span class="badge badge-outline">{{ getPaymentStatusLabel(order.paymentStatus) }}</span>
|
||||
<span class="badge badge-outline">{{ getDeliveryStatusLabel(order.deliveryStatus) }}</span>
|
||||
<StatusTag :type="getOrderStatusType(order.status)">{{ getOrderStatusLabel(order.status) }}</StatusTag>
|
||||
<StatusTag :type="getPaymentStatusType(order.paymentStatus)">{{ getPaymentStatusLabel(order.paymentStatus) }}</StatusTag>
|
||||
<StatusTag :type="getDeliveryStatusType(order.deliveryStatus)">{{ getDeliveryStatusLabel(order.deliveryStatus) }}</StatusTag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,7 +54,8 @@ import { normalizeTelefuncError } from "../../../lib/app-error";
|
||||
import { ref, onMounted } from "vue";
|
||||
import { useData } from "vike-vue/useData";
|
||||
import { formatCents } from "../../../lib/utils/money";
|
||||
import { getDeliveryStatusLabel, getOrderStatusLabel, getPaymentProviderLabel, getPaymentStatusLabel } from "../../../lib/utils/order-status";
|
||||
import { getDeliveryStatusLabel, getDeliveryStatusType, getOrderStatusLabel, getOrderStatusType, getPaymentProviderLabel, getPaymentStatusLabel, getPaymentStatusType } from "../../../lib/utils/order-status";
|
||||
import StatusTag from "../../../components/StatusTag.vue";
|
||||
import { onCreatePayment } from "./createPayment.telefunc";
|
||||
import { onQueryAlipayPayment } from "./queryAlipayPayment.telefunc";
|
||||
import type { Data } from "./+data";
|
||||
|
||||
Reference in New Issue
Block a user