feat(backend): 开发订单管理和供应商管理功能
- 新增订单管理页面,实现订单列表展示、搜索、分页等功能 - 新增供应商管理页面,实现供应商列表展示、搜索、分页等功能- 添加订单和供应商相关模型及数据库迁移 - 实现订单状态更新和供应商信息编辑功能 - 优化后端路由结构,移除不必要的代码
This commit is contained in:
75
admin-system/src/api/supplier.ts
Normal file
75
admin-system/src/api/supplier.ts
Normal file
@@ -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<Supplier>({
|
||||
url: `/suppliers/${id}`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
// 创建供应商
|
||||
export const createSupplier = (data: SupplierCreateForm) => {
|
||||
return request<Supplier>({
|
||||
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<Supplier>({
|
||||
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<SupplierStatistics>({
|
||||
url: '/suppliers/stats/overview',
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
142
admin-system/src/api/transport.ts
Normal file
142
admin-system/src/api/transport.ts
Normal file
@@ -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<ApiResponse<PaginatedResponse<Transport>>> => {
|
||||
return request({
|
||||
url: '/transports',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取运输详情
|
||||
* @param id 运输ID
|
||||
* @returns 运输详情
|
||||
*/
|
||||
export const getTransportDetail = (id: number): Promise<ApiResponse<Transport>> => {
|
||||
return request({
|
||||
url: `/transports/${id}`,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建运输记录
|
||||
* @param data 运输创建表单
|
||||
* @returns 创建的运输记录
|
||||
*/
|
||||
export const createTransport = (data: TransportCreateForm): Promise<ApiResponse<Transport>> => {
|
||||
return request({
|
||||
url: '/transports',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新运输记录
|
||||
* @param id 运输ID
|
||||
* @param data 运输更新表单
|
||||
* @returns 更新的运输记录
|
||||
*/
|
||||
export const updateTransport = (id: number, data: TransportUpdateForm): Promise<ApiResponse<Transport>> => {
|
||||
return request({
|
||||
url: `/transports/${id}`,
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除运输记录
|
||||
* @param id 运输ID
|
||||
* @returns 删除结果
|
||||
*/
|
||||
export const deleteTransport = (id: number): Promise<ApiResponse<null>> => {
|
||||
return request({
|
||||
url: `/transports/${id}`,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取车辆列表
|
||||
* @param params 查询参数
|
||||
* @returns 车辆列表
|
||||
*/
|
||||
export const getVehicleList = (params: VehicleListParams): Promise<ApiResponse<PaginatedResponse<Vehicle>>> => {
|
||||
return request({
|
||||
url: '/transports/vehicles',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取车辆详情
|
||||
* @param id 车辆ID
|
||||
* @returns 车辆详情
|
||||
*/
|
||||
export const getVehicleDetail = (id: number): Promise<ApiResponse<Vehicle>> => {
|
||||
return request({
|
||||
url: `/transports/vehicles/${id}`,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建车辆记录
|
||||
* @param data 车辆创建表单
|
||||
* @returns 创建的车辆记录
|
||||
*/
|
||||
export const createVehicle = (data: VehicleCreateForm): Promise<ApiResponse<Vehicle>> => {
|
||||
return request({
|
||||
url: '/transports/vehicles',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新车辆记录
|
||||
* @param id 车辆ID
|
||||
* @param data 车辆更新表单
|
||||
* @returns 更新的车辆记录
|
||||
*/
|
||||
export const updateVehicle = (id: number, data: VehicleUpdateForm): Promise<ApiResponse<Vehicle>> => {
|
||||
return request({
|
||||
url: `/transports/vehicles/${id}`,
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除车辆记录
|
||||
* @param id 车辆ID
|
||||
* @returns 删除结果
|
||||
*/
|
||||
export const deleteVehicle = (id: number): Promise<ApiResponse<null>> => {
|
||||
return request({
|
||||
url: `/transports/vehicles/${id}`,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
24
admin-system/src/types/api.ts
Normal file
24
admin-system/src/types/api.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
// API响应相关类型定义
|
||||
|
||||
/**
|
||||
* 分页响应数据结构
|
||||
*/
|
||||
export interface PaginatedResponse<T> {
|
||||
list: T[];
|
||||
pagination: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* API响应基础结构
|
||||
*/
|
||||
export interface ApiResponse<T> {
|
||||
success: boolean;
|
||||
data: T;
|
||||
message: string;
|
||||
code: number;
|
||||
}
|
||||
70
admin-system/src/types/supplier.ts
Normal file
70
admin-system/src/types/supplier.ts
Normal file
@@ -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<string, number>;
|
||||
regionStats: Record<string, number>;
|
||||
}
|
||||
111
admin-system/src/types/transport.ts
Normal file
111
admin-system/src/types/transport.ts
Normal file
@@ -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;
|
||||
}
|
||||
@@ -1,34 +1,409 @@
|
||||
<template>
|
||||
<div class="order-management">
|
||||
<el-card>
|
||||
<div class="page-header">
|
||||
<h2>订单管理</h2>
|
||||
<p>管理活牛采购订单的全生命周期流程</p>
|
||||
</div>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>订单管理</span>
|
||||
<el-button type="primary" @click="handleCreateOrder">创建订单</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-empty description="订单管理功能开发中..." />
|
||||
<!-- 搜索条件 -->
|
||||
<el-form :model="searchForm" label-width="80px" class="search-form">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="订单号">
|
||||
<el-input v-model="searchForm.orderNo" placeholder="请输入订单号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="订单状态">
|
||||
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
|
||||
<el-option
|
||||
v-for="status in orderStatusOptions"
|
||||
:key="status.value"
|
||||
:label="status.label"
|
||||
:value="status.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="下单时间">
|
||||
<el-date-picker
|
||||
v-model="searchForm.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<!-- 订单列表 -->
|
||||
<el-table :data="orderList" v-loading="loading" style="width: 100%" stripe>
|
||||
<el-table-column prop="orderNo" label="订单号" width="180" />
|
||||
<el-table-column prop="buyerName" label="采购方" width="120" />
|
||||
<el-table-column prop="supplierName" label="供应商" width="120" />
|
||||
<el-table-column prop="cattleBreed" label="牛品种" width="100" />
|
||||
<el-table-column prop="cattleCount" label="数量" width="80" />
|
||||
<el-table-column prop="expectedWeight" label="预估重量(kg)" width="120" />
|
||||
<el-table-column prop="totalAmount" label="总金额(元)" width="120" />
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getStatusTagType(row.status)">
|
||||
{{ getOrderStatusText(row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createdAt" label="下单时间" width="180" />
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="handleViewDetail(row)">详情</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="handleUpdateStatus(row)"
|
||||
v-if="canUpdateStatus(row)"
|
||||
>
|
||||
更新状态
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.currentPage"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="pagination.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
class="pagination"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- 订单详情对话框 -->
|
||||
<el-dialog v-model="detailDialogVisible" title="订单详情" width="60%">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="订单号">{{ currentOrder.orderNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="订单状态">
|
||||
<el-tag :type="getStatusTagType(currentOrder.status)">
|
||||
{{ getOrderStatusText(currentOrder.status) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="采购方">{{ currentOrder.buyerName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="供应商">{{ currentOrder.supplierName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="牛品种">{{ currentOrder.cattleBreed }}</el-descriptions-item>
|
||||
<el-descriptions-item label="数量">{{ currentOrder.cattleCount }} 头</el-descriptions-item>
|
||||
<el-descriptions-item label="预估重量">{{ currentOrder.expectedWeight }} kg</el-descriptions-item>
|
||||
<el-descriptions-item label="单价">{{ currentOrder.unitPrice }} 元/kg</el-descriptions-item>
|
||||
<el-descriptions-item label="总金额">{{ currentOrder.totalAmount }} 元</el-descriptions-item>
|
||||
<el-descriptions-item label="已支付">{{ currentOrder.paidAmount }} 元</el-descriptions-item>
|
||||
<el-descriptions-item label="待支付">{{ currentOrder.remainingAmount }} 元</el-descriptions-item>
|
||||
<el-descriptions-item label="交付地址">{{ currentOrder.deliveryAddress }}</el-descriptions-item>
|
||||
<el-descriptions-item label="期望交付日期">{{ currentOrder.expectedDeliveryDate }}</el-descriptions-item>
|
||||
<el-descriptions-item label="实际交付日期">{{ currentOrder.actualDeliveryDate || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="下单时间">{{ currentOrder.createdAt }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">{{ currentOrder.notes || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 更新状态对话框 -->
|
||||
<el-dialog v-model="statusDialogVisible" title="更新订单状态" width="400px">
|
||||
<el-form :model="statusForm" label-width="80px">
|
||||
<el-form-item label="当前状态">
|
||||
<el-tag :type="getStatusTagType(currentOrder.status)">
|
||||
{{ getOrderStatusText(currentOrder.status) }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="新状态" prop="status" :rules="{ required: true, message: '请选择状态', trigger: 'change' }">
|
||||
<el-select v-model="statusForm.status" placeholder="请选择新状态" style="width: 100%">
|
||||
<el-option
|
||||
v-for="status in getNextStatusOptions(currentOrder.status)"
|
||||
:key="status.value"
|
||||
:label="status.label"
|
||||
:value="status.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="statusDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitStatusUpdate">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 订单管理页面
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { Order, OrderStatus, OrderListParams } from '@/types/order'
|
||||
import { getOrderList, getOrderDetail, updateOrder } from '@/api/order'
|
||||
|
||||
// 订单列表数据
|
||||
const orderList = ref<Order[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
orderNo: '',
|
||||
status: '',
|
||||
dateRange: []
|
||||
})
|
||||
|
||||
// 分页信息
|
||||
const pagination = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 对话框控制
|
||||
const detailDialogVisible = ref(false)
|
||||
const statusDialogVisible = ref(false)
|
||||
|
||||
// 当前选中的订单
|
||||
const currentOrder = ref<Order>({
|
||||
id: 0,
|
||||
orderNo: '',
|
||||
buyerId: 0,
|
||||
buyerName: '',
|
||||
supplierId: 0,
|
||||
supplierName: '',
|
||||
cattleBreed: '',
|
||||
cattleCount: 0,
|
||||
expectedWeight: 0,
|
||||
unitPrice: 0,
|
||||
totalAmount: 0,
|
||||
paidAmount: 0,
|
||||
remainingAmount: 0,
|
||||
status: 'pending',
|
||||
deliveryAddress: '',
|
||||
expectedDeliveryDate: '',
|
||||
createdAt: '',
|
||||
updatedAt: ''
|
||||
})
|
||||
|
||||
// 状态更新表单
|
||||
const statusForm = reactive({
|
||||
status: '' as OrderStatus
|
||||
})
|
||||
|
||||
// 订单状态选项
|
||||
const orderStatusOptions = [
|
||||
{ value: 'pending', label: '待确认' },
|
||||
{ value: 'confirmed', label: '已确认' },
|
||||
{ value: 'preparing', label: '准备中' },
|
||||
{ value: 'shipping', label: '运输中' },
|
||||
{ value: 'delivered', label: '已送达' },
|
||||
{ value: 'accepted', label: '已验收' },
|
||||
{ value: 'completed', label: '已完成' },
|
||||
{ value: 'cancelled', label: '已取消' },
|
||||
{ value: 'refunded', label: '已退款' }
|
||||
]
|
||||
|
||||
// 获取订单状态文本
|
||||
const getOrderStatusText = (status: OrderStatus) => {
|
||||
const statusMap: Record<OrderStatus, string> = {
|
||||
pending: '待确认',
|
||||
confirmed: '已确认',
|
||||
preparing: '准备中',
|
||||
shipping: '运输中',
|
||||
delivered: '已送达',
|
||||
accepted: '已验收',
|
||||
completed: '已完成',
|
||||
cancelled: '已取消',
|
||||
refunded: '已退款'
|
||||
}
|
||||
return statusMap[status] || status
|
||||
}
|
||||
|
||||
// 获取状态标签类型
|
||||
const getStatusTagType = (status: OrderStatus) => {
|
||||
const typeMap: Record<OrderStatus, 'primary' | 'success' | 'warning' | 'danger' | 'info'> = {
|
||||
pending: 'warning',
|
||||
confirmed: 'primary',
|
||||
preparing: 'primary',
|
||||
shipping: 'primary',
|
||||
delivered: 'primary',
|
||||
accepted: 'primary',
|
||||
completed: 'success',
|
||||
cancelled: 'danger',
|
||||
refunded: 'danger'
|
||||
}
|
||||
return typeMap[status] || 'info'
|
||||
}
|
||||
|
||||
// 获取下一状态选项
|
||||
const getNextStatusOptions = (currentStatus: OrderStatus) => {
|
||||
const nextStatusMap: Record<OrderStatus, OrderStatus[]> = {
|
||||
pending: ['confirmed', 'cancelled'],
|
||||
confirmed: ['preparing', 'cancelled'],
|
||||
preparing: ['shipping'],
|
||||
shipping: ['delivered'],
|
||||
delivered: ['accepted'],
|
||||
accepted: ['completed'],
|
||||
completed: [],
|
||||
cancelled: [],
|
||||
refunded: []
|
||||
}
|
||||
|
||||
const nextStatuses = nextStatusMap[currentStatus] || []
|
||||
return orderStatusOptions.filter(option => nextStatuses.includes(option.value as OrderStatus))
|
||||
}
|
||||
|
||||
// 判断是否可以更新状态
|
||||
const canUpdateStatus = (order: Order) => {
|
||||
return order.status !== 'completed' && order.status !== 'cancelled' && order.status !== 'refunded'
|
||||
}
|
||||
|
||||
// 获取订单列表
|
||||
const fetchOrderList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: OrderListParams = {
|
||||
page: pagination.currentPage,
|
||||
pageSize: pagination.pageSize,
|
||||
orderNo: searchForm.orderNo || undefined,
|
||||
status: searchForm.status || undefined
|
||||
}
|
||||
|
||||
if (searchForm.dateRange && searchForm.dateRange.length === 2) {
|
||||
params.startDate = searchForm.dateRange[0]
|
||||
params.endDate = searchForm.dateRange[1]
|
||||
}
|
||||
|
||||
const res = await getOrderList(params)
|
||||
orderList.value = res.data.items
|
||||
pagination.total = res.data.total
|
||||
} catch (error) {
|
||||
ElMessage.error('获取订单列表失败')
|
||||
console.error(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = () => {
|
||||
pagination.currentPage = 1
|
||||
fetchOrderList()
|
||||
}
|
||||
|
||||
// 处理重置
|
||||
const handleReset = () => {
|
||||
searchForm.orderNo = ''
|
||||
searchForm.status = ''
|
||||
searchForm.dateRange = []
|
||||
pagination.currentPage = 1
|
||||
fetchOrderList()
|
||||
}
|
||||
|
||||
// 处理分页大小变化
|
||||
const handleSizeChange = (val: number) => {
|
||||
pagination.pageSize = val
|
||||
pagination.currentPage = 1
|
||||
fetchOrderList()
|
||||
}
|
||||
|
||||
// 处理当前页变化
|
||||
const handleCurrentChange = (val: number) => {
|
||||
pagination.currentPage = val
|
||||
fetchOrderList()
|
||||
}
|
||||
|
||||
// 处理创建订单
|
||||
const handleCreateOrder = () => {
|
||||
ElMessage.info('创建订单功能开发中...')
|
||||
}
|
||||
|
||||
// 处理查看详情
|
||||
const handleViewDetail = async (order: Order) => {
|
||||
try {
|
||||
const res = await getOrderDetail(order.id)
|
||||
currentOrder.value = res.data
|
||||
detailDialogVisible.value = true
|
||||
} catch (error) {
|
||||
ElMessage.error('获取订单详情失败')
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理更新状态
|
||||
const handleUpdateStatus = (order: Order) => {
|
||||
currentOrder.value = order
|
||||
statusForm.status = '' as OrderStatus
|
||||
statusDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 提交状态更新
|
||||
const submitStatusUpdate = async () => {
|
||||
if (!statusForm.status) {
|
||||
ElMessage.warning('请选择新状态')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await updateOrder(currentOrder.value.id, { status: statusForm.status })
|
||||
ElMessage.success('订单状态更新成功')
|
||||
statusDialogVisible.value = false
|
||||
fetchOrderList()
|
||||
} catch (error) {
|
||||
ElMessage.error('订单状态更新失败')
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
fetchOrderList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.order-management {
|
||||
.page-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
|
||||
h2 {
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #666;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -6,13 +6,559 @@
|
||||
<p>管理供应商信息、资质认证和绩效评估</p>
|
||||
</div>
|
||||
|
||||
<el-empty description="供应商管理功能开发中..." />
|
||||
<!-- 搜索和操作栏 -->
|
||||
<div class="toolbar">
|
||||
<el-form :model="searchForm" label-width="80px" class="search-form">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="供应商名称">
|
||||
<el-input v-model="searchForm.name" placeholder="请输入供应商名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="供应商编码">
|
||||
<el-input v-model="searchForm.code" placeholder="请输入供应商编码" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
|
||||
<el-option label="活跃" value="active" />
|
||||
<el-option label="非活跃" value="inactive" />
|
||||
<el-option label="已暂停" value="suspended" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="toolbar-buttons">
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
<el-button type="success" @click="handleCreate">新增供应商</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 供应商列表 -->
|
||||
<el-table :data="supplierList" border stripe v-loading="loading">
|
||||
<el-table-column prop="name" label="供应商名称" min-width="120" />
|
||||
<el-table-column prop="code" label="编码" width="100" />
|
||||
<el-table-column prop="contact" label="联系人" width="100" />
|
||||
<el-table-column prop="phone" label="联系电话" width="120" />
|
||||
<el-table-column prop="region" label="所属地区" width="100" />
|
||||
<el-table-column prop="qualificationLevel" label="资质等级" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getQualificationLevelType(row.qualificationLevel)">
|
||||
{{ row.qualificationLevel }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="rating" label="评级" width="80" />
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getStatusType(row.status)">
|
||||
{{ getStatusText(row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="certifications" label="认证信息" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
v-for="cert in row.certifications"
|
||||
:key="cert"
|
||||
size="small"
|
||||
style="margin-right: 5px;"
|
||||
>
|
||||
{{ cert }}
|
||||
</el-tag>
|
||||
<span v-if="!row.certifications || row.certifications.length === 0">无</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="handleView(row)">查看</el-button>
|
||||
<el-button size="small" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-popconfirm
|
||||
title="确定要删除这个供应商吗?"
|
||||
@confirm="handleDelete(row.id)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button size="small" type="danger">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.page"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="pagination.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 供应商详情对话框 -->
|
||||
<el-dialog v-model="detailDialogVisible" title="供应商详情" width="600px">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="供应商名称">{{ currentSupplier?.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="编码">{{ currentSupplier?.code }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系人">{{ currentSupplier?.contact }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ currentSupplier?.phone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="地址">{{ currentSupplier?.address }}</el-descriptions-item>
|
||||
<el-descriptions-item label="营业执照">{{ currentSupplier?.businessLicense }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属地区">{{ getRegionText(currentSupplier?.region) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="资质等级">
|
||||
<el-tag :type="getQualificationLevelType(currentSupplier?.qualificationLevel)">
|
||||
{{ currentSupplier?.qualificationLevel }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="认证信息">
|
||||
<el-tag
|
||||
v-for="cert in currentSupplier?.certifications"
|
||||
:key="cert"
|
||||
size="small"
|
||||
style="margin-right: 5px;"
|
||||
>
|
||||
{{ cert }}
|
||||
</el-tag>
|
||||
<span v-if="!currentSupplier?.certifications || currentSupplier?.certifications.length === 0">无</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="支持牛种">{{ getFormattedCattleTypes(currentSupplier?.cattleTypes) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="供应能力">{{ currentSupplier?.capacity }} 头</el-descriptions-item>
|
||||
<el-descriptions-item label="评级">{{ currentSupplier?.rating }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="getStatusType(currentSupplier?.status)">
|
||||
{{ getStatusText(currentSupplier?.status) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="合作开始日期">{{ currentSupplier?.cooperationStartDate }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ currentSupplier?.created_at }}</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">{{ currentSupplier?.updated_at }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer>
|
||||
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 供应商编辑对话框 -->
|
||||
<el-dialog v-model="editDialogVisible" :title="editForm.id ? '编辑供应商' : '新增供应商'" width="600px">
|
||||
<el-form :model="editForm" :rules="editRules" ref="editFormRef" label-width="100px">
|
||||
<el-form-item label="供应商名称" prop="name">
|
||||
<el-input v-model="editForm.name" />
|
||||
</el-form-item>
|
||||
<el-form-item label="编码" prop="code">
|
||||
<el-input v-model="editForm.code" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系人" prop="contact">
|
||||
<el-input v-model="editForm.contact" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话" prop="phone">
|
||||
<el-input v-model="editForm.phone" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地址" prop="address">
|
||||
<el-input v-model="editForm.address" type="textarea" />
|
||||
</el-form-item>
|
||||
<el-form-item label="营业执照" prop="businessLicense">
|
||||
<el-input v-model="editForm.businessLicense" placeholder="请输入营业执照编号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属地区" prop="region">
|
||||
<el-select v-model="editForm.region" placeholder="请选择所属地区">
|
||||
<el-option
|
||||
v-for="region in regionOptions"
|
||||
:key="region.value"
|
||||
:label="region.label"
|
||||
:value="region.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="资质等级" prop="qualificationLevel">
|
||||
<el-select v-model="editForm.qualificationLevel" placeholder="请选择资质等级">
|
||||
<el-option label="A+" value="A+" />
|
||||
<el-option label="A" value="A" />
|
||||
<el-option label="B+" value="B+" />
|
||||
<el-option label="B" value="B" />
|
||||
<el-option label="C" value="C" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="认证信息" prop="certifications">
|
||||
<el-select
|
||||
v-model="editForm.certifications"
|
||||
multiple
|
||||
placeholder="请选择认证信息"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="ISO9001" value="ISO9001" />
|
||||
<el-option label="ISO14001" value="ISO14001" />
|
||||
<el-option label="有机认证" value="organic" />
|
||||
<el-option label="无公害认证" value="pollutionFree" />
|
||||
<el-option label="绿色食品认证" value="greenFood" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="支持牛种" prop="cattleTypes">
|
||||
<el-select
|
||||
v-model="editForm.cattleTypes"
|
||||
multiple
|
||||
placeholder="请选择支持的牛种类型"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="cattleType in cattleTypeOptions"
|
||||
:key="cattleType"
|
||||
:label="cattleType"
|
||||
:value="cattleType"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="供应能力" prop="capacity">
|
||||
<el-input-number v-model="editForm.capacity" :min="0" /> 头
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status" v-if="editForm.id">
|
||||
<el-select v-model="editForm.status" placeholder="请选择状态">
|
||||
<el-option label="活跃" value="active" />
|
||||
<el-option label="非活跃" value="inactive" />
|
||||
<el-option label="已暂停" value="suspended" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="editDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSave">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 供应商管理页面
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import {
|
||||
getSupplierList,
|
||||
getSupplierDetail,
|
||||
createSupplier,
|
||||
updateSupplier,
|
||||
deleteSupplier
|
||||
} from '@/api/supplier'
|
||||
import type { Supplier, SupplierListParams, SupplierCreateForm, SupplierUpdateForm } from '@/types/supplier'
|
||||
|
||||
// 数据状态
|
||||
const loading = ref(false)
|
||||
const supplierList = ref<Supplier[]>([])
|
||||
const detailDialogVisible = ref(false)
|
||||
const editDialogVisible = ref(false)
|
||||
|
||||
// 当前查看的供应商
|
||||
const currentSupplier = ref<Supplier | null>(null)
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive<SupplierListParams>({
|
||||
name: '',
|
||||
code: '',
|
||||
status: '',
|
||||
page: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
// 分页信息
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 编辑表单
|
||||
const editForm = reactive<SupplierCreateForm & SupplierUpdateForm & { id?: number }>({
|
||||
id: undefined,
|
||||
name: '',
|
||||
code: '',
|
||||
contact: '',
|
||||
phone: '',
|
||||
address: '',
|
||||
businessLicense: '',
|
||||
region: '',
|
||||
qualificationLevel: '',
|
||||
certifications: [],
|
||||
cattleTypes: [],
|
||||
capacity: 0,
|
||||
status: 'active'
|
||||
})
|
||||
|
||||
// 编辑表单验证规则
|
||||
const editRules = {
|
||||
name: [{ required: true, message: '请输入供应商名称', trigger: 'blur' }],
|
||||
code: [{ required: true, message: '请输入供应商编码', trigger: 'blur' }],
|
||||
contact: [{ required: true, message: '请输入联系人', trigger: 'blur' }],
|
||||
phone: [
|
||||
{ required: true, message: '请输入联系电话', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||
],
|
||||
address: [{ required: true, message: '请输入地址', trigger: 'blur' }],
|
||||
region: [{ required: true, message: '请选择所属地区', trigger: 'change' }],
|
||||
qualificationLevel: [{ required: true, message: '请选择资质等级', trigger: 'change' }],
|
||||
cattleTypes: [{ required: true, message: '请选择支持的牛种类型', trigger: 'change' }],
|
||||
capacity: [{ required: true, message: '请输入供应能力', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
// 编辑表单引用
|
||||
const editFormRef = ref<FormInstance>()
|
||||
|
||||
// 地区选项
|
||||
const regionOptions = [
|
||||
{ label: '华北地区', value: 'north' },
|
||||
{ label: '华南地区', value: 'south' },
|
||||
{ label: '华东地区', value: 'east' },
|
||||
{ label: '华西地区', value: 'west' },
|
||||
{ label: '东北地区', value: 'northeast' },
|
||||
{ label: '西北地区', value: 'northwest' },
|
||||
{ label: '东南地区', value: 'southeast' },
|
||||
{ label: '西南地区', value: 'southwest' },
|
||||
{ label: '华中地区', value: 'central' }
|
||||
]
|
||||
|
||||
// 牛种类型选项
|
||||
const cattleTypeOptions = [
|
||||
'西门塔尔牛',
|
||||
'夏洛莱牛',
|
||||
'利木赞牛',
|
||||
'安格斯牛',
|
||||
'海福特牛',
|
||||
'鲁西黄牛',
|
||||
'延边牛',
|
||||
'秦川牛',
|
||||
'南阳牛',
|
||||
'晋南牛'
|
||||
]
|
||||
|
||||
// 获取供应商列表
|
||||
const fetchSupplierList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
...searchForm,
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize
|
||||
}
|
||||
|
||||
const res = await getSupplierList(params)
|
||||
supplierList.value = res.data.list
|
||||
pagination.total = res.data.pagination.total
|
||||
} catch (error) {
|
||||
ElMessage.error('获取供应商列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 状态标签类型映射
|
||||
const getStatusType = (status: string) => {
|
||||
switch (status) {
|
||||
case 'active': return 'success'
|
||||
case 'inactive': return 'info'
|
||||
case 'suspended': return 'danger'
|
||||
default: return 'info'
|
||||
}
|
||||
}
|
||||
|
||||
// 状态文本映射
|
||||
const getStatusText = (status: string) => {
|
||||
switch (status) {
|
||||
case 'active': return '活跃'
|
||||
case 'inactive': return '非活跃'
|
||||
case 'suspended': return '已暂停'
|
||||
default: return status
|
||||
}
|
||||
}
|
||||
|
||||
// 地区文本映射
|
||||
const getRegionText = (region: string) => {
|
||||
const regionOption = regionOptions.find(option => option.value === region)
|
||||
return regionOption ? regionOption.label : region
|
||||
}
|
||||
|
||||
// 格式化牛种类型显示
|
||||
const getFormattedCattleTypes = (cattleTypes: string) => {
|
||||
try {
|
||||
const types = JSON.parse(cattleTypes)
|
||||
return Array.isArray(types) ? types.join(', ') : cattleTypes
|
||||
} catch {
|
||||
return cattleTypes
|
||||
}
|
||||
}
|
||||
|
||||
// 资质等级标签类型映射
|
||||
const getQualificationLevelType = (level: string) => {
|
||||
switch (level) {
|
||||
case 'A+': return 'success'
|
||||
case 'A': return 'success'
|
||||
case 'B+': return 'warning'
|
||||
case 'B': return 'warning'
|
||||
case 'C': return 'danger'
|
||||
default: return 'info'
|
||||
}
|
||||
}
|
||||
|
||||
// 查询
|
||||
const handleSearch = () => {
|
||||
pagination.page = 1
|
||||
fetchSupplierList()
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchForm.name = ''
|
||||
searchForm.code = ''
|
||||
searchForm.status = ''
|
||||
pagination.page = 1
|
||||
fetchSupplierList()
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleView = async (supplier: Supplier) => {
|
||||
try {
|
||||
const res = await getSupplierDetail(supplier.id)
|
||||
currentSupplier.value = res.data
|
||||
detailDialogVisible.value = true
|
||||
} catch (error) {
|
||||
ElMessage.error('获取供应商详情失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 新增供应商
|
||||
const handleCreate = () => {
|
||||
// 重置表单
|
||||
Object.assign(editForm, {
|
||||
id: undefined,
|
||||
name: '',
|
||||
code: '',
|
||||
contact: '',
|
||||
phone: '',
|
||||
address: '',
|
||||
businessLicense: '',
|
||||
region: '',
|
||||
qualificationLevel: '',
|
||||
certifications: [],
|
||||
cattleTypes: [],
|
||||
capacity: 0,
|
||||
status: 'active'
|
||||
})
|
||||
editDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑供应商
|
||||
const handleEdit = (supplier: Supplier) => {
|
||||
// 解析牛种类型
|
||||
let cattleTypes: string[] = []
|
||||
try {
|
||||
cattleTypes = typeof supplier.cattleTypes === 'string' ? JSON.parse(supplier.cattleTypes) : supplier.cattleTypes
|
||||
} catch {
|
||||
cattleTypes = []
|
||||
}
|
||||
|
||||
// 解析认证信息
|
||||
let certifications: string[] = []
|
||||
try {
|
||||
certifications = typeof supplier.certifications === 'string' ? JSON.parse(supplier.certifications) : supplier.certifications
|
||||
} catch {
|
||||
certifications = []
|
||||
}
|
||||
|
||||
Object.assign(editForm, {
|
||||
...supplier,
|
||||
cattleTypes,
|
||||
certifications
|
||||
})
|
||||
editDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 保存供应商
|
||||
const handleSave = async () => {
|
||||
if (!editFormRef.value) return
|
||||
|
||||
try {
|
||||
await editFormRef.value.validate()
|
||||
|
||||
if (editForm.id) {
|
||||
// 更新供应商
|
||||
const updateData: SupplierUpdateForm = {
|
||||
name: editForm.name,
|
||||
code: editForm.code,
|
||||
contact: editForm.contact,
|
||||
phone: editForm.phone,
|
||||
address: editForm.address,
|
||||
businessLicense: editForm.businessLicense,
|
||||
region: editForm.region,
|
||||
qualificationLevel: editForm.qualificationLevel,
|
||||
certifications: editForm.certifications,
|
||||
cattleTypes: editForm.cattleTypes,
|
||||
capacity: editForm.capacity,
|
||||
status: editForm.status
|
||||
}
|
||||
|
||||
await updateSupplier(editForm.id, updateData)
|
||||
ElMessage.success('供应商更新成功')
|
||||
} else {
|
||||
// 创建供应商
|
||||
const createData: SupplierCreateForm = {
|
||||
name: editForm.name,
|
||||
code: editForm.code,
|
||||
contact: editForm.contact,
|
||||
phone: editForm.phone,
|
||||
address: editForm.address,
|
||||
businessLicense: editForm.businessLicense,
|
||||
region: editForm.region,
|
||||
qualificationLevel: editForm.qualificationLevel,
|
||||
certifications: editForm.certifications,
|
||||
cattleTypes: editForm.cattleTypes,
|
||||
capacity: editForm.capacity
|
||||
}
|
||||
|
||||
await createSupplier(createData)
|
||||
ElMessage.success('供应商创建成功')
|
||||
}
|
||||
|
||||
editDialogVisible.value = false
|
||||
fetchSupplierList()
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error.message || '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 删除供应商
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await deleteSupplier(id)
|
||||
ElMessage.success('供应商删除成功')
|
||||
fetchSupplierList()
|
||||
} catch (error) {
|
||||
ElMessage.error('删除供应商失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 分页大小改变
|
||||
const handleSizeChange = (val: number) => {
|
||||
pagination.pageSize = val
|
||||
pagination.page = 1
|
||||
fetchSupplierList()
|
||||
}
|
||||
|
||||
// 页码改变
|
||||
const handleCurrentChange = (val: number) => {
|
||||
pagination.page = val
|
||||
fetchSupplierList()
|
||||
}
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
fetchSupplierList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -30,5 +576,24 @@
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.search-form {
|
||||
.toolbar-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,34 +1,478 @@
|
||||
<template>
|
||||
<div class="transport-management">
|
||||
<el-card>
|
||||
<div class="page-header">
|
||||
<h2>运输管理</h2>
|
||||
<p>实时跟踪运输过程,监控车辆位置和牛只状态</p>
|
||||
</div>
|
||||
<h1 class="page-title">运输管理</h1>
|
||||
<p class="page-description">实时跟踪运输过程,监控车辆位置和牛只状态</p>
|
||||
|
||||
<!-- 搜索和操作栏 -->
|
||||
<div class="toolbar">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="订单ID">
|
||||
<el-input v-model="searchForm.orderId" placeholder="请输入订单ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
|
||||
<el-option label="已计划" value="scheduled" />
|
||||
<el-option label="运输中" value="in_transit" />
|
||||
<el-option label="已完成" value="completed" />
|
||||
<el-option label="已取消" value="cancelled" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-empty description="运输管理功能开发中..." />
|
||||
</el-card>
|
||||
<div class="actions">
|
||||
<el-button type="primary" @click="handleCreate">新增运输</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 运输列表 -->
|
||||
<el-table :data="transportList" style="width: 100%" v-loading="loading">
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="order_id" label="订单ID" width="100" />
|
||||
<el-table-column prop="start_location" label="起始地" />
|
||||
<el-table-column prop="end_location" label="目的地" />
|
||||
<el-table-column prop="scheduled_start_time" label="计划开始时间" width="180">
|
||||
<template #default="scope">
|
||||
{{ formatDateTime(scope.row.scheduled_start_time) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="scheduled_end_time" label="计划结束时间" width="180">
|
||||
<template #default="scope">
|
||||
{{ formatDateTime(scope.row.scheduled_end_time) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusTagType(scope.row.status)">
|
||||
{{ formatStatus(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="cattle_count" label="牛只数量" width="100" />
|
||||
<el-table-column label="操作" width="200">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="handleView(scope.row)">查看</el-button>
|
||||
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="handleDelete(scope.row)" :disabled="scope.row.status !== 'scheduled'">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.page"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="pagination.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
|
||||
<!-- 运输详情对话框 -->
|
||||
<el-dialog v-model="detailDialogVisible" title="运输详情" width="600px">
|
||||
<el-form :model="currentTransport" label-width="120px" v-if="currentTransport">
|
||||
<el-form-item label="ID:">
|
||||
<span>{{ currentTransport.id }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="订单ID:">
|
||||
<span>{{ currentTransport.order_id }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="司机ID:">
|
||||
<span>{{ currentTransport.driver_id }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="车辆ID:">
|
||||
<span>{{ currentTransport.vehicle_id }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="起始地:">
|
||||
<span>{{ currentTransport.start_location }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="目的地:">
|
||||
<span>{{ currentTransport.end_location }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="计划开始时间:">
|
||||
<span>{{ formatDateTime(currentTransport.scheduled_start_time) }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="计划结束时间:">
|
||||
<span>{{ formatDateTime(currentTransport.scheduled_end_time) }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="实际开始时间:">
|
||||
<span>{{ currentTransport.actual_start_time ? formatDateTime(currentTransport.actual_start_time) : '-' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="实际结束时间:">
|
||||
<span>{{ currentTransport.actual_end_time ? formatDateTime(currentTransport.actual_end_time) : '-' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:">
|
||||
<el-tag :type="getStatusTagType(currentTransport.status)">
|
||||
{{ formatStatus(currentTransport.status) }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="牛只数量:">
|
||||
<span>{{ currentTransport.cattle_count }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="特殊要求:">
|
||||
<span>{{ currentTransport.special_requirements || '-' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间:">
|
||||
<span>{{ formatDateTime(currentTransport.created_at) }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="更新时间:">
|
||||
<span>{{ formatDateTime(currentTransport.updated_at) }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 运输编辑对话框 -->
|
||||
<el-dialog v-model="editDialogVisible" :title="isEditing ? '编辑运输' : '新增运输'" width="600px">
|
||||
<el-form :model="editForm" :rules="editRules" ref="editFormRef" label-width="120px">
|
||||
<el-form-item label="订单ID:" prop="order_id">
|
||||
<el-input v-model.number="editForm.order_id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="司机ID:" prop="driver_id">
|
||||
<el-input v-model.number="editForm.driver_id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="车辆ID:" prop="vehicle_id">
|
||||
<el-input v-model.number="editForm.vehicle_id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="起始地:" prop="start_location">
|
||||
<el-input v-model="editForm.start_location" />
|
||||
</el-form-item>
|
||||
<el-form-item label="目的地:" prop="end_location">
|
||||
<el-input v-model="editForm.end_location" />
|
||||
</el-form-item>
|
||||
<el-form-item label="计划开始时间:" prop="scheduled_start_time">
|
||||
<el-date-picker
|
||||
v-model="editForm.scheduled_start_time"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择计划开始时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="计划结束时间:" prop="scheduled_end_time">
|
||||
<el-date-picker
|
||||
v-model="editForm.scheduled_end_time"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择计划结束时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="牛只数量:" prop="cattle_count">
|
||||
<el-input v-model.number="editForm.cattle_count" />
|
||||
</el-form-item>
|
||||
<el-form-item label="特殊要求:">
|
||||
<el-input v-model="editForm.special_requirements" type="textarea" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" prop="status" v-if="isEditing">
|
||||
<el-select v-model="editForm.status" placeholder="请选择状态">
|
||||
<el-option label="已计划" value="scheduled" />
|
||||
<el-option label="运输中" value="in_transit" />
|
||||
<el-option label="已完成" value="completed" />
|
||||
<el-option label="已取消" value="cancelled" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="editDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSave">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 运输管理页面
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox, type FormInstance } from 'element-plus';
|
||||
import {
|
||||
getTransportList,
|
||||
getTransportDetail,
|
||||
createTransport,
|
||||
updateTransport,
|
||||
deleteTransport
|
||||
} from '@/api/transport';
|
||||
import type { Transport, TransportCreateForm, TransportUpdateForm, TransportListParams, TransportStatus } from '@/types/transport';
|
||||
|
||||
// 数据状态
|
||||
const loading = ref(false);
|
||||
const transportList = ref<Transport[]>([]);
|
||||
const detailDialogVisible = ref(false);
|
||||
const editDialogVisible = ref(false);
|
||||
const isEditing = ref(false);
|
||||
const currentTransport = ref<Transport | null>(null);
|
||||
const editFormRef = ref<FormInstance>();
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
orderId: '',
|
||||
status: '' as TransportStatus | ''
|
||||
});
|
||||
|
||||
// 分页
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 0
|
||||
});
|
||||
|
||||
// 编辑表单
|
||||
const editForm = reactive<TransportCreateForm & TransportUpdateForm>({
|
||||
order_id: 0,
|
||||
driver_id: 0,
|
||||
vehicle_id: 0,
|
||||
start_location: '',
|
||||
end_location: '',
|
||||
scheduled_start_time: '',
|
||||
scheduled_end_time: '',
|
||||
cattle_count: 0,
|
||||
special_requirements: '',
|
||||
status: 'scheduled'
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const editRules = {
|
||||
order_id: [{ required: true, message: '请输入订单ID', trigger: 'blur' }],
|
||||
driver_id: [{ required: true, message: '请输入司机ID', trigger: 'blur' }],
|
||||
vehicle_id: [{ required: true, message: '请输入车辆ID', trigger: 'blur' }],
|
||||
start_location: [{ required: true, message: '请输入起始地', trigger: 'blur' }],
|
||||
end_location: [{ required: true, message: '请输入目的地', trigger: 'blur' }],
|
||||
scheduled_start_time: [{ required: true, message: '请选择计划开始时间', trigger: 'change' }],
|
||||
scheduled_end_time: [{ required: true, message: '请选择计划结束时间', trigger: 'change' }],
|
||||
cattle_count: [{ required: true, message: '请输入牛只数量', trigger: 'blur' }]
|
||||
};
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (dateString: string) => {
|
||||
if (!dateString) return '-';
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('zh-CN');
|
||||
};
|
||||
|
||||
// 获取状态标签类型
|
||||
const getStatusTagType = (status: string) => {
|
||||
switch (status) {
|
||||
case 'scheduled': return 'info';
|
||||
case 'in_transit': return 'primary';
|
||||
case 'completed': return 'success';
|
||||
case 'cancelled': return 'danger';
|
||||
default: return 'info';
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化状态显示
|
||||
const formatStatus = (status: string) => {
|
||||
switch (status) {
|
||||
case 'scheduled': return '已计划';
|
||||
case 'in_transit': return '运输中';
|
||||
case 'completed': return '已完成';
|
||||
case 'cancelled': return '已取消';
|
||||
default: return status;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取运输列表
|
||||
const fetchTransportList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const params: TransportListParams = {
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
orderId: searchForm.orderId ? Number(searchForm.orderId) : undefined,
|
||||
status: searchForm.status || undefined
|
||||
};
|
||||
|
||||
const response = await getTransportList(params);
|
||||
if (response.success) {
|
||||
transportList.value = response.data.list;
|
||||
pagination.total = response.data.pagination.total;
|
||||
} else {
|
||||
ElMessage.error(response.message || '获取运输列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('获取运输列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
pagination.page = 1;
|
||||
fetchTransportList();
|
||||
};
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchForm.orderId = '';
|
||||
searchForm.status = '';
|
||||
pagination.page = 1;
|
||||
fetchTransportList();
|
||||
};
|
||||
|
||||
// 查看详情
|
||||
const handleView = async (transport: Transport) => {
|
||||
try {
|
||||
const response = await getTransportDetail(transport.id);
|
||||
if (response.success) {
|
||||
currentTransport.value = response.data;
|
||||
detailDialogVisible.value = true;
|
||||
} else {
|
||||
ElMessage.error(response.message || '获取运输详情失败');
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('获取运输详情失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 新增运输
|
||||
const handleCreate = () => {
|
||||
isEditing.value = false;
|
||||
// 重置表单
|
||||
Object.assign(editForm, {
|
||||
order_id: 0,
|
||||
driver_id: 0,
|
||||
vehicle_id: 0,
|
||||
start_location: '',
|
||||
end_location: '',
|
||||
scheduled_start_time: '',
|
||||
scheduled_end_time: '',
|
||||
cattle_count: 0,
|
||||
special_requirements: '',
|
||||
status: 'scheduled'
|
||||
});
|
||||
editDialogVisible.value = true;
|
||||
};
|
||||
|
||||
// 编辑运输
|
||||
const handleEdit = (transport: Transport) => {
|
||||
isEditing.value = true;
|
||||
// 填充表单数据
|
||||
Object.assign(editForm, transport);
|
||||
editDialogVisible.value = true;
|
||||
};
|
||||
|
||||
// 保存运输记录
|
||||
const handleSave = async () => {
|
||||
if (!editFormRef.value) return;
|
||||
|
||||
await editFormRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
let response;
|
||||
if (isEditing.value && currentTransport.value) {
|
||||
// 更新运输记录
|
||||
response = await updateTransport(currentTransport.value.id, editForm);
|
||||
} else {
|
||||
// 创建运输记录
|
||||
response = await createTransport(editForm);
|
||||
}
|
||||
|
||||
if (response.success) {
|
||||
ElMessage.success(isEditing.value ? '运输记录更新成功' : '运输记录创建成功');
|
||||
editDialogVisible.value = false;
|
||||
fetchTransportList();
|
||||
} else {
|
||||
ElMessage.error(response.message || (isEditing.value ? '运输记录更新失败' : '运输记录创建失败'));
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(isEditing.value ? '运输记录更新失败' : '运输记录创建失败');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 删除运输记录
|
||||
const handleDelete = (transport: Transport) => {
|
||||
ElMessageBox.confirm(
|
||||
`确定要删除运输记录 ${transport.id} 吗?`,
|
||||
'确认删除',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(async () => {
|
||||
try {
|
||||
const response = await deleteTransport(transport.id);
|
||||
if (response.success) {
|
||||
ElMessage.success('运输记录删除成功');
|
||||
fetchTransportList();
|
||||
} else {
|
||||
ElMessage.error(response.message || '运输记录删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('运输记录删除失败');
|
||||
}
|
||||
}).catch(() => {
|
||||
// 用户取消删除
|
||||
});
|
||||
};
|
||||
|
||||
// 分页相关操作
|
||||
const handleSizeChange = (val: number) => {
|
||||
pagination.pageSize = val;
|
||||
fetchTransportList();
|
||||
};
|
||||
|
||||
const handleCurrentChange = (val: number) => {
|
||||
pagination.page = val;
|
||||
fetchTransportList();
|
||||
};
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
fetchTransportList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style scoped>
|
||||
.transport-management {
|
||||
.page-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
|
||||
h2 {
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.page-description {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.el-pagination {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
@@ -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'))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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: '批量操作失败',
|
||||
|
||||
@@ -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);
|
||||
|
||||
388
backend/src/controllers/TransportController.js
Normal file
388
backend/src/controllers/TransportController.js
Normal file
@@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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',
|
||||
|
||||
83
backend/src/models/Transport.js
Normal file
83
backend/src/models/Transport.js
Normal file
@@ -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;
|
||||
64
backend/src/models/Vehicle.js
Normal file
64
backend/src/models/Vehicle.js
Normal file
@@ -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;
|
||||
406
backend/src/routes/suppliers.js
Normal file
406
backend/src/routes/suppliers.js
Normal file
@@ -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;
|
||||
78
backend/src/routes/transports.js
Normal file
78
backend/src/routes/transports.js
Normal file
@@ -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;
|
||||
@@ -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
|
||||
- detail
|
||||
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
Reference in New Issue
Block a user