2026-05-26-admin-panel.md 9.7 KB

Sub-Plan D: Vue 3 后台管理

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development 前置依赖: Spring Boot 后端 /api/admin/** 接口就绪

目标: 运营后台,提供数据查询和系统配置管理

技术: Vite + Vue 3 + Element Plus + Axios


项目结构

admin/
├── index.html
├── vite.config.js
├── package.json
├── src/
│   ├── main.js
│   ├── App.vue
│   ├── router/index.js
│   ├── api/
│   │   ├── request.js         # axios 封装
│   │   ├── dashboard.js
│   │   ├── users.js
│   │   ├── charts.js
│   │   ├── orders.js
│   │   ├── commissions.js
│   │   └── config.js
│   ├── views/
│   │   ├── login/index.vue
│   │   ├── dashboard/index.vue
│   │   ├── users/index.vue
│   │   ├── charts/index.vue
│   │   ├── orders/index.vue
│   │   ├── commissions/index.vue
│   │   ├── withdraw/index.vue
│   │   └── settings/index.vue
│   └── components/
│       └── Sidebar.vue

Task 1: 项目初始化

  • Step 1: npm create vite@latest admin -- --template vue
  • Step 2: 安装 Element Plus: npm install element-plus axios vue-router@4 @element-plus/icons-vue
  • [ ] Step 3: 配置 vite.config.js

    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    
    export default defineConfig({
    plugins: [vue()],
    server: {
    port: 3000,
    proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } }
    }
    })
    
  • [ ] Step 4: 创建路由

    // router/index.js
    const routes = [
    { path: '/login', component: () => import('@/views/login/index.vue') },
    { path: '/', component: () => import('@/components/Sidebar.vue'), children: [
    { path: '', redirect: '/dashboard' },
    { path: 'dashboard', component: () => import('@/views/dashboard/index.vue') },
    { path: 'users', component: () => import('@/views/users/index.vue') },
    { path: 'charts', component: () => import('@/views/charts/index.vue') },
    { path: 'orders', component: () => import('@/views/orders/index.vue') },
    { path: 'commissions', component: () => import('@/views/commissions/index.vue') },
    { path: 'withdraw', component: () => import('@/views/withdraw/index.vue') },
    { path: 'settings', component: () => import('@/views/settings/index.vue') },
    ]}
    ]
    
  • [ ] Step 5: Commit


Task 2: API 层

  • [ ] Step 1: request.js

    import axios from 'axios'
    import { ElMessage } from 'element-plus'
    
    const request = axios.create({ baseURL: '/api/admin' })
    request.interceptors.request.use(config => {
    const token = localStorage.getItem('adminToken')
    if (token) config.headers.Authorization = 'Bearer ' + token
    return config
    })
    request.interceptors.response.use(
    res => { if (res.data.code === 0) return res.data.data; ElMessage.error(res.data.message); return Promise.reject(res.data) },
    err => { ElMessage.error('网络错误'); return Promise.reject(err) }
    )
    export default request
    
  • [ ] Step 2: API 模块

    // api/dashboard.js
    export const getStats = () => request.post('/dashboard/stats')
    
    // api/users.js
    export const listUsers = (data) => request.post('/users/list', data)
    export const userDetail = (id) => request.post('/users/detail', { id })
    
    // api/charts.js
    export const listCharts = (data) => request.post('/charts/list', data)
    
    // api/orders.js
    export const listOrders = (data) => request.post('/orders/list', data)
    export const refund = (id) => request.post('/orders/refund', { id })
    
    // api/commissions.js
    export const listCommissions = (data) => request.post('/commissions/list', data)
    
    // api/withdraw.js
    export const listWithdraw = (data) => request.post('/withdraw/list', data)
    export const approveWithdraw = (id) => request.post('/withdraw/approve', { id })
    
    // api/config.js
    export const getConfig = () => request.post('/config/get')
    export const updateConfig = (key, value) => request.post('/config/update', { key, value })
    
  • [ ] Step 3: Commit


Task 3: 登录页 + 仪表盘

  • [ ] Step 1: 登录页 — login/index.vue

    <template>
    <view class="login-page">
    <el-card class="login-card">
      <h2>数字能量学管理后台</h2>
      <el-form @submit.prevent="onLogin">
        <el-input v-model="username" placeholder="管理员账号" />
        <el-input v-model="password" type="password" placeholder="密码" />
        <el-button type="primary" @click="onLogin" :loading="loading">登录</el-button>
      </el-form>
    </el-card>
    </view>
    </template>
    
    <script setup>
    import { ref } from 'vue'
    import { useRouter } from 'vue-router'
    import request from '@/api/request'
    const router = useRouter()
    const username = ref(''), password = ref(''), loading = ref(false)
    async function onLogin() {
    loading.value = true
    try {
    const res = await request.post('/login', { username: username.value, password: password.value })
    localStorage.setItem('adminToken', res.token)
    router.push('/dashboard')
    } catch(e) { /* error shown by interceptor */ }
    loading.value = false
    }
    </script>
    
  • [ ] Step 2: 仪表盘 — dashboard/index.vue

    <template>
    <div>
    <h2>数据概览</h2>
    <el-row :gutter="20">
      <el-col :span="6" v-for="item in stats" :key="item.label">
        <el-card><div class="stat-value">{{ item.value }}</div><div>{{ item.label }}</div></el-card>
      </el-col>
    </el-row>
    </div>
    </template>
    
    <script setup>
    import { ref, onMounted } from 'vue'
    import { getStats } from '@/api/dashboard'
    const stats = ref([])
    onMounted(async () => { stats.value = Object.entries(await getStats()).map(([label, value]) => ({ label, value })) })
    </script>
    
  • [ ] Step 3: Commit


Task 4: 用户管理页

  • [ ] Step 1: users/index.vue — 表格 + 搜索

    <template>
    <div>
    <h2>用户管理</h2>
    <el-input v-model="keyword" placeholder="搜索昵称/ID" class="search-bar" />
    <el-button @click="search">搜索</el-button>
    <el-table :data="users" stripe>
      <el-table-column prop="id" label="ID" width="80" />
      <el-table-column prop="nickName" label="昵称" />
      <el-table-column prop="tier" label="等级" width="100">
        <template #default="{row}">{{ row.tier === 'vip' ? '能量师' : row.tier === 'admin' ? '管理员' : '普通用户' }}</template>
      </el-table-column>
      <el-table-column prop="vipExpireAt" label="到期时间" />
      <el-table-column prop="referralCode" label="推广码" />
      <el-table-column label="操作">
        <template #default="{row}"><el-button size="small" @click="showDetail(row.id)">详情</el-button></template>
      </el-table-column>
    </el-table>
    <el-pagination v-model:current="page" :total="total" @current-change="load" />
    </div>
    </template>
    
    <script setup>
    import { ref, onMounted } from 'vue'
    import { listUsers } from '@/api/users'
    const users = ref([]), total = ref(0), page = ref(1), keyword = ref('')
    async function load() { const res = await listUsers({ page: page.value, keyword: keyword.value }); users.value = res.list; total.value = res.total }
    onMounted(load)
    const search = () => { page.value = 1; load() }
    const showDetail = (id) => { /* 弹窗或跳转 */ }
    </script>
    
  • [ ] Step 2: Commit


Task 5: 订单 & 佣金 & 提现审核

  • Step 1: orders/index.vue — 订单列表

表格列:ID / 用户ID / 金额(元) / 商品 / 状态 / 支付时间 / 操作(退款)

  • Step 2: commissions/index.vue — 佣金列表

表格列:ID / 来源用户 / 获得用户 / 层级(一级/二级) / 金额 / 状态 / 时间

  • Step 3: withdraw/index.vue — 提现审核

表格 + 操作按钮:「审核通过」「拒绝」

// 审核通过
const approve = async (id) => {
  await approveWithdraw(id)
  ElMessage.success('已审核通过')
  load()
}
  • Step 4: Commit

Task 6: 系统配置页

  • [ ] Step 1: settings/index.vue

    <template>
    <div>
    <h2>系统配置</h2>
    <el-form label-width="180px">
      <el-form-item label="每日命盘限额(免费用户)">
        <el-input-number v-model="config.quota_chart" :min="1" :max="20" />
      </el-form-item>
      <el-form-item label="每日聊天限额(免费用户)">
        <el-input-number v-model="config.quota_chat" :min="1" :max="50" />
      </el-form-item>
      <el-form-item label="一级佣金比例">
        <el-slider v-model="config.commission_level1" :min="10" :max="40" :format-tooltip="v => v + '%'" />
      </el-form-item>
      <el-form-item label="二级佣金比例">
        <el-slider v-model="config.commission_level2" :min="5" :max="20" :format-tooltip="v => v + '%'" />
      </el-form-item>
      <el-form-item label="年费价格(元)">
        <el-input-number v-model="config.price_yearly" :min="99" :max="999" />
      </el-form-item>
      <el-button type="primary" @click="onSave">保存配置</el-button>
    </el-form>
    </div>
    </template>
    
    <script setup>
    import { ref, onMounted } from 'vue'
    import { getConfig, updateConfig } from '@/api/config'
    import { ElMessage } from 'element-plus'
    
    const config = ref({ quota_chart: 3, quota_chat: 5, commission_level1: 30, commission_level2: 10, price_yearly: 398 })
    
    onMounted(async () => {
    const data = await getConfig()
    Object.keys(config.value).forEach(k => { if (data[k] !== undefined) config.value[k] = Number(data[k]) })
    })
    
    async function onSave() {
    for (const [key, value] of Object.entries(config.value)) {
    await updateConfig(key, String(value))
    }
    ElMessage.success('配置已保存')
    }
    </script>
    
  • [ ] Step 2: Commit