|
|
@@ -0,0 +1,218 @@
|
|
|
+<template>
|
|
|
+ <div class="purchase-mgmt admin-page">
|
|
|
+ <el-card>
|
|
|
+ <div slot="header" class="admin-page-header">
|
|
|
+ <span class="admin-page-title">健康启航计划管理</span>
|
|
|
+ <el-button type="primary" size="mini" @click="loadList" :loading="loading">刷新</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 统计卡片 -->
|
|
|
+ <el-row :gutter="16" style="margin-bottom: 16px;">
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card shadow="hover" class="stat-card">
|
|
|
+ <div class="stat-value">{{ stats.totalOrders }}</div>
|
|
|
+ <div class="stat-label">总订单数</div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card shadow="hover" class="stat-card">
|
|
|
+ <div class="stat-value">{{ stats.paidOrders }}</div>
|
|
|
+ <div class="stat-label">已支付</div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card shadow="hover" class="stat-card">
|
|
|
+ <div class="stat-value">{{ stats.activeUsers }}</div>
|
|
|
+ <div class="stat-label">活跃体验用户</div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card shadow="hover" class="stat-card">
|
|
|
+ <div class="stat-value">{{ stats.revenue }}</div>
|
|
|
+ <div class="stat-label">总收入(元)</div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- 筛选 -->
|
|
|
+ <el-form :inline="true" size="small" style="margin-bottom: 16px;">
|
|
|
+ <el-form-item label="状态">
|
|
|
+ <el-select v-model="filter.status" placeholder="全部" clearable style="width: 120px;">
|
|
|
+ <el-option label="待支付" value="pending" />
|
|
|
+ <el-option label="已支付" value="paid" />
|
|
|
+ <el-option label="已取消" value="cancelled" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="搜索">
|
|
|
+ <el-input v-model="filter.keyword" placeholder="用户ID/订单号" clearable style="width: 200px;" @keyup.enter.native="loadList" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" size="small" @click="loadList">查询</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <!-- 列表 -->
|
|
|
+ <el-table :data="list" v-loading="loading" border stripe style="width: 100%;">
|
|
|
+ <el-table-column prop="orderNo" label="订单号" width="180" />
|
|
|
+ <el-table-column prop="userId" label="用户ID" width="100" />
|
|
|
+ <el-table-column prop="familyId" label="家庭ID" width="100" />
|
|
|
+ <el-table-column prop="amount" label="金额(分)" width="100">
|
|
|
+ <template slot-scope="{ row }">{{ row.amount / 100 }}元</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="status" label="状态" width="100">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-tag :type="statusType(row.status)" size="small">{{ statusText(row.status) }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="startDate" label="开始时间" width="160">
|
|
|
+ <template slot-scope="{ row }">{{ formatDateTime(row.startDate) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="endDate" label="结束时间" width="160">
|
|
|
+ <template slot-scope="{ row }">{{ formatDateTime(row.endDate) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="isEarlyRenewal" label="早期续费" width="90">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-tag :type="row.isEarlyRenewal ? 'success' : 'info'" size="small">{{ row.isEarlyRenewal ? '是' : '否' }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="createdAt" label="创建时间" width="160">
|
|
|
+ <template slot-scope="{ row }">{{ formatDateTime(row.createdAt) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="120" fixed="right">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-button type="text" size="small" @click="viewDetail(row)">详情</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <!-- 分页 -->
|
|
|
+ <el-pagination
|
|
|
+ style="margin-top: 16px; text-align: right;"
|
|
|
+ layout="total, prev, pager, next"
|
|
|
+ :total="total"
|
|
|
+ :current-page="page"
|
|
|
+ :page-size="size"
|
|
|
+ @current-change="handlePageChange"
|
|
|
+ />
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 详情弹窗 -->
|
|
|
+ <el-dialog title="订单详情" :visible.sync="dialogVisible" width="600px">
|
|
|
+ <el-descriptions :column="2" border size="small" v-if="currentOrder">
|
|
|
+ <el-descriptions-item label="订单号">{{ currentOrder.orderNo }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="用户ID">{{ currentOrder.userId }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="家庭ID">{{ currentOrder.familyId }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="金额">{{ currentOrder.amount / 100 }}元</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="状态">
|
|
|
+ <el-tag :type="statusType(currentOrder.status)" size="small">{{ statusText(currentOrder.status) }}</el-tag>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="支付方式">{{ currentOrder.paymentMethod || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="开始时间">{{ formatDateTime(currentOrder.startDate) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="结束时间">{{ formatDateTime(currentOrder.endDate) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="是否早期续费">{{ currentOrder.isEarlyRenewal ? '是' : '否' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="已发放CF值">{{ currentOrder.cfRewardGiven || 0 }} CF</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="创建时间">{{ formatDateTime(currentOrder.createdAt) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="支付时间">{{ formatDateTime(currentOrder.paidAt) }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import request from '@/utils/request'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'PurchaseManagement',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ loading: false,
|
|
|
+ list: [],
|
|
|
+ total: 0,
|
|
|
+ page: 1,
|
|
|
+ size: 20,
|
|
|
+ filter: { status: '', keyword: '' },
|
|
|
+ dialogVisible: false,
|
|
|
+ currentOrder: null,
|
|
|
+ stats: {
|
|
|
+ totalOrders: 0,
|
|
|
+ paidOrders: 0,
|
|
|
+ activeUsers: 0,
|
|
|
+ revenue: '0.00'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ this.loadList()
|
|
|
+ this.loadStats()
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ async loadList() {
|
|
|
+ this.loading = true
|
|
|
+ try {
|
|
|
+ const res = await request({
|
|
|
+ url: '/api/admin/purchase/list',
|
|
|
+ method: 'post',
|
|
|
+ data: { page: this.page, size: this.size, ...this.filter }
|
|
|
+ })
|
|
|
+ if (res.data && res.data.code === 200) {
|
|
|
+ this.list = res.data.data.records || []
|
|
|
+ this.total = res.data.data.total || 0
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载列表失败', e)
|
|
|
+ } finally {
|
|
|
+ this.loading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async loadStats() {
|
|
|
+ try {
|
|
|
+ const res = await request({
|
|
|
+ url: '/api/admin/purchase/stats',
|
|
|
+ method: 'post'
|
|
|
+ })
|
|
|
+ if (res.data && res.data.code === 200) {
|
|
|
+ this.stats = res.data.data
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载统计失败', e)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ viewDetail(row) {
|
|
|
+ this.currentOrder = row
|
|
|
+ this.dialogVisible = true
|
|
|
+ },
|
|
|
+ handlePageChange(page) {
|
|
|
+ this.page = page
|
|
|
+ this.loadList()
|
|
|
+ },
|
|
|
+ statusType(status) {
|
|
|
+ const map = { pending: 'warning', paid: 'success', cancelled: 'info' }
|
|
|
+ return map[status] || ''
|
|
|
+ },
|
|
|
+ statusText(status) {
|
|
|
+ const map = { pending: '待支付', paid: '已支付', cancelled: '已取消' }
|
|
|
+ return map[status] || status
|
|
|
+ },
|
|
|
+ formatDateTime(dateStr) {
|
|
|
+ if (!dateStr) return '-'
|
|
|
+ const d = new Date(dateStr)
|
|
|
+ if (isNaN(d)) return dateStr.toString().substring(0, 19)
|
|
|
+ return d.getFullYear() + '-' +
|
|
|
+ String(d.getMonth() + 1).padStart(2, '0') + '-' +
|
|
|
+ String(d.getDate()).padStart(2, '0') + ' ' +
|
|
|
+ String(d.getHours()).padStart(2, '0') + ':' +
|
|
|
+ String(d.getMinutes()).padStart(2, '0') + ':' +
|
|
|
+ String(d.getSeconds()).padStart(2, '0')
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.admin-page { padding: 16px; }
|
|
|
+.admin-page-header { display: flex; justify-content: space-between; align-items: center; }
|
|
|
+.admin-page-title { font-size: 16px; font-weight: bold; }
|
|
|
+.stat-card { text-align: center; }
|
|
|
+.stat-value { font-size: 28rpx; font-weight: bold; color: #F97316; }
|
|
|
+.stat-label { font-size: 12px; color: #999; margin-top: 4px; }
|
|
|
+</style>
|