feat(backend): 开发订单管理和供应商管理功能

- 新增订单管理页面,实现订单列表展示、搜索、分页等功能
- 新增供应商管理页面,实现供应商列表展示、搜索、分页等功能- 添加订单和供应商相关模型及数据库迁移
- 实现订单状态更新和供应商信息编辑功能
- 优化后端路由结构,移除不必要的代码
This commit is contained in:
ylweng
2025-09-18 23:51:25 +08:00
parent 8637c05970
commit 5b6b50b60b
21 changed files with 3593 additions and 400 deletions

View 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'
})
}

View 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'
});
};

View 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;
}

View 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>;
}

View 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;
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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'))

View File

@@ -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);

View File

@@ -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

View File

@@ -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: '批量操作失败',

View File

@@ -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);

View 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
});
}
};

View File

@@ -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) => {

View File

@@ -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',

View 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;

View 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;

View 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;

View 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;

View File

@@ -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