新增摸大红业务并修复后台链接按钮白字

支持分类筛选、搜索、下单支付建群与固定二维码;合并迁移种子数据;后台表格编辑/详情链接恢复主题色可见。
This commit is contained in:
yml2213
2026-07-16 18:08:18 +08:00
parent 97a34e0329
commit 6d61027318
49 changed files with 9533 additions and 20 deletions
@@ -0,0 +1,244 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Refresh } from '@element-plus/icons-vue'
import {
cancelAdminMohongOrder,
completeAdminMohongOrder,
fetchAdminMohongOrders,
mohongOrderStatusLabel,
type MohongOrder,
} from '@/features/mohong/api/mohong'
import { formatCent } from '@/shared/utils/money'
import { formatDateTime } from '@/shared/utils/time'
import { readError } from '@/shared/utils/error'
import AdminTablePagination from '../components/AdminTablePagination.vue'
const loading = ref(false)
const items = ref<MohongOrder[]>([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(20)
const keyword = ref('')
const status = ref('')
const detail = ref<MohongOrder | null>(null)
onMounted(load)
async function load() {
loading.value = true
try {
const result = await fetchAdminMohongOrders({
keyword: keyword.value || undefined,
status: status.value || undefined,
page: page.value,
page_size: pageSize.value,
})
items.value = result.items || []
total.value = result.total || 0
} catch (error) {
ElMessage.error(readError(error, '加载失败'))
} finally {
loading.value = false
}
}
async function handleComplete(row: MohongOrder) {
try {
await ElMessageBox.confirm(`确认将订单 ${row.order_no} 标记为已完成?`, '提示', {
type: 'warning',
})
await completeAdminMohongOrder(row.id)
ElMessage.success('已完成')
await load()
} catch (error) {
if (error !== 'cancel') ElMessage.error(readError(error, '操作失败'))
}
}
async function handleCancel(row: MohongOrder) {
try {
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消订单', {
inputPlaceholder: '可选',
confirmButtonText: '确认取消',
cancelButtonText: '返回',
})
await cancelAdminMohongOrder(row.id, value || '后台取消')
ElMessage.success('已取消')
await load()
} catch (error) {
if (error !== 'cancel') ElMessage.error(readError(error, '操作失败'))
}
}
async function copyText(text: string) {
try {
await navigator.clipboard.writeText(text)
ElMessage.success('已复制')
} catch {
ElMessage.error('复制失败')
}
}
</script>
<template>
<div class="admin-page">
<div class="page-head">
<div>
<h2>摸大红订单</h2>
<p>查看订单完成履约复制用户订单信息</p>
</div>
<el-button :icon="Refresh" @click="load">刷新</el-button>
</div>
<div class="filters">
<el-input v-model="keyword" clearable placeholder="订单号" style="width: 220px" @keyup.enter="load" />
<el-select v-model="status" clearable placeholder="状态" style="width: 140px">
<el-option label="待支付" value="pending_payment" />
<el-option label="已支付" value="paid" />
<el-option label="已完成" value="completed" />
<el-option label="已取消" value="cancelled" />
</el-select>
<el-button type="primary" @click=";(page = 1), load()">查询</el-button>
</div>
<el-table v-loading="loading" :data="items" border stripe>
<el-table-column prop="order_no" label="订单号" min-width="170" />
<el-table-column prop="product_title" label="商品" min-width="140" />
<el-table-column label="数量" width="70" prop="quantity" />
<el-table-column label="金额" width="100">
<template #default="{ row }">¥{{ row.amount || formatCent(row.amount_cent) }}</template>
</el-table-column>
<el-table-column label="买家" min-width="140">
<template #default="{ row }">
{{ row.buyer_nickname || '-' }}
<div class="sub">{{ row.buyer_phone || '' }}</div>
</template>
</el-table-column>
<el-table-column label="状态" width="100">
<template #default="{ row }">{{ mohongOrderStatusLabel(row.status) }}</template>
</el-table-column>
<el-table-column label="下单时间" min-width="160">
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
</el-table-column>
<el-table-column label="操作" width="220" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="detail = row">详情</el-button>
<el-button
v-if="row.status === 'paid'"
link
type="success"
@click="handleComplete(row)"
>
完成
</el-button>
<el-button
v-if="row.status === 'pending_payment' || row.status === 'paid'"
link
type="danger"
@click="handleCancel(row)"
>
取消
</el-button>
</template>
</el-table-column>
</el-table>
<AdminTablePagination
:current-page="page"
:page-size="pageSize"
:total="total"
@update:current-page="page = $event"
@update:page-size="pageSize = $event"
@page-change="load"
/>
<el-drawer
:model-value="Boolean(detail)"
size="420px"
title="订单详情"
destroy-on-close
@update:model-value="(v: boolean) => !v && (detail = null)"
>
<template v-if="detail">
<el-descriptions :column="1" border>
<el-descriptions-item label="订单号">{{ detail.order_no }}</el-descriptions-item>
<el-descriptions-item label="状态">
{{ mohongOrderStatusLabel(detail.status) }}
</el-descriptions-item>
<el-descriptions-item label="商品">{{ detail.product_title }}</el-descriptions-item>
<el-descriptions-item label="数量">{{ detail.quantity }}</el-descriptions-item>
<el-descriptions-item label="金额">
¥{{ detail.amount || formatCent(detail.amount_cent) }}
</el-descriptions-item>
<el-descriptions-item label="买家">
{{ detail.buyer_nickname }} {{ detail.buyer_phone }}
</el-descriptions-item>
<el-descriptions-item label="会话ID">
{{ detail.conversation_id || '-' }}
</el-descriptions-item>
</el-descriptions>
<div v-if="detail.copy_text" class="copy-box">
<div class="copy-head">
<strong>复制文案</strong>
<el-button size="small" @click="copyText(detail.copy_text)">复制</el-button>
</div>
<pre>{{ detail.copy_text }}</pre>
</div>
<div v-if="detail.qrcode_url_snapshot" class="qr-box">
<strong>二维码快照</strong>
<el-image :src="detail.qrcode_url_snapshot" fit="contain" style="width: 180px; height: 180px" />
</div>
</template>
</el-drawer>
</div>
</template>
<style scoped>
.admin-page {
display: grid;
gap: 16px;
}
.page-head {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.page-head h2 {
margin: 0 0 4px;
}
.page-head p,
.sub {
margin: 0;
color: #6b7a90;
font-size: 12px;
}
.filters {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.copy-box,
.qr-box {
margin-top: 16px;
}
.copy-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.copy-box pre {
margin: 0;
padding: 12px;
background: #f7f9fc;
border-radius: 8px;
white-space: pre-wrap;
font-size: 13px;
line-height: 1.6;
}
.qr-box {
display: grid;
gap: 8px;
}
</style>