34 KiB
34 KiB
OpenClaw Skills 前后端联调详细修改文档
📋 文档概述
本文档详细说明如何将前端从 localStorage 模式修改为与后端 API 联调模式。包含所有需要修改的文件、具体代码变更、数据结构映射关系和测试方案。
文档版本: v2.0
创建日期: 2026-03-17
目标: 完成前后端完全联调
📊 前后端架构对比
当前前端架构
前端
├── localStorage (数据存储)
├── mockData.js (数据初始化)
├── localService.js (业务逻辑)
└── stores (状态管理)
目标架构
前端 后端
├── apiService.js ←→ ├── RESTful API
├── stores ←→ ├── JWT 认证
└── 响应式渲染 ←→ └── MySQL + Redis
🔧 第一阶段:基础设施配置
1.1 安装 axios 依赖
修改文件: frontend/package.json
修改内容:
{
"dependencies": {
"vue": "^3.4.21",
"vue-router": "^4.3.0",
"pinia": "^2.1.7",
"element-plus": "^2.6.1",
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.6.8"
}
}
执行命令:
cd frontend
npm install
1.2 创建 API 服务层
新建文件: frontend/src/service/apiService.js
文件内容:
import axios from 'axios'
import { ElMessage } from 'element-plus'
import router from '@/router'
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api/v1',
timeout: 30000,
headers: {
'Content-Type': 'application/json'
}
})
apiClient.interceptors.request.use(
config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
error => Promise.reject(error)
)
apiClient.interceptors.response.use(
response => {
const res = response.data
if (res.code === 200) {
return {
success: true,
data: res.data,
message: res.message || '操作成功'
}
} else {
ElMessage.error(res.message || '操作失败')
return {
success: false,
message: res.message || '操作失败',
code: res.code
}
}
},
error => {
if (error.response) {
const { status, data } = error.response
if (status === 401) {
ElMessage.error('登录已过期,请重新登录')
localStorage.removeItem('token')
localStorage.removeItem('user')
router.push('/login')
} else if (status === 403) {
ElMessage.error('没有权限访问')
} else if (status === 500) {
ElMessage.error('服务器错误')
} else if (data?.message) {
ElMessage.error(data.message)
} else {
ElMessage.error('网络错误')
}
} else {
ElMessage.error('网络连接失败')
}
return {
success: false,
message: error.message || '网络错误'
}
}
)
export const userService = {
async sendSmsCode(phone) {
return await apiClient.post('/users/sms-code', { phone })
},
async register(phone, password, smsCode, inviteCode = null) {
const data = { phone, password, smsCode }
if (inviteCode) data.inviteCode = inviteCode
const result = await apiClient.post('/users/register', data)
if (result.success && result.data?.token) {
localStorage.setItem('token', result.data.token)
if (result.data?.user) {
localStorage.setItem('user', JSON.stringify(result.data.user))
}
}
return result
},
async login(phone, password) {
const result = await apiClient.post('/users/login', { phone, password })
if (result.success && result.data?.token) {
localStorage.setItem('token', result.data.token)
if (result.data?.user) {
localStorage.setItem('user', JSON.stringify(result.data.user))
}
}
return result
},
async getProfile() {
return await apiClient.get('/users/profile')
},
async updateProfile(data) {
return await apiClient.put('/users/profile', data)
},
async updatePassword(oldPassword, newPassword) {
return await apiClient.put('/users/password', { oldPassword, newPassword })
},
async logout() {
const result = await apiClient.post('/users/logout')
localStorage.removeItem('token')
localStorage.removeItem('user')
return result
},
async dailySign() {
return await apiClient.post('/points/sign-in')
},
async joinGroup() {
return { success: true, message: '加入成功' }
},
async getAllUsers() {
return await apiClient.get('/admin/users')
},
async banUser(userId) {
return await apiClient.put(`/admin/users/${userId}/ban`)
},
async unbanUser(userId) {
return await apiClient.put(`/admin/users/${userId}/unban`)
}
}
export const skillService = {
async getSkills(params = {}) {
const { pageNum = 1, pageSize = 20, categoryId, keyword, sort = 'newest' } = params
const queryParams = new URLSearchParams()
queryParams.append('pageNum', pageNum)
queryParams.append('pageSize', pageSize)
if (categoryId) queryParams.append('categoryId', categoryId)
if (keyword) queryParams.append('keyword', keyword)
if (sort) queryParams.append('sort', sort)
return await apiClient.get(`/skills?${queryParams.toString()}`)
},
async getSkillById(skillId) {
return await apiClient.get(`/skills/${skillId}`)
},
async getCategories() {
return {
success: true,
data: [
{ id: 1, name: '办公自动化' },
{ id: 2, name: '数据分析' },
{ id: 3, name: '网络爬虫' },
{ id: 4, name: 'AI 工具' },
{ id: 5, name: '图像处理' }
]
}
},
async searchSkills(keyword, filters = {}) {
const params = { keyword, ...filters }
return await this.getSkills(params)
},
async uploadSkill(data) {
return await apiClient.post('/skills', data)
},
async updateSkill(skillId, data) {
return await apiClient.put(`/skills/${skillId}`, data)
},
async deleteSkill(skillId) {
return await apiClient.delete(`/skills/${skillId}`)
},
async approveSkill(skillId) {
return await apiClient.post(`/admin/skills/${skillId}/approve`)
},
async rejectSkill(skillId, reason) {
return await apiClient.post(`/admin/skills/${skillId}/reject`, { reason })
},
async setFeatured(skillId, featured) {
return await apiClient.put(`/admin/skills/${skillId}/featured`, { featured })
},
async setHot(skillId, hot) {
return await apiClient.put(`/admin/skills/${skillId}/hot`, { hot })
},
async getComments(skillId) {
return await apiClient.get(`/skills/${skillId}/reviews`)
},
async addComment(skillId, rating, content, images = []) {
return await apiClient.post(`/skills/${skillId}/reviews`, { rating, content, images })
},
async likeComment(commentId) {
return await apiClient.post(`/comments/${commentId}/like`)
},
async deleteComment(commentId) {
return await apiClient.delete(`/comments/${commentId}`)
},
async getAllComments() {
return await apiClient.get('/admin/comments')
},
async getAllSkills() {
return await this.getSkills({ pageNum: 1, pageSize: 1000 })
}
}
export const orderService = {
async createOrder(skillIds, pointsToUse = 0, paymentMethod = 'wechat') {
return await apiClient.post('/orders', { skillIds, pointsToUse, paymentMethod })
},
async getOrders(params = {}) {
const { pageNum = 1, pageSize = 20 } = params
return await apiClient.get(`/orders?pageNum=${pageNum}&pageSize=${pageSize}`)
},
async getOrderById(orderId) {
return await apiClient.get(`/orders/${orderId}`)
},
async payOrder(orderId, paymentNo) {
return await apiClient.post(`/orders/${orderId}/pay?paymentNo=${paymentNo}`)
},
async cancelOrder(orderId, reason = '') {
return await apiClient.post(`/orders/${orderId}/cancel?reason=${reason}`)
},
async applyRefund(orderId, reason, images = []) {
return await apiClient.post(`/orders/${orderId}/refund`, { reason, images })
},
async getUserPurchasedSkills(userId) {
const result = await this.getOrders()
if (result.success && result.data?.records) {
const purchasedSkills = result.data.records
.filter(o => o.status === 'completed')
.map(o => ({
...o.items?.[0] || {},
purchasedAt: o.completedAt,
orderId: o.id
}))
return { success: true, data: purchasedSkills }
}
return result
},
async getAllOrders() {
return await apiClient.get('/admin/orders')
}
}
export const pointService = {
async getBalance() {
return await apiClient.get('/points/balance')
},
async getPointRecords(params = {}) {
const { pageNum = 1, pageSize = 20, type, source } = params
let url = `/points/records?pageNum=${pageNum}&pageSize=${pageSize}`
if (type) url += `&type=${type}`
if (source) url += `&source=${source}`
return await apiClient.get(url)
},
async recharge(amount, paymentMethod = 'wechat') {
return await apiClient.post('/payments/recharge', { amount, paymentMethod })
},
async getRechargeTiers() {
return {
success: true,
data: [
{ amount: 10, bonusPoints: 10 },
{ amount: 50, bonusPoints: 60 },
{ amount: 100, bonusPoints: 150 },
{ amount: 500, bonusPoints: 800 },
{ amount: 1000, bonusPoints: 2000 }
]
}
},
async getPointRules() {
return {
success: true,
data: {
register: 100,
invite: 50,
signin: 5,
review: 10,
reviewWithImage: 20,
joinGroup: 20
}
}
},
async getPaymentRecords(params = {}) {
const { pageNum = 1, pageSize = 20 } = params
return await apiClient.get(`/payments/records?pageNum=${pageNum}&pageSize=${pageSize}`)
},
async getAllPointRecords() {
return await apiClient.get('/admin/points')
}
}
export const inviteService = {
async getMyInviteCode() {
return await apiClient.get('/invites/my-code')
},
async bindInviteCode(inviteCode) {
return await apiClient.post('/invites/bind', { inviteCode })
},
async getInviteRecords(params = {}) {
const { pageNum = 1, pageSize = 20 } = params
return await apiClient.get(`/invites/records?pageNum=${pageNum}&pageSize=${pageSize}`)
},
async getInviteStats() {
return await apiClient.get('/invites/stats')
}
}
export const notificationService = {
async getUserNotifications(userId) {
return []
},
async markAsRead(notificationId) {
return { success: true }
},
async markAllAsRead(userId) {
return { success: true }
},
async getUnreadCount(userId) {
return 0
},
async deleteNotification(notificationId) {
return { success: true }
}
}
export const adminService = {
async login(username, password) {
return await apiClient.post('/admin/login', { username, password })
},
async getDashboardStats() {
return await apiClient.get('/admin/dashboard/stats')
},
async getSystemConfig() {
return await apiClient.get('/admin/config')
},
async updateSystemConfig(config) {
return await apiClient.put('/admin/config', config)
}
}
export default {
userService,
skillService,
orderService,
pointService,
inviteService,
notificationService,
adminService
}
1.3 创建环境配置文件
新建文件: frontend/.env.development
文件内容:
VITE_API_BASE_URL=http://localhost:8080/api/v1
新建文件: frontend/.env.production
文件内容:
VITE_API_BASE_URL=https://api.openclaw.com/api/v1
1.4 修改 Vite 配置(添加代理)
修改文件: frontend/vite.config.js
当前文件内容(如果不存在则新建):
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from 'node:url'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
})
🔧 第二阶段:状态管理修改
2.1 修改用户 Store
修改文件: frontend/src/stores/user.js
完整替换内容:
import { defineStore } from 'pinia'
import { userService, notificationService } from '@/service/apiService'
export const useUserStore = defineStore('user', {
state: () => ({
user: null,
token: localStorage.getItem('token') || null,
isLoggedIn: !!localStorage.getItem('token'),
notifications: [],
unreadCount: 0
}),
getters: {
userInfo: (state) => state.user,
userPoints: (state) => state.user?.availablePoints || 0,
userLevel: (state) => state.user?.memberLevel || '普通会员',
isVip: (state) => false
},
actions: {
initUser() {
const savedUser = localStorage.getItem('user')
const token = localStorage.getItem('token')
if (savedUser && token) {
try {
this.user = JSON.parse(savedUser)
this.token = token
this.isLoggedIn = true
this.loadUserProfile()
} catch (e) {
this.logout()
}
}
},
async loadUserProfile() {
const result = await userService.getProfile()
if (result.success && result.data) {
this.user = result.data
localStorage.setItem('user', JSON.stringify(result.data))
}
},
async login(phone, password) {
const result = await userService.login(phone, password)
if (result.success) {
this.token = result.data.token
this.user = result.data.user
this.isLoggedIn = true
this.loadNotifications()
}
return result
},
async register(data) {
const result = await userService.register(
data.phone,
data.password,
data.smsCode,
data.inviteCode
)
if (result.success) {
this.token = result.data.token
this.user = result.data.user
this.isLoggedIn = true
this.loadNotifications()
}
return result
},
async logout() {
await userService.logout()
this.user = null
this.token = null
this.isLoggedIn = false
this.notifications = []
this.unreadCount = 0
},
async updateUserInfo(updates) {
if (this.user) {
const result = await userService.updateProfile(updates)
if (result.success && result.data) {
this.user = result.data
localStorage.setItem('user', JSON.stringify(result.data))
}
return result
}
return { success: false, message: '未登录' }
},
refreshUser() {
this.loadUserProfile()
},
async loadNotifications() {
if (this.user) {
const result = await notificationService.getUserNotifications(this.user.id)
this.notifications = result.success ? result.data : []
this.unreadCount = await notificationService.getUnreadCount(this.user.id)
}
},
async markNotificationRead(notificationId) {
await notificationService.markAsRead(notificationId)
this.loadNotifications()
},
async markAllNotificationsRead() {
if (this.user) {
await notificationService.markAllAsRead(this.user.id)
this.loadNotifications()
}
},
async dailySign() {
if (this.user) {
const result = await userService.dailySign()
if (result.success) {
this.refreshUser()
}
return result
}
return { success: false, message: '未登录' }
},
async joinGroup() {
if (this.user) {
const result = await userService.joinGroup()
if (result.success) {
this.refreshUser()
}
return result
}
return { success: false, message: '未登录' }
}
}
})
2.2 修改 Skill Store
修改文件: frontend/src/stores/skill.js
完整替换内容:
import { defineStore } from 'pinia'
import { skillService, orderService } from '@/service/apiService'
export const useSkillStore = defineStore('skill', {
state: () => ({
skills: [],
categories: [],
currentSkill: null,
searchResults: [],
filters: {
keyword: '',
categoryId: null,
priceType: null,
minPrice: undefined,
maxPrice: undefined,
minRating: undefined,
sortBy: 'newest'
},
loading: false,
pagination: {
current: 1,
pageSize: 20,
total: 0,
pages: 0
}
}),
getters: {
featuredSkills: (state) => state.skills.filter(s => s.isFeatured),
hotSkills: (state) => state.skills.filter(s => s.isHot),
newSkills: (state) => state.skills.filter(s => s.isNew),
freeSkills: (state) => state.skills.filter(s => s.isFree),
paidSkills: (state) => state.skills.filter(s => !s.isFree)
},
actions: {
async loadSkills(params = {}) {
this.loading = true
const result = await skillService.getSkills({
pageNum: this.pagination.current,
pageSize: this.pagination.pageSize,
...params
})
if (result.success && result.data) {
this.skills = result.data.records || []
this.pagination = {
current: result.data.current || 1,
pageSize: result.data.size || 20,
total: result.data.total || 0,
pages: result.data.pages || 0
}
}
await this.loadCategories()
this.loading = false
return result
},
async loadCategories() {
const result = await skillService.getCategories()
if (result.success) {
this.categories = result.data || []
}
},
async loadSkillById(skillId) {
const result = await skillService.getSkillById(skillId)
if (result.success && result.data) {
this.currentSkill = result.data
}
return result
},
async searchSkills(keyword, filters = {}) {
this.filters = { ...this.filters, keyword, ...filters }
const result = await skillService.searchSkills(keyword, this.filters)
if (result.success && result.data) {
this.searchResults = result.data.records || []
}
return result
},
setFilters(filters) {
this.filters = { ...this.filters, ...filters }
},
clearFilters() {
this.filters = {
keyword: '',
categoryId: null,
priceType: null,
minPrice: undefined,
maxPrice: undefined,
minRating: undefined,
sortBy: 'newest'
}
this.searchResults = []
},
async getSkillsByCategory(categoryId) {
return await this.loadSkills({ categoryId })
},
async getComments(skillId) {
const result = await skillService.getComments(skillId)
return result.success ? result.data : []
},
async addComment(userId, skillId, rating, content, images) {
const result = await skillService.addComment(skillId, rating, content, images)
if (result.success) {
await this.loadSkillById(skillId)
}
return result
},
async likeComment(commentId) {
return await skillService.likeComment(commentId)
},
async hasUserPurchased(userId, skillId) {
const result = await orderService.getUserPurchasedSkills(userId)
if (result.success && result.data) {
return result.data.some(s => s.skillId === skillId)
}
return false
}
}
})
2.3 修改订单 Store
修改文件: frontend/src/stores/order.js
完整替换内容:
import { defineStore } from 'pinia'
import { orderService } from '@/service/apiService'
export const useOrderStore = defineStore('order', {
state: () => ({
orders: [],
currentOrder: null,
loading: false,
pagination: {
current: 1,
pageSize: 20,
total: 0,
pages: 0
}
}),
getters: {
pendingOrders: (state) => state.orders.filter(o => o.status === 'pending'),
completedOrders: (state) => state.orders.filter(o => o.status === 'completed'),
refundedOrders: (state) => state.orders.filter(o => o.status === 'refunded')
},
actions: {
async loadUserOrders(userId, params = {}) {
this.loading = true
const result = await orderService.getOrders({
pageNum: this.pagination.current,
pageSize: this.pagination.pageSize,
...params
})
if (result.success && result.data) {
this.orders = result.data.records || []
this.pagination = {
current: result.data.current || 1,
pageSize: result.data.size || 20,
total: result.data.total || 0,
pages: result.data.pages || 0
}
}
this.loading = false
return result
},
async loadAllOrders() {
return await this.loadUserOrders(null)
},
async createOrder(skillIds, payType, pointsToUse = 0) {
const result = await orderService.createOrder(skillIds, pointsToUse, payType)
if (result.success) {
this.currentOrder = result.data
this.orders.unshift(result.data)
}
return result
},
async payOrder(orderId, paymentNo) {
const result = await orderService.payOrder(orderId, paymentNo)
if (result.success && result.data) {
const index = this.orders.findIndex(o => o.id === orderId)
if (index !== -1) {
this.orders[index] = result.data
}
}
return result
},
async cancelOrder(orderId, reason = '') {
const result = await orderService.cancelOrder(orderId, reason)
if (result.success) {
const index = this.orders.findIndex(o => o.id === orderId)
if (index !== -1) {
this.orders[index].status = 'cancelled'
}
}
return result
},
async applyRefund(orderId, reason, images = []) {
const result = await orderService.applyRefund(orderId, reason, images)
if (result.success) {
const index = this.orders.findIndex(o => o.id === orderId)
if (index !== -1) {
this.orders[index].status = 'refunding'
}
}
return result
},
async getOrderById(orderId) {
return await orderService.getOrderById(orderId)
},
async getUserPurchasedSkills(userId) {
return await orderService.getUserPurchasedSkills(userId)
}
}
})
2.4 修改积分 Store
修改文件: frontend/src/stores/point.js
完整替换内容:
import { defineStore } from 'pinia'
import { pointService, userService } from '@/service/apiService'
export const usePointStore = defineStore('point', {
state: () => ({
records: [],
rechargeTiers: [],
pointRules: null,
loading: false,
pagination: {
current: 1,
pageSize: 20,
total: 0,
pages: 0
}
}),
getters: {
incomeRecords: (state) => state.records.filter(r => r.pointsType === 'earn'),
expenseRecords: (state) => state.records.filter(r => r.pointsType === 'expense'),
totalIncome: (state) => state.records.filter(r => r.pointsType === 'earn').reduce((sum, r) => sum + r.amount, 0),
totalExpense: (state) => state.records.filter(r => r.pointsType === 'expense').reduce((sum, r) => sum + r.amount, 0)
},
actions: {
async loadUserRecords(userId, filters = {}) {
this.loading = true
const result = await pointService.getPointRecords({
pageNum: this.pagination.current,
pageSize: this.pagination.pageSize,
...filters
})
if (result.success && result.data) {
this.records = result.data.records || []
this.pagination = {
current: result.data.current || 1,
pageSize: result.data.size || 20,
total: result.data.total || 0,
pages: result.data.pages || 0
}
}
this.loading = false
return result
},
async loadAllRecords() {
return await this.loadUserRecords(null)
},
async loadRechargeTiers() {
const result = await pointService.getRechargeTiers()
if (result.success) {
this.rechargeTiers = result.data
}
},
async loadPointRules() {
const result = await pointService.getPointRules()
if (result.success) {
this.pointRules = result.data
}
},
async recharge(userId, amount) {
const result = await pointService.recharge(amount)
if (result.success) {
await this.loadUserRecords(userId)
}
return result
},
async getInviteRecords(userId) {
const result = await userService.getInviteRecords?.(userId)
return result?.data || []
}
}
})
2.5 修改管理后台 Store
修改文件: frontend/src/stores/admin.js
完整替换内容:
import { defineStore } from 'pinia'
import { adminService, userService, skillService, orderService } from '@/service/apiService'
export const useAdminStore = defineStore('admin', {
state: () => ({
admin: null,
isLoggedIn: false,
dashboardStats: null,
users: [],
skills: [],
orders: [],
comments: [],
systemConfig: null,
loading: false
}),
actions: {
async login(username, password) {
const result = await adminService.login(username, password)
if (result.success) {
this.admin = result.data
this.isLoggedIn = true
}
return result
},
logout() {
this.admin = null
this.isLoggedIn = false
sessionStorage.removeItem('admin_user')
},
async loadDashboardStats() {
this.loading = true
const result = await adminService.getDashboardStats()
if (result.success) {
this.dashboardStats = result.data
}
this.loading = false
return this.dashboardStats
},
async loadUsers() {
this.loading = true
const result = await userService.getAllUsers()
if (result.success) {
this.users = result.data?.records || result.data || []
}
this.loading = false
return this.users
},
async loadSkills() {
this.loading = true
const result = await skillService.getAllSkills()
if (result.success) {
this.skills = result.data?.records || result.data || []
}
this.loading = false
return this.skills
},
async loadOrders() {
this.loading = true
const result = await orderService.getAllOrders()
if (result.success) {
this.orders = result.data?.records || result.data || []
}
this.loading = false
return this.orders
},
async loadComments() {
this.loading = true
const result = await skillService.getAllComments()
if (result.success) {
this.comments = result.data?.records || result.data || []
}
this.loading = false
return this.comments
},
async loadSystemConfig() {
const result = await adminService.getSystemConfig()
if (result.success) {
this.systemConfig = result.data
}
return this.systemConfig
},
async updateSystemConfig(config) {
const result = await adminService.updateSystemConfig(config)
if (result.success) {
this.systemConfig = config
}
return result
},
async banUser(userId) {
const result = await userService.banUser(userId)
if (result.success) {
await this.loadUsers()
}
return result
},
async unbanUser(userId) {
const result = await userService.unbanUser(userId)
if (result.success) {
await this.loadUsers()
}
return result
},
async approveSkill(skillId) {
const result = await skillService.approveSkill(skillId)
if (result.success) {
await this.loadSkills()
}
return result
},
async rejectSkill(skillId, reason) {
const result = await skillService.rejectSkill(skillId, reason)
if (result.success) {
await this.loadSkills()
}
return result
}
}
})
🔧 第三阶段:路由和主入口修改
3.1 修改路由守卫
修改文件: frontend/src/router/index.js
修改内容: 替换整个路由守卫部分
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
// ... 保持原有的路由配置不变
]
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { top: 0 }
}
}
})
router.beforeEach((to, from, next) => {
document.title = to.meta.title ? `${to.meta.title} - OpenClaw Skills` : 'OpenClaw Skills'
if (to.meta.requiresAuth) {
const token = localStorage.getItem('token')
if (!token) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
return
}
}
if (to.meta.requiresAdmin) {
const admin = sessionStorage.getItem('admin_user')
if (!admin) {
next('/admin/login')
return
}
}
next()
})
export default router
3.2 修改主入口文件
修改文件: frontend/src/main.js
完整替换内容:
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'
import { useUserStore } from './stores'
import './styles/index.scss'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.use(router)
app.use(ElementPlus, {
locale: zhCn
})
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
const userStore = useUserStore()
userStore.initUser()
app.mount('#app')
📊 数据结构映射关系
用户数据映射
| 前端字段 (localStorage) | 后端字段 (API) | 说明 |
|---|---|---|
| id | id | 用户ID |
| phone | phone | 手机号 |
| nickname | nickname | 昵称 |
| avatar | avatarUrl | 头像URL |
| points | availablePoints | 可用积分 |
| level | memberLevel | 会员等级 |
| levelName | - | 前端计算 |
| inviteCode | inviteCode | 邀请码 |
| isVip | - | 后端暂无 |
Skill 数据映射
| 前端字段 | 后端字段 | 说明 |
|---|---|---|
| id | id | Skill ID |
| name | name | 名称 |
| description | description | 描述 |
| cover | coverImageUrl | 封面图 |
| price | price | 价格 |
| categoryId | categoryId | 分类ID |
| downloadCount | downloadCount | 下载次数 |
| rating | rating | 评分 |
| status | - | 前端使用 |
订单数据映射
| 前端字段 | 后端字段 | 说明 |
|---|---|---|
| id | id | 订单ID |
| orderNo | orderNo | 订单号 |
| skillId | items[0].skillId | Skill ID |
| skillName | items[0].skillName | Skill 名称 |
| status | status | 订单状态 |
| payType | paymentMethod | 支付方式 |
📝 需要修改的视图文件清单
以下文件需要检查和修改导入语句:
| 文件路径 | 需要修改的内容 |
|---|---|
frontend/src/views/user/login.vue |
修改导入 localService → apiService |
frontend/src/views/user/register.vue |
修改导入 localService → apiService |
frontend/src/views/user/profile.vue |
修改导入 localService → apiService |
frontend/src/views/user/orders.vue |
修改导入 localService → apiService |
frontend/src/views/user/points.vue |
修改导入 localService → apiService |
frontend/src/views/user/recharge.vue |
修改导入 localService → apiService |
frontend/src/views/user/invite.vue |
修改导入 localService → apiService |
frontend/src/views/skill/list.vue |
修改导入 localService → apiService |
frontend/src/views/skill/detail.vue |
修改导入 localService → apiService |
frontend/src/views/order/pay.vue |
修改导入 localService → apiService |
frontend/src/views/order/detail.vue |
修改导入 localService → apiService |
frontend/src/views/home/index.vue |
修改导入 localService → apiService |
frontend/src/views/admin/dashboard.vue |
修改导入 localService → apiService |
frontend/src/views/admin/users.vue |
修改导入 localService → apiService |
frontend/src/views/admin/skills.vue |
修改导入 localService → apiService |
frontend/src/views/admin/orders.vue |
修改导入 localService → apiService |
修改示例:
// 修改前
import { userService } from '@/service/localService'
// 修改后
import { userService } from '@/service/apiService'
✅ 联调测试计划
测试步骤
-
环境准备
# 启动后端服务 cd openclaw-backend/openclaw-backend mvn spring-boot:run # 启动前端服务 cd frontend npm install npm run dev -
基础功能测试
- 用户注册(含短信验证码)
- 用户登录
- 获取用户信息
- Skill 列表查询
- Skill 详情查看
-
核心业务测试
- 创建订单
- 积分支付
- 充值功能
- 每日签到
- 邀请功能
-
管理后台测试
- 管理员登录
- 用户管理
- Skill 审核
- 订单管理
🎯 实施优先级
P0 - 必须完成(1-2天)
- 安装 axios 依赖
- 创建 apiService.js
- 修改 user.js store
- 修改 skill.js store
- 修改路由守卫
P1 - 重要功能(2-3天)
- 修改 order.js store
- 修改 point.js store
- 修改 admin.js store
- 修改 main.js
- 修改主要视图文件
P2 - 完善和优化(3-5天)
- 修改剩余视图文件
- 添加加载状态
- 错误处理优化
- 完整联调测试
⚠️ 注意事项
- 数据兼容性:后端返回的数据结构与前端期望的可能不一致,需要做适配
- 接口缺失:部分功能后端可能暂无接口,需要使用模拟数据或等待后端开发
- Token 管理:确保 Token 在 localStorage 中正确存储和清除
- 错误处理:apiService 已经统一处理了错误,但视图层仍需关注特殊情况
- 分页:后端使用 pageNum/pageSize,前端需要适配分页逻辑
📞 技术支持
如有问题,请检查:
- 后端服务是否正常启动(http://localhost:8080)
- 浏览器控制台是否有错误信息
- Network 面板查看 API 请求和响应
- 检查 Token 是否正确设置在请求头中
文档结束