# 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` **修改内容**: ```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" } } ``` **执行命令**: ```bash cd frontend npm install ``` --- ### 1.2 创建 API 服务层 **新建文件**: `frontend/src/service/apiService.js` **文件内容**: ```javascript 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` **文件内容**: ```env VITE_API_BASE_URL=http://localhost:8080/api/v1 ``` **新建文件**: `frontend/.env.production` **文件内容**: ```env VITE_API_BASE_URL=https://api.openclaw.com/api/v1 ``` --- ### 1.4 修改 Vite 配置(添加代理) **修改文件**: `frontend/vite.config.js` **当前文件内容(如果不存在则新建)**: ```javascript 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` **完整替换内容**: ```javascript 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` **完整替换内容**: ```javascript 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` **完整替换内容**: ```javascript 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` **完整替换内容**: ```javascript 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` **完整替换内容**: ```javascript 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` **修改内容**: 替换整个路由守卫部分 ```javascript 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` **完整替换内容**: ```javascript 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 | **修改示例**: ```javascript // 修改前 import { userService } from '@/service/localService' // 修改后 import { userService } from '@/service/apiService' ``` --- ## ✅ 联调测试计划 ### 测试步骤 1. **环境准备** ```bash # 启动后端服务 cd openclaw-backend/openclaw-backend mvn spring-boot:run # 启动前端服务 cd frontend npm install npm run dev ``` 2. **基础功能测试** - [ ] 用户注册(含短信验证码) - [ ] 用户登录 - [ ] 获取用户信息 - [ ] Skill 列表查询 - [ ] Skill 详情查看 3. **核心业务测试** - [ ] 创建订单 - [ ] 积分支付 - [ ] 充值功能 - [ ] 每日签到 - [ ] 邀请功能 4. **管理后台测试** - [ ] 管理员登录 - [ ] 用户管理 - [ ] 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天) - [ ] 修改剩余视图文件 - [ ] 添加加载状态 - [ ] 错误处理优化 - [ ] 完整联调测试 --- ## ⚠️ 注意事项 1. **数据兼容性**:后端返回的数据结构与前端期望的可能不一致,需要做适配 2. **接口缺失**:部分功能后端可能暂无接口,需要使用模拟数据或等待后端开发 3. **Token 管理**:确保 Token 在 localStorage 中正确存储和清除 4. **错误处理**:apiService 已经统一处理了错误,但视图层仍需关注特殊情况 5. **分页**:后端使用 pageNum/pageSize,前端需要适配分页逻辑 --- ## 📞 技术支持 如有问题,请检查: 1. 后端服务是否正常启动(http://localhost:8080) 2. 浏览器控制台是否有错误信息 3. Network 面板查看 API 请求和响应 4. 检查 Token 是否正确设置在请求头中 --- **文档结束**