feat(backend): 添加 Swagger 文档并优化认证接口
- 在 .env 文件中添加 ENABLE_SWAGGER 环境变量 - 在 app.js 中集成 Swagger UI - 重构 auth 路由,添加请求参数验证 - 更新 API 文档,遵循 OpenAPI 3.0 规范 -优化认证接口的错误处理和响应格式
This commit is contained in:
201
admin-system/package-lock.json
generated
201
admin-system/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
110
admin-system/src/api/animal.ts
Normal file
110
admin-system/src/api/animal.ts
Normal 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 })
|
||||
}
|
||||
@@ -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
|
||||
73
admin-system/src/api/merchant.ts
Normal file
73
admin-system/src/api/merchant.ts
Normal 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`)
|
||||
}
|
||||
95
admin-system/src/api/order.ts
Normal file
95
admin-system/src/api/order.ts
Normal 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')
|
||||
}
|
||||
133
admin-system/src/api/promotion.ts
Normal file
133
admin-system/src/api/promotion.ts
Normal 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')
|
||||
}
|
||||
120
admin-system/src/api/system.ts
Normal file
120
admin-system/src/api/system.ts
Normal 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')
|
||||
}
|
||||
68
admin-system/src/api/travel.ts
Normal file
68
admin-system/src/api/travel.ts
Normal 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`)
|
||||
}
|
||||
81
admin-system/src/api/user.ts
Normal file
81
admin-system/src/api/user.ts
Normal 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 })
|
||||
}
|
||||
35
admin-system/src/pages/NotFound.vue
Normal file
35
admin-system/src/pages/NotFound.vue
Normal 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>
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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('操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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('设置已重置')
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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('用户创建成功')
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
NODE_ENV=development
|
||||
PORT=3001
|
||||
HOST=0.0.0.0
|
||||
ENABLE_SWAGGER=true
|
||||
|
||||
# MySQL数据库配置
|
||||
DB_HOST=192.168.0.240
|
||||
@@ -47,8 +48,4 @@ WECHAT_SECRET=your-wechat-secret
|
||||
|
||||
# 文件上传配置
|
||||
UPLOAD_MAX_SIZE=10485760
|
||||
UPLOAD_ALLOWED_TYPES=image/jpeg,image/png,image/gif
|
||||
|
||||
# 调试配置
|
||||
DEBUG=jiebanke:*
|
||||
LOG_LEVEL=info
|
||||
UPLOAD_ALLOWED_TYPES=image/jpeg,image/png,image/gif
|
||||
225
backend/package-lock.json
generated
225
backend/package-lock.json
generated
@@ -27,6 +27,8 @@
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"mysql2": "^3.14.3",
|
||||
"redis": "^5.8.2",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"swagger-ui-express": "^5.0.1",
|
||||
"winston": "^3.11.0",
|
||||
"xss-clean": "^0.1.4"
|
||||
},
|
||||
@@ -53,6 +55,46 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@apidevtools/json-schema-ref-parser": {
|
||||
"version": "9.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz",
|
||||
"integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==",
|
||||
"dependencies": {
|
||||
"@jsdevtools/ono": "^7.1.3",
|
||||
"@types/json-schema": "^7.0.6",
|
||||
"call-me-maybe": "^1.0.1",
|
||||
"js-yaml": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@apidevtools/openapi-schemas": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz",
|
||||
"integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@apidevtools/swagger-methods": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz",
|
||||
"integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg=="
|
||||
},
|
||||
"node_modules/@apidevtools/swagger-parser": {
|
||||
"version": "10.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz",
|
||||
"integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==",
|
||||
"dependencies": {
|
||||
"@apidevtools/json-schema-ref-parser": "^9.0.6",
|
||||
"@apidevtools/openapi-schemas": "^2.0.4",
|
||||
"@apidevtools/swagger-methods": "^3.0.2",
|
||||
"@jsdevtools/ono": "^7.1.3",
|
||||
"call-me-maybe": "^1.0.1",
|
||||
"z-schema": "^5.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"openapi-types": ">=7"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||
@@ -1047,6 +1089,11 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsdevtools/ono": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
|
||||
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="
|
||||
},
|
||||
"node_modules/@mongodb-js/saslprep": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz",
|
||||
@@ -1166,6 +1213,12 @@
|
||||
"@redis/client": "^5.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@scarf/scarf": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
|
||||
"integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
|
||||
"hasInstallScript": true
|
||||
},
|
||||
"node_modules/@sinclair/typebox": {
|
||||
"version": "0.27.8",
|
||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
||||
@@ -1264,6 +1317,11 @@
|
||||
"@types/istanbul-lib-report": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
|
||||
@@ -1451,8 +1509,7 @@
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
@@ -1597,8 +1654,7 @@
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"node_modules/basic-auth": {
|
||||
"version": "2.0.1",
|
||||
@@ -1673,7 +1729,6 @@
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -1801,6 +1856,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/call-me-maybe": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
|
||||
"integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ=="
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -2028,6 +2088,14 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz",
|
||||
"integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/component-emitter": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
|
||||
@@ -2040,8 +2108,7 @@
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
},
|
||||
"node_modules/concat-stream": {
|
||||
"version": "1.6.2",
|
||||
@@ -2264,7 +2331,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
||||
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esutils": "^2.0.2"
|
||||
},
|
||||
@@ -2574,7 +2640,6 @@
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -2919,8 +2984,7 @@
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
@@ -3268,7 +3332,6 @@
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
@@ -4059,7 +4122,6 @@
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
@@ -4245,6 +4307,12 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lodash.get": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||
"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
|
||||
"deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead."
|
||||
},
|
||||
"node_modules/lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
@@ -4255,6 +4323,12 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
|
||||
},
|
||||
"node_modules/lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
||||
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead."
|
||||
},
|
||||
"node_modules/lodash.isinteger": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||
@@ -4281,6 +4355,11 @@
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.mergewith": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
|
||||
"integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ=="
|
||||
},
|
||||
"node_modules/lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
@@ -4465,7 +4544,6 @@
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
@@ -4855,7 +4933,6 @@
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@@ -4883,6 +4960,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/openapi-types": {
|
||||
"version": "12.1.3",
|
||||
"resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz",
|
||||
"integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
@@ -4990,7 +5073,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -5949,6 +6031,78 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/swagger-jsdoc": {
|
||||
"version": "6.2.8",
|
||||
"resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz",
|
||||
"integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==",
|
||||
"dependencies": {
|
||||
"commander": "6.2.0",
|
||||
"doctrine": "3.0.0",
|
||||
"glob": "7.1.6",
|
||||
"lodash.mergewith": "^4.6.2",
|
||||
"swagger-parser": "^10.0.3",
|
||||
"yaml": "2.0.0-1"
|
||||
},
|
||||
"bin": {
|
||||
"swagger-jsdoc": "bin/swagger-jsdoc.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/swagger-jsdoc/node_modules/glob": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/swagger-parser": {
|
||||
"version": "10.0.3",
|
||||
"resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz",
|
||||
"integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==",
|
||||
"dependencies": {
|
||||
"@apidevtools/swagger-parser": "10.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/swagger-ui-dist": {
|
||||
"version": "5.28.0",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.28.0.tgz",
|
||||
"integrity": "sha512-I9ibQtr77BPzT28WFWMVktzQOtWzoSS2J99L0Att8gDar1atl1YTRI7NUFSr4kj8VvWICgylanYHIoHjITc7iA==",
|
||||
"dependencies": {
|
||||
"@scarf/scarf": "=1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/swagger-ui-express": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz",
|
||||
"integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==",
|
||||
"dependencies": {
|
||||
"swagger-ui-dist": ">=5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= v0.10.32"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"express": ">=4.0.0 || >=5.0.0-beta"
|
||||
}
|
||||
},
|
||||
"node_modules/test-exclude": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
|
||||
@@ -6322,8 +6476,7 @@
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
},
|
||||
"node_modules/write-file-atomic": {
|
||||
"version": "4.0.2",
|
||||
@@ -6375,6 +6528,14 @@
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.0.0-1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz",
|
||||
"integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "17.7.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||
@@ -6413,6 +6574,34 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/z-schema": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz",
|
||||
"integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==",
|
||||
"dependencies": {
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"validator": "^13.7.0"
|
||||
},
|
||||
"bin": {
|
||||
"z-schema": "bin/z-schema"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"commander": "^9.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/z-schema/node_modules/commander": {
|
||||
"version": "9.5.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
|
||||
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": "^12.20.0 || >=14"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"mysql2": "^3.14.3",
|
||||
"redis": "^5.8.2",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"swagger-ui-express": "^5.0.1",
|
||||
"winston": "^3.11.0",
|
||||
"xss-clean": "^0.1.4"
|
||||
},
|
||||
@@ -49,4 +51,4 @@
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ const morgan = require('morgan')
|
||||
const rateLimit = require('express-rate-limit')
|
||||
const xss = require('xss-clean')
|
||||
const hpp = require('hpp')
|
||||
const swaggerUi = require('swagger-ui-express')
|
||||
const swaggerSpec = require('./config/swagger')
|
||||
|
||||
console.log('🔧 初始化Express应用...')
|
||||
|
||||
@@ -70,6 +72,12 @@ app.use(hpp({ // 防止参数污染
|
||||
// 静态文件服务
|
||||
app.use('/uploads', express.static('uploads'))
|
||||
|
||||
// Swagger文档路由
|
||||
if (process.env.NODE_ENV === 'development' || process.env.ENABLE_SWAGGER === 'true') {
|
||||
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec))
|
||||
console.log('📚 Swagger文档已启用: http://localhost:3001/api-docs')
|
||||
}
|
||||
|
||||
// 健康检查路由
|
||||
app.get('/health', (req, res) => {
|
||||
res.status(200).json({
|
||||
|
||||
133
backend/src/config/swagger.js
Normal file
133
backend/src/config/swagger.js
Normal file
@@ -0,0 +1,133 @@
|
||||
const swaggerJsdoc = require('swagger-jsdoc')
|
||||
|
||||
const options = {
|
||||
definition: {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: '结伴客API文档',
|
||||
version: '1.0.0',
|
||||
description: '结伴客小程序后端API接口文档'
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
url: 'http://localhost:3001/api/v1',
|
||||
description: '开发环境服务器'
|
||||
},
|
||||
{
|
||||
url: 'https://your-domain.com/api/v1',
|
||||
description: '生产环境服务器'
|
||||
}
|
||||
],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
bearerAuth: {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
bearerFormat: 'JWT'
|
||||
}
|
||||
},
|
||||
schemas: {
|
||||
User: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
description: '用户ID'
|
||||
},
|
||||
openid: {
|
||||
type: 'string',
|
||||
description: '微信openid'
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
description: '用户名'
|
||||
},
|
||||
nickname: {
|
||||
type: 'string',
|
||||
description: '昵称'
|
||||
},
|
||||
avatar: {
|
||||
type: 'string',
|
||||
description: '头像URL'
|
||||
},
|
||||
gender: {
|
||||
type: 'string',
|
||||
enum: ['male', 'female', 'other'],
|
||||
description: '性别'
|
||||
},
|
||||
birthday: {
|
||||
type: 'string',
|
||||
format: 'date',
|
||||
description: '生日'
|
||||
},
|
||||
phone: {
|
||||
type: 'string',
|
||||
description: '手机号'
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
description: '邮箱'
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['active', 'inactive', 'banned'],
|
||||
description: '用户状态'
|
||||
},
|
||||
level: {
|
||||
type: 'integer',
|
||||
description: '用户等级'
|
||||
},
|
||||
points: {
|
||||
type: 'integer',
|
||||
description: '用户积分'
|
||||
},
|
||||
created_at: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '创建时间'
|
||||
},
|
||||
updated_at: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '更新时间'
|
||||
}
|
||||
}
|
||||
},
|
||||
ApiResponse: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: '请求是否成功'
|
||||
},
|
||||
code: {
|
||||
type: 'integer',
|
||||
description: '状态码'
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
description: '响应消息'
|
||||
},
|
||||
data: {
|
||||
type: 'object',
|
||||
description: '响应数据'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
]
|
||||
},
|
||||
apis: [
|
||||
'./src/routes/*.js',
|
||||
'./src/controllers/*.js'
|
||||
]
|
||||
}
|
||||
|
||||
const specs = swaggerJsdoc(options)
|
||||
|
||||
module.exports = specs
|
||||
@@ -1,33 +1,329 @@
|
||||
const express = require('express')
|
||||
const { catchAsync } = require('../utils/errors')
|
||||
const { authenticate, optionalAuthenticate } = require('../middleware/auth')
|
||||
const {
|
||||
register,
|
||||
login,
|
||||
getCurrentUser,
|
||||
updateProfile,
|
||||
changePassword,
|
||||
wechatLogin
|
||||
} = require('../controllers/authControllerMySQL')
|
||||
const { body } = require('express-validator')
|
||||
const authController = require('../controllers/authControllerMySQL')
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
// 用户注册
|
||||
router.post('/register', catchAsync(register))
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: Auth
|
||||
* description: 用户认证相关接口
|
||||
*/
|
||||
|
||||
// 用户登录
|
||||
router.post('/login', catchAsync(login))
|
||||
/**
|
||||
* @swagger
|
||||
* /auth/register:
|
||||
* post:
|
||||
* summary: 用户注册
|
||||
* tags: [Auth]
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - username
|
||||
* - password
|
||||
* properties:
|
||||
* username:
|
||||
* type: string
|
||||
* description: 用户名
|
||||
* example: testuser
|
||||
* password:
|
||||
* type: string
|
||||
* description: 密码
|
||||
* example: password123
|
||||
* nickname:
|
||||
* type: string
|
||||
* description: 昵称
|
||||
* example: 测试用户
|
||||
* email:
|
||||
* type: string
|
||||
* description: 邮箱
|
||||
* example: test@example.com
|
||||
* phone:
|
||||
* type: string
|
||||
* description: 手机号
|
||||
* example: 13800000000
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 注册成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* user:
|
||||
* $ref: '#/components/schemas/User'
|
||||
* token:
|
||||
* type: string
|
||||
* message:
|
||||
* type: string
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.post(
|
||||
'/register',
|
||||
[
|
||||
body('username').notEmpty().withMessage('用户名不能为空'),
|
||||
body('password').isLength({ min: 6 }).withMessage('密码长度不能少于6位')
|
||||
],
|
||||
authController.register
|
||||
)
|
||||
|
||||
// 微信登录
|
||||
router.post('/wechat-login', catchAsync(wechatLogin))
|
||||
/**
|
||||
* @swagger
|
||||
* /auth/login:
|
||||
* post:
|
||||
* summary: 用户登录
|
||||
* tags: [Auth]
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - username
|
||||
* - password
|
||||
* properties:
|
||||
* username:
|
||||
* type: string
|
||||
* description: 用户名/邮箱/手机号
|
||||
* example: testuser
|
||||
* password:
|
||||
* type: string
|
||||
* description: 密码
|
||||
* example: password123
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 登录成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* user:
|
||||
* $ref: '#/components/schemas/User'
|
||||
* token:
|
||||
* type: string
|
||||
* message:
|
||||
* type: string
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 401:
|
||||
* description: 用户名或密码错误
|
||||
* 404:
|
||||
* description: 用户不存在
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.post(
|
||||
'/login',
|
||||
[
|
||||
body('username').notEmpty().withMessage('用户名不能为空'),
|
||||
body('password').notEmpty().withMessage('密码不能为空')
|
||||
],
|
||||
authController.login
|
||||
)
|
||||
|
||||
// 获取当前用户信息(需要认证)
|
||||
router.get('/me', authenticate, catchAsync(getCurrentUser))
|
||||
/**
|
||||
* @swagger
|
||||
* /auth/me:
|
||||
* get:
|
||||
* summary: 获取当前用户信息
|
||||
* tags: [Auth]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* user:
|
||||
* $ref: '#/components/schemas/User'
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/me', authController.getCurrentUser)
|
||||
|
||||
// 更新用户信息(需要认证)
|
||||
router.put('/profile', authenticate, catchAsync(updateProfile))
|
||||
/**
|
||||
* @swagger
|
||||
* /auth/profile:
|
||||
* put:
|
||||
* summary: 更新用户个人信息
|
||||
* tags: [Auth]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* nickname:
|
||||
* type: string
|
||||
* description: 昵称
|
||||
* avatar:
|
||||
* type: string
|
||||
* description: 头像URL
|
||||
* gender:
|
||||
* type: string
|
||||
* enum: [male, female, other]
|
||||
* description: 性别
|
||||
* birthday:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 生日
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* user:
|
||||
* $ref: '#/components/schemas/User'
|
||||
* message:
|
||||
* type: string
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.put('/profile', authController.updateProfile)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /auth/password:
|
||||
* put:
|
||||
* summary: 修改密码
|
||||
* tags: [Auth]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - currentPassword
|
||||
* - newPassword
|
||||
* properties:
|
||||
* currentPassword:
|
||||
* type: string
|
||||
* description: 当前密码
|
||||
* newPassword:
|
||||
* type: string
|
||||
* description: 新密码
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 修改成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 401:
|
||||
* description: 当前密码错误
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.put(
|
||||
'/password',
|
||||
[
|
||||
body('currentPassword').notEmpty().withMessage('当前密码不能为空'),
|
||||
body('newPassword').isLength({ min: 6 }).withMessage('新密码长度不能少于6位')
|
||||
],
|
||||
authController.changePassword
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /auth/wechat:
|
||||
* post:
|
||||
* summary: 微信登录/注册
|
||||
* tags: [Auth]
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - code
|
||||
* properties:
|
||||
* code:
|
||||
* type: string
|
||||
* description: 微信授权码
|
||||
* userInfo:
|
||||
* type: object
|
||||
* description: 微信用户信息
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 登录成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* user:
|
||||
* $ref: '#/components/schemas/User'
|
||||
* token:
|
||||
* type: string
|
||||
* message:
|
||||
* type: string
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.post('/wechat', authController.wechatLogin)
|
||||
|
||||
// 修改密码(需要认证)
|
||||
router.put('/password', authenticate, catchAsync(changePassword))
|
||||
|
||||
module.exports = router
|
||||
@@ -96,8 +96,8 @@ const startServer = async () => {
|
||||
console.log(`📊 环境: ${process.env.NODE_ENV || 'development'}`)
|
||||
console.log(`⏰ 启动时间: ${new Date().toLocaleString()}`)
|
||||
console.log('💾 数据库: MySQL')
|
||||
console.log(`🔴 Redis: ${redisConfig.isConnected() ? '已连接' : '未连接'}`)
|
||||
console.log(`🐰 RabbitMQ: ${rabbitMQConfig.isConnected() ? '已连接' : '未连接'}`)
|
||||
console.log(`🔴 Redis: ${redisConfig.isConnected ? '已连接' : '未连接'}`)
|
||||
console.log(`🐰 RabbitMQ: ${rabbitMQConfig.isConnected ? '已连接' : '未连接'}`)
|
||||
console.log('========================================\n')
|
||||
})
|
||||
|
||||
@@ -130,7 +130,7 @@ const startServer = async () => {
|
||||
}
|
||||
|
||||
// 关闭RabbitMQ连接
|
||||
if (rabbitMQConfig.isConnected()) {
|
||||
if (rabbitMQConfig.isConnected) {
|
||||
console.log('🔐 关闭RabbitMQ连接...')
|
||||
await rabbitMQConfig.close()
|
||||
console.log('✅ RabbitMQ连接已关闭')
|
||||
|
||||
30
backend/test-swagger.js
Normal file
30
backend/test-swagger.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const http = require('http');
|
||||
|
||||
// 发送请求到Swagger UI
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 3001,
|
||||
path: '/api-docs/',
|
||||
method: 'GET'
|
||||
};
|
||||
|
||||
const req = http.request(options, (res) => {
|
||||
console.log(`状态码: ${res.statusCode}`);
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
// 检查响应中是否包含Swagger UI的关键字
|
||||
if (chunk.toString().includes('Swagger UI')) {
|
||||
console.log('Swagger UI 已成功启动并运行');
|
||||
} else {
|
||||
console.log('收到响应,但可能不是Swagger UI页面');
|
||||
}
|
||||
// 只输出前200个字符来检查内容
|
||||
console.log('响应前200字符:', chunk.toString().substring(0, 200));
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
console.error('请求出错:', error.message);
|
||||
});
|
||||
|
||||
req.end();
|
||||
183
package-lock.json
generated
183
package-lock.json
generated
@@ -6,6 +6,9 @@
|
||||
"": {
|
||||
"dependencies": {
|
||||
"mysql2": "^3.14.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"less": "^4.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-ssl-profiles": {
|
||||
@@ -16,6 +19,18 @@
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"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/denque": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||
@@ -24,6 +39,19 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"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/generate-function": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
|
||||
@@ -32,6 +60,13 @@
|
||||
"is-property": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"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/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
@@ -43,11 +78,56 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"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/is-property": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
|
||||
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="
|
||||
},
|
||||
"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/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/long": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||
@@ -75,6 +155,33 @@
|
||||
"url": "https://github.com/sponsors/wellwelwel"
|
||||
}
|
||||
},
|
||||
"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/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/mysql2": {
|
||||
"version": "3.14.3",
|
||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.3.tgz",
|
||||
@@ -105,16 +212,86 @@
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"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/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/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/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/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"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/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/seq-queue": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
|
||||
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
|
||||
},
|
||||
"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/sqlstring": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
|
||||
@@ -122,6 +299,12 @@
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"mysql2": "^3.14.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"less": "^4.4.1"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user