-
暂无使用数据
+
暂无使用数据
-
{{ i + 1 }}
-
{{ it.name || it.tool_key }}
-
+
{{ i + 1 }}
+
{{ it.name || it.tool_key }}
+
-
{{ it.count }}
+
{{ it.count }}
diff --git a/src/components/TrendChart.vue b/src/components/TrendChart.vue
index 27da0e1..cc78c62 100644
--- a/src/components/TrendChart.vue
+++ b/src/components/TrendChart.vue
@@ -41,25 +41,31 @@ export default {
- 暂无趋势数据
+ 暂无趋势数据
+
+
diff --git a/src/layouts/AdminLayout.vue b/src/layouts/AdminLayout.vue
index 8e81b3e..763aeb9 100644
--- a/src/layouts/AdminLayout.vue
+++ b/src/layouts/AdminLayout.vue
@@ -1,56 +1,76 @@
-
+
全部
@@ -86,7 +110,15 @@ function onTableChange(pag) {
- {{ record.content }}
+
+ {{ record.content }}
+
+
+
+
+ {{ record.reply }}
+
+ —
@@ -94,14 +126,45 @@ function onTableChange(pag) {
-
- 标记已处理
+
+ {{ record.reply ? '查看/回复' : '处理/回复' }}
- —
+
+
+
+ {{ handleTarget?.content }}
+ {{ handleTarget?.contact || '未留' }}
+ {{ handleTarget?.createdAt }}
+
+
+
+ 官方回复(选填,用户端可见并红点提醒)
+
+
+
+
+ 仅提交回复不填也视为「已处理」;再次回复会重新提醒用户(红点重置)。
+
+
+
+
diff --git a/src/views/orders/OrderList.vue b/src/views/orders/OrderList.vue
index 7f0d23d..0310b24 100644
--- a/src/views/orders/OrderList.vue
+++ b/src/views/orders/OrderList.vue
@@ -13,7 +13,10 @@ import AppModal from '@/components/AppModal.vue'
// 订单管理 + 退款
// 契约:GET /order/list(snake_case 响应)、POST /order/refund(camelCase 请求)。
// 状态:0待支付 1已发货 2已退款 3已关闭;类型:1会员套餐 2次数包。
-// 退款为不可逆资金动作:行内按钮 → 二次确认弹窗(可勾选「同时取消订单购买的服务」)。
+// 退款状态机(refund_status):0未退款 1退款中 2退款成功 3退款失败。
+// - wxpay 渠道:确认退款 → 平台退款(refund_status=1 退款中)→ 平台推送/轮询确认
+// → 成功(status=2,按发起时勾选回收权益)或失败(refund_status=3,可重试)。
+// - apple 渠道(iOS):开发者无法主动退款,按钮变为「手工退款登记」(必填原因,当场落终态)。
// ============================================================================
const userStore = useUserStore()
@@ -42,22 +45,35 @@ const STATUS_META = {
2: { label: '已退款', color: 'red' },
3: { label: '已关闭', color: 'default' },
}
+const REFUND_STATUS_META = {
+ 0: { label: '未退款', color: 'default' },
+ 1: { label: '退款中', color: 'processing' },
+ 2: { label: '退款成功', color: 'red' },
+ 3: { label: '退款失败', color: 'volcano' },
+}
const TYPE_META = { 1: '会员套餐', 2: '次数包' }
const PROMO_META = { 0: '无', 1: '优惠码', 2: '免单券' }
+const CHANNEL_META = {
+ wxpay: { label: '微信支付', color: 'green' },
+ apple: { label: 'iOS·App Store', color: 'purple' },
+}
const statusMeta = (v) => STATUS_META[v] || { label: String(v), color: 'default' }
+const refundStatusMeta = (v) => REFUND_STATUS_META[v] || { label: String(v), color: 'default' }
const typeLabel = (v) => TYPE_META[v] || '-'
const promoLabel = (v) => PROMO_META[v] || '-'
+const channelMeta = (v) => CHANNEL_META[v] || null
const money = (cents) => `¥${((Number(cents) || 0) / 100).toFixed(2)}`
const columns = [
{ title: '订单号', dataIndex: 'out_trade_no', key: 'out_trade_no', width: 190, ellipsis: true },
- { title: '用户', key: 'user', width: 150, ellipsis: true },
+ { title: '用户', key: 'user', width: 140, ellipsis: true },
{ title: '商品', key: 'title', ellipsis: true },
- { title: '类型', key: 'order_type', width: 100 },
+ { title: '类型', key: 'order_type', width: 92 },
{ title: '实付', key: 'paid_price_cents', width: 100 },
- { title: '状态', key: 'status', width: 100 },
+ { title: '渠道', key: 'pay_channel', width: 96 },
+ { title: '状态', key: 'status', width: 118 },
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at', width: 170 },
- { title: '操作', key: 'action', width: 90, align: 'right' },
+ { title: '操作', key: 'action', width: 108, align: 'right' },
]
async function load() {
@@ -102,33 +118,55 @@ function onTableChange(pag) {
const isEmpty = computed(() => !loading.value && !loadError.value && !list.value.length)
const hasFilter = computed(() => !!query.keyword || query.status !== undefined)
-// ===== 退款(二次确认 + 可选回收服务)=====
+// ===== 退款(二次确认 + 可选回收服务 + 原因 + 手工登记)=====
const refundOpen = ref(false)
const refundSaving = ref(false)
const refundTarget = ref(null)
const revokeService = ref(false)
+const refundReason = ref('')
+const refundManual = ref(false)
+
+// apple 渠道订单:开发者无法主动退款,只能登记(用户向 App Store 申请)
+const isAppleOrder = computed(() => refundTarget.value?.pay_channel === 'apple')
+
+// 行内按钮文案:apple → 手工退款;退款失败(3) → 重试
+function refundActionLabel(record) {
+ if (record.refund_status === 3) return '重试退款'
+ return record.pay_channel === 'apple' ? '手工退款' : '退款'
+}
function openRefund(record) {
- if (record.status !== 1) {
- message.warning('仅「已开通」的订单可退款')
+ // 允许发起:已开通(1) 且(未退款 / 退款失败可重试)
+ if (record.status !== 1 || (record.refund_status !== 0 && record.refund_status !== 3)) {
+ message.warning('仅「已开通」且未在退款流程中的订单可退款')
return
}
refundTarget.value = record
revokeService.value = false // 默认不勾选:默认保留用户已购服务
+ refundReason.value = ''
+ refundManual.value = record.pay_channel === 'apple'
refundOpen.value = true
}
async function confirmRefund() {
if (!refundTarget.value) return
+ // 手工登记(显式勾选或 apple 渠道)必填原因
+ const needReason = refundManual.value || isAppleOrder.value
+ if (needReason && !refundReason.value.trim()) {
+ message.warning('手工退款登记必须填写退款原因')
+ return
+ }
refundSaving.value = true
try {
const res = await orderRefundApi({
outTradeNo: refundTarget.value.out_trade_no,
revokeService: revokeService.value,
+ reason: refundReason.value.trim() || undefined,
+ manual: refundManual.value || undefined,
})
// 拦截器对业务错误(非 0 code)已弹提示并返回 undefined;成功返回 result 对象
if (res) {
- message.success(res.message || '退款成功')
+ message.success(res.message || '退款已受理')
refundOpen.value = false
load()
}
@@ -184,6 +222,12 @@ async function confirmRefund() {
{{ money(record.paid_price_cents) }}
{{ money(record.origin_price_cents) }}
+
+
+ {{ channelMeta(record.pay_channel).label }}
+
+ -
+
{{ record.status_text || statusMeta(record.status).label }}
@@ -191,11 +235,19 @@ async function confirmRefund() {
{{ record.service_revoked ? '已回收' : '未回收' }}
+
+
+
+ {{ refundStatusMeta(record.refund_status).label }}
+
+
-
- 退款
+
+ {{ refundActionLabel(record) }}
-
@@ -208,6 +260,8 @@ async function confirmRefund() {
ok-text="确认退款" ok-type="danger" @ok="confirmRefund">
+
{{ refundTarget?.out_trade_no }}
{{ refundTarget?.title || '-' }}
@@ -215,13 +269,30 @@ async function confirmRefund() {
{{ refundTarget?.nickname || refundTarget?.username || ('用户#' + refundTarget?.user_id) }}
{{ money(refundTarget?.paid_price_cents) }}
+
+ {{ isAppleOrder ? 'iOS · App Store' : '微信虚拟支付' }}
+
同时取消订单购买的服务
勾选后将回收用户已获权益:会员套餐将回退对应有效期,次数包将扣回对应付费次数;
- 不勾选则仅将订单置为「已退款」,用户权益保留。
+ 不勾选则仅退款,用户权益保留。微信支付订单在平台确认退款成功后自动执行回收。
+
+
+ 手工退款登记(已在微信支付商户平台 / App Store 后台完成退款,此处仅记录)
+
+
+
+ 退款原因{{ (refundManual || isAppleOrder) ? '(必填)' : '(选填)' }}
+
+
+
+
+ 提交后将调用微信退款接口(支付 365 天内可退),订单进入「退款中」;
+ 平台确认成功后自动置为「已退款」并按上方勾选回收权益,失败则标记可重试。
@@ -262,4 +333,8 @@ async function confirmRefund() {
font-size: var(--font-xs);
line-height: 1.5;
}
+.refund-reason-label {
+ font-size: var(--font-sm);
+ color: var(--text-2);
+}