diff --git a/admin-system/src/api/supplier.ts b/admin-system/src/api/supplier.ts new file mode 100644 index 0000000..9cbf1c8 --- /dev/null +++ b/admin-system/src/api/supplier.ts @@ -0,0 +1,75 @@ +import request from '@/utils/request' +import type { + Supplier, + SupplierListParams, + SupplierCreateForm, + SupplierUpdateForm, + SupplierStatistics +} from '@/types/supplier' + +// 获取供应商列表 +export const getSupplierList = (params: SupplierListParams) => { + return request<{ + list: Supplier[] + pagination: { + page: number + pageSize: number + total: number + totalPages: number + } + }>({ + url: '/suppliers', + method: 'GET', + params + }) +} + +// 获取供应商详情 +export const getSupplierDetail = (id: number) => { + return request({ + url: `/suppliers/${id}`, + method: 'GET' + }) +} + +// 创建供应商 +export const createSupplier = (data: SupplierCreateForm) => { + return request({ + url: '/suppliers', + method: 'POST', + data: { + ...data, + cattleTypes: Array.isArray(data.cattleTypes) ? data.cattleTypes : JSON.parse(data.cattleTypes || '[]'), + certifications: Array.isArray(data.certifications) ? data.certifications : JSON.parse(data.certifications || '[]') + } + }) +} + +// 更新供应商 +export const updateSupplier = (id: number, data: SupplierUpdateForm) => { + return request({ + url: `/suppliers/${id}`, + method: 'PUT', + data: { + ...data, + cattleTypes: data.cattleTypes ? (Array.isArray(data.cattleTypes) ? data.cattleTypes : JSON.parse(data.cattleTypes)) : undefined, + certifications: data.certifications ? (Array.isArray(data.certifications) ? data.certifications : JSON.parse(data.certifications)) : undefined + } + }) +} + +// 删除供应商 +export const deleteSupplier = (id: number) => { + return request({ + url: `/suppliers/${id}`, + method: 'DELETE' + }) +} + +// 获取供应商统计信息 +export const getSupplierStats = () => { + return request({ + url: '/suppliers/stats/overview', + method: 'GET' + }) +} \ No newline at end of file diff --git a/admin-system/src/api/transport.ts b/admin-system/src/api/transport.ts new file mode 100644 index 0000000..b5408e2 --- /dev/null +++ b/admin-system/src/api/transport.ts @@ -0,0 +1,142 @@ +import request from '@/utils/request'; +import type { + Transport, + TransportCreateForm, + TransportUpdateForm, + TransportListParams, + Vehicle, + VehicleCreateForm, + VehicleUpdateForm, + VehicleListParams +} from '@/types/transport'; +import type { PaginatedResponse, ApiResponse } from '@/types/api'; + +// 运输管理相关API接口 + +/** + * 获取运输列表 + * @param params 查询参数 + * @returns 运输列表 + */ +export const getTransportList = (params: TransportListParams): Promise>> => { + return request({ + url: '/transports', + method: 'get', + params + }); +}; + +/** + * 获取运输详情 + * @param id 运输ID + * @returns 运输详情 + */ +export const getTransportDetail = (id: number): Promise> => { + return request({ + url: `/transports/${id}`, + method: 'get' + }); +}; + +/** + * 创建运输记录 + * @param data 运输创建表单 + * @returns 创建的运输记录 + */ +export const createTransport = (data: TransportCreateForm): Promise> => { + return request({ + url: '/transports', + method: 'post', + data + }); +}; + +/** + * 更新运输记录 + * @param id 运输ID + * @param data 运输更新表单 + * @returns 更新的运输记录 + */ +export const updateTransport = (id: number, data: TransportUpdateForm): Promise> => { + return request({ + url: `/transports/${id}`, + method: 'put', + data + }); +}; + +/** + * 删除运输记录 + * @param id 运输ID + * @returns 删除结果 + */ +export const deleteTransport = (id: number): Promise> => { + return request({ + url: `/transports/${id}`, + method: 'delete' + }); +}; + +/** + * 获取车辆列表 + * @param params 查询参数 + * @returns 车辆列表 + */ +export const getVehicleList = (params: VehicleListParams): Promise>> => { + return request({ + url: '/transports/vehicles', + method: 'get', + params + }); +}; + +/** + * 获取车辆详情 + * @param id 车辆ID + * @returns 车辆详情 + */ +export const getVehicleDetail = (id: number): Promise> => { + return request({ + url: `/transports/vehicles/${id}`, + method: 'get' + }); +}; + +/** + * 创建车辆记录 + * @param data 车辆创建表单 + * @returns 创建的车辆记录 + */ +export const createVehicle = (data: VehicleCreateForm): Promise> => { + return request({ + url: '/transports/vehicles', + method: 'post', + data + }); +}; + +/** + * 更新车辆记录 + * @param id 车辆ID + * @param data 车辆更新表单 + * @returns 更新的车辆记录 + */ +export const updateVehicle = (id: number, data: VehicleUpdateForm): Promise> => { + return request({ + url: `/transports/vehicles/${id}`, + method: 'put', + data + }); +}; + +/** + * 删除车辆记录 + * @param id 车辆ID + * @returns 删除结果 + */ +export const deleteVehicle = (id: number): Promise> => { + return request({ + url: `/transports/vehicles/${id}`, + method: 'delete' + }); +}; \ No newline at end of file diff --git a/admin-system/src/types/api.ts b/admin-system/src/types/api.ts new file mode 100644 index 0000000..137ec22 --- /dev/null +++ b/admin-system/src/types/api.ts @@ -0,0 +1,24 @@ +// API响应相关类型定义 + +/** + * 分页响应数据结构 + */ +export interface PaginatedResponse { + list: T[]; + pagination: { + page: number; + pageSize: number; + total: number; + totalPages: number; + }; +} + +/** + * API响应基础结构 + */ +export interface ApiResponse { + success: boolean; + data: T; + message: string; + code: number; +} \ No newline at end of file diff --git a/admin-system/src/types/supplier.ts b/admin-system/src/types/supplier.ts new file mode 100644 index 0000000..3db6e1a --- /dev/null +++ b/admin-system/src/types/supplier.ts @@ -0,0 +1,70 @@ +// 供应商类型定义 +export interface Supplier { + id: number; + name: string; + code: string; + contact: string; + phone: string; + address: string; + businessLicense?: string; // 营业执照 + qualificationLevel: 'A+' | 'A' | 'B+' | 'B' | 'C'; // 资质等级:A+, A, B+, B, C + certifications?: string[]; // 认证信息 + cattleTypes: string[]; // 牛种类型 + capacity: number; // 供应容量 + rating: number; // 评分 + cooperationStartDate: string; // 合作开始日期 + status: 'active' | 'inactive' | 'suspended'; // 状态 + region: 'north' | 'south' | 'east' | 'west' | 'northeast' | 'northwest' | 'southeast' | 'southwest' | 'central'; // 地区 + created_at: string; + updated_at: string; +} + +// 供应商创建表单类型 +export interface SupplierCreateForm { + name: string; + code: string; + contact: string; + phone: string; + address: string; + businessLicense?: string; + qualificationLevel: 'A+' | 'A' | 'B+' | 'B' | 'C'; + certifications?: string[]; + cattleTypes: string[]; + capacity: number; + region: 'north' | 'south' | 'east' | 'west' | 'northeast' | 'northwest' | 'southeast' | 'southwest' | 'central'; +} + +// 供应商更新表单类型 +export interface SupplierUpdateForm { + name?: string; + contact?: string; + phone?: string; + address?: string; + businessLicense?: string; + qualificationLevel?: 'A+' | 'A' | 'B+' | 'B' | 'C'; + certifications?: string[]; + cattleTypes?: string[]; + capacity?: number; + region?: 'north' | 'south' | 'east' | 'west' | 'northeast' | 'northwest' | 'southeast' | 'southwest' | 'central'; + status?: 'active' | 'inactive' | 'suspended'; +} + +// 供应商列表查询参数 +export interface SupplierListParams { + page?: number; + pageSize?: number; + keyword?: string; + region?: string; + qualificationLevel?: string; + status?: string; +} + +// 供应商统计信息 +export interface SupplierStatistics { + totalSuppliers: number; + activeSuppliers: number; + averageRating: number; + totalCapacity: number; + levelStats: Record; + regionStats: Record; +} \ No newline at end of file diff --git a/admin-system/src/types/transport.ts b/admin-system/src/types/transport.ts new file mode 100644 index 0000000..f15acce --- /dev/null +++ b/admin-system/src/types/transport.ts @@ -0,0 +1,111 @@ +// 运输管理相关类型定义 + +// 运输状态枚举 +export type TransportStatus = 'scheduled' | 'in_transit' | 'completed' | 'cancelled'; + +// 运输记录接口 +export interface Transport { + id: number; + order_id: number; + driver_id: number; + vehicle_id: number; + start_location: string; + end_location: string; + scheduled_start_time: string; // ISO 8601 格式 + scheduled_end_time: string; // ISO 8601 格式 + actual_start_time?: string; // ISO 8601 格式 + actual_end_time?: string; // ISO 8601 格式 + status: TransportStatus; + cattle_count: number; + special_requirements?: string; + created_at: string; // ISO 8601 格式 + updated_at: string; // ISO 8601 格式 +} + +// 运输记录创建表单 +export interface TransportCreateForm { + order_id: number; + driver_id: number; + vehicle_id: number; + start_location: string; + end_location: string; + scheduled_start_time: string; // ISO 8601 格式 + scheduled_end_time: string; // ISO 8601 格式 + cattle_count: number; + special_requirements?: string; +} + +// 运输记录更新表单 +export interface TransportUpdateForm { + driver_id?: number; + vehicle_id?: number; + start_location?: string; + end_location?: string; + scheduled_start_time?: string; // ISO 8601 格式 + scheduled_end_time?: string; // ISO 8601 格式 + actual_start_time?: string; // ISO 8601 格式 + actual_end_time?: string; // ISO 8601 格式 + status?: TransportStatus; + cattle_count?: number; + special_requirements?: string; +} + +// 运输列表查询参数 +export interface TransportListParams { + page?: number; + pageSize?: number; + status?: TransportStatus; + orderId?: number; +} + +// 车辆状态枚举 +export type VehicleStatus = 'available' | 'in_use' | 'maintenance' | 'retired'; + +// 车辆接口 +export interface Vehicle { + id: number; + license_plate: string; // 车牌号 + vehicle_type: string; // 车辆类型 + capacity: number; // 载重能力(公斤) + driver_id: number; // 司机ID + status: VehicleStatus; + last_maintenance_date?: string; // ISO 8601 格式 + next_maintenance_date?: string; // ISO 8601 格式 + insurance_expiry_date?: string; // ISO 8601 格式 + registration_expiry_date?: string; // ISO 8601 格式 + created_at: string; // ISO 8601 格式 + updated_at: string; // ISO 8601 格式 +} + +// 车辆创建表单 +export interface VehicleCreateForm { + license_plate: string; + vehicle_type: string; + capacity: number; + driver_id: number; + status: VehicleStatus; + last_maintenance_date?: string; // ISO 8601 格式 + next_maintenance_date?: string; // ISO 8601 格式 + insurance_expiry_date?: string; // ISO 8601 格式 + registration_expiry_date?: string; // ISO 8601 格式 +} + +// 车辆更新表单 +export interface VehicleUpdateForm { + license_plate?: string; + vehicle_type?: string; + capacity?: number; + driver_id?: number; + status?: VehicleStatus; + last_maintenance_date?: string; // ISO 8601 格式 + next_maintenance_date?: string; // ISO 8601 格式 + insurance_expiry_date?: string; // ISO 8601 格式 + registration_expiry_date?: string; // ISO 8601 格式 +} + +// 车辆列表查询参数 +export interface VehicleListParams { + page?: number; + pageSize?: number; + status?: VehicleStatus; +} \ No newline at end of file diff --git a/admin-system/src/views/order/index.vue b/admin-system/src/views/order/index.vue index bc590ac..e82db68 100644 --- a/admin-system/src/views/order/index.vue +++ b/admin-system/src/views/order/index.vue @@ -1,34 +1,409 @@ \ No newline at end of file diff --git a/admin-system/src/views/supplier/index.vue b/admin-system/src/views/supplier/index.vue index 3d4b307..636d28b 100644 --- a/admin-system/src/views/supplier/index.vue +++ b/admin-system/src/views/supplier/index.vue @@ -6,13 +6,559 @@

管理供应商信息、资质认证和绩效评估

- + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ 查询 + 重置 + 新增供应商 +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + {{ currentSupplier?.name }} + {{ currentSupplier?.code }} + {{ currentSupplier?.contact }} + {{ currentSupplier?.phone }} + {{ currentSupplier?.address }} + {{ currentSupplier?.businessLicense }} + {{ getRegionText(currentSupplier?.region) }} + + + {{ currentSupplier?.qualificationLevel }} + + + + + {{ cert }} + + + + {{ getFormattedCattleTypes(currentSupplier?.cattleTypes) }} + {{ currentSupplier?.capacity }} 头 + {{ currentSupplier?.rating }} + + + {{ getStatusText(currentSupplier?.status) }} + + + {{ currentSupplier?.cooperationStartDate }} + {{ currentSupplier?.created_at }} + {{ currentSupplier?.updated_at }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 头 + + + + + + + + + + + \ No newline at end of file diff --git a/admin-system/src/views/transport/index.vue b/admin-system/src/views/transport/index.vue index b245bae..eb59928 100644 --- a/admin-system/src/views/transport/index.vue +++ b/admin-system/src/views/transport/index.vue @@ -1,34 +1,478 @@ - \ No newline at end of file diff --git a/backend/app.js b/backend/app.js index a2c7da6..d1bc88e 100644 --- a/backend/app.js +++ b/backend/app.js @@ -23,8 +23,6 @@ app.use(morgan('combined')) // 日志 app.use(express.json({ limit: '10mb' })) app.use(express.urlencoded({ extended: true, limit: '10mb' })) - - // 限流 const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 分钟 @@ -53,47 +51,14 @@ app.use('/swagger', swaggerUi.serve, swaggerUi.setup(specs, { customSiteTitle: 'NiuMall API 文档' })) -// API 路由 -app.use('/api/auth', require('./routes/auth')) -app.use('/api/users', require('./routes/users')) -app.use('/api/orders', require('./routes/orders')) -app.use('/api/suppliers', require('./routes/suppliers')) -app.use('/api/transport', require('./routes/transport')) -app.use('/api/finance', require('./routes/finance')) -app.use('/api/quality', require('./routes/quality')) - -// 静态文件服务 -app.use('/static', express.static('public')); - -// API文档路由重定向 -app.get('/docs', (req, res) => { - res.redirect('/swagger'); -}); - -// 404 处理 -app.use((req, res) => { - res.status(404).json({ - success: false, - message: '接口不存在', - path: req.path - }) +// 提供Swagger JSON文件 +app.get('/api-docs-json', (req, res) => { + res.setHeader('Content-Type', 'application/json') + res.send(specs) }) -// 错误处理中间件 -app.use((err, req, res, next) => { - console.error('Error:', err) - - res.status(err.status || 500).json({ - success: false, - message: err.message || '服务器内部错误', - timestamp: new Date().toISOString(), - ...(process.env.NODE_ENV === 'development' && { stack: err.stack }) - }) -}) +const PORT = process.env.PORT || 4330 -const PORT = process.env.PORT || 3000 - -// 启动服务器 const startServer = async () => { try { // 测试数据库连接 @@ -112,6 +77,7 @@ const startServer = async () => { console.log(`🌐 访问地址: http://localhost:${PORT}`) console.log(`📊 健康检查: http://localhost:${PORT}/health`) console.log(`📚 API文档: http://localhost:${PORT}/swagger`) + console.log(`📄 API文档JSON: http://localhost:${PORT}/api-docs-json`) }) } catch (error) { console.error('❌ 服务器启动失败:', error) @@ -120,3 +86,9 @@ const startServer = async () => { } startServer() + +// API 路由 +app.use('/api/auth', require('./routes/auth')) +app.use('/api/users', require('./routes/users')) +app.use('/api/orders', require('./routes/orders')) +app.use('/api/payments', require('./routes/payments')) diff --git a/backend/models/index.js b/backend/models/index.js index f7d0974..2a02f48 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -124,7 +124,73 @@ const models = { }), // 订单模型 - Order: defineOrder(sequelize) + Order: defineOrder(sequelize), + + // 供应商模型 + Supplier: sequelize.define('Supplier', { + id: { + type: Sequelize.BIGINT, + primaryKey: true, + autoIncrement: true + }, + name: { + type: Sequelize.STRING(100), + allowNull: false + }, + code: { + type: Sequelize.STRING(20), + allowNull: false, + unique: true + }, + contact: { + type: Sequelize.STRING(50), + allowNull: false + }, + phone: { + type: Sequelize.STRING(20), + allowNull: false, + unique: true + }, + address: { + type: Sequelize.STRING(200), + allowNull: false + }, + businessLicense: { + type: Sequelize.STRING(255) + }, + qualificationLevel: { + type: Sequelize.STRING(10), + allowNull: false + }, + certifications: { + type: Sequelize.JSON + }, + cattleTypes: { + type: Sequelize.JSON + }, + capacity: { + type: Sequelize.INTEGER + }, + rating: { + type: Sequelize.DECIMAL(3, 2) + }, + cooperationStartDate: { + type: Sequelize.DATE + }, + status: { + type: Sequelize.ENUM('active', 'inactive', 'suspended'), + defaultValue: 'active' + }, + region: { + type: Sequelize.STRING(20), + allowNull: false + } + }, { + tableName: 'suppliers', + timestamps: true, + createdAt: 'created_at', + updatedAt: 'updated_at' + }) }; // 同步数据库模型 @@ -138,6 +204,10 @@ const syncModels = async () => { await models.Order.sync({ alter: true }); console.log('✅ 订单表同步成功'); + // 同步供应商表(如果不存在则创建) + await models.Supplier.sync({ alter: true }); + console.log('✅ 供应商表同步成功'); + console.log('✅ 数据库模型同步完成'); } catch (error) { console.error('❌ 数据库模型同步失败:', error); diff --git a/backend/routes/orders.js b/backend/routes/orders.js index 4c93956..c9c141f 100644 --- a/backend/routes/orders.js +++ b/backend/routes/orders.js @@ -598,4 +598,98 @@ router.delete('/:id', async (req, res) => { } }) +/** + * @swagger + * /api/orders/{id}/status: + * patch: + * summary: 更新订单状态 + * tags: [订单管理] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: 订单ID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * enum: [pending, confirmed, preparing, shipping, delivered, accepted, completed, cancelled, refunded] + * description: 订单状态 + * required: + * - status + * responses: + * 200: + * description: 更新成功 + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * message: + * type: string + * data: + * $ref: '#/components/schemas/Order' + * 400: + * description: 参数验证失败 + * 401: + * description: 未授权 + * 404: + * description: 订单不存在 + * 500: + * description: 服务器内部错误 + */ +// 更新订单状态 +router.patch('/:id/status', async (req, res) => { + try { + const { id } = req.params + const { status } = req.body + + // 验证状态值是否有效 + const validStatuses = ['pending', 'confirmed', 'preparing', 'shipping', 'delivered', 'accepted', 'completed', 'cancelled', 'refunded'] + if (!validStatuses.includes(status)) { + return res.status(400).json({ + success: false, + message: '无效的订单状态', + details: `状态必须是以下值之一: ${validStatuses.join(', ')}` + }) + } + + // 查找订单 + const order = await Order.findByPk(id) + + if (!order) { + return res.status(404).json({ + success: false, + message: '订单不存在' + }) + } + + // 更新订单状态 + await order.update({ status }) + + res.json({ + success: true, + message: '订单状态更新成功', + data: order + }) + } catch (error) { + console.error('更新订单状态失败:', error) + res.status(500).json({ + success: false, + message: '更新订单状态失败' + }) + } +}) + module.exports = router \ No newline at end of file diff --git a/backend/routes/suppliers.js b/backend/routes/suppliers.js index edf6059..ef76162 100644 --- a/backend/routes/suppliers.js +++ b/backend/routes/suppliers.js @@ -1,67 +1,8 @@ const express = require('express'); const router = express.Router(); const Joi = require('joi'); - -// 模拟供应商数据 -let suppliers = [ - { - id: 1, - name: '山东优质牲畜合作社', - code: 'SUP001', - contact: '李经理', - phone: '15888888888', - address: '山东省济南市历城区牲畜养殖基地', - businessLicense: 'SUP001_license.pdf', - qualificationLevel: 'A', - certifications: ['动物防疫合格证', '饲料生产许可证'], - cattleTypes: ['肉牛', '奶牛'], - capacity: 5000, - rating: 4.8, - cooperationStartDate: '2022-01-15', - status: 'active', - region: 'east', - createdAt: new Date('2022-01-15'), - updatedAt: new Date('2024-01-15') - }, - { - id: 2, - name: '内蒙古草原牲畜有限公司', - code: 'SUP002', - contact: '王总', - phone: '13999999999', - address: '内蒙古呼和浩特市草原牧场', - businessLicense: 'SUP002_license.pdf', - qualificationLevel: 'A+', - certifications: ['有机认证', '绿色食品认证'], - cattleTypes: ['草原牛', '黄牛'], - capacity: 8000, - rating: 4.9, - cooperationStartDate: '2021-08-20', - status: 'active', - region: 'north', - createdAt: new Date('2021-08-20'), - updatedAt: new Date('2024-01-20') - }, - { - id: 3, - name: '四川高原牲畜养殖场', - code: 'SUP003', - contact: '张场长', - phone: '18777777777', - address: '四川省成都市高原养殖区', - businessLicense: 'SUP003_license.pdf', - qualificationLevel: 'B+', - certifications: ['无公害产品认证'], - cattleTypes: ['高原牛'], - capacity: 3000, - rating: 4.5, - cooperationStartDate: '2022-06-10', - status: 'active', - region: 'southwest', - createdAt: new Date('2022-06-10'), - updatedAt: new Date('2024-01-10') - } -]; +const { Supplier } = require('../models'); +const { Sequelize } = require('sequelize'); // 验证schemas const supplierCreateSchema = Joi.object({ @@ -70,7 +11,9 @@ const supplierCreateSchema = Joi.object({ contact: Joi.string().min(2).max(50).required(), phone: Joi.string().pattern(/^1[3-9]\d{9}$/).required(), address: Joi.string().min(5).max(200).required(), + businessLicense: Joi.string().max(255).optional(), qualificationLevel: Joi.string().valid('A+', 'A', 'B+', 'B', 'C').required(), + certifications: Joi.array().items(Joi.string()).optional(), cattleTypes: Joi.array().items(Joi.string()).min(1).required(), capacity: Joi.number().integer().min(1).required(), region: Joi.string().valid('north', 'south', 'east', 'west', 'northeast', 'northwest', 'southeast', 'southwest', 'central').required() @@ -81,7 +24,9 @@ const supplierUpdateSchema = Joi.object({ contact: Joi.string().min(2).max(50), phone: Joi.string().pattern(/^1[3-9]\d{9}$/), address: Joi.string().min(5).max(200), + businessLicense: Joi.string().max(255).optional(), qualificationLevel: Joi.string().valid('A+', 'A', 'B+', 'B', 'C'), + certifications: Joi.array().items(Joi.string()).optional(), cattleTypes: Joi.array().items(Joi.string()).min(1), capacity: Joi.number().integer().min(1), region: Joi.string().valid('north', 'south', 'east', 'west', 'northeast', 'northwest', 'southeast', 'southwest', 'central'), @@ -89,7 +34,7 @@ const supplierUpdateSchema = Joi.object({ }); // 获取供应商列表 -router.get('/', (req, res) => { +router.get('/', async (req, res) => { try { const { page = 1, @@ -97,53 +42,74 @@ router.get('/', (req, res) => { keyword, region, qualificationLevel, - status = 'active' + status } = req.query; - let filteredSuppliers = [...suppliers]; - - // 关键词搜索 - if (keyword) { - filteredSuppliers = filteredSuppliers.filter(supplier => - supplier.name.includes(keyword) || - supplier.code.includes(keyword) || - supplier.contact.includes(keyword) - ); - } - - // 区域筛选 - if (region) { - filteredSuppliers = filteredSuppliers.filter(supplier => supplier.region === region); - } - - // 资质等级筛选 - if (qualificationLevel) { - filteredSuppliers = filteredSuppliers.filter(supplier => supplier.qualificationLevel === qualificationLevel); - } - + // 构建查询条件 + const whereConditions = {}; + // 状态筛选 if (status) { - filteredSuppliers = filteredSuppliers.filter(supplier => supplier.status === status); + whereConditions.status = status; + } + + // 区域筛选 + if (region) { + whereConditions.region = region; + } + + // 资质等级筛选 + if (qualificationLevel) { + whereConditions.qualificationLevel = qualificationLevel; + } + + // 关键词搜索 + if (keyword) { + whereConditions[Sequelize.Op.or] = [ + { name: { [Sequelize.Op.like]: `%${keyword}%` } }, + { code: { [Sequelize.Op.like]: `%${keyword}%` } }, + { contact: { [Sequelize.Op.like]: `%${keyword}%` } } + ]; } - // 分页处理 - const startIndex = (page - 1) * pageSize; - const endIndex = startIndex + parseInt(pageSize); - const paginatedSuppliers = filteredSuppliers.slice(startIndex, endIndex); + // 分页参数 + const offset = (page - 1) * pageSize; + const limit = parseInt(pageSize); + + // 查询数据库 + const { rows, count } = await Supplier.findAndCountAll({ + where: whereConditions, + offset, + limit, + order: [['created_at', 'DESC']] + }); + + // 解析JSON字段 + const processedRows = rows.map(row => { + const rowData = row.toJSON(); + if (rowData.cattleTypes && typeof rowData.cattleTypes === 'string') { + rowData.cattleTypes = JSON.parse(rowData.cattleTypes); + } + if (rowData.certifications && typeof rowData.certifications === 'string') { + rowData.certifications = JSON.parse(rowData.certifications); + } + return rowData; + }); res.json({ success: true, data: { - list: paginatedSuppliers, + list: processedRows, pagination: { page: parseInt(page), - pageSize: parseInt(pageSize), - total: filteredSuppliers.length, - totalPages: Math.ceil(filteredSuppliers.length / pageSize) + pageSize: limit, + total: count, + totalPages: Math.ceil(count / limit) } } }); } catch (error) { + console.error('获取供应商列表失败:', error); res.status(500).json({ success: false, message: '获取供应商列表失败', @@ -153,10 +119,12 @@ router.get('/', (req, res) => { }); // 获取供应商详情 -router.get('/:id', (req, res) => { +router.get('/:id', async (req, res) => { try { const { id } = req.params; - const supplier = suppliers.find(s => s.id === parseInt(id)); + + // 查询数据库 + const supplier = await Supplier.findByPk(id); if (!supplier) { return res.status(404).json({ @@ -165,11 +133,21 @@ router.get('/:id', (req, res) => { }); } + // 解析JSON字段 + const supplierData = supplier.toJSON(); + if (supplierData.cattleTypes && typeof supplierData.cattleTypes === 'string') { + supplierData.cattleTypes = JSON.parse(supplierData.cattleTypes); + } + if (supplierData.certifications && typeof supplierData.certifications === 'string') { + supplierData.certifications = JSON.parse(supplierData.certifications); + } + res.json({ success: true, - data: supplier + data: supplierData }); } catch (error) { + console.error('获取供应商详情失败:', error); res.status(500).json({ success: false, message: '获取供应商详情失败', @@ -179,7 +157,7 @@ router.get('/:id', (req, res) => { }); // 创建供应商 -router.post('/', (req, res) => { +router.post('/', async (req, res) => { try { const { error, value } = supplierCreateSchema.validate(req.body); if (error) { @@ -191,7 +169,7 @@ router.post('/', (req, res) => { } // 检查编码是否重复 - const existingSupplier = suppliers.find(s => s.code === value.code); + const existingSupplier = await Supplier.findOne({ where: { code: value.code } }); if (existingSupplier) { return res.status(400).json({ success: false, @@ -199,19 +177,25 @@ router.post('/', (req, res) => { }); } - const newSupplier = { - id: Math.max(...suppliers.map(s => s.id)) + 1, - ...value, - businessLicense: '', - certifications: [], - rating: 0, - cooperationStartDate: new Date().toISOString().split('T')[0], - status: 'active', - createdAt: new Date(), - updatedAt: new Date() - }; + // 检查电话是否重复 + const existingPhone = await Supplier.findOne({ where: { phone: value.phone } }); + if (existingPhone) { + return res.status(400).json({ + success: false, + message: '供应商电话已存在' + }); + } - suppliers.push(newSupplier); + // 创建新供应商 + const newSupplier = await Supplier.create({ + ...value, + businessLicense: value.businessLicense || '', + certifications: value.certifications ? JSON.stringify(value.certifications) : JSON.stringify([]), + cattleTypes: JSON.stringify(value.cattleTypes), + rating: 0, + cooperationStartDate: new Date(), + status: 'active' + }); res.status(201).json({ success: true, @@ -219,6 +203,7 @@ router.post('/', (req, res) => { data: newSupplier }); } catch (error) { + console.error('创建供应商失败:', error); res.status(500).json({ success: false, message: '创建供应商失败', @@ -228,7 +213,7 @@ router.post('/', (req, res) => { }); // 更新供应商 -router.put('/:id', (req, res) => { +router.put('/:id', async (req, res) => { try { const { id } = req.params; const { error, value } = supplierUpdateSchema.validate(req.body); @@ -241,26 +226,41 @@ router.put('/:id', (req, res) => { }); } - const supplierIndex = suppliers.findIndex(s => s.id === parseInt(id)); - if (supplierIndex === -1) { + // 查找供应商 + const supplier = await Supplier.findByPk(id); + if (!supplier) { return res.status(404).json({ success: false, message: '供应商不存在' }); } - suppliers[supplierIndex] = { - ...suppliers[supplierIndex], + // 如果更新了电话号码,检查是否重复 + if (value.phone && value.phone !== supplier.phone) { + const existingPhone = await Supplier.findOne({ where: { phone: value.phone } }); + if (existingPhone) { + return res.status(400).json({ + success: false, + message: '供应商电话已存在' + }); + } + } + + // 更新供应商信息 + await supplier.update({ ...value, - updatedAt: new Date() - }; + businessLicense: value.businessLicense !== undefined ? value.businessLicense : undefined, + certifications: value.certifications !== undefined ? JSON.stringify(value.certifications) : undefined, + cattleTypes: value.cattleTypes ? JSON.stringify(value.cattleTypes) : undefined + }); res.json({ success: true, message: '供应商更新成功', - data: suppliers[supplierIndex] + data: supplier }); } catch (error) { + console.error('更新供应商失败:', error); res.status(500).json({ success: false, message: '更新供应商失败', @@ -270,25 +270,28 @@ router.put('/:id', (req, res) => { }); // 删除供应商 -router.delete('/:id', (req, res) => { +router.delete('/:id', async (req, res) => { try { const { id } = req.params; - const supplierIndex = suppliers.findIndex(s => s.id === parseInt(id)); - - if (supplierIndex === -1) { + + // 查找供应商 + const supplier = await Supplier.findByPk(id); + if (!supplier) { return res.status(404).json({ success: false, message: '供应商不存在' }); } - suppliers.splice(supplierIndex, 1); + // 删除供应商 + await supplier.destroy(); res.json({ success: true, message: '供应商删除成功' }); } catch (error) { + console.error('删除供应商失败:', error); res.status(500).json({ success: false, message: '删除供应商失败', @@ -298,22 +301,56 @@ router.delete('/:id', (req, res) => { }); // 获取供应商统计信息 -router.get('/stats/overview', (req, res) => { +router.get('/stats/overview', async (req, res) => { try { - const totalSuppliers = suppliers.length; - const activeSuppliers = suppliers.filter(s => s.status === 'active').length; - const averageRating = suppliers.reduce((sum, s) => sum + s.rating, 0) / totalSuppliers; - const totalCapacity = suppliers.reduce((sum, s) => sum + s.capacity, 0); + // 获取总数和活跃数 + const totalSuppliers = await Supplier.count(); + const activeSuppliers = await Supplier.count({ where: { status: 'active' } }); + + // 获取平均评分(排除评分为0的供应商) + const ratingResult = await Supplier.findOne({ + attributes: [ + [Sequelize.fn('AVG', Sequelize.col('rating')), 'averageRating'] + ], + where: { + rating: { + [Sequelize.Op.gt]: 0 + } + } + }); + const averageRating = ratingResult ? parseFloat(ratingResult.getDataValue('averageRating')).toFixed(2) : 0; + + // 获取总产能 + const capacityResult = await Supplier.findOne({ + attributes: [ + [Sequelize.fn('SUM', Sequelize.col('capacity')), 'totalCapacity'] + ] + }); + const totalCapacity = capacityResult ? capacityResult.getDataValue('totalCapacity') : 0; // 按等级统计 - const levelStats = suppliers.reduce((stats, supplier) => { - stats[supplier.qualificationLevel] = (stats[supplier.qualificationLevel] || 0) + 1; + const levelStatsResult = await Supplier.findAll({ + attributes: [ + 'qualificationLevel', + [Sequelize.fn('COUNT', Sequelize.col('id')), 'count'] + ], + group: ['qualificationLevel'] + }); + const levelStats = levelStatsResult.reduce((stats, item) => { + stats[item.qualificationLevel] = item.getDataValue('count'); return stats; }, {}); // 按区域统计 - const regionStats = suppliers.reduce((stats, supplier) => { - stats[supplier.region] = (stats[supplier.region] || 0) + 1; + const regionStatsResult = await Supplier.findAll({ + attributes: [ + 'region', + [Sequelize.fn('COUNT', Sequelize.col('id')), 'count'] + ], + group: ['region'] + }); + const regionStats = regionStatsResult.reduce((stats, item) => { + stats[item.region] = item.getDataValue('count'); return stats; }, {}); @@ -322,13 +359,14 @@ router.get('/stats/overview', (req, res) => { data: { totalSuppliers, activeSuppliers, - averageRating: Math.round(averageRating * 10) / 10, + averageRating: parseFloat(averageRating), totalCapacity, levelStats, regionStats } }); } catch (error) { + console.error('获取供应商统计信息失败:', error); res.status(500).json({ success: false, message: '获取供应商统计信息失败', @@ -337,64 +375,53 @@ router.get('/stats/overview', (req, res) => { } }); -// 批量操作 -router.post('/batch', (req, res) => { +// 批量操作供应商 +router.post('/batch', async (req, res) => { try { - const { action, ids } = req.body; + const { ids, action } = req.body; - if (!action || !Array.isArray(ids) || ids.length === 0) { + if (!ids || !Array.isArray(ids) || ids.length === 0) { return res.status(400).json({ success: false, - message: '参数错误' + message: '请选择要操作的供应商' }); } - let affectedCount = 0; + if (!['activate', 'deactivate', 'delete'].includes(action)) { + return res.status(400).json({ + success: false, + message: '无效的操作类型' + }); + } switch (action) { case 'activate': - suppliers.forEach(supplier => { - if (ids.includes(supplier.id)) { - supplier.status = 'active'; - supplier.updatedAt = new Date(); - affectedCount++; - } - }); + await Supplier.update( + { status: 'active' }, + { where: { id: ids } } + ); break; - case 'deactivate': - suppliers.forEach(supplier => { - if (ids.includes(supplier.id)) { - supplier.status = 'inactive'; - supplier.updatedAt = new Date(); - affectedCount++; - } - }); + await Supplier.update( + { status: 'inactive' }, + { where: { id: ids } } + ); break; - case 'delete': - suppliers = suppliers.filter(supplier => { - if (ids.includes(supplier.id)) { - affectedCount++; - return false; + await Supplier.destroy({ + where: { + id: ids } - return true; }); break; - - default: - return res.status(400).json({ - success: false, - message: '不支持的操作类型' - }); } res.json({ success: true, - message: `批量${action}成功`, - data: { affectedCount } + message: '批量操作成功' }); } catch (error) { + console.error('批量操作失败:', error); res.status(500).json({ success: false, message: '批量操作失败', diff --git a/backend/src/controllers/OrderController.js b/backend/src/controllers/OrderController.js index c650659..c6ba619 100644 --- a/backend/src/controllers/OrderController.js +++ b/backend/src/controllers/OrderController.js @@ -6,12 +6,30 @@ const createOrder = async (req, res) => { try { const orderData = req.body; - // 设置买家ID - orderData.buyer_id = req.user.id; + // 设置买家ID和名称(从token中获取) + orderData.buyerId = req.user.id; + orderData.buyerName = req.user.username; // 生成订单号 - const orderNo = `ORD${Date.now()}${Math.floor(Math.random() * 1000).toString().padStart(3, '0')}`; - orderData.order_no = orderNo; + const orderNo = `ORD${new Date().getFullYear()}${(new Date().getMonth() + 1).toString().padStart(2, '0')}${new Date().getDate().toString().padStart(2, '0')}${Date.now().toString().slice(-6)}`; + orderData.orderNo = orderNo; + + // 计算总金额 + orderData.totalAmount = (orderData.expectedWeight * orderData.unitPrice).toFixed(2); + + // 初始化已付金额和剩余金额 + orderData.paidAmount = "0.00"; + orderData.remainingAmount = orderData.totalAmount; + + // 如果没有提供供应商名称,则使用默认值 + if (!orderData.supplierName) { + orderData.supplierName = "供应商"; + } + + // 如果没有提供牛品种类,则使用默认值 + if (!orderData.cattleBreed) { + orderData.cattleBreed = "西门塔尔牛"; + } // 创建订单 const order = await Order.create(orderData); diff --git a/backend/src/controllers/TransportController.js b/backend/src/controllers/TransportController.js new file mode 100644 index 0000000..d8abc98 --- /dev/null +++ b/backend/src/controllers/TransportController.js @@ -0,0 +1,388 @@ +const Transport = require('../models/Transport'); +const Vehicle = require('../models/Vehicle'); +const Order = require('../models/Order'); +const { Op } = require('sequelize'); + +// 获取运输列表 +exports.getTransportList = async (req, res) => { + try { + const { page = 1, pageSize = 20, status, orderId } = req.query; + + // 构建查询条件 + const where = {}; + if (status) where.status = status; + if (orderId) where.order_id = orderId; + + // 分页查询 + const { count, rows } = await Transport.findAndCountAll({ + where, + limit: parseInt(pageSize), + offset: (parseInt(page) - 1) * parseInt(pageSize), + order: [['created_at', 'DESC']] + }); + + res.json({ + success: true, + data: { + list: rows, + pagination: { + page: parseInt(page), + pageSize: parseInt(pageSize), + total: count, + totalPages: Math.ceil(count / pageSize) + } + } + }); + } catch (error) { + console.error('获取运输列表失败:', error); + res.status(500).json({ + success: false, + message: '获取运输列表失败', + error: error.message + }); + } +}; + +// 获取运输详情 +exports.getTransportDetail = async (req, res) => { + try { + const { id } = req.params; + + const transport = await Transport.findByPk(id); + if (!transport) { + return res.status(404).json({ + success: false, + message: '运输记录不存在' + }); + } + + // 获取关联的车辆信息 + const vehicle = await Vehicle.findByPk(transport.vehicle_id); + + res.json({ + success: true, + data: { + ...transport.toJSON(), + vehicle + } + }); + } catch (error) { + console.error('获取运输详情失败:', error); + res.status(500).json({ + success: false, + message: '获取运输详情失败', + error: error.message + }); + } +}; + +// 创建运输记录 +exports.createTransport = async (req, res) => { + try { + const { + order_id, + driver_id, + vehicle_id, + start_location, + end_location, + scheduled_start_time, + scheduled_end_time, + cattle_count, + special_requirements + } = req.body; + + // 检查订单是否存在 + const order = await Order.findByPk(order_id); + if (!order) { + return res.status(400).json({ + success: false, + message: '订单不存在' + }); + } + + // 检查车辆是否存在 + const vehicle = await Vehicle.findByPk(vehicle_id); + if (!vehicle) { + return res.status(400).json({ + success: false, + message: '车辆不存在' + }); + } + + // 创建运输记录 + const transport = await Transport.create({ + order_id, + driver_id, + vehicle_id, + start_location, + end_location, + scheduled_start_time, + scheduled_end_time, + cattle_count, + special_requirements + }); + + res.status(201).json({ + success: true, + message: '运输记录创建成功', + data: transport + }); + } catch (error) { + console.error('创建运输记录失败:', error); + res.status(500).json({ + success: false, + message: '创建运输记录失败', + error: error.message + }); + } +}; + +// 更新运输记录 +exports.updateTransport = async (req, res) => { + try { + const { id } = req.params; + const updateData = req.body; + + const transport = await Transport.findByPk(id); + if (!transport) { + return res.status(404).json({ + success: false, + message: '运输记录不存在' + }); + } + + // 更新运输记录 + await transport.update(updateData); + + res.json({ + success: true, + message: '运输记录更新成功', + data: transport + }); + } catch (error) { + console.error('更新运输记录失败:', error); + res.status(500).json({ + success: false, + message: '更新运输记录失败', + error: error.message + }); + } +}; + +// 删除运输记录 +exports.deleteTransport = async (req, res) => { + try { + const { id } = req.params; + + const transport = await Transport.findByPk(id); + if (!transport) { + return res.status(404).json({ + success: false, + message: '运输记录不存在' + }); + } + + // 删除运输记录 + await transport.destroy(); + + res.json({ + success: true, + message: '运输记录删除成功' + }); + } catch (error) { + console.error('删除运输记录失败:', error); + res.status(500).json({ + success: false, + message: '删除运输记录失败', + error: error.message + }); + } +}; + +// 获取车辆列表 +exports.getVehicleList = async (req, res) => { + try { + const { page = 1, pageSize = 20, status } = req.query; + + // 构建查询条件 + const where = {}; + if (status) where.status = status; + + // 分页查询 + const { count, rows } = await Vehicle.findAndCountAll({ + where, + limit: parseInt(pageSize), + offset: (parseInt(page) - 1) * parseInt(pageSize), + order: [['created_at', 'DESC']] + }); + + res.json({ + success: true, + data: { + list: rows, + pagination: { + page: parseInt(page), + pageSize: parseInt(pageSize), + total: count, + totalPages: Math.ceil(count / pageSize) + } + } + }); + } catch (error) { + console.error('获取车辆列表失败:', error); + res.status(500).json({ + success: false, + message: '获取车辆列表失败', + error: error.message + }); + } +}; + +// 获取车辆详情 +exports.getVehicleDetail = async (req, res) => { + try { + const { id } = req.params; + + const vehicle = await Vehicle.findByPk(id); + if (!vehicle) { + return res.status(404).json({ + success: false, + message: '车辆不存在' + }); + } + + res.json({ + success: true, + data: vehicle + }); + } catch (error) { + console.error('获取车辆详情失败:', error); + res.status(500).json({ + success: false, + message: '获取车辆详情失败', + error: error.message + }); + } +}; + +// 创建车辆记录 +exports.createVehicle = async (req, res) => { + try { + const { + license_plate, + vehicle_type, + capacity, + driver_id, + status + } = req.body; + + // 检查车牌号是否已存在 + const existingVehicle = await Vehicle.findOne({ where: { license_plate } }); + if (existingVehicle) { + return res.status(400).json({ + success: false, + message: '车牌号已存在' + }); + } + + // 创建车辆记录 + const vehicle = await Vehicle.create({ + license_plate, + vehicle_type, + capacity, + driver_id, + status + }); + + res.status(201).json({ + success: true, + message: '车辆记录创建成功', + data: vehicle + }); + } catch (error) { + console.error('创建车辆记录失败:', error); + res.status(500).json({ + success: false, + message: '创建车辆记录失败', + error: error.message + }); + } +}; + +// 更新车辆记录 +exports.updateVehicle = async (req, res) => { + try { + const { id } = req.params; + const updateData = req.body; + + const vehicle = await Vehicle.findByPk(id); + if (!vehicle) { + return res.status(404).json({ + success: false, + message: '车辆不存在' + }); + } + + // 检查车牌号是否已存在(排除当前车辆) + if (updateData.license_plate) { + const existingVehicle = await Vehicle.findOne({ + where: { + license_plate: updateData.license_plate, + id: { [Op.ne]: id } + } + }); + if (existingVehicle) { + return res.status(400).json({ + success: false, + message: '车牌号已存在' + }); + } + } + + // 更新车辆记录 + await vehicle.update(updateData); + + res.json({ + success: true, + message: '车辆记录更新成功', + data: vehicle + }); + } catch (error) { + console.error('更新车辆记录失败:', error); + res.status(500).json({ + success: false, + message: '更新车辆记录失败', + error: error.message + }); + } +}; + +// 删除车辆记录 +exports.deleteVehicle = async (req, res) => { + try { + const { id } = req.params; + + const vehicle = await Vehicle.findByPk(id); + if (!vehicle) { + return res.status(404).json({ + success: false, + message: '车辆不存在' + }); + } + + // 删除车辆记录 + await vehicle.destroy(); + + res.json({ + success: true, + message: '车辆记录删除成功' + }); + } catch (error) { + console.error('删除车辆记录失败:', error); + res.status(500).json({ + success: false, + message: '删除车辆记录失败', + error: error.message + }); + } +}; \ No newline at end of file diff --git a/backend/src/main.js b/backend/src/main.js index fe69d13..dd2b6c2 100644 --- a/backend/src/main.js +++ b/backend/src/main.js @@ -25,6 +25,8 @@ const authRoutes = require('./routes/auth'); const userRoutes = require('./routes/users'); const orderRoutes = require('./routes/orders'); const paymentRoutes = require('./routes/payments'); +const supplierRoutes = require('./routes/suppliers'); +const transportRoutes = require('./routes/transports'); // 创建Express应用 const app = express(); @@ -53,6 +55,8 @@ app.use('/api/auth', authRoutes); app.use('/api/users', userRoutes); app.use('/api/orders', orderRoutes); app.use('/api/payments', paymentRoutes); +app.use('/api/suppliers', supplierRoutes); +app.use('/api/transports', transportRoutes); // 基本路由 app.get('/', (req, res) => { diff --git a/backend/src/models/Order.js b/backend/src/models/Order.js index 31c3a23..f383500 100644 --- a/backend/src/models/Order.js +++ b/backend/src/models/Order.js @@ -8,70 +8,106 @@ const Order = sequelize.define('Order', { primaryKey: true, autoIncrement: true }, - order_no: { + orderNo: { type: DataTypes.STRING(50), allowNull: false, - unique: true + unique: true, + field: 'orderNo' }, - buyer_id: { + buyerId: { type: DataTypes.BIGINT, - allowNull: false + allowNull: false, + field: 'buyerId' }, - trader_id: { + buyerName: { + type: DataTypes.STRING(100), + allowNull: false, + field: 'buyerName' + }, + supplierId: { type: DataTypes.BIGINT, - allowNull: true + allowNull: false, + field: 'supplierId' }, - supplier_id: { + supplierName: { + type: DataTypes.STRING(100), + allowNull: false, + field: 'supplierName' + }, + traderId: { type: DataTypes.BIGINT, - allowNull: false + allowNull: true, + field: 'traderId' }, - driver_id: { - type: DataTypes.BIGINT, - allowNull: true + traderName: { + type: DataTypes.STRING(100), + allowNull: true, + field: 'traderName' }, - breed_type: { + cattleBreed: { type: DataTypes.STRING(20), - allowNull: false + allowNull: false, + field: 'cattleBreed' }, - min_weight: { - type: DataTypes.DECIMAL(10,2), - allowNull: false - }, - max_weight: { - type: DataTypes.DECIMAL(10,2), - allowNull: false - }, - total_count: { + cattleCount: { type: DataTypes.INTEGER, - allowNull: false + allowNull: false, + field: 'cattleCount' }, - total_weight: { + expectedWeight: { type: DataTypes.DECIMAL(10,2), - allowNull: true + allowNull: false, + field: 'expectedWeight' }, - unit_price: { + actualWeight: { type: DataTypes.DECIMAL(10,2), - allowNull: false + allowNull: true, + field: 'actualWeight' }, - total_amount: { + unitPrice: { + type: DataTypes.DECIMAL(10,2), + allowNull: false, + field: 'unitPrice' + }, + totalAmount: { type: DataTypes.DECIMAL(15,2), - allowNull: false + allowNull: false, + field: 'totalAmount' + }, + paidAmount: { + type: DataTypes.DECIMAL(15,2), + allowNull: false, + field: 'paidAmount' + }, + remainingAmount: { + type: DataTypes.DECIMAL(15,2), + allowNull: false, + field: 'remainingAmount' }, status: { type: DataTypes.ENUM('pending', 'confirmed', 'loading', 'shipping', 'delivered', 'completed', 'cancelled'), - defaultValue: 'pending' + defaultValue: 'pending', + field: 'status' }, - delivery_address: { + deliveryAddress: { type: DataTypes.STRING(255), - allowNull: false + allowNull: false, + field: 'deliveryAddress' }, - delivery_date: { - type: DataTypes.DATEONLY, - allowNull: false + expectedDeliveryDate: { + type: DataTypes.DATE, + allowNull: false, + field: 'expectedDeliveryDate' }, - special_requirements: { + actualDeliveryDate: { + type: DataTypes.DATE, + allowNull: true, + field: 'actualDeliveryDate' + }, + notes: { type: DataTypes.TEXT, - allowNull: true + allowNull: true, + field: 'notes' } }, { tableName: 'orders', diff --git a/backend/src/models/Transport.js b/backend/src/models/Transport.js new file mode 100644 index 0000000..895d1d8 --- /dev/null +++ b/backend/src/models/Transport.js @@ -0,0 +1,83 @@ +const { DataTypes } = require('sequelize'); +const sequelize = require('../config/database'); + +// 运输管理模型 +const Transport = sequelize.define('Transport', { + id: { + type: DataTypes.BIGINT, + primaryKey: true, + autoIncrement: true + }, + order_id: { + type: DataTypes.BIGINT, + allowNull: false, + comment: '关联订单ID' + }, + driver_id: { + type: DataTypes.BIGINT, + allowNull: false, + comment: '司机ID' + }, + vehicle_id: { + type: DataTypes.BIGINT, + allowNull: false, + comment: '车辆ID' + }, + start_location: { + type: DataTypes.STRING(255), + allowNull: false, + comment: '起始地点' + }, + end_location: { + type: DataTypes.STRING(255), + allowNull: false, + comment: '目的地' + }, + scheduled_start_time: { + type: DataTypes.DATE, + allowNull: false, + comment: '计划开始时间' + }, + actual_start_time: { + type: DataTypes.DATE, + allowNull: true, + comment: '实际开始时间' + }, + scheduled_end_time: { + type: DataTypes.DATE, + allowNull: false, + comment: '计划结束时间' + }, + actual_end_time: { + type: DataTypes.DATE, + allowNull: true, + comment: '实际结束时间' + }, + status: { + type: DataTypes.ENUM('scheduled', 'in_transit', 'completed', 'cancelled'), + defaultValue: 'scheduled', + comment: '运输状态: scheduled(已安排), in_transit(运输中), completed(已完成), cancelled(已取消)' + }, + estimated_arrival_time: { + type: DataTypes.DATE, + allowNull: true, + comment: '预计到达时间' + }, + cattle_count: { + type: DataTypes.INTEGER, + allowNull: false, + comment: '运输牛只数量' + }, + special_requirements: { + type: DataTypes.TEXT, + allowNull: true, + comment: '特殊要求' + } +}, { + tableName: 'transports', + timestamps: true, + createdAt: 'created_at', + updatedAt: 'updated_at' +}); + +module.exports = Transport; \ No newline at end of file diff --git a/backend/src/models/Vehicle.js b/backend/src/models/Vehicle.js new file mode 100644 index 0000000..2658b34 --- /dev/null +++ b/backend/src/models/Vehicle.js @@ -0,0 +1,64 @@ +const { DataTypes } = require('sequelize'); +const sequelize = require('../config/database'); + +// 车辆管理模型 +const Vehicle = sequelize.define('Vehicle', { + id: { + type: DataTypes.BIGINT, + primaryKey: true, + autoIncrement: true + }, + license_plate: { + type: DataTypes.STRING(20), + allowNull: false, + unique: true, + comment: '车牌号' + }, + vehicle_type: { + type: DataTypes.STRING(50), + allowNull: false, + comment: '车辆类型' + }, + capacity: { + type: DataTypes.INTEGER, + allowNull: false, + comment: '载重能力(公斤)' + }, + driver_id: { + type: DataTypes.BIGINT, + allowNull: false, + comment: '司机ID' + }, + status: { + type: DataTypes.ENUM('available', 'in_use', 'maintenance', 'retired'), + defaultValue: 'available', + comment: '车辆状态: available(可用), in_use(使用中), maintenance(维护中), retired(已退役)' + }, + last_maintenance_date: { + type: DataTypes.DATE, + allowNull: true, + comment: '上次维护日期' + }, + next_maintenance_date: { + type: DataTypes.DATE, + allowNull: true, + comment: '下次维护日期' + }, + insurance_expiry_date: { + type: DataTypes.DATE, + allowNull: true, + comment: '保险到期日期' + }, + registration_expiry_date: { + type: DataTypes.DATE, + allowNull: true, + comment: '注册到期日期' + } +}, { + tableName: 'vehicles', + timestamps: true, + createdAt: 'created_at', + updatedAt: 'updated_at' +}); + +module.exports = Vehicle; \ No newline at end of file diff --git a/backend/src/routes/suppliers.js b/backend/src/routes/suppliers.js new file mode 100644 index 0000000..1d44f43 --- /dev/null +++ b/backend/src/routes/suppliers.js @@ -0,0 +1,406 @@ +const express = require('express'); +const router = express.Router(); +const Joi = require('joi'); +const { Supplier } = require('../../models'); +const { Sequelize } = require('sequelize'); + +// 验证schemas +const supplierCreateSchema = Joi.object({ + name: Joi.string().min(2).max(100).required(), + code: Joi.string().min(3).max(20).required(), + contact: Joi.string().min(2).max(50).required(), + phone: Joi.string().pattern(/^1[3-9]\d{9}$/).required(), + address: Joi.string().min(5).max(200).required(), + qualificationLevel: Joi.string().valid('A+', 'A', 'B+', 'B', 'C').required(), + cattleTypes: Joi.array().items(Joi.string()).min(1).required(), + capacity: Joi.number().integer().min(1).required(), + region: Joi.string().valid('north', 'south', 'east', 'west', 'northeast', 'northwest', 'southeast', 'southwest', 'central').required() +}); + +const supplierUpdateSchema = Joi.object({ + name: Joi.string().min(2).max(100), + contact: Joi.string().min(2).max(50), + phone: Joi.string().pattern(/^1[3-9]\d{9}$/), + address: Joi.string().min(5).max(200), + qualificationLevel: Joi.string().valid('A+', 'A', 'B+', 'B', 'C'), + cattleTypes: Joi.array().items(Joi.string()).min(1), + capacity: Joi.number().integer().min(1), + region: Joi.string().valid('north', 'south', 'east', 'west', 'northeast', 'northwest', 'southeast', 'southwest', 'central'), + status: Joi.string().valid('active', 'inactive', 'suspended') +}); + +// 获取供应商列表 +router.get('/', async (req, res) => { + try { + const { + page = 1, + pageSize = 20, + keyword, + region, + qualificationLevel, + status + } = req.query; + + // 构建查询条件 + const whereConditions = {}; + + // 状态筛选 + if (status) { + whereConditions.status = status; + } + + // 区域筛选 + if (region) { + whereConditions.region = region; + } + + // 资质等级筛选 + if (qualificationLevel) { + whereConditions.qualificationLevel = qualificationLevel; + } + + // 关键词搜索 + if (keyword) { + whereConditions[Sequelize.Op.or] = [ + { name: { [Sequelize.Op.like]: `%${keyword}%` } }, + { code: { [Sequelize.Op.like]: `%${keyword}%` } }, + { contact: { [Sequelize.Op.like]: `%${keyword}%` } } + ]; + } + + // 分页参数 + const offset = (page - 1) * pageSize; + const limit = parseInt(pageSize); + + // 查询数据库 + const { rows, count } = await Supplier.findAndCountAll({ + where: whereConditions, + offset, + limit, + order: [['created_at', 'DESC']] + }); + + res.json({ + success: true, + data: { + list: rows, + pagination: { + page: parseInt(page), + pageSize: limit, + total: count, + totalPages: Math.ceil(count / limit) + } + } + }); + } catch (error) { + console.error('获取供应商列表失败:', error); + res.status(500).json({ + success: false, + message: '获取供应商列表失败', + error: error.message + }); + } +}); + +// 获取供应商详情 +router.get('/:id', async (req, res) => { + try { + const { id } = req.params; + + // 查询数据库 + const supplier = await Supplier.findByPk(id); + + if (!supplier) { + return res.status(404).json({ + success: false, + message: '供应商不存在' + }); + } + + res.json({ + success: true, + data: supplier + }); + } catch (error) { + console.error('获取供应商详情失败:', error); + res.status(500).json({ + success: false, + message: '获取供应商详情失败', + error: error.message + }); + } +}); + +// 创建供应商 +router.post('/', async (req, res) => { + try { + const { error, value } = supplierCreateSchema.validate(req.body); + if (error) { + return res.status(400).json({ + success: false, + message: '参数验证失败', + errors: error.details.map(detail => detail.message) + }); + } + + // 检查编码是否重复 + const existingSupplier = await Supplier.findOne({ where: { code: value.code } }); + if (existingSupplier) { + return res.status(400).json({ + success: false, + message: '供应商编码已存在' + }); + } + + // 检查电话是否重复 + const existingPhone = await Supplier.findOne({ where: { phone: value.phone } }); + if (existingPhone) { + return res.status(400).json({ + success: false, + message: '供应商电话已存在' + }); + } + + // 创建新供应商 + const newSupplier = await Supplier.create({ + ...value, + businessLicense: '', + certifications: JSON.stringify([]), + cattleTypes: JSON.stringify(value.cattleTypes), + rating: 0, + cooperationStartDate: new Date(), + status: 'active' + }); + + res.status(201).json({ + success: true, + message: '供应商创建成功', + data: newSupplier + }); + } catch (error) { + console.error('创建供应商失败:', error); + res.status(500).json({ + success: false, + message: '创建供应商失败', + error: error.message + }); + } +}); + +// 更新供应商 +router.put('/:id', async (req, res) => { + try { + const { id } = req.params; + const { error, value } = supplierUpdateSchema.validate(req.body); + + if (error) { + return res.status(400).json({ + success: false, + message: '参数验证失败', + errors: error.details.map(detail => detail.message) + }); + } + + // 查找供应商 + const supplier = await Supplier.findByPk(id); + if (!supplier) { + return res.status(404).json({ + success: false, + message: '供应商不存在' + }); + } + + // 如果更新了电话号码,检查是否重复 + if (value.phone && value.phone !== supplier.phone) { + const existingPhone = await Supplier.findOne({ where: { phone: value.phone } }); + if (existingPhone) { + return res.status(400).json({ + success: false, + message: '供应商电话已存在' + }); + } + } + + // 更新供应商信息 + await supplier.update({ + ...value, + cattleTypes: value.cattleTypes ? JSON.stringify(value.cattleTypes) : undefined + }); + + res.json({ + success: true, + message: '供应商更新成功', + data: supplier + }); + } catch (error) { + console.error('更新供应商失败:', error); + res.status(500).json({ + success: false, + message: '更新供应商失败', + error: error.message + }); + } +}); + +// 删除供应商 +router.delete('/:id', async (req, res) => { + try { + const { id } = req.params; + + // 查找供应商 + const supplier = await Supplier.findByPk(id); + if (!supplier) { + return res.status(404).json({ + success: false, + message: '供应商不存在' + }); + } + + // 删除供应商 + await supplier.destroy(); + + res.json({ + success: true, + message: '供应商删除成功' + }); + } catch (error) { + console.error('删除供应商失败:', error); + res.status(500).json({ + success: false, + message: '删除供应商失败', + error: error.message + }); + } +}); + +// 获取供应商统计信息 +router.get('/stats/overview', async (req, res) => { + try { + // 获取总数和活跃数 + const totalSuppliers = await Supplier.count(); + const activeSuppliers = await Supplier.count({ where: { status: 'active' } }); + + // 获取平均评分(排除评分为0的供应商) + const ratingResult = await Supplier.findOne({ + attributes: [ + [Sequelize.fn('AVG', Sequelize.col('rating')), 'averageRating'] + ], + where: { + rating: { + [Sequelize.Op.gt]: 0 + } + } + }); + const averageRating = ratingResult ? parseFloat(ratingResult.getDataValue('averageRating')).toFixed(2) : 0; + + // 获取总产能 + const capacityResult = await Supplier.findOne({ + attributes: [ + [Sequelize.fn('SUM', Sequelize.col('capacity')), 'totalCapacity'] + ] + }); + const totalCapacity = capacityResult ? capacityResult.getDataValue('totalCapacity') : 0; + + // 按等级统计 + const levelStatsResult = await Supplier.findAll({ + attributes: [ + 'qualificationLevel', + [Sequelize.fn('COUNT', Sequelize.col('id')), 'count'] + ], + group: ['qualificationLevel'] + }); + const levelStats = levelStatsResult.reduce((stats, item) => { + stats[item.qualificationLevel] = item.getDataValue('count'); + return stats; + }, {}); + + // 按区域统计 + const regionStatsResult = await Supplier.findAll({ + attributes: [ + 'region', + [Sequelize.fn('COUNT', Sequelize.col('id')), 'count'] + ], + group: ['region'] + }); + const regionStats = regionStatsResult.reduce((stats, item) => { + stats[item.region] = item.getDataValue('count'); + return stats; + }, {}); + + res.json({ + success: true, + data: { + totalSuppliers, + activeSuppliers, + averageRating: parseFloat(averageRating), + totalCapacity, + levelStats, + regionStats + } + }); + } catch (error) { + console.error('获取供应商统计信息失败:', error); + res.status(500).json({ + success: false, + message: '获取供应商统计信息失败', + error: error.message + }); + } +}); + +// 批量操作供应商 +router.post('/batch', async (req, res) => { + try { + const { ids, action } = req.body; + + if (!ids || !Array.isArray(ids) || ids.length === 0) { + return res.status(400).json({ + success: false, + message: '请选择要操作的供应商' + }); + } + + if (!['activate', 'deactivate', 'delete'].includes(action)) { + return res.status(400).json({ + success: false, + message: '无效的操作类型' + }); + } + + switch (action) { + case 'activate': + await Supplier.update( + { status: 'active' }, + { where: { id: ids } } + ); + break; + case 'deactivate': + await Supplier.update( + { status: 'inactive' }, + { where: { id: ids } } + ); + break; + case 'delete': + await Supplier.destroy({ + where: { + id: ids + } + }); + break; + } + + res.json({ + success: true, + message: '批量操作成功' + }); + } catch (error) { + console.error('批量操作失败:', error); + res.status(500).json({ + success: false, + message: '批量操作失败', + error: error.message + }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/backend/src/routes/transports.js b/backend/src/routes/transports.js new file mode 100644 index 0000000..dd460c2 --- /dev/null +++ b/backend/src/routes/transports.js @@ -0,0 +1,78 @@ +const express = require('express'); +const router = express.Router(); +const transportController = require('../controllers/TransportController'); +const { authenticate, checkRole } = require('../middleware/auth'); + +// 运输管理路由 +// 获取运输列表 +router.get('/', + authenticate, + checkRole(['admin', 'logistics_manager']), + transportController.getTransportList +); + +// 获取运输详情 +router.get('/:id', + authenticate, + checkRole(['admin', 'logistics_manager']), + transportController.getTransportDetail +); + +// 创建运输记录 +router.post('/', + authenticate, + checkRole(['admin', 'logistics_manager']), + transportController.createTransport +); + +// 更新运输记录 +router.put('/:id', + authenticate, + checkRole(['admin', 'logistics_manager']), + transportController.updateTransport +); + +// 删除运输记录 +router.delete('/:id', + authenticate, + checkRole(['admin']), + transportController.deleteTransport +); + +// 车辆管理路由 +// 获取车辆列表 +router.get('/vehicles', + authenticate, + checkRole(['admin', 'logistics_manager']), + transportController.getVehicleList +); + +// 获取车辆详情 +router.get('/vehicles/:id', + authenticate, + checkRole(['admin', 'logistics_manager']), + transportController.getVehicleDetail +); + +// 创建车辆记录 +router.post('/vehicles', + authenticate, + checkRole(['admin', 'logistics_manager']), + transportController.createVehicle +); + +// 更新车辆记录 +router.put('/vehicles/:id', + authenticate, + checkRole(['admin', 'logistics_manager']), + transportController.updateVehicle +); + +// 删除车辆记录 +router.delete('/vehicles/:id', + authenticate, + checkRole(['admin']), + transportController.deleteVehicle +); + +module.exports = router; \ No newline at end of file diff --git a/docs/api/orders.yaml b/docs/api/orders.yaml index 861b561..72fccf1 100644 --- a/docs/api/orders.yaml +++ b/docs/api/orders.yaml @@ -4,11 +4,15 @@ info: description: 订单管理相关接口文档 version: 1.0.0 +servers: + - url: http://localhost:4330/api + description: 开发服务器 + paths: - /api/orders/: + /orders/: get: summary: 获取订单列表 - description: 获取系统中的订单列表,支持分页 + description: 获取系统中的订单列表,支持分页和筛选 parameters: - name: skip in: query @@ -24,15 +28,26 @@ paths: schema: type: integer default: 100 + - name: keyword + in: query + description: 搜索关键词(订单号) + required: false + schema: + type: string + - name: status + in: query + description: 订单状态筛选 + required: false + schema: + type: string + enum: [pending, confirmed, processing, shipped, delivered, cancelled, returned, completed, archived] responses: '200': description: 成功返回订单列表 content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/Order' + $ref: '#/components/schemas/PagedResponse' '401': description: 未授权 content: @@ -45,6 +60,8 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + security: + - bearerAuth: [] post: summary: 创建订单 description: 创建一个新的订单 @@ -53,7 +70,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/OrderCreate' + $ref: '#/components/schemas/CreateOrderRequest' responses: '200': description: 成功创建订单 @@ -79,8 +96,10 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + security: + - bearerAuth: [] - /api/orders/{order_id}: + /orders/{order_id}: get: summary: 获取订单详情 description: 根据订单ID获取订单详细信息 @@ -116,6 +135,8 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + security: + - bearerAuth: [] put: summary: 更新订单信息 description: 根据订单ID更新订单信息 @@ -131,7 +152,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/OrderUpdate' + $ref: '#/components/schemas/UpdateOrderRequest' responses: '200': description: 成功更新订单信息 @@ -163,6 +184,8 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + security: + - bearerAuth: [] delete: summary: 删除订单 description: 根据订单ID删除订单 @@ -198,6 +221,66 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + security: + - bearerAuth: [] + + /orders/{order_id}/status: + patch: + summary: 更新订单状态 + description: 根据订单ID更新订单状态 + parameters: + - name: order_id + in: path + description: 订单ID + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + status: + type: string + enum: [pending, confirmed, processing, shipped, delivered, cancelled, returned, completed, archived] + description: 订单状态 + required: + - status + responses: + '200': + description: 成功更新订单状态 + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + '404': + description: 订单未找到 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: 未授权 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '422': + description: 请求参数验证失败 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: 服务器内部错误 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + security: + - bearerAuth: [] components: schemas: @@ -209,51 +292,75 @@ components: description: 订单ID order_no: type: string - description: 订单号 + description: 订单编号 buyer_id: type: integer description: 买家ID - seller_id: + supplier_id: type: integer - description: 卖家ID - variety_type: - type: string - description: 品种类型 - weight_range: - type: string - description: 重量范围 - weight_actual: + description: 供应商ID + total_amount: type: number format: float - description: 实际重量 - price_per_unit: + description: 订单总金额 + advance_amount: type: number format: float - description: 单价 - total_price: + description: 预付金额 + final_amount: type: number format: float - description: 总价 - advance_payment: - type: number - format: float - description: 预付款 - final_payment: - type: number - format: float - description: 尾款 + description: 尾款金额 status: type: string - enum: [pending, confirmed, processing, shipped, delivered, cancelled, completed] + enum: [pending, confirmed, processing, shipped, delivered, cancelled, returned, completed, archived] description: 订单状态 + payment_status: + type: string + enum: [unpaid, partial_paid, fully_paid, refunded] + description: 支付状态 + delivery_status: + type: string + enum: [pending, preparing, shipped, delivered, returned] + description: 发货状态 + logistics_status: + type: string + enum: [pending, in_transit, delivered, exception] + description: 物流状态 + quality_status: + type: string + enum: [pending, inspected, qualified, unqualified] + description: 质检状态 + buyer_rating: + type: integer + description: 买家评分 + buyer_comment: + type: string + description: 买家评论 + supplier_rating: + type: integer + description: 供应商评分 + supplier_comment: + type: string + description: 供应商评论 delivery_address: type: string description: 收货地址 - delivery_time: + delivery_contact: type: string - format: date-time - description: 交付时间 - remark: + description: 收货联系人 + delivery_phone: + type: string + description: 收货电话 + expected_delivery_date: + type: string + format: date + description: 期望送达日期 + actual_delivery_date: + type: string + format: date + description: 实际送达日期 + notes: type: string description: 备注 created_at: @@ -268,124 +375,158 @@ components: - id - order_no - buyer_id - - seller_id - - variety_type - - weight_range - - price_per_unit - - total_price - - advance_payment - - final_payment + - supplier_id + - total_amount + - advance_amount + - final_amount - status + - payment_status + - delivery_status + - logistics_status + - quality_status - created_at - updated_at - OrderCreate: + CreateOrderRequest: type: object properties: buyer_id: type: integer description: 买家ID - seller_id: + supplier_id: type: integer - description: 卖家ID - variety_type: - type: string - description: 品种类型 - weight_range: - type: string - description: 重量范围 - weight_actual: + description: 供应商ID + total_amount: type: number format: float - description: 实际重量 - price_per_unit: + description: 订单总金额 + advance_amount: type: number format: float - description: 单价 - total_price: + description: 预付金额 + final_amount: type: number format: float - description: 总价 - advance_payment: - type: number - format: float - description: 预付款 - final_payment: - type: number - format: float - description: 尾款 - status: - type: string - enum: [pending, confirmed, processing, shipped, delivered, cancelled, completed] - description: 订单状态 + description: 尾款金额 delivery_address: type: string description: 收货地址 - delivery_time: + delivery_contact: type: string - format: date-time - description: 交付时间 - remark: + description: 收货联系人 + delivery_phone: + type: string + description: 收货电话 + expected_delivery_date: + type: string + format: date + description: 期望送达日期 + notes: type: string description: 备注 required: - buyer_id - - seller_id - - variety_type - - weight_range - - price_per_unit - - total_price + - supplier_id + - total_amount + - advance_amount + - final_amount - OrderUpdate: + UpdateOrderRequest: type: object properties: buyer_id: type: integer description: 买家ID - seller_id: + supplier_id: type: integer - description: 卖家ID - variety_type: - type: string - description: 品种类型 - weight_range: - type: string - description: 重量范围 - weight_actual: + description: 供应商ID + total_amount: type: number format: float - description: 实际重量 - price_per_unit: + description: 订单总金额 + advance_amount: type: number format: float - description: 单价 - total_price: + description: 预付金额 + final_amount: type: number format: float - description: 总价 - advance_payment: - type: number - format: float - description: 预付款 - final_payment: - type: number - format: float - description: 尾款 + description: 尾款金额 status: type: string - enum: [pending, confirmed, processing, shipped, delivered, cancelled, completed] + enum: [pending, confirmed, processing, shipped, delivered, cancelled, returned, completed, archived] description: 订单状态 + payment_status: + type: string + enum: [unpaid, partial_paid, fully_paid, refunded] + description: 支付状态 + delivery_status: + type: string + enum: [pending, preparing, shipped, delivered, returned] + description: 发货状态 + logistics_status: + type: string + enum: [pending, in_transit, delivered, exception] + description: 物流状态 + quality_status: + type: string + enum: [pending, inspected, qualified, unqualified] + description: 质检状态 + buyer_rating: + type: integer + description: 买家评分 + buyer_comment: + type: string + description: 买家评论 + supplier_rating: + type: integer + description: 供应商评分 + supplier_comment: + type: string + description: 供应商评论 delivery_address: type: string description: 收货地址 - delivery_time: + delivery_contact: type: string - format: date-time - description: 交付时间 - remark: + description: 收货联系人 + delivery_phone: + type: string + description: 收货电话 + expected_delivery_date: + type: string + format: date + description: 期望送达日期 + actual_delivery_date: + type: string + format: date + description: 实际送达日期 + notes: type: string description: 备注 + PagedResponse: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Order' + total: + type: integer + description: 总记录数 + skip: + type: integer + description: 跳过的记录数 + limit: + type: integer + description: 返回的记录数 + required: + - data + - total + - skip + - limit + Error: type: object properties: @@ -393,4 +534,10 @@ components: type: string description: 错误信息 required: - - detail \ No newline at end of file + - detail + +securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT \ No newline at end of file