feat(backend): 添加 Swagger 文档并优化认证接口

- 在 .env 文件中添加 ENABLE_SWAGGER 环境变量
- 在 app.js 中集成 Swagger UI
- 重构 auth 路由,添加请求参数验证
- 更新 API 文档,遵循 OpenAPI 3.0 规范
-优化认证接口的错误处理和响应格式
This commit is contained in:
2025-08-30 15:29:51 +08:00
parent 7f9bfbb381
commit 0cad74b06f
28 changed files with 2123 additions and 691 deletions

View File

@@ -25,6 +25,7 @@
"@vue/tsconfig": "^0.4.0",
"eslint": "^8.22.0",
"eslint-plugin-vue": "^9.0.0",
"less": "^4.4.1",
"prettier": "^2.8.0",
"typescript": "~5.0.0",
"vite": "^4.3.0",
@@ -1334,6 +1335,18 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
"node_modules/copy-anything": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz",
"integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
"dev": true,
"dependencies": {
"is-what": "^3.14.1"
},
"funding": {
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/core-js": {
"version": "3.45.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz",
@@ -1475,6 +1488,19 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/errno": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
"integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
"dev": true,
"optional": true,
"dependencies": {
"prr": "~1.0.1"
},
"bin": {
"errno": "cli.js"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -2066,6 +2092,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"optional": true
},
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -2126,6 +2159,19 @@
"he": "bin/he"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
"optional": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -2135,6 +2181,19 @@
"node": ">= 4"
}
},
"node_modules/image-size": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
"integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
"dev": true,
"optional": true,
"bin": {
"image-size": "bin/image-size.js"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -2224,6 +2283,12 @@
"node": ">=0.10.0"
}
},
"node_modules/is-what": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz",
"integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==",
"dev": true
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -2274,6 +2339,38 @@
"json-buffer": "3.0.1"
}
},
"node_modules/less": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/less/-/less-4.4.1.tgz",
"integrity": "sha512-X9HKyiXPi0f/ed0XhgUlBeFfxrlDP3xR4M7768Zl+WXLUViuL9AOPPJP4nCV0tgRWvTYvpNmN0SFhZOQzy16PA==",
"dev": true,
"dependencies": {
"copy-anything": "^2.0.1",
"parse-node-version": "^1.0.1",
"tslib": "^2.3.0"
},
"bin": {
"lessc": "bin/lessc"
},
"engines": {
"node": ">=14"
},
"optionalDependencies": {
"errno": "^0.1.1",
"graceful-fs": "^4.1.2",
"image-size": "~0.5.0",
"make-dir": "^2.1.0",
"mime": "^1.4.1",
"needle": "^3.1.0",
"source-map": "~0.6.0"
}
},
"node_modules/less/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true
},
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -2337,6 +2434,30 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
"dev": true,
"optional": true,
"dependencies": {
"pify": "^4.0.1",
"semver": "^5.6.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/make-dir/node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true,
"optional": true,
"bin": {
"semver": "bin/semver"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -2367,6 +2488,19 @@
"node": ">=8.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"dev": true,
"optional": true,
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -2444,6 +2578,23 @@
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
"dev": true
},
"node_modules/needle": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz",
"integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==",
"dev": true,
"optional": true,
"dependencies": {
"iconv-lite": "^0.6.3",
"sax": "^1.2.4"
},
"bin": {
"needle": "bin/needle"
},
"engines": {
"node": ">= 4.4.x"
}
},
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@@ -2524,6 +2675,15 @@
"node": ">=6"
}
},
"node_modules/parse-node-version": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz",
"integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==",
"dev": true,
"engines": {
"node": ">= 0.10"
}
},
"node_modules/path-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
@@ -2583,6 +2743,16 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pify": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"dev": true,
"optional": true,
"engines": {
"node": ">=6"
}
},
"node_modules/pinia": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz",
@@ -2673,6 +2843,13 @@
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/prr": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
"integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
"dev": true,
"optional": true
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -2781,6 +2958,20 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true,
"optional": true
},
"node_modules/sax": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"dev": true,
"optional": true
},
"node_modules/scroll-into-view-if-needed": {
"version": "2.2.31",
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz",
@@ -2836,6 +3027,16 @@
"node": ">=8"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",

View File

@@ -11,14 +11,14 @@
"type-check": "vue-tsc --noEmit"
},
"dependencies": {
"vue": "^3.3.0",
"vue-router": "^4.2.0",
"pinia": "^2.1.0",
"@ant-design/icons-vue": "^6.1.0",
"ant-design-vue": "^4.0.0",
"axios": "^1.4.0",
"@ant-design/icons-vue": "^6.1.0",
"dayjs": "^1.11.0",
"lodash-es": "^4.17.21"
"lodash-es": "^4.17.21",
"pinia": "^2.1.0",
"vue": "^3.3.0",
"vue-router": "^4.2.0"
},
"devDependencies": {
"@types/lodash-es": "^4.17.0",
@@ -26,16 +26,17 @@
"@vitejs/plugin-vue": "^4.2.0",
"@vue/eslint-config-typescript": "^11.0.0",
"@vue/tsconfig": "^0.4.0",
"typescript": "~5.0.0",
"vite": "^4.3.0",
"vue-tsc": "^1.4.0",
"eslint": "^8.22.0",
"eslint-plugin-vue": "^9.0.0",
"prettier": "^2.8.0"
"less": "^4.4.1",
"prettier": "^2.8.0",
"typescript": "~5.0.0",
"vite": "^4.3.0",
"vue-tsc": "^1.4.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
}

View File

@@ -0,0 +1,110 @@
import { request } from '@/api'
import type { AxiosResponse } from 'axios'
// 动物类型
export type AnimalType = 'alpaca' | 'dog' | 'cat' | 'rabbit'
// 动物状态
export type AnimalStatus = 'available' | 'claimed' | 'reserved'
// 认领状态
export type ClaimStatus = 'pending' | 'approved' | 'rejected' | 'completed'
// 动物数据结构
export interface Animal {
id: number
name: string
type: AnimalType
breed: string
age: number
price: number
status: AnimalStatus
image_url: string
description: string
created_at: string
updated_at: string
}
// 动物认领记录
export interface AnimalClaim {
id: number
animal_id: number
animal_name: string
animal_image: string
user_name: string
user_phone: string
status: ClaimStatus
applied_at: string
processed_at: string
}
// 动物查询参数
export interface AnimalQueryParams {
page?: number
pageSize?: number
keyword?: string
type?: AnimalType
status?: AnimalStatus
}
// 认领记录查询参数
export interface ClaimQueryParams {
page?: number
pageSize?: number
keyword?: string
status?: ClaimStatus
}
// API响应结构
export interface ApiResponse<T> {
success: boolean
code: number
message: string
data: T
pagination?: {
current: number
pageSize: number
total: number
totalPages: number
}
}
// 获取动物列表
export const getAnimals = async (params?: AnimalQueryParams): Promise<ApiResponse<Animal[]>> => {
return request.get<ApiResponse<Animal[]>>('/animals', { params })
}
// 获取动物详情
export const getAnimal = async (id: number): Promise<ApiResponse<Animal>> => {
return request.get<ApiResponse<Animal>>(`/animals/${id}`)
}
// 创建动物
export const createAnimal = async (animalData: Partial<Animal>): Promise<ApiResponse<Animal>> => {
return request.post<ApiResponse<Animal>>('/animals', animalData)
}
// 更新动物
export const updateAnimal = async (id: number, animalData: Partial<Animal>): Promise<ApiResponse<Animal>> => {
return request.put<ApiResponse<Animal>>(`/animals/${id}`, animalData)
}
// 删除动物
export const deleteAnimal = async (id: number): Promise<ApiResponse<void>> => {
return request.delete<ApiResponse<void>>(`/animals/${id}`)
}
// 获取认领记录列表
export const getAnimalClaims = async (params?: ClaimQueryParams): Promise<ApiResponse<AnimalClaim[]>> => {
return request.get<ApiResponse<AnimalClaim[]>>('/animals/claims', { params })
}
// 审核动物认领(通过)
export const approveAnimalClaim = async (id: number): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>(`/animals/claims/${id}/approve`)
}
// 拒绝动物认领
export const rejectAnimalClaim = async (id: number, reason: string): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>(`/animals/claims/${id}/reject`, { reason })
}

View File

@@ -151,189 +151,12 @@ export const authAPI = {
request.post('/auth/logout')
}
// 用户管理API
export const userAPI = {
// 获取用户列表
getUsers: (params?: {
page?: number
pageSize?: number
search?: string
status?: string
}) => request.get('/users', { params }),
// 获取用户详情
getUser: (id: number) => request.get(`/users/${id}`),
// 创建用户
createUser: (data: any) => request.post('/users', data),
// 更新用户
updateUser: (id: number, data: any) => request.put(`/users/${id}`, data),
// 删除用户
deleteUser: (id: number) => request.delete(`/users/${id}`),
// 批量操作
batchUsers: (ids: number[], action: string) =>
request.post('/users/batch', { ids, action })
}
// 商家管理API
export const merchantAPI = {
// 获取商家列表
getMerchants: (params?: {
page?: number
pageSize?: number
search?: string
status?: string
type?: string
}) => request.get('/merchants', { params }),
// 获取商家详情
getMerchant: (id: number) => request.get(`/merchants/${id}`),
// 审核商家
approveMerchant: (id: number, data: any) => request.post(`/merchants/${id}/approve`, data),
// 拒绝商家
rejectMerchant: (id: number, reason: string) => request.post(`/merchants/${id}/reject`, { reason }),
// 禁用商家
disableMerchant: (id: number) => request.post(`/merchants/${id}/disable`),
// 启用商家
enableMerchant: (id: number) => request.post(`/merchants/${id}/enable`)
}
// 旅行管理API
export const travelAPI = {
// 获取旅行计划列表
getTravelPlans: (params?: {
page?: number
pageSize?: number
search?: string
status?: string
destination?: string
}) => request.get('/travel/plans', { params }),
// 获取旅行计划详情
getTravelPlan: (id: number) => request.get(`/travel/plans/${id}`),
// 审核旅行计划
approveTravelPlan: (id: number) => request.post(`/travel/plans/${id}/approve`),
// 拒绝旅行计划
rejectTravelPlan: (id: number, reason: string) => request.post(`/travel/plans/${id}/reject`, { reason }),
// 关闭旅行计划
closeTravelPlan: (id: number) => request.post(`/travel/plans/${id}/close`)
}
// 动物管理API
export const animalAPI = {
// 获取动物列表
getAnimals: (params?: {
page?: number
pageSize?: number
search?: string
status?: string
type?: string
}) => request.get('/animals', { params }),
// 获取动物详情
getAnimal: (id: number) => request.get(`/animals/${id}`),
// 创建动物
createAnimal: (data: any) => request.post('/animals', data),
// 更新动物
updateAnimal: (id: number, data: any) => request.put(`/animals/${id}`, data),
// 删除动物
deleteAnimal: (id: number) => request.delete(`/animals/${id}`),
// 审核动物认领
approveAnimalClaim: (id: number) => request.post(`/animals/claims/${id}/approve`),
// 拒绝动物认领
rejectAnimalClaim: (id: number, reason: string) => request.post(`/animals/claims/${id}/reject`, { reason })
}
// 订单管理API
export const orderAPI = {
// 获取订单列表
getOrders: (params?: {
page?: number
pageSize?: number
search?: string
status?: string
type?: string
startDate?: string
endDate?: string
}) => request.get('/orders', { params }),
// 获取订单详情
getOrder: (id: number) => request.get(`/orders/${id}`),
// 更新订单状态
updateOrderStatus: (id: number, status: string) => request.put(`/orders/${id}/status`, { status }),
// 导出订单
exportOrders: (params: any) => request.get('/orders/export', {
params,
responseType: 'blob'
})
}
// 推广管理API
export const promotionAPI = {
// 获取推广数据
getPromotionStats: () => request.get('/promotion/stats'),
// 获取推广记录
getPromotionRecords: (params?: {
page?: number
pageSize?: number
search?: string
status?: string
}) => request.get('/promotion/records', { params }),
// 审核提现申请
approveWithdrawal: (id: number) => request.post(`/promotion/withdrawals/${id}/approve`),
// 拒绝提现申请
rejectWithdrawal: (id: number, reason: string) => request.post(`/promotion/withdrawals/${id}/reject`, { reason }),
// 导出推广数据
exportPromotionData: (params: any) => request.get('/promotion/export', {
params,
responseType: 'blob'
})
}
// 系统管理API
export const systemAPI = {
// 获取系统配置
getConfig: () => request.get('/system/config'),
// 更新系统配置
updateConfig: (data: any) => request.put('/system/config', data),
// 获取操作日志
getOperationLogs: (params?: {
page?: number
pageSize?: number
search?: string
action?: string
startDate?: string
endDate?: string
}) => request.get('/system/logs', { params }),
// 清理缓存
clearCache: () => request.post('/system/cache/clear'),
// 系统健康检查
healthCheck: () => request.get('/system/health')
}
export * from './user'
export * from './merchant'
export * from './travel'
export * from './animal'
export * from './order'
export * from './promotion'
export * from './system'
export default api

View File

@@ -0,0 +1,73 @@
import { request } from '@/api'
import type { AxiosResponse } from 'axios'
// 商家类型
export type MerchantType = 'flower_shop' | 'activity_organizer' | 'farm_owner'
// 商家状态
export type MerchantStatus = 'pending' | 'approved' | 'rejected' | 'disabled'
// 商家数据结构
export interface Merchant {
id: number
business_name: string
merchant_type: MerchantType
contact_person: string
contact_phone: string
status: MerchantStatus
created_at: string
updated_at: string
}
// 商家查询参数
export interface MerchantQueryParams {
page?: number
pageSize?: number
keyword?: string
type?: MerchantType
status?: MerchantStatus
}
// API响应结构
export interface ApiResponse<T> {
success: boolean
code: number
message: string
data: T
pagination?: {
current: number
pageSize: number
total: number
totalPages: number
}
}
// 获取商家列表
export const getMerchants = async (params?: MerchantQueryParams): Promise<ApiResponse<Merchant[]>> => {
return request.get<ApiResponse<Merchant[]>>('/merchants', { params })
}
// 获取商家详情
export const getMerchant = async (id: number): Promise<ApiResponse<Merchant>> => {
return request.get<ApiResponse<Merchant>>(`/merchants/${id}`)
}
// 审核商家(通过)
export const approveMerchant = async (id: number): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>(`/merchants/${id}/approve`)
}
// 拒绝商家入驻申请
export const rejectMerchant = async (id: number, reason: string): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>(`/merchants/${id}/reject`, { reason })
}
// 禁用商家
export const disableMerchant = async (id: number): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>(`/merchants/${id}/disable`)
}
// 启用商家
export const enableMerchant = async (id: number): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>(`/merchants/${id}/enable`)
}

View File

@@ -0,0 +1,95 @@
import { request } from '@/api'
import type { AxiosResponse } from 'axios'
// 订单状态
export type OrderStatus = 'pending' | 'paid' | 'shipped' | 'completed' | 'cancelled' | 'refunded'
// 支付方式
export type PaymentMethod = 'wechat' | 'alipay' | 'bank' | 'balance'
// 订单数据结构
export interface Order {
id: number
order_no: string
user_id: number
user_name: string
user_phone: string
amount: number
status: OrderStatus
payment_method: PaymentMethod
created_at: string
paid_at: string
shipped_at: string
completed_at: string
}
// 订单查询参数
export interface OrderQueryParams {
page?: number
pageSize?: number
order_no?: string
status?: OrderStatus
orderTime?: [string, string]
}
// 统计数据
export interface OrderStatistics {
today_orders: number
today_sales: number
month_orders: number
month_sales: number
}
// API响应结构
export interface ApiResponse<T> {
success: boolean
code: number
message: string
data: T
pagination?: {
current: number
pageSize: number
total: number
totalPages: number
}
}
// 获取订单列表
export const getOrders = async (params?: OrderQueryParams): Promise<ApiResponse<Order[]>> => {
return request.get<ApiResponse<Order[]>>('/orders', { params })
}
// 获取订单详情
export const getOrder = async (id: number): Promise<ApiResponse<Order>> => {
return request.get<ApiResponse<Order>>(`/orders/${id}`)
}
// 更新订单状态
export const updateOrderStatus = async (id: number, status: OrderStatus): Promise<ApiResponse<void>> => {
return request.put<ApiResponse<void>>(`/orders/${id}/status`, { status })
}
// 发货
export const shipOrder = async (id: number): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>(`/orders/${id}/ship`)
}
// 完成订单
export const completeOrder = async (id: number): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>(`/orders/${id}/complete`)
}
// 取消订单
export const cancelOrder = async (id: number): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>(`/orders/${id}/cancel`)
}
// 退款
export const refundOrder = async (id: number): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>(`/orders/${id}/refund`)
}
// 获取订单统计数据
export const getOrderStatistics = async (): Promise<ApiResponse<OrderStatistics>> => {
return request.get<ApiResponse<OrderStatistics>>('/orders/statistics')
}

View File

@@ -0,0 +1,133 @@
import { request } from '@/api'
import type { AxiosResponse } from 'axios'
// 推广活动状态
export type PromotionStatus = 'active' | 'upcoming' | 'ended' | 'paused'
// 奖励类型
export type RewardType = 'cash' | 'points' | 'coupon'
// 奖励状态
export type RewardStatus = 'pending' | 'issued' | 'failed'
// 推广活动数据结构
export interface PromotionActivity {
id: number
name: string
description: string
reward_type: RewardType
reward_amount: number
status: PromotionStatus
start_time: string
end_time: string
max_participants: number
current_participants: number
created_at: string
updated_at: string
}
// 奖励记录
export interface RewardRecord {
id: number
user_id: number
user_name: string
user_phone: string
activity_id: number
activity_name: string
reward_type: RewardType
reward_amount: number
status: RewardStatus
issued_at: string
created_at: string
}
// 推广活动查询参数
export interface PromotionQueryParams {
page?: number
pageSize?: number
name?: string
status?: PromotionStatus
activityTime?: [string, string]
}
// 奖励记录查询参数
export interface RewardQueryParams {
page?: number
pageSize?: number
user?: string
reward_type?: RewardType
status?: RewardStatus
}
// 统计数据
export interface PromotionStatistics {
total_activities: number
active_activities: number
total_rewards: number
issued_rewards: number
total_amount: number
}
// API响应结构
export interface ApiResponse<T> {
success: boolean
code: number
message: string
data: T
pagination?: {
current: number
pageSize: number
total: number
totalPages: number
}
}
// 获取推广活动列表
export const getPromotionActivities = async (params?: PromotionQueryParams): Promise<ApiResponse<PromotionActivity[]>> => {
return request.get<ApiResponse<PromotionActivity[]>>('/promotion/activities', { params })
}
// 获取推广活动详情
export const getPromotionActivity = async (id: number): Promise<ApiResponse<PromotionActivity>> => {
return request.get<ApiResponse<PromotionActivity>>(`/promotion/activities/${id}`)
}
// 创建推广活动
export const createPromotionActivity = async (activityData: Partial<PromotionActivity>): Promise<ApiResponse<PromotionActivity>> => {
return request.post<ApiResponse<PromotionActivity>>('/promotion/activities', activityData)
}
// 更新推广活动
export const updatePromotionActivity = async (id: number, activityData: Partial<PromotionActivity>): Promise<ApiResponse<PromotionActivity>> => {
return request.put<ApiResponse<PromotionActivity>>(`/promotion/activities/${id}`, activityData)
}
// 删除推广活动
export const deletePromotionActivity = async (id: number): Promise<ApiResponse<void>> => {
return request.delete<ApiResponse<void>>(`/promotion/activities/${id}`)
}
// 暂停推广活动
export const pausePromotionActivity = async (id: number): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>(`/promotion/activities/${id}/pause`)
}
// 恢复推广活动
export const resumePromotionActivity = async (id: number): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>(`/promotion/activities/${id}/resume`)
}
// 获取奖励记录列表
export const getRewardRecords = async (params?: RewardQueryParams): Promise<ApiResponse<RewardRecord[]>> => {
return request.get<ApiResponse<RewardRecord[]>>('/promotion/rewards', { params })
}
// 发放奖励
export const issueReward = async (id: number): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>(`/promotion/rewards/${id}/issue`)
}
// 获取推广统计数据
export const getPromotionStatistics = async (): Promise<ApiResponse<PromotionStatistics>> => {
return request.get<ApiResponse<PromotionStatistics>>('/promotion/statistics')
}

View File

@@ -0,0 +1,120 @@
import { request } from '@/api'
import type { AxiosResponse } from 'axios'
// 服务类型
export type ServiceType = 'database' | 'cache' | 'mq'
// 服务状态
export type ServiceStatus = 'running' | 'stopped'
// 系统服务数据结构
export interface Service {
id: number
name: string
type: ServiceType
description: string
status: ServiceStatus
}
// 系统配置
export interface SystemSettings {
systemName: string
systemVersion: string
maintenanceMode: boolean
sessionTimeout: number
pageSize: number
enableSwagger: boolean
}
// 系统信息
export interface SystemInfo {
version: string
environment: string
uptime: string
startTime: string
}
// 数据库状态
export interface DatabaseStatus {
status: ServiceStatus
type: string
connections: string
queriesPerMinute: number
}
// 缓存状态
export interface CacheStatus {
status: ServiceStatus
memoryUsage: string
hitRate: string
keyCount: number
}
// API响应结构
export interface ApiResponse<T> {
success: boolean
code: number
message: string
data: T
}
// 获取系统服务列表
export const getServices = async (): Promise<ApiResponse<Service[]>> => {
return request.get<ApiResponse<Service[]>>('/system/services')
}
// 启动服务
export const startService = async (id: number): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>(`/system/services/${id}/start`)
}
// 停止服务
export const stopService = async (id: number): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>(`/system/services/${id}/stop`)
}
// 获取系统信息
export const getSystemInfo = async (): Promise<ApiResponse<SystemInfo>> => {
return request.get<ApiResponse<SystemInfo>>('/system/info')
}
// 获取数据库状态
export const getDatabaseStatus = async (): Promise<ApiResponse<DatabaseStatus>> => {
return request.get<ApiResponse<DatabaseStatus>>('/system/database')
}
// 获取缓存状态
export const getCacheStatus = async (): Promise<ApiResponse<CacheStatus>> => {
return request.get<ApiResponse<CacheStatus>>('/system/cache')
}
// 获取系统配置
export const getSystemSettings = async (): Promise<ApiResponse<SystemSettings>> => {
return request.get<ApiResponse<SystemSettings>>('/system/settings')
}
// 更新系统配置
export const updateSystemSettings = async (settings: SystemSettings): Promise<ApiResponse<void>> => {
return request.put<ApiResponse<void>>('/system/settings', settings)
}
// 获取操作日志
export const getOperationLogs = async (params?: {
page?: number
pageSize?: number
search?: string
startDate?: string
endDate?: string
}): Promise<ApiResponse<any[]>> => {
return request.get<ApiResponse<any[]>>('/system/logs', { params })
}
// 清理缓存
export const clearCache = async (): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>('/system/cache/clear')
}
// 系统健康检查
export const healthCheck = async (): Promise<ApiResponse<any>> => {
return request.get<ApiResponse<any>>('/system/health')
}

View File

@@ -0,0 +1,68 @@
import { request } from '@/api'
import type { AxiosResponse } from 'axios'
// 旅行计划状态
export type TravelStatus = 'recruiting' | 'full' | 'completed' | 'cancelled'
// 旅行计划数据结构
export interface TravelPlan {
id: number
destination: string
start_date: string
end_date: string
budget: number
max_members: number
current_members: number
status: TravelStatus
creator: string
created_at: string
updated_at: string
}
// 旅行计划查询参数
export interface TravelQueryParams {
page?: number
pageSize?: number
destination?: string
status?: TravelStatus
travelTime?: [string, string]
}
// API响应结构
export interface ApiResponse<T> {
success: boolean
code: number
message: string
data: T
pagination?: {
current: number
pageSize: number
total: number
totalPages: number
}
}
// 获取旅行计划列表
export const getTravelPlans = async (params?: TravelQueryParams): Promise<ApiResponse<TravelPlan[]>> => {
return request.get<ApiResponse<TravelPlan[]>>('/travel/plans', { params })
}
// 获取旅行计划详情
export const getTravelPlan = async (id: number): Promise<ApiResponse<TravelPlan>> => {
return request.get<ApiResponse<TravelPlan>>(`/travel/plans/${id}`)
}
// 审核旅行计划
export const approveTravelPlan = async (id: number): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>(`/travel/plans/${id}/approve`)
}
// 拒绝旅行计划
export const rejectTravelPlan = async (id: number, reason: string): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>(`/travel/plans/${id}/reject`, { reason })
}
// 关闭旅行计划
export const closeTravelPlan = async (id: number): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>(`/travel/plans/${id}/close`)
}

View File

@@ -0,0 +1,81 @@
import { request } from '@/api'
import type { AxiosResponse } from 'axios'
// 用户状态类型
export type UserStatus = 'active' | 'inactive' | 'banned'
// 用户等级类型
export type UserLevel = number
// 用户数据结构
export interface User {
id: number
openid: string
nickname: string
avatar: string
gender: string
birthday: string
phone: string
email: string
status: UserStatus
level: UserLevel
points: number
created_at: string
updated_at: string
}
// 用户查询参数
export interface UserQueryParams {
page?: number
pageSize?: number
keyword?: string
status?: UserStatus
registerTime?: [string, string]
}
// API响应结构
export interface ApiResponse<T> {
success: boolean
code: number
message: string
data: T
pagination?: {
current: number
pageSize: number
total: number
totalPages: number
}
}
// 获取用户列表
export const getUsers = async (params?: UserQueryParams): Promise<ApiResponse<User[]>> => {
return request.get<ApiResponse<User[]>>('/users', { params })
}
// 获取用户详情
export const getUser = async (id: number): Promise<ApiResponse<User>> => {
return request.get<ApiResponse<User>>(`/users/${id}`)
}
// 创建用户
export const createUser = async (userData: Partial<User>): Promise<ApiResponse<User>> => {
return request.post<ApiResponse<User>>('/users', userData)
}
// 更新用户
export const updateUser = async (id: number, userData: Partial<User>): Promise<ApiResponse<User>> => {
return request.put<ApiResponse<User>>(`/users/${id}`, userData)
}
// 删除用户
export const deleteUser = async (id: number): Promise<ApiResponse<void>> => {
return request.delete<ApiResponse<void>>(`/users/${id}`)
}
// 批量操作用户
export const batchUsers = async (
ids: number[],
action: 'delete' | 'ban' | 'activate'
): Promise<ApiResponse<void>> => {
return request.post<ApiResponse<void>>('/users/batch', { ids, action })
}

View File

@@ -0,0 +1,35 @@
<template>
<div class="not-found">
<a-result
status="404"
title="404"
sub-title="抱歉您访问的页面不存在"
>
<template #extra>
<a-button type="primary" @click="goHome">
返回首页
</a-button>
</template>
</a-result>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
const goHome = () => {
router.push('/')
}
</script>
<style scoped lang="less">
.not-found {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f0f2f5;
}
</style>

View File

@@ -243,31 +243,8 @@ import {
CheckOutlined,
CloseOutlined
} from '@ant-design/icons-vue'
interface Animal {
id: number
name: string
type: string
breed: string
age: number
price: number
status: string
image_url: string
description: string
created_at: string
}
interface AnimalClaim {
id: number
animal_id: number
animal_name: string
animal_image: string
user_name: string
user_phone: string
status: string
applied_at: string
processed_at: string
}
import { getAnimals, deleteAnimal, getAnimalClaims, approveAnimalClaim, rejectAnimalClaim } from '@/api/animal'
import type { Animal, AnimalClaim } from '@/api/animal'
interface SearchForm {
keyword: string
@@ -295,52 +272,13 @@ const claimSearchForm = reactive<ClaimSearchForm>({
status: ''
})
// 模拟数据
const animalList = ref<Animal[]>([
{
id: 1,
name: '小白',
type: 'alpaca',
breed: '苏利羊驼',
age: 2,
price: 1000,
status: 'available',
image_url: 'https://api.dicebear.com/7.x/bottts/svg?seed=alpaca',
description: '温顺可爱的羊驼',
created_at: '2024-01-10'
},
{
id: 2,
name: '旺财',
type: 'dog',
breed: '金毛寻回犬',
age: 1,
price: 800,
status: 'claimed',
image_url: 'https://api.dicebear.com/7.x/bottts/svg?seed=dog',
description: '活泼聪明的金毛',
created_at: '2024-02-15'
}
])
const claimList = ref<AnimalClaim[]>([
{
id: 1,
animal_id: 1,
animal_name: '小白',
animal_image: 'https://api.dicebear.com/7.x/bottts/svg?seed=alpaca',
user_name: '张先生',
user_phone: '13800138000',
status: 'pending',
applied_at: '2024-03-01',
processed_at: ''
}
])
const animalList = ref<Animal[]>([])
const claimList = ref<AnimalClaim[]>([])
const pagination = reactive({
current: 1,
pageSize: 20,
total: 50,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number) => `共 ${total} 条记录`
@@ -349,7 +287,7 @@ const pagination = reactive({
const claimPagination = reactive({
current: 1,
pageSize: 20,
total: 30,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number) => `共 ${total} 条记录`
@@ -459,7 +397,7 @@ const claimColumns = [
},
{
title: '操作',
极速版 key: 'actions',
key: 'actions',
width: 150,
align: 'center'
}
@@ -537,7 +475,16 @@ onMounted(() => {
const loadAnimals = async () => {
loading.value = true
try {
await new Promise(resolve => setTimeout(resolve, 500))
const response = await getAnimals({
page: pagination.current,
pageSize: pagination.pageSize,
keyword: searchForm.keyword,
type: searchForm.type,
status: searchForm.status
})
animalList.value = response.data
pagination.total = response.pagination?.total || 0
} catch (error) {
message.error('加载动物列表失败')
} finally {
@@ -548,7 +495,15 @@ const loadAnimals = async () => {
const loadClaims = async () => {
claimLoading.value = true
try {
await new Promise(resolve => setTimeout(resolve, 500))
const response = await getAnimalClaims({
page: claimPagination.current,
pageSize: claimPagination.pageSize,
keyword: claimSearchForm.keyword,
status: claimSearchForm.status
})
claimList.value = response.data
claimPagination.total = response.pagination?.total || 0
} catch (error) {
message.error('加载认领记录失败')
} finally {
@@ -616,7 +571,7 @@ const handleEditAnimal = (record: Animal) => {
message.info(`编辑动物: ${record.name}`)
}
const handleDeleteAnimal = (record: Animal) => {
const handleDeleteAnimal = async (record: Animal) => {
Modal.confirm({
title: '确认删除',
content: `确定要删除动物 "${record.name}" 吗?`,
@@ -624,6 +579,7 @@ const handleDeleteAnimal = (record: Animal) => {
okType: 'danger',
onOk: async () => {
try {
await deleteAnimal(record.id)
message.success('动物已删除')
loadAnimals()
} catch (error) {
@@ -633,12 +589,13 @@ const handleDeleteAnimal = (record: Animal) => {
})
}
const handleApproveClaim = (record: AnimalClaim) => {
const handleApproveClaim = async (record: AnimalClaim) => {
Modal.confirm({
title: '确认通过',
content: `确定要通过用户 "${record.user_name}" 的认领申请吗?`,
onOk: async () => {
try {
await approveAnimalClaim(record.id)
message.success('认领申请已通过')
loadClaims()
} catch (error) {
@@ -648,12 +605,13 @@ const handleApproveClaim = (record: AnimalClaim) => {
})
}
const handleRejectClaim = (record: AnimalClaim) => {
const handleRejectClaim = async (record: AnimalClaim) => {
Modal.confirm({
title: '确认拒绝',
content: `确定要拒绝用户 "${record.user_name}" 的认领申请吗?`,
onOk: async () => {
try {
await rejectAnimalClaim(record.id, '拒绝原因')
message.success('认领申请已拒绝')
loadClaims()
} catch (error) {

View File

@@ -150,16 +150,8 @@ import {
StopOutlined,
PlayCircleOutlined
} from '@ant-design/icons-vue'
interface Merchant {
id: number
business_name: string
merchant_type: string
contact_person: string
contact_phone: string
status: string
created_at: string
}
import { getMerchants, approveMerchant, rejectMerchant, disableMerchant, enableMerchant } from '@/api/merchant'
import type { Merchant } from '@/api/merchant'
interface SearchForm {
keyword: string
@@ -175,32 +167,11 @@ const searchForm = reactive<SearchForm>({
status: ''
})
// 模拟商家数据
const merchantList = ref<Merchant[]>([
{
id: 1,
business_name: '鲜花坊',
merchant_type: 'flower_shop',
contact_person: '张经理',
contact_phone: '13800138000',
status: 'approved',
created_at: '2024-01-15'
},
{
id: 2,
business_name: '阳光农场',
merchant_type: 'farm_owner',
contact_person: '李场主',
contact_phone: '13800138001',
status: 'pending',
created_at: '2024-02-20'
}
])
const merchantList = ref<Merchant[]>([])
const pagination = reactive({
current: 1,
pageSize: 20,
total: 50,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number) => `共 ${total} 条记录`
@@ -300,8 +271,16 @@ onMounted(() => {
const loadMerchants = async () => {
loading.value = true
try {
// TODO: 调用真实API
await new Promise(resolve => setTimeout(resolve, 500))
const response = await getMerchants({
page: pagination.current,
pageSize: pagination.pageSize,
keyword: searchForm.keyword,
type: searchForm.type,
status: searchForm.status
})
merchantList.value = response.data
pagination.total = response.pagination?.total || 0
} catch (error) {
message.error('加载商家列表失败')
} finally {
@@ -345,6 +324,7 @@ const handleApprove = async (record: Merchant) => {
content: `确定要通过商家 "${record.business_name}" 的入驻申请吗?`,
onOk: async () => {
try {
await approveMerchant(record.id)
message.success('商家入驻申请已通过')
loadMerchants()
} catch (error) {
@@ -360,6 +340,7 @@ const handleReject = async (record: Merchant) => {
content: `确定要拒绝商家 "${record.business_name}" 的入驻申请吗?`,
onOk: async () => {
try {
await rejectMerchant(record.id, '拒绝原因')
message.success('商家入驻申请已拒绝')
loadMerchants()
} catch (error) {
@@ -375,6 +356,7 @@ const handleDisable = async (record: Merchant) => {
content: `确定要禁用商家 "${record.business_name}" 吗?`,
onOk: async () => {
try {
await disableMerchant(record.id)
message.success('商家已禁用')
loadMerchants()
} catch (error) {
@@ -390,6 +372,7 @@ const handleEnable = async (record: Merchant) => {
content: `确定要启用商家 "${record.business_name}" 吗?`,
onOk: async () => {
try {
await enableMerchant(record.id)
message.success('商家已启用')
loadMerchants()
} catch (error) {

View File

@@ -31,7 +31,7 @@
<a-select-option value="shipped">已发货</a-select-option>
<a-select-option value="completed">已完成</a-select-option>
<a-select-option value="cancelled">已取消</a-select-option>
<a-select-option value极速版="refunded">已退款</a-select-option>
<a-select-option value="refunded">已退款</a-select-option>
</a-select>
</a-form-item>
@@ -92,11 +92,11 @@
<template v-if="['pending', 'paid'].includes(record.status)">
<a-button size="small" danger @click="handleCancel(record)">
<极速版CloseOutlined />取消
<CloseOutlined />取消
</a-button>
</template>
<template v-if="record.status === '极速版paid'">
<template v-if="record.status === 'paid'">
<a-button size="small" danger @click="handleRefund(record)">
<RollbackOutlined />退款
</a-button>
@@ -120,10 +120,10 @@
<a-statistic title="今日销售额" :value="statistics.today_sales" :precision="2" prefix="¥" :value-style="{ color: '#cf1322' }" />
</a-col>
<a-col :span="6">
<a-statistic title="本月订单" :value="statistics.month_orders" :precision="0" :value-style="{ color: '#1890极速版ff' }" />
<a-statistic title="本月订单" :value="statistics.month_orders" :precision="0" :value-style="{ color: '#1890ff' }" />
</a-col>
<a-col :span="6">
<a-statistic title="本月销售额" :value="statistics.month_sales" :precision="2" prefix="¥" :极速版value-style="{ color: '#722ed1' }" />
<a-statistic title="本月销售额" :value="statistics.month_sales" :precision="2" prefix="¥" :value-style="{ color: '#722ed1' }" />
</a-col>
</a-row>
</a-card>
@@ -156,21 +156,8 @@ import {
RollbackOutlined,
ShoppingOutlined
} from '@ant-design/icons-vue'
interface Order {
id: number
order_no: string
user_id: number
user_name: string
user_phone: string
amount: number
status: string
payment_method: string
created_at: string
paid_at: string
shipped_at: string
completed_at: string
}
import { getOrders, updateOrderStatus, getOrderStatistics } from '@/api/order'
import type { Order, OrderStatistics } from '@/api/order'
interface SearchForm {
order_no: string
@@ -178,13 +165,6 @@ interface SearchForm {
orderTime: any[]
}
interface Statistics {
today_orders: number
today_sales: number
month_orders: number
month_sales: number
}
const activeTab = ref('orders')
const loading = ref(false)
@@ -194,48 +174,18 @@ const searchForm = reactive<SearchForm>({
orderTime: []
})
const statistics = reactive<Statistics>({
const statistics = reactive<OrderStatistics>({
today_orders: 0,
today_sales: 0,
month_orders: 0,
month_sales: 0
})
const orderList = ref<Order[]>([
{
id: 1,
order_no: 'ORD202403150001',
user_id: 1001,
user_name: '张先生',
user_phone: '13800138000',
amount: 299.99,
status: 'paid',
payment_method: 'wechat',
created_at: '2024-03-15 10:30:00',
paid_at: '2024-03-15 10:35:00',
shipped_at: '',
completed_at: ''
},
{
id: 2,
order_no: 'ORD202403140002',
user_id: 1002,
user_name: '李女士',
user_phone: '13800138001',
amount: 极速版199.99,
status: 'shipped',
payment_method: 'alipay',
created_at: '2024-03-14 14:20:00',
paid_at: '2024-03-14 14:25:00',
shipped_at: '2024-03-15 09:00:00',
completed_at: ''
}
])
const orderList = ref<Order[]>([])
const pagination = reactive({
current: 1,
pageSize: 20,
total: 50,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number) => `共 ${total} 条记录`
@@ -244,7 +194,7 @@ const pagination = reactive({
const orderColumns = [
{ title: '订单号', key: 'order_no', width: 160 },
{ title: '用户', dataIndex: 'user_name', key: 'user_name', width: 100 },
{ title: '联系电话', dataIndex: 'user极速版_phone', key: 'user_phone', width: 120 },
{ title: '联系电话', dataIndex: 'user_phone', key: 'user_phone', width: 120 },
{ title: '金额', key: 'amount', width: 100, align: 'center' },
{ title: '状态', key: 'status', width: 100, align: 'center' },
{ title: '支付方式', key: 'payment_method', width: 100, align: 'center' },
@@ -294,7 +244,15 @@ onMounted(() => {
const loadOrders = async () => {
loading.value = true
try {
await new Promise(resolve => setTimeout(resolve, 500))
const response = await getOrders({
page: pagination.current,
pageSize: pagination.pageSize,
order_no: searchForm.order_no,
status: searchForm.status
})
orderList.value = response.data
pagination.total = response.pagination?.total || 0
} catch (error) {
message.error('加载订单列表失败')
} finally {
@@ -304,10 +262,8 @@ const loadOrders = async () => {
const loadStatistics = async () => {
try {
statistics.today_orders = 15
statistics.today_sales = 4500.50
statistics.month_orders = 120
statistics.month_sales = 35600.80
const response = await getOrderStatistics()
Object.assign(statistics, response.data)
} catch (error) {
message.error('加载统计数据失败')
}
@@ -345,7 +301,7 @@ const handleRefresh = () => {
message.success('数据已刷新')
}
const handleTableChange: TableProps['onChange极速版'] = (pag) => {
const handleTableChange: TableProps['onChange'] = (pag) => {
pagination.current = pag.current!
pagination.pageSize = pag.pageSize!
loadOrders()
@@ -361,6 +317,7 @@ const handleShip = async (record: Order) => {
content: `确定要发货订单 "${record.order_no}" 吗?`,
onOk: async () => {
try {
await updateOrderStatus(record.id, 'shipped')
message.success('订单已发货')
loadOrders()
} catch (error) {
@@ -376,21 +333,23 @@ const handleComplete = async (record: Order) => {
content: `确定要完成订单 "${record.order_no}" 吗?`,
onOk: async () => {
try {
await updateOrderStatus(record.id, 'completed')
message.success('订单已完成')
loadOrders()
} catch (error) {
message.error('操作失败')
}
极速版 }
}
})
}
const handleCancel = async (record: Order) => {
Modal.confirm({
title: '确认取消',
content: `确定要取消订单 "${record.order极速版_no}" 吗?`,
on极速版Ok: async () => {
content: `确定要取消订单 "${record.order_no}" 吗?`,
onOk: async () => {
try {
await updateOrderStatus(record.id, 'cancelled')
message.success('订单已取消')
loadOrders()
} catch (error) {
@@ -406,6 +365,7 @@ const handleRefund = async (record: Order) => {
content: `确定要退款订单 "${record.order_no}" 吗?退款金额: ¥${record.amount}`,
onOk: async () => {
try {
await updateOrderStatus(record.id, 'refunded')
message.success('退款申请已提交')
loadOrders()
} catch (error) {

View File

@@ -15,7 +15,7 @@
</template>
</a-page-header>
<a-tabs v-model:activeKey="activeTab" @change="handle极速版TabChange">
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange">
<a-tab-pane key="activities" tab="推广活动">
<a-card>
<div class="search-container">
@@ -27,14 +27,14 @@
<a-form-item label="状态">
<a-select v-model:value="searchForm.status" placeholder="全部状态" style="width: 120px" allow-clear>
<a-select-option value="active">进行中</a-select-option>
<a-select-option value="upcoming">未开始</a-select-极速版option>
<a-select-option value="upcoming">未开始</a-select-option>
<a-select-option value="ended">已结束</a-select-option>
<a-select-option value="paused极速版">已暂停</a-select-option>
<a-select-option value="paused">已暂停</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="活动时间">
<a-range-picker v-model:value="searchForm.activityTime" :placeholder="['开始极速版时间', '结束时间']" />
<a-range-picker v-model:value="searchForm.activityTime" :placeholder="['开始时间', '结束时间']" />
</a-form-item>
<a-form-item>
@@ -67,7 +67,7 @@
<template v-else-if="column.key === 'reward_amount'">
<span v-if="record.reward_type === 'cash'">¥{{ record.reward_amount }}</span>
<span v-else-if="record.reward_type === 'points'">{{ record.reward_amount }}积分</span>
<span v-else-if="record.reward_type === 'coupon'">{{ record.re极速版ward_amount }}元券</span>
<span v-else-if="record.reward_type === 'coupon'">{{ record.reward_amount }}元券</span>
</template>
<template v-else-if="column.key === 'participants'">
@@ -89,10 +89,10 @@
<a-button size="small" @click="handleEditActivity(record)">
<EditOutlined />编辑
</极速版a-button>
</a-button>
<template v-if="record.status === 'active'">
极速版 <a-button size="small" danger @click="handlePauseActivity(record)">
<a-button size="small" danger @click="handlePauseActivity(record)">
<PauseOutlined />暂停
</a-button>
</template>
@@ -127,7 +127,7 @@
<a-select-option value="points">积分</a-select-option>
<a-select-option value="coupon">优惠券</a-select-option>
</a-select>
</极速版a-form-item>
</a-form-item>
<a-form-item label="状态">
<a-select v-model:value="rewardSearchForm.status" placeholder="全部状态" style="width: 120px" allow-clear>
@@ -152,7 +152,7 @@
:data-source="rewardList"
:loading="rewardLoading"
:pagination="rewardPagination"
:极速版row-key="record => record.id"
:row-key="record => record.id"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'reward_type'">
@@ -177,7 +177,7 @@
</a-button>
</template>
<a-button size="small" @click="handleViewRew极速版ard(record)">
<a-button size="small" @click="handleViewReward(record)">
<EyeOutlined />详情
</a-button>
</a-space>
@@ -185,7 +185,7 @@
</template>
</a-table>
</a-card>
</a极速版-tab-pane>
</a-tab-pane>
</a-tabs>
</div>
</template>
@@ -206,33 +206,15 @@ import {
CheckOutlined
} from '@ant-design/icons-vue'
interface PromotionActivity {
id: number
name: string
description: string
reward_type: string极速版
reward_amount: number
status: string
start_time: string
end_time: string
max_participants:极速版 number
current_participants: number
created_at: string
}
interface RewardRecord {
id: number
user_id: number
user_name: string
user_phone: string
activity_id: number
activity_name: string
reward_type: string
reward_amount: number
status: string
issued_at: string
created_at: string
}
import {
getPromotionActivities,
deletePromotionActivity,
pausePromotionActivity,
resumePromotionActivity,
getRewardRecords,
issueReward
} from '@/api/promotion'
import type { PromotionActivity, RewardRecord } from '@/api/promotion'
interface SearchForm {
name: string
@@ -262,55 +244,13 @@ const rewardSearchForm = reactive<RewardSearchForm>({
status: ''
})
const activityList = ref<PromotionActivity[]>([
{
id: 1,
name: '邀请好友得现金',
description: '邀请好友注册即可获得现金奖励',
reward_type: 'cash',
reward_amount: 10,
status: 'active',
start_time: '极速版2024-03-01',
end_time: '2024-03-31',
max_participants: 1000极速版,
current_participants: 356,
created_at: '2024-02-20'
},
{
id: 2,
name: '分享活动得积分',
description: '分享活动页面即可获得积分奖励',
reward_type: 'points',
reward_amount: 100,
status: 'upcoming',
start_time: '2024-04-01',
end_time: '202极速版4-04-30',
max_participants: 500,
current_p极速版articipants: 0,
created_at: '2024-03-10'
}
])
const rewardList = ref<RewardRecord[]>([
{
id: 1,
user_id: 1001,
user_name: '张先生',
user_phone: '13800138000',
activity_id: 1,
activity_name: '邀请好友极速版得现金',
reward_type: 'cash',
reward_amount: 10,
status: 'issued',
issued_at: '2024-03-05 14:30:00',
created_at: '2024-03-05 14:25:00'
}
])
const activityList = ref<PromotionActivity[]>([])
const rewardList = ref<RewardRecord[]>([])
const pagination = reactive({
current: 1,
pageSize: 20,
total: 50,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number) => `共 ${total} 条记录`
@@ -319,15 +259,15 @@ const pagination = reactive({
const rewardPagination = reactive({
current: 1,
pageSize: 20,
total: 30,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number) => `共 ${total} 条记录`
})
const activityColumns = [
{ title: '活动名称', dataIndex: 'name', key: '极速版name', width: 150 },
{ title极速版: '奖励类型', key: 'reward_type', width: 100, align: 'center' },
{ title: '活动名称', dataIndex: 'name', key: 'name', width: 150 },
{ title: '奖励类型', key: 'reward_type', width: 100, align: 'center' },
{ title: '奖励金额', key: 'reward_amount', width: 100, align: 'center' },
{ title: '状态', key: 'status', width: 100, align: 'center' },
{ title: '活动时间', key: 'time', width: 200,
@@ -343,10 +283,10 @@ const rewardColumns = [
{ title: '用户', dataIndex: 'user_name', key: 'user_name', width: 100 },
{ title: '联系电话', dataIndex: 'user_phone', key: 'user_phone', width: 120 },
{ title: '活动名称', dataIndex: 'activity_name', key: 'activity_name', width: 150 },
{ title: '奖励类型', key: 'reward_type', width: 100, align: 'center极速版' },
{ title: '奖励类型', key: 'reward_type', width: 100, align: 'center' },
{ title: '奖励金额', key: 'reward_amount', width: 100, align: 'center' },
{ title: '状态', key: 'status', width: 100, align: 'center' },
{ title: '发放时间', dataIndex: 'issued_at极速版', key: 'issued_at', width: 极速版150 },
{ title: '发放时间', dataIndex: 'issued_at', key: 'issued_at', width: 150 },
{ title: '申请时间', dataIndex: 'created_at', key: 'created_at', width: 150 },
{ title: '操作', key: 'actions', width: 120, align: 'center' }
]
@@ -361,7 +301,7 @@ const getStatusColor = (status: string) => {
return colors[status as keyof typeof colors] || 'default'
}
const getStatusText = (status: string)极速版 {
const getStatusText = (status: string) => {
const texts = {
active: '进行中',
upcoming: '未开始',
@@ -382,7 +322,7 @@ const getRewardTypeText = (type: string) => {
const getRewardStatusColor = (status: string) => {
const colors = {
pending极速版: 'orange',
pending: 'orange',
issued: 'green',
failed: 'red'
}
@@ -406,7 +346,15 @@ onMounted(() => {
const loadActivities = async () => {
loading.value = true
try {
await new Promise(resolve => setTimeout(resolve, 500))
const response = await getPromotionActivities({
page: pagination.current,
pageSize: pagination.pageSize,
name: searchForm.name,
status: searchForm.status
})
activityList.value = response.data
pagination.total = response.pagination?.total || 0
} catch (error) {
message.error('加载活动列表失败')
} finally {
@@ -417,7 +365,16 @@ const loadActivities = async () => {
const loadRewards = async () => {
rewardLoading.value = true
try {
await new Promise(resolve => setTimeout(resolve, 500))
const response = await getRewardRecords({
page: rewardPagination.current,
pageSize: rewardPagination.pageSize,
user: rewardSearchForm.user,
reward_type: rewardSearchForm.reward_type,
status: rewardSearchForm.status
})
rewardList.value = response.data
rewardPagination.total = response.pagination?.total || 0
} catch (error) {
message.error('加载奖励记录失败')
} finally {
@@ -430,7 +387,7 @@ const handleTabChange = (key: string) => {
loadActivities()
} else if (key === 'rewards') {
loadRewards()
极速版 }
}
}
const handleSearch = () => {
@@ -444,7 +401,7 @@ const handleReset = () => {
status: '',
activityTime: []
})
pagination.current = 1极速版
pagination.current = 1
loadActivities()
}
@@ -483,15 +440,16 @@ const handleViewActivity = (record: PromotionActivity) => {
}
const handleEditActivity = (record: PromotionActivity) => {
message.info(`编辑活动: ${record.name极速版}`)
message.info(`编辑活动: ${record.name}`)
}
const handlePauseActivity = async (record: PromotionActivity) => {
Modal.confirm({
title: '确认暂停',
content: `确定要暂停活动 "${record.name}" 极速版吗?`,
content: `确定要暂停活动 "${record.name}" 吗?`,
onOk: async () => {
try {
await pausePromotionActivity(record.id)
message.success('活动已暂停')
loadActivities()
} catch (error) {
@@ -507,6 +465,7 @@ const handleResumeActivity = async (record: PromotionActivity) => {
content: `确定要继续活动 "${record.name}" 吗?`,
onOk: async () => {
try {
await resumePromotionActivity(record.id)
message.success('活动已继续')
loadActivities()
} catch (error) {
@@ -524,6 +483,7 @@ const handleDeleteActivity = async (record: PromotionActivity) => {
okType: 'danger',
onOk: async () => {
try {
await deletePromotionActivity(record.id)
message.success('活动已删除')
loadActivities()
} catch (error) {
@@ -537,11 +497,12 @@ const handleIssueReward = async (record: RewardRecord) => {
Modal.confirm({
title: '确认发放',
content: `确定要发放奖励给用户 "${record.user_name}" 吗?`,
onOk: async ()极速版 => {
onOk: async () => {
try {
await issueReward(record.id)
message.success('奖励已发放')
loadRewards()
极速版 } catch (error) {
} catch (error) {
message.error('操作失败')
}
}

View File

@@ -15,10 +15,10 @@
<a-col :span="8">
<a-card title="系统信息" size="small">
<a-descriptions :column="1" bordered size="small">
<a-descriptions-item label="系统版本">v1.0.0</a-descriptions-item>
<a-descriptions-item label="运行环境">Production</a-descriptions-item>
<极速版a-descriptions-item label="启动时间">2024-03-15 10:00:00</a-descriptions-item>
<a-descriptions-item label="运行时长">12天3小时</a-descriptions-item>
<a-descriptions-item label="系统版本">{{ systemInfo.version }}</a-descriptions-item>
<a-descriptions-item label="运行环境">{{ systemInfo.environment }}</a-descriptions-item>
<a-descriptions-item label="启动时间">{{ systemInfo.startTime }}</a-descriptions-item>
<a-descriptions-item label="运行时长">{{ systemInfo.uptime }}</a-descriptions-item>
</a-descriptions>
</a-card>
</a-col>
@@ -27,11 +27,13 @@
<a-card title="数据库状态" size="small">
<a-descriptions :column="1" bordered size="small">
<a-descriptions-item label="连接状态">
<a-tag color="green">正常</a-tag>
<a-tag :color="databaseStatus.status === 'running' ? 'green' : 'red'">
{{ databaseStatus.status === 'running' ? '正常' : '异常' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label极速版="数据库类型">MySQL</a-descriptions-item>
<a-descriptions-item label="连接数">15极速版/100</a-descriptions-item>
<a-descriptions-item label="查询次数">1,234/分钟</a-descriptions-item>
<a-descriptions-item label="数据库类型">{{ databaseStatus.type }}</a-descriptions-item>
<a-descriptions-item label="连接数">{{ databaseStatus.connections }}</a-descriptions-item>
<a-descriptions-item label="查询次数">{{ databaseStatus.queriesPerMinute }}/分钟</a-descriptions-item>
</a-descriptions>
</a-card>
</a-col>
@@ -40,11 +42,13 @@
<a-card title="缓存状态" size="small">
<a-descriptions :column="1" bordered size="small">
<a-descriptions-item label="Redis状态">
<a-tag color="green">正常</a-tag>
<a-tag :color="cacheStatus.status === 'running' ? 'green' : 'red'">
{{ cacheStatus.status === 'running' ? '正常' : '异常' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="内存使用">65%</a-descriptions-item>
<a-descriptions-item label="命中率">92%</a-descriptions-item>
<a-descriptions-item label="键数量">1,234</a-descriptions-item>
<a-descriptions-item label="内存使用">{{ cacheStatus.memoryUsage }}</a-descriptions-item>
<a-descriptions-item label="命中率">{{ cacheStatus.hitRate }}</a-descriptions-item>
<a-descriptions-item label="键数量">{{ cacheStatus.keyCount }}</a-descriptions-item>
</a-descriptions>
</a-card>
</a-col>
@@ -90,14 +94,14 @@
<a-col :span="12">
<a-card title="系统日志" size="small">
<a-timeline>
<a-t极速版imeline-item color="green">
<a-timeline-item color="green">
<p>用户登录成功 - admin (2024-03-15 14:30:22)</p>
</a-timeline-item>
<a-timeline-item color="blue">
<p>数据库备份完成 - 备份文件: backup_20240315.sql (2024-03-15 14:00:00)</p>
</a-timeline-item>
<a-timeline-item color="orange">
<p>系统警告 - 内存使用率超过80% (2024-03-15 13:极速版45:18)</极速版p>
<p>系统警告 - 内存使用率超过80% (2024-03-15 13:45:18)</p>
</a-timeline-item>
<a-timeline-item color="green">
<p>定时任务执行 - 清理过期日志 (2024-03-15 13:30:00)</p>
@@ -112,7 +116,7 @@
<a-row :gutter="16" style="margin-top: 16px;">
<a-col :span="24">
<a-card title="系统极速版设置" size="small">
<a-card title="系统设置" size="small">
<a-form :model="systemSettings" layout="vertical">
<a-row :gutter="16">
<a-col :span="8">
@@ -122,17 +126,17 @@
</a-col>
<a-col :span="8">
<a-form-item label="系统版本">
<a-input v-model:value="systemSettings.systemVersion" placeholder="请输入系统版本极速版" />
<a-input v-model:value="systemSettings.systemVersion" placeholder="请输入系统版本" />
</a-form-item>
</极速版a-col>
</a-col>
<a-col :span="8">
<a-form-item label="维护模式">
<a-switch v-model:checked="systemSettings.maintenanceMode" />
</极速版a-form-item>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="极速版16">
<a-row :gutter="16">
<a-col :span="8">
<a-form-item label="会话超时(分钟)">
<a-input-number v-model:value="systemSettings.sessionTimeout" :min="5" :max="480" style="width: 100%" />
@@ -162,7 +166,7 @@
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ref, reactive, onMounted } from 'vue'
import { message, Modal } from 'ant-design-vue'
import {
ReloadOutlined,
@@ -170,47 +174,39 @@ import {
CloudServerOutlined,
MessageOutlined
} from '@ant-design/icons-vue'
import {
getServices,
startService,
stopService,
getSystemInfo,
getDatabaseStatus,
getCacheStatus,
getSystemSettings,
updateSystemSettings
} from '@/api/system'
import type { Service, SystemInfo, DatabaseStatus, CacheStatus, SystemSettings } from '@/api/system'
interface Service {
id: number
name: string
type: string
description: string
status: string
}
const services = ref<Service[]>([])
const systemInfo = ref<SystemInfo>({
version: 'v1.0.0',
environment: 'Production',
uptime: '12天3小时',
startTime: '2024-03-15 10:00:00'
})
interface SystemSettings {
systemName: string
systemVersion: string
maintenanceMode: boolean
sessionTimeout: number
pageSize: number
enableSwagger: boolean
}
const databaseStatus = ref<DatabaseStatus>({
status: 'running',
type: 'MySQL',
connections: '15/100',
queriesPerMinute: 1234
})
const services = ref<Service[]>([
{
id: 1,
name: 'MySQL数据库',
type: 'database',
description: '主数据库服务',
status: 'running'
},
{
id: 2,
name: 'Redis缓存',
type: 'cache',
description: '缓存服务',
status: 'running'
},
{
id: 3,
name: 'RabbitMQ',
type: 'mq',
description: '消息队列服务',
status: 'stopped'
}
])
const cacheStatus = ref<CacheStatus>({
status: 'running',
memoryUsage: '65%',
hitRate: '92%',
keyCount: 1234
})
const systemSettings = reactive<SystemSettings>({
systemName: '结伴客管理系统',
@@ -221,18 +217,76 @@ const systemSettings = reactive<SystemSettings>({
enableSwagger: true
})
onMounted(() => {
loadSystemInfo()
loadDatabaseStatus()
loadCacheStatus()
loadServices()
loadSystemSettings()
})
const loadSystemInfo = async () => {
try {
const response = await getSystemInfo()
systemInfo.value = response.data
} catch (error) {
message.error('加载系统信息失败')
}
}
const loadDatabaseStatus = async () => {
try {
const response = await getDatabaseStatus()
databaseStatus.value = response.data
} catch (error) {
message.error('加载数据库状态失败')
}
}
const loadCacheStatus = async () => {
try {
const response = await getCacheStatus()
cacheStatus.value = response.data
} catch (error) {
message.error('加载缓存状态失败')
}
}
const loadServices = async () => {
try {
const response = await getServices()
services.value = response.data
} catch (error) {
message.error('加载服务列表失败')
}
}
const loadSystemSettings = async () => {
try {
const response = await getSystemSettings()
Object.assign(systemSettings, response.data)
} catch (error) {
message.error('加载系统设置失败')
}
}
const handleRefresh = () => {
loadSystemInfo()
loadDatabaseStatus()
loadCacheStatus()
loadServices()
message.success('系统状态已刷新')
}
const handleStopService = (service: Service极速版) => {
const handleStopService = (service: Service) => {
Modal.confirm({
title: '确认停止',
content: `确定要停止服务 "${service.name}" 吗?`,
onOk: async () => {
try {
service.status = 'stopped'
await stopService(service.id)
message.success('服务已停止')
loadServices()
} catch (error) {
message.error('操作失败')
}
@@ -246,8 +300,9 @@ const handleStartService = (service: Service) => {
content: `确定要启动服务 "${service.name}" 吗?`,
onOk: async () => {
try {
service.status = 'running'
await startService(service.id)
message.success('服务已启动')
loadServices()
} catch (error) {
message.error('操作失败')
}
@@ -259,17 +314,22 @@ const viewLogs = () => {
message.info('查看系统日志功能开发中')
}
const saveSettings = () => {
message.success('系统设置已保存')
const saveSettings = async () => {
try {
await updateSystemSettings(systemSettings)
message.success('系统设置已保存')
} catch (error) {
message.error('保存设置失败')
}
}
const resetSettings = () => {
Object.assign(systemSettings, {
systemName: '结伴客管理系统',
systemVersion: 'v1.0.极速版0',
systemVersion: 'v1.0.0',
maintenanceMode: false,
sessionTimeout: 30,
pageSize: 极速版20,
pageSize: 20,
enableSwagger: true
})
message.success('设置已重置')

View File

@@ -150,19 +150,8 @@ import {
RocketOutlined,
CloseOutlined
} from '@ant-design/icons-vue'
interface TravelPlan {
id: number
destination: string
start_date: string
end_date: string
budget: number
max_members: number
current_members: number
status: string
creator: string
created_at: string
}
import { getTravelPlans, closeTravelPlan } from '@/api/travel'
import type { TravelPlan } from '@/api/travel'
interface SearchForm {
destination: string
@@ -178,38 +167,11 @@ const searchForm = reactive<SearchForm>({
travelTime: []
})
// 模拟旅行数据
const travelList = ref<TravelPlan[]>([
{
id: 1,
destination: '西藏',
start_date: '2024-07-01',
end_date: '2024-07-15',
budget: 5000,
max_members: 6,
current_members: 3,
status: 'recruiting',
creator: '旅行爱好者',
created_at: '2024-06-01'
},
{
id: 2,
destination: '云南',
start_date: '2024-08-10',
end_date: '2024-08-20',
budget: 3000,
max_members: 4,
current_members: 4,
status: 'full',
creator: '探险家',
created_at: '2024-07-15'
}
])
const travelList = ref<TravelPlan[]>([])
const pagination = reactive({
current: 1,
pageSize: 20,
total: 50,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number) => `共 ${total} 条记录`
@@ -289,8 +251,15 @@ onMounted(() => {
const loadTravelPlans = async () => {
loading.value = true
try {
// TODO: 调用真实API
await new Promise(resolve => setTimeout(resolve, 500))
const response = await getTravelPlans({
page: pagination.current,
pageSize: pagination.pageSize,
destination: searchForm.destination,
status: searchForm.status
})
travelList.value = response.data
pagination.total = response.pagination?.total || 0
} catch (error) {
message.error('加载旅行计划失败')
} finally {
@@ -299,7 +268,7 @@ const loadTravelPlans = async () => {
}
const handleSearch = () => {
pagination.current = 极速版1
pagination.current = 1
loadTravelPlans()
}
@@ -346,12 +315,13 @@ const handlePromote = (record: TravelPlan) => {
})
}
const handleClose = (record: TravelPlan) => {
const handleClose = async (record: TravelPlan) => {
Modal.confirm({
title: '确认关闭',
content: `确定要关闭旅行计划 "${record.destination}" 吗?`,
onOk: async () => {
try {
await closeTravelPlan(record.id)
message.success('旅行计划已关闭')
loadTravelPlans()
} catch (error) {

View File

@@ -251,20 +251,8 @@ import {
DownOutlined
} from '@ant-design/icons-vue'
import type { TableProps } from 'ant-design-vue'
interface User {
id: number
username: string
nickname: string
avatar: string
email: string
phone: string
status: string
level: number
points: number
created_at: string
updated_at: string
}
import { getUsers, updateUser, createUser, deleteUser } from '@/api/user'
import type { User } from '@/api/user'
interface SearchForm {
keyword: string
@@ -324,40 +312,11 @@ const formRules = {
]
}
// 模拟用户数据
const userList = ref<User[]>([
{
id: 1,
username: 'admin',
nickname: '系统管理员',
avatar: 'https://api.dicebear.com/7.x/miniavs/svg?seed=admin',
email: 'admin@jiebanke.com',
phone: '13800138000',
status: 'active',
level: 10,
points: 10000,
created_at: '2024-01-01',
updated_at: '2024-01-01'
},
{
id: 2,
username: 'user001',
nickname: '旅行爱好者',
avatar: 'https://api.dicebear.com/7.x/miniavs/svg?seed=user1',
email: 'user001@example.com',
phone: '13800138001',
status: 'active',
level: 3,
points: 1500,
created_at: '2024-02-15',
updated_at: '2024-02-15'
}
])
const userList = ref<User[]>([])
const pagination = reactive({
current: 1,
pageSize: 20,
total: 50,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number) => `共 ${total} 条记录`
@@ -458,15 +417,15 @@ onMounted(() => {
const loadUsers = async () => {
loading.value = true
try {
// TODO: 调用真实API
// const response = await userAPI.getUsers({
// page: pagination.current,
// pageSize: pagination.pageSize,
// ...searchForm
// })
await new Promise(resolve => setTimeout(resolve, 500))
// userList.value = response.data.users
// pagination.total = response.data.pagination.total
const response = await getUsers({
page: pagination.current,
pageSize: pagination.pageSize,
keyword: searchForm.keyword,
status: searchForm.status
})
userList.value = response.data
pagination.total = response.pagination?.total || 0
} catch (error) {
message.error('加载用户列表失败')
} finally {
@@ -539,7 +498,7 @@ const handleDisable = async (record: User) => {
content: `确定要禁用用户 "${record.nickname}" 吗?`,
onOk: async () => {
try {
// await userAPI.updateUser(record.id, { status: 'inactive' })
await updateUser(record.id, { status: 'inactive' })
message.success('用户已禁用')
loadUsers()
} catch (error) {
@@ -555,7 +514,7 @@ const handleEnable = async (record: User) => {
content: `确定要启用用户 "${record.nickname}" 吗?`,
onOk: async () => {
try {
// await userAPI.updateUser(record.id, { status: 'active' })
await updateUser(record.id, { status: 'active' })
message.success('用户已启用')
loadUsers()
} catch (error) {
@@ -571,7 +530,7 @@ const handleBan = async (record: User) => {
content: `确定要封禁用户 "${record.nickname}" 吗?`,
onOk: async () => {
try {
// await userAPI.updateUser(record.id, { status: 'banned' })
await updateUser(record.id, { status: 'banned' })
message.success('用户已封禁')
loadUsers()
} catch (error) {
@@ -590,7 +549,7 @@ const handleDelete = async (record: User) => {
cancelText: '取消',
onOk: async () => {
try {
// await userAPI.deleteUser(record.id)
await deleteUser(record.id)
message.success('用户已删除')
loadUsers()
} catch (error) {
@@ -607,11 +566,11 @@ const handleModalOk = async () => {
if (editingUser.value) {
// 编辑用户
// await userAPI.updateUser(editingUser.value.id, formState)
await updateUser(editingUser.value.id, formState)
message.success('用户信息更新成功')
} else {
// 创建用户
// await userAPI.createUser(formState)
await createUser(formState)
message.success('用户创建成功')
}