diff --git a/index.html b/index.html index 8388c4b..3724b92 100644 --- a/index.html +++ b/index.html @@ -1,10 +1,20 @@ - + - Vite + Vue + 工具百宝箱 · 管理后台 +
diff --git a/src/App.vue b/src/App.vue index 72d9eab..bab4df2 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,21 +1,27 @@ diff --git a/src/api/request/feedback.js b/src/api/request/feedback.js index 81da3fd..2ee6295 100644 --- a/src/api/request/feedback.js +++ b/src/api/request/feedback.js @@ -4,6 +4,6 @@ export function feedbackListApi(params) { return get('feedback/list', params) } -export function feedbackHandleApi(id) { - return post('feedback/handle', { id }) +export function feedbackHandleApi(id, reply) { + return post('feedback/handle', { id, reply: reply || undefined }) } diff --git a/src/components/BarRank.vue b/src/components/BarRank.vue index 41a9bf5..9664113 100644 --- a/src/components/BarRank.vue +++ b/src/components/BarRank.vue @@ -18,17 +18,17 @@ function barColor(i) { 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 @@ + + 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) }} + @@ -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 后台完成退款,此处仅记录) + + + + + +
+ 提交后将调用微信退款接口(支付 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); +}