feat(pl): add Excel export for carrier list
Add backend /transport/carrier/export and wire ListExportDropdown on the carrier page. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
e72bcc67bc
commit
85c96f0a5d
3 files changed
+132
-1
No files matched your search
@@ -1,11 +1,20 @@
|
||||
"""运输计划管理 - 基础数据 API(承运商、车辆、人员)"""
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import case, func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.utils.excel_export import (
|
||||
EXPORT_MAX_ROWS,
|
||||
ExportColumn,
|
||||
ExportValidationError,
|
||||
export_rows_to_xlsx_response,
|
||||
filter_export_columns,
|
||||
)
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.db_auth import DbUserContext, get_db_current_user
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator, user_info_from_context
|
||||
@@ -114,6 +123,101 @@ def list_carriers(
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
CARRIER_EXPORT_COLUMNS = [
|
||||
ExportColumn(title="承运商名称", field="carrierName"),
|
||||
ExportColumn(title="联系人", field="contactName"),
|
||||
ExportColumn(title="联系电话", field="contactPhone"),
|
||||
ExportColumn(title="合作状态", field="statusLabel"),
|
||||
ExportColumn(title="资质提示", field="qualificationHint"),
|
||||
ExportColumn(title="协议有效期", field="transportAgreementExpireDate"),
|
||||
ExportColumn(title="备注", field="remark"),
|
||||
]
|
||||
|
||||
|
||||
class CarrierExportBody(BaseModel):
|
||||
q: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
audit_status: Optional[str] = Field(default=None, alias="auditStatus")
|
||||
ids: Optional[List[str]] = None
|
||||
columns: Optional[List[str]] = None
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
@router.post("/carrier/export")
|
||||
def export_carriers(
|
||||
body: CarrierExportBody,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
):
|
||||
"""导出承运商列表(当前筛选全量,或 ids 已选)。"""
|
||||
try:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
query = db.query(PlCarrier).filter(
|
||||
PlCarrier.tenant_no == tenant_no,
|
||||
PlCarrier.del_flag == 0,
|
||||
)
|
||||
if body.status:
|
||||
query = query.filter(PlCarrier.status == body.status)
|
||||
if body.audit_status:
|
||||
query = query.filter(PlCarrier.audit_status == body.audit_status)
|
||||
if body.q:
|
||||
query = query.filter(
|
||||
or_(
|
||||
func.lower(PlCarrier.carrier_name).contains(body.q.lower()),
|
||||
func.lower(PlCarrier.contact_name).contains(body.q.lower()),
|
||||
)
|
||||
)
|
||||
if body.ids:
|
||||
query = query.filter(PlCarrier.id.in_(body.ids))
|
||||
query = query.order_by(
|
||||
case((PlCarrier.order_num.is_(None), 1), else_=0).asc(),
|
||||
PlCarrier.create_time.desc(),
|
||||
)
|
||||
rows = query.limit(EXPORT_MAX_ROWS + 1).all()
|
||||
items = []
|
||||
for row in rows:
|
||||
data = carrier_to_read(row).model_dump(by_alias=True)
|
||||
data["statusLabel"] = "合作中" if str(data.get("status")) == "1" else "已终止"
|
||||
if data.get("transportAgreementExpired"):
|
||||
data["qualificationHint"] = "协议已过期"
|
||||
elif data.get("qualificationComplete") is False:
|
||||
data["qualificationHint"] = "资质不全"
|
||||
else:
|
||||
data["qualificationHint"] = "资质齐全"
|
||||
items.append(data)
|
||||
requested = None
|
||||
if body.columns:
|
||||
requested = []
|
||||
for key in body.columns:
|
||||
if key in (
|
||||
"qualificationAttachments",
|
||||
"qualificationAttachment",
|
||||
"photoUrls",
|
||||
):
|
||||
continue
|
||||
if key == "status":
|
||||
requested.append("statusLabel")
|
||||
elif key == "qualificationHint":
|
||||
requested.append("qualificationHint")
|
||||
else:
|
||||
requested.append(key)
|
||||
cols = filter_export_columns(CARRIER_EXPORT_COLUMNS, requested)
|
||||
date_str = datetime.now().strftime("%Y%m%d")
|
||||
return export_rows_to_xlsx_response(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
sheet_title="承运商列表",
|
||||
columns=cols,
|
||||
rows=items,
|
||||
filename_stem=f"承运商列表_{date_str}",
|
||||
)
|
||||
except ExportValidationError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
|
||||
|
||||
@router.post("/carrier", response_model=CarrierRead, status_code=status.HTTP_201_CREATED)
|
||||
def create_carrier(
|
||||
payload: CarrierCreate,
|
||||
|
||||
@@ -46,6 +46,8 @@ export interface CarrierQuery {
|
||||
export const carrierApi = {
|
||||
list: (params?: CarrierQuery) =>
|
||||
request.get<PlListResponse<Carrier>>('/transport/carrier', { params }),
|
||||
export: (data: Record<string, any>) =>
|
||||
request.post('/transport/carrier/export', data, { responseType: 'blob' }),
|
||||
get: (id: string) => request.get<Carrier>(`/transport/carrier/${id}`),
|
||||
create: (data: Partial<Carrier>) => request.post<Carrier>('/transport/carrier', data),
|
||||
update: (id: string, data: Partial<Carrier>) => request.put<Carrier>(`/transport/carrier/${id}`, data),
|
||||
|
||||
@@ -63,6 +63,15 @@
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-button>
|
||||
|
||||
<ListExportDropdown
|
||||
:export-fn="carrierApi.export"
|
||||
:get-filter-params="getCarrierExportFilterParams"
|
||||
:has-selection="true"
|
||||
:selected-ids="selectedRows.map((r) => r.id)"
|
||||
:visible-columns="carrierExportColumns"
|
||||
filename="承运商列表.xlsx"
|
||||
/>
|
||||
|
||||
<ListToolbarEndActions
|
||||
:loading="loading"
|
||||
:column-defs="listColumnDefs"
|
||||
@@ -320,6 +329,7 @@ import AttachmentUploadField from '@/components/business/attachment-upload-field
|
||||
import PlCarrierListAttachmentCell from '@/components/business/pl-carrier-list-attachment-cell/index.vue'
|
||||
import PlCarrierViewDialog from '@/components/business/pl-carrier-view-dialog/index.vue'
|
||||
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
|
||||
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
|
||||
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
|
||||
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
|
||||
import { useDbInstantSearch } from '@/composables/useDbInstantSearch'
|
||||
@@ -352,6 +362,21 @@ const {
|
||||
const submitLoading = ref(false)
|
||||
const carrierList = ref<Carrier[]>([])
|
||||
const selectedRows = ref<Carrier[]>([])
|
||||
|
||||
function getCarrierExportFilterParams() {
|
||||
const quick = quickSearchKeyword.value?.trim()
|
||||
return {
|
||||
q: quick || searchForm.q || undefined,
|
||||
status: searchForm.status || undefined
|
||||
}
|
||||
}
|
||||
|
||||
const carrierExportColumns = computed(() => {
|
||||
const cols = [...visibleColumns.value]
|
||||
if (!cols.includes('carrierName')) cols.unshift('carrierName')
|
||||
return cols
|
||||
})
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
const formRef = ref()
|
||||
|
||||
Reference in new issue
Block a user