308 lines
10 KiB
TypeScript
308 lines
10 KiB
TypeScript
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
|
||
}
|
||
}
|