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
npm create vite@latest admin -- --template vuenpm 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
[ ] 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
[ ] 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
[ ] 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
表格列:ID / 用户ID / 金额(元) / 商品 / 状态 / 支付时间 / 操作(退款)
表格列:ID / 来源用户 / 获得用户 / 层级(一级/二级) / 金额 / 状态 / 时间
表格 + 操作按钮:「审核通过」「拒绝」
// 审核通过
const approve = async (id) => {
await approveWithdraw(id)
ElMessage.success('已审核通过')
load()
}
[ ] 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