Files
shanxia2.0/web-admin/src/composables/usePlPlanArrangeActions.ts
T
2026-08-21 16:50:47 +08:00

308 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type {
Plan,
PlanProduct,
TransportArrangeResult,
TransportInventoryLink,
TransportSiteLink
} from '@/api/pl'
import { planApi } from '@/api/pl'
import DbPoundSlipQrcodeDialog from '@/components/business/db/DbPoundSlipQrcodeDialog.vue'
import { useDictStore } from '@/store/modules/dict'
import { formatDate } from '@/utils/format'
import { appendAmapNavLine, buildAmapNavigationUrl } from '@/utils/amap/navigation'
import { pickBxData } from '@/utils/http/bx'
import { formatPlanProductListText } from '@/utils/pl-display'
import { buildSiteSummaryFromInventoryLinks } from '@/utils/db-site-summary'
export function usePlPlanArrangeActions() {
const dictStore = useDictStore()
const plan = ref<Partial<Plan> | null>(null)
const products = ref<PlanProduct[]>([])
const driverUrl = ref('')
const licensePlate = ref('')
const siteRecords = ref<TransportSiteLink[]>([])
const inventoryRecords = ref<TransportInventoryLink[]>([])
const qrcodeVisible = ref(false)
const selectedInventoryId = ref('')
const selectedInventoryUnit = ref('')
const qrcodeDialogRef = ref<InstanceType<typeof DbPoundSlipQrcodeDialog> | null>(null)
const showArrangeActions = computed(
() =>
Boolean(
plan.value?.transportStatus === 'arranged' &&
plan.value?.followStatus !== 'cancelled' &&
(driverUrl.value || siteRecords.value.length || inventoryRecords.value.length)
)
)
const hasSiteInventoryRecords = computed(() => siteRecords.value.length > 0)
const hasSiteLinks = computed(() => siteRecords.value.some(item => Boolean(item.siteUrl?.trim())))
/** 勾选「生成地磅单」后才会关联 db_purchase_plan,磅单二维码仅针对这类记录 */
const dibangInventoryRecords = computed(() =>
inventoryRecords.value.filter(item => Boolean(item.dbPurchasePlanId?.trim()))
)
const hasDibangInventoryRecords = computed(() => dibangInventoryRecords.value.length > 0)
function getUnitLabel(unit: string) {
return dictStore.getLabel('crm_product_unit', unit) || unit
}
function resetArrangeActions() {
plan.value = null
products.value = []
driverUrl.value = ''
licensePlate.value = ''
siteRecords.value = []
inventoryRecords.value = []
qrcodeVisible.value = false
selectedInventoryId.value = ''
selectedInventoryUnit.value = ''
}
function applyArrangeResult(
result: TransportArrangeResult,
contextPlan?: Partial<Plan>,
contextProducts?: PlanProduct[]
) {
if (contextPlan) plan.value = contextPlan
if (contextProducts) products.value = contextProducts
driverUrl.value = result.driverUrl || ''
licensePlate.value = result.licensePlate || ''
siteRecords.value = result.siteRecords || []
inventoryRecords.value = result.inventoryRecords || []
}
async function loadArrangeActions(planId: string) {
resetArrangeActions()
const planRes = await planApi.get(planId)
const data = pickBxData<Plan>(planRes)
if (!data) return
plan.value = data
products.value = Array.isArray(data.products) ? data.products : []
if (data.transportStatus !== 'arranged' || data.followStatus === 'cancelled') {
return
}
const infoRes = await planApi.getArrangeInfo(planId)
const info = pickBxData<{
driverUrl?: string
driver_url?: string
licensePlate?: string
license_plate?: string
siteRecords?: TransportSiteLink[]
site_records?: TransportSiteLink[]
inventoryRecords?: TransportInventoryLink[]
inventory_records?: TransportInventoryLink[]
}>(infoRes)
driverUrl.value = info?.driverUrl || info?.driver_url || ''
licensePlate.value = info?.licensePlate || info?.license_plate || data.licensePlate || ''
siteRecords.value = info?.siteRecords || info?.site_records || []
inventoryRecords.value = info?.inventoryRecords || info?.inventory_records || []
}
function buildPlanSummaryText(options?: {
startPoint?: string
endPoint?: string
pickupAddress?: string
pickupLng?: number | null
pickupLat?: number | null
destinationAddress?: string
destinationLng?: number | null
destinationLat?: number | null
pickupContact?: string
pickupNotes?: string
deliveryContact?: string
destinationNotes?: string
licensePlate?: string
driverUrl?: string
}) {
const plate =
options?.licensePlate || licensePlate.value || plan.value?.licensePlate || '—'
const lines = [`【${plate} - 运输安排】`, `计划号:${plan.value?.planNo || '—'}`]
lines.push(`车牌:${plate}`)
lines.push(
`时间:${formatDate(plan.value?.pickupTime) || plan.value?.planDate || '—'}`
)
const productText = products.value
.map(p => formatPlanProductListText(p, getUnitLabel, { usePlannedQuantity: true }))
.join(';')
lines.push(`产品:${productText || '—'}`)
const pickupAddress =
options?.pickupAddress || plan.value?.pickupAddress || options?.startPoint || '—'
const destinationAddress =
options?.destinationAddress ||
plan.value?.destinationAddress ||
plan.value?.destinationName ||
options?.endPoint ||
'—'
lines.push(`取货地址:${pickupAddress}`)
lines.push(`送货地址:${destinationAddress}`)
const pickupLng = options?.pickupLng ?? plan.value?.pickupLng
const pickupLat = options?.pickupLat ?? plan.value?.pickupLat
const destinationLng = options?.destinationLng ?? plan.value?.destinationLng
const destinationLat = options?.destinationLat ?? plan.value?.destinationLat
appendAmapNavLine(
lines,
'取货导航',
buildAmapNavigationUrl(pickupLng, pickupLat, pickupAddress !== '—' ? pickupAddress : '取货点')
)
appendAmapNavLine(
lines,
'送货导航',
buildAmapNavigationUrl(
destinationLng,
destinationLat,
destinationAddress !== '—' ? destinationAddress : '送货点'
)
)
lines.push(
`取货联系方式:${options?.pickupContact || plan.value?.pickupContact || '—'}`
)
lines.push(`取货注意事项:${options?.pickupNotes || plan.value?.pickupNotes || '—'}`)
lines.push(
`送货联系方式:${options?.deliveryContact || plan.value?.deliveryContact || '—'}`
)
lines.push(`送货注意事项:${options?.destinationNotes || plan.value?.destinationNotes || '—'}`)
lines.push(`司机链接:${options?.driverUrl || driverUrl.value || '—'}`)
return lines.join('\n')
}
async function copyDriverUrl(url?: string) {
const target = url || driverUrl.value
if (!target) return
await navigator.clipboard.writeText(target)
ElMessage.success('已复制司机链接')
}
function resolveSiteRecordProductText(record: TransportSiteLink) {
const product = products.value.find(item => item.id === record.planProductId)
if (product) {
return formatPlanProductListText(product, getUnitLabel, { usePlannedQuantity: true })
}
return record.productName || '—'
}
function buildSiteSummaryText(options?: { licensePlate?: string }) {
return buildSiteSummaryFromInventoryLinks({
planNo: plan.value?.planNo,
licensePlate: options?.licensePlate || licensePlate.value || plan.value?.licensePlate,
pickupTime: plan.value?.pickupTime,
planDate: plan.value?.planDate,
records: siteRecords.value.map(record => ({
...record,
productText: resolveSiteRecordProductText(record)
}))
})
}
async function copySiteSummary(options?: { licensePlate?: string }) {
if (!hasSiteLinks.value) {
ElMessage.warning('暂无现场链接')
return
}
await navigator.clipboard.writeText(buildSiteSummaryText(options))
ElMessage.success('已复制给现场')
}
async function copyPlanSummary(options?: {
startPoint?: string
endPoint?: string
pickupAddress?: string
pickupLng?: number | null
pickupLat?: number | null
destinationAddress?: string
destinationLng?: number | null
destinationLat?: number | null
pickupContact?: string
pickupNotes?: string
deliveryContact?: string
destinationNotes?: string
licensePlate?: string
driverUrl?: string
}) {
await navigator.clipboard.writeText(buildPlanSummaryText(options))
ElMessage.success('已复制给司机')
}
function resolveInventoryUnitLabel(record: TransportInventoryLink) {
const product = products.value.find(item => item.id === record.planProductId)
const unitRaw = product?.unit || ''
return unitRaw ? getUnitLabel(unitRaw) : '吨'
}
async function copyPoundSlipQrcode() {
const records = dibangInventoryRecords.value
if (!records.length) {
ElMessage.warning('未勾选生成地磅单,暂无磅单二维码')
return
}
if (records.length === 1) {
selectedInventoryId.value = records[0].dbInventoryId
selectedInventoryUnit.value = resolveInventoryUnitLabel(records[0])
qrcodeVisible.value = true
await nextTick()
await qrcodeDialogRef.value?.copyQrcodeImage()
return
}
try {
const { value } = await ElMessageBox.prompt('请选择要复制二维码的产品行', '复制磅单二维码', {
inputPlaceholder: records.map((item, idx) => `${idx + 1}. ${item.productName}`).join('\n'),
confirmButtonText: '确定',
inputValidator: val => {
const index = Number(val) - 1
if (!Number.isInteger(index) || index < 0 || index >= records.length) {
return '请输入有效序号'
}
return true
}
})
const index = Number(value) - 1
const record = records[index]
selectedInventoryId.value = record.dbInventoryId
selectedInventoryUnit.value = resolveInventoryUnitLabel(record)
qrcodeVisible.value = true
await nextTick()
await qrcodeDialogRef.value?.copyQrcodeImage()
} catch {
// 用户取消
}
}
return {
plan,
products,
driverUrl,
licensePlate,
siteRecords,
inventoryRecords,
qrcodeVisible,
selectedInventoryId,
selectedInventoryUnit,
qrcodeDialogRef,
showArrangeActions,
hasSiteInventoryRecords,
hasSiteLinks,
dibangInventoryRecords,
hasDibangInventoryRecords,
resetArrangeActions,
applyArrangeResult,
loadArrangeActions,
buildPlanSummaryText,
buildSiteSummaryText,
copyDriverUrl,
copySiteSummary,
copyPlanSummary,
copyPoundSlipQrcode
}
}