Commit bb6917a9 by qinjianhui

feat: 新订单功能

parent 1679242b
---
description
glob
alwaysApply: true
---
## 技术栈
Vue3 + Element Plus + Pinia + Axios + TypeScript + Vite
## 代码编写原则
-- 只编写解决问题所需的最少代码
-- 避免冗余实现和过渡设计
-- 实现一个复杂功能时,需要合理拆分组件,使每个文件的代码更易维护
-- 疯转组件时,要考虑组件的复用性
import axios from './axios' import axios from './axios'
import type { BasePaginationData, BaseRespData } from '@/types/api' import type { BasePaginationData, BaseRespData } from '@/types/api'
import type { import type {
BatchManageData,
FactoryOrderNewListData, FactoryOrderNewListData,
LogListData, LogListData,
PickCompleteData,
PickFailData,
ProductListData, ProductListData,
RestockData,
SearchForm, SearchForm,
StatusTreeNode, StatusTreeNode,
} from '@/types/api/factoryOrderNew' } from '@/types/api/factoryOrderNew'
...@@ -78,3 +82,118 @@ export function transferOldFlowApi(ids: (number | string)[]) { ...@@ -78,3 +82,118 @@ export function transferOldFlowApi(ids: (number | string)[]) {
) )
} }
export function confirmOrderWithWarehouseApi(
ids: (number | string)[],
warehouseId: number | string,
) {
return axios.post<never, BaseRespData<void>>(
'factory/orderNew/confirmWithWarehouse',
{ ids, warehouseId },
)
}
export function cancelOrderWithReasonApi(
ids: (number | string)[],
reason: string,
) {
return axios.post<never, BaseRespData<void>>('factory/orderNew/cancel', {
ids,
reason,
})
}
export function suspendOrderApi(
ids: (number | string)[],
reason: string,
needCustomerHandle: string,
) {
return axios.post<never, BaseRespData<void>>('factory/orderNew/suspend', {
ids,
reason,
needCustomerHandle,
})
}
export function cancelSuspendApi(ids: (number | string)[]) {
return axios.post<never, BaseRespData<void>>(
'factory/orderNew/cancelSuspend',
{ ids },
)
}
export function archiveOrderApi(ids: (number | string)[]) {
return axios.post<never, BaseRespData<void>>('factory/orderNew/archive', {
ids,
})
}
export function printPickOrderApi(ids: (number | string)[]) {
return axios.post<never, BaseRespData<void>>(
'factory/orderNew/printPickOrder',
{ ids },
)
}
export function applyReplenishApi(ids: (number | string)[]) {
return axios.post<never, BaseRespData<void>>(
'factory/orderNew/applyReplenish',
{ ids },
)
}
export function batchDeleteApi(ids: (number | string)[]) {
return axios.post<never, BaseRespData<void>>(
'factory/orderNew/batchDelete',
{ ids },
)
}
export function restockCheckApi(id: number | string) {
return axios.post<never, BaseRespData<void>>(
'factory/orderNew/restockCheck',
{ id },
)
}
export function pickCompleteApi(ids: (number | string)[]) {
return axios.post<never, BaseRespData<PickCompleteData[]>>(
'factory/orderNew/pickComplete',
{ ids },
)
}
export function pickFailApi(ids: (number | string)[]) {
return axios.post<never, BaseRespData<PickFailData[]>>(
'factory/orderNew/pickFail',
{ ids },
)
}
export function getRestockListApi(params: {
stockSku?: string
styleNo?: string
}) {
return axios.get<never, BaseRespData<RestockData[]>>(
'factory/orderNew/restockList',
{ params },
)
}
export function getBatchManageListApi(
data: Record<string, unknown>,
currentPage: number,
pageSize: number,
) {
return axios.post<never, BasePaginationData<BatchManageData>>(
'factory/orderNew/batchManage/list_page',
{ ...data, currentPage, pageSize },
)
}
export function createOutboundOrderApi(ids: (number | string)[]) {
return axios.post<never, BaseRespData<void>>(
'factory/orderNew/createOutboundOrder',
{ ids },
)
}
...@@ -89,3 +89,61 @@ export interface LogListData { ...@@ -89,3 +89,61 @@ export interface LogListData {
createTime?: string createTime?: string
} }
export interface BatchManageData {
id: number
batchNo?: string
downloadStatus?: string
orderCount?: number
totalCount?: number
creator?: string
craftType?: string
timesRange?: string
createTime?: string
extractTimes?: number
}
export interface RestockData {
id: number
warehouseName?: string
skuImage?: string
productName?: string
styleNo?: string
stockSku?: string
shortageQuantity?: number
availableQuantity?: number
stockQuantity?: number
occupiedQuantity?: number
}
export interface PickCompleteData {
id: number
warehouseName?: string
skuImage?: string
productName?: string
styleNo?: string
stockSku?: string
pickQuantity?: number
availableStock?: number
stockQuantity?: number
producingQuantity?: number
occupiedQuantity?: number
pickStatus?: 'success' | 'partial' | 'fail'
}
export interface PickFailData {
id: number
warehouseName?: string
skuImage?: string
productName?: string
styleNo?: string
stockSku?: string
currentStock?: number
occupiedQuantity?: number
currentAvailableStock?: number
producingQuantity?: number
suggestOutQuantity?: number
afterOutStock?: number
afterOutOccupied?: number
afterOutAvailable?: number
}
<template>
<div class="batch-manage">
<div class="batch-manage-filter">
<ElForm :inline="true" :model="filterForm" size="default">
<ElFormItem label="创建时间">
<el-date-picker
v-model="filterForm.createTimeRange"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
start-placeholder="开始时间"
end-placeholder="结束时间"
style="width: 320px"
/>
</ElFormItem>
<ElFormItem label="开始时间">
<el-date-picker
v-model="filterForm.startTimeRange"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
start-placeholder="开始时间"
end-placeholder="结束时间"
style="width: 320px"
/>
</ElFormItem>
<ElFormItem label="完成时间">
<el-date-picker
v-model="filterForm.finishTimeRange"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
start-placeholder="开始时间"
end-placeholder="结束时间"
style="width: 320px"
/>
</ElFormItem>
<ElFormItem label="创建人">
<ElInput
v-model="filterForm.creator"
placeholder="创建人"
clearable
style="width: 120px"
/>
</ElFormItem>
<ElFormItem label="编排组">
<ElInput
v-model="filterForm.arrangeGroup"
placeholder="编排组"
clearable
style="width: 120px"
/>
</ElFormItem>
<ElFormItem label="工艺类型">
<ElSelect
v-model="filterForm.craftType"
placeholder="请选择"
clearable
style="width: 120px"
>
<ElOption label="烫画" value="TH" />
<ElOption label="直喷" value="ZP" />
<ElOption label="刺绣" value="CX" />
</ElSelect>
</ElFormItem>
<ElFormItem label="下载状态">
<ElSelect
v-model="filterForm.downloadStatus"
placeholder="请选择"
clearable
style="width: 120px"
>
<ElOption label="未下载" value="NOT_DOWNLOADED" />
<ElOption label="已下载" value="DOWNLOADED" />
</ElSelect>
</ElFormItem>
<ElFormItem label="排版状态">
<ElSelect
v-model="filterForm.layoutStatus"
placeholder="请选择"
clearable
style="width: 120px"
>
<ElOption label="未排版" value="NOT_LAYOUT" />
<ElOption label="已排版" value="LAYOUT" />
</ElSelect>
</ElFormItem>
<ElFormItem label="自动排版">
<ElSelect
v-model="filterForm.autoLayout"
placeholder="请选择"
clearable
style="width: 120px"
>
<ElOption label="是" :value="true" />
<ElOption label="否" :value="false" />
</ElSelect>
</ElFormItem>
<ElFormItem label="批次号">
<ElInput
v-model="filterForm.batchNo"
placeholder="批次号"
clearable
style="width: 140px"
/>
</ElFormItem>
</ElForm>
</div>
<div class="batch-manage-actions">
<ElButton type="danger" @click="handleBatchDelete">批量删除</ElButton>
</div>
<div class="batch-manage-table">
<ElTable
v-loading="loading"
:data="tableData"
border
style="width: 100%"
@selection-change="handleSelectionChange"
>
<ElTableColumn type="selection" width="45" />
<ElTableColumn prop="batchNo" label="批次号" min-width="120" />
<ElTableColumn prop="downloadStatus" label="下载状态" min-width="90" align="center">
<template #default="{ row }">
<el-tag :type="row.downloadStatus === '已下载' ? 'success' : 'info'" size="small">
{{ row.downloadStatus || '未下载' }}
</el-tag>
</template>
</ElTableColumn>
<ElTableColumn prop="orderCount" label="订单数量" min-width="90" align="center" />
<ElTableColumn prop="totalCount" label="全部数量" min-width="90" align="center" />
<ElTableColumn prop="creator" label="创建人" min-width="80" />
<ElTableColumn prop="craftType" label="工艺类型" min-width="90" />
<ElTableColumn prop="timesRange" label="次数范围" min-width="90" />
<ElTableColumn prop="createTime" label="创建时间" min-width="160" sortable />
<ElTableColumn prop="createTime" label="创建时间排序" min-width="110" sortable />
<ElTableColumn prop="extractTimes" label="提取次数排序" min-width="110" sortable />
<ElTableColumn label="操作" min-width="200" fixed="right">
<template #default="{ row }">
<ElButton type="primary" link size="small" @click="handleView(row)">
查看
</ElButton>
<ElButton type="primary" link size="small" @click="handleDownload(row)">
下载
</ElButton>
</template>
</ElTableColumn>
</ElTable>
</div>
<ElPagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[50, 100, 200, 300]"
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
style="margin-top: 10px"
@size-change="handlePageSizeChange"
@current-change="handleCurrentPageChange"
/>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getBatchManageListApi, batchDeleteApi } from '@/api/factoryOrderNew'
import type { BatchManageData } from '@/types/api/factoryOrderNew'
const loading = ref(false)
const tableData = ref<BatchManageData[]>([])
const selectedRows = ref<BatchManageData[]>([])
const currentPage = ref(1)
const pageSize = ref(50)
const total = ref(0)
const filterForm = reactive({
createTimeRange: [] as string[],
startTimeRange: [] as string[],
finishTimeRange: [] as string[],
creator: '',
arrangeGroup: '',
craftType: '',
downloadStatus: '',
layoutStatus: '',
autoLayout: '' as string | boolean,
batchNo: '',
})
const loadData = async () => {
loading.value = true
try {
const res = await getBatchManageListApi(
{ ...filterForm },
currentPage.value,
pageSize.value,
)
const page = res.data
tableData.value = page?.records || []
total.value = page?.total || 0
} catch (_e) {
tableData.value = []
} finally {
loading.value = false
}
}
const handleSelectionChange = (rows: BatchManageData[]) => {
selectedRows.value = rows
}
const handleBatchDelete = async () => {
if (!selectedRows.value.length) {
ElMessage.warning('请先选择数据')
return
}
await ElMessageBox.confirm('确定批量删除选中的批次吗?', '提示', {
type: 'warning',
})
try {
await batchDeleteApi(selectedRows.value.map((r) => r.id))
ElMessage.success('删除成功')
loadData()
} catch (e: any) {
ElMessage.error(e?.message || '删除失败')
}
}
const handleView = (row: BatchManageData) => {
ElMessage.info(`查看批次 ${row.batchNo}`)
}
const handleDownload = (row: BatchManageData) => {
ElMessage.info(`下载批次 ${row.batchNo}`)
}
const handlePageSizeChange = (size: number) => {
pageSize.value = size
currentPage.value = 1
loadData()
}
const handleCurrentPageChange = (page: number) => {
currentPage.value = page
loadData()
}
const refresh = () => {
currentPage.value = 1
loadData()
}
onMounted(() => {
loadData()
})
defineExpose({ refresh })
</script>
<style scoped lang="scss">
.batch-manage {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.batch-manage-filter {
flex-shrink: 0;
padding-bottom: 10px;
}
.batch-manage-actions {
flex-shrink: 0;
margin-bottom: 10px;
}
.batch-manage-table {
flex: 1;
overflow: auto;
}
</style>
<template>
<ElDialog
v-model="visible"
title="取消订单"
width="450px"
:close-on-click-modal="false"
@close="handleClose"
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="100px">
<ElFormItem label="取消原因" prop="reason">
<ElSelect
v-model="form.reason"
placeholder="请选择取消原因"
style="width: 100%"
>
<ElOption
v-for="item in cancelReasons"
:key="item"
:label="item"
:value="item"
/>
</ElSelect>
</ElFormItem>
</ElForm>
<template #footer>
<ElButton @click="visible = false">取消</ElButton>
<ElButton type="primary" :loading="submitLoading" @click="handleSubmit">
确认
</ElButton>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { cancelOrderWithReasonApi } from '@/api/factoryOrderNew'
const emit = defineEmits<{
success: []
}>()
const visible = ref(false)
const submitLoading = ref(false)
const formRef = ref<FormInstance>()
const orderIds = ref<(number | string)[]>([])
const cancelReasons = ['协商取消', '客户取消', '其他']
const form = reactive({
reason: '',
})
const rules: FormRules = {
reason: [{ required: true, message: '请选择取消原因', trigger: 'change' }],
}
const open = (ids: (number | string)[]) => {
orderIds.value = ids
form.reason = ''
visible.value = true
}
const handleClose = () => {
formRef.value?.resetFields()
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate()
submitLoading.value = true
try {
await cancelOrderWithReasonApi(orderIds.value, form.reason)
ElMessage.success('取消订单成功')
visible.value = false
emit('success')
} catch (e: any) {
ElMessageBox.alert(e?.message || '取消订单失败', '取消订单失败', {
type: 'error',
})
} finally {
submitLoading.value = false
}
}
defineExpose({ open })
</script>
<template>
<div class="card-layout">
<div v-if="data.length > 0" class="card-grid">
<div
v-for="item in data"
:key="item.id"
class="card-grid-item"
@click="handleCardClick(item)"
>
<CommonCard
:card-item="item"
:active="isSelected(item)"
:show-sku="false"
:show-product-info="false"
:image-field="'variantImage'"
>
<template #bottom_left>
<span
v-if="item.factorySubOrderNumber"
class="operation-number"
:title="`操作单号:${item.factorySubOrderNumber}`"
@click.stop="copyText(String(item.factorySubOrderNumber))"
>
{{ item.factorySubOrderNumber }}
</span>
</template>
<template #top_right>
<img
v-if="item.craftCode && ['ZPZY', 'CXZY', 'THZY'].includes(item.craftCode)"
:src="`/images/pic/${item.craftCode}.png`"
width="60"
height="60"
/>
</template>
<template #operations>
<div v-if="item.customizedQuantity" class="quantity-badge">
{{ item.customizedQuantity }}
</div>
</template>
<template #info>
<div class="card-info-grid">
<div class="card-info-row full">
<span class="info-value ellipsis" :title="item.productName || ''">
{{ item.productName }}
</span>
</div>
<div class="card-info-row">
<span class="info-label">工艺:</span>
<span class="info-value">{{ item.craftName }}</span>
</div>
<div class="card-info-row">
<span class="info-label">店铺单号:</span>
<span class="info-value">{{ item.shopNumber }}</span>
</div>
<div class="card-info-row">
<span class="info-label">客户号:</span>
<span class="info-value">{{ item.userMark }}</span>
</div>
<div class="card-info-row">
<span class="info-label">变体SKU</span>
<span
class="info-value clickable ellipsis"
:title="item.variantSku || ''"
@click.stop="copyText(item.variantSku || '')"
>
{{ item.variantSku }}
</span>
</div>
<div class="card-info-row">
<span class="info-label">库存SKU</span>
<span
class="info-value clickable ellipsis"
:title="item.thirdSkuCode || ''"
@click.stop="copyText(item.thirdSkuCode || '')"
>
{{ item.thirdSkuCode }}
</span>
</div>
<div class="card-info-row">
<span class="info-label">款号:</span>
<span class="info-value">{{ item.supplierProductNo }}</span>
</div>
<div v-if="item.batchArrangeNumber" class="card-info-row">
<span class="info-label">批次号:</span>
<span class="info-value">{{ item.batchArrangeNumber }}</span>
</div>
<div class="card-info-row">
<span class="info-label">状态:</span>
<span class="info-value">{{ item.statusName }}</span>
</div>
</div>
</template>
</CommonCard>
</div>
</div>
<div v-else class="card-empty">暂无数据</div>
<div class="card-pagination">
<div class="card-selected-info">
已选择
<span class="highlight">{{ selectedItems.length }}</span>
条数据
</div>
<ElPagination
v-model:current-page="currentPageModel"
v-model:page-size="pageSizeModel"
:page-sizes="[50, 100, 200, 300]"
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="$emit('page-size-change', $event)"
@current-change="$emit('current-page-change', $event)"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { ElMessage } from 'element-plus'
interface CardItem {
id: number | string
[key: string]: unknown
}
const props = defineProps<{
data: CardItem[]
total: number
currentPage: number
pageSize: number
}>()
const emit = defineEmits<{
'page-size-change': [size: number]
'current-page-change': [page: number]
'selection-change': [items: CardItem[]]
}>()
const selectedItems = ref<CardItem[]>([])
const currentPageModel = computed({
get: () => props.currentPage,
set: (val) => emit('current-page-change', val),
})
const pageSizeModel = computed({
get: () => props.pageSize,
set: (val) => emit('page-size-change', val),
})
const isSelected = (item: CardItem) =>
selectedItems.value.some((s) => s.id === item.id)
const handleCardClick = (item: CardItem) => {
const idx = selectedItems.value.findIndex((s) => s.id === item.id)
if (idx >= 0) {
selectedItems.value.splice(idx, 1)
} else {
selectedItems.value.push(item)
}
emit('selection-change', [...selectedItems.value])
}
const copyText = (text: string) => {
navigator.clipboard.writeText(text)
ElMessage.success('复制成功')
}
const clearSelection = () => {
selectedItems.value = []
emit('selection-change', [])
}
const getSelectedIds = () => selectedItems.value.map((i) => i.id)
defineExpose({ clearSelection, getSelectedIds })
</script>
<style scoped lang="scss">
.card-layout {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.card-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-template-rows: max-content;
gap: 10px;
flex: 1;
overflow-y: auto;
padding: 5px;
}
.card-grid-item {
cursor: pointer;
}
.card-empty {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
color: #909399;
font-size: 14px;
}
.card-pagination {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 0;
}
.card-selected-info {
font-size: 13px;
color: #606266;
.highlight {
color: #f56c6c;
font-weight: bold;
}
}
.operation-number {
font-size: 12px;
cursor: pointer;
color: #409eff;
&:hover {
text-decoration: underline;
}
}
.quantity-badge {
background-color: #e6a23c;
color: #fff;
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
}
.card-info-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
font-size: 12px;
margin-top: 6px;
.full {
grid-column: 1 / -1;
}
}
.card-info-row {
display: flex;
overflow: hidden;
}
.info-label {
flex-shrink: 0;
color: #909399;
}
.info-value {
flex: 1;
min-width: 0;
&.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&.clickable {
cursor: pointer;
color: #409eff;
&:hover {
text-decoration: underline;
}
}
}
</style>
<template>
<ElDialog
v-model="visible"
title="确认接单"
width="450px"
:close-on-click-modal="false"
@close="handleClose"
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="100px">
<ElFormItem label="发货仓库" prop="warehouseId">
<ElSelect
v-model="form.warehouseId"
placeholder="请选择发货仓库"
filterable
style="width: 100%"
>
<ElOption
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
</ElForm>
<template #footer>
<ElButton @click="visible = false">取消</ElButton>
<ElButton type="primary" :loading="submitLoading" @click="handleSubmit">
确认
</ElButton>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { loadWarehouseListApi } from '@/api/common'
import { confirmOrderWithWarehouseApi } from '@/api/factoryOrderNew'
import type { WarehouseListData } from '@/types'
const emit = defineEmits<{
success: []
}>()
const visible = ref(false)
const submitLoading = ref(false)
const formRef = ref<FormInstance>()
const warehouseList = ref<WarehouseListData[]>([])
const orderIds = ref<(number | string)[]>([])
const form = reactive({
warehouseId: '' as number | string,
})
const rules: FormRules = {
warehouseId: [{ required: true, message: '请选择发货仓库', trigger: 'change' }],
}
const loadWarehouseList = async () => {
try {
const res = await loadWarehouseListApi()
warehouseList.value = res.data || []
} catch (_e) {
/* empty */
}
}
const open = (ids: (number | string)[]) => {
orderIds.value = ids
form.warehouseId = ''
visible.value = true
loadWarehouseList()
}
const handleClose = () => {
formRef.value?.resetFields()
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate()
submitLoading.value = true
try {
await confirmOrderWithWarehouseApi(orderIds.value, form.warehouseId)
ElMessage.success('接单成功')
visible.value = false
emit('success')
} catch (e: unknown) {
ElMessageBox.alert((e as Error)?.message || '接单失败', '接单失败', {
type: 'error',
})
} finally {
submitLoading.value = false
}
}
defineExpose({ open })
</script>
<template>
<ElDialog
v-model="visible"
title="拣胚完成"
width="1200px"
:close-on-click-modal="false"
@close="handleClose"
>
<div class="pick-complete-tip">
根据拣胚情况分三种提示:
</div>
<div class="pick-complete-hints">
<div>情况1文案:本次选择的操作单可以直接拣胚完成</div>
<div>情况2文案:本次选择的操作单只能部分拣胚完成</div>
<div>情况3文案:本次选择操作单库存不足,无法拣胚完成</div>
</div>
<div class="pick-complete-actions" style="margin-bottom: 10px">
<ElButton type="primary" @click="handleCreateInbound">
创建入库单
</ElButton>
</div>
<ElTable :data="tableData" border style="width: 100%">
<ElTableColumn type="selection" width="40" />
<ElTableColumn type="index" label="序号" width="60" align="center" />
<ElTableColumn prop="warehouseName" label="仓库名称" min-width="100" />
<ElTableColumn label="SKU图片" width="80" align="center">
<template #default="{ row }">
<el-image
v-if="row.skuImage"
:src="row.skuImage"
style="width: 50px; height: 50px"
fit="contain"
:preview-src-list="[row.skuImage]"
preview-teleported
/>
</template>
</ElTableColumn>
<ElTableColumn prop="productName" label="商品名称" min-width="120" show-overflow-tooltip />
<ElTableColumn prop="styleNo" label="款号" min-width="80" />
<ElTableColumn prop="stockSku" label="库存SKU" min-width="120" />
<ElTableColumn prop="pickQuantity" label="本次拣胚数量" min-width="110" align="center">
<template #default="{ row }">
<span style="color: #e6a23c; font-weight: bold">{{ row.pickQuantity }}</span>
</template>
</ElTableColumn>
<ElTableColumn prop="availableStock" label="可调配库存" min-width="100" align="center" />
<ElTableColumn prop="stockQuantity" label="库存数量" min-width="90" align="center" />
<ElTableColumn prop="producingQuantity" label="生产中数量" min-width="100" align="center" />
<ElTableColumn prop="occupiedQuantity" label="占用数量" min-width="90" align="center" />
<ElTableColumn label="拣胚情况" min-width="130" align="center" fixed="right">
<template #default="{ row }">
<span v-if="row.pickStatus === 'fail'" style="color: #f56c6c">
✕ 无法拣胚
</span>
<ElButton
v-else-if="row.pickStatus === 'partial'"
type="warning"
size="small"
>
调整拣胚顺序
</ElButton>
<span v-else style="color: #67c23a">
✓ 直接拣胚
</span>
</template>
</ElTableColumn>
</ElTable>
<template #footer>
<ElButton @click="visible = false">取消</ElButton>
<ElButton type="primary" :loading="submitLoading" @click="handleSubmit">
确定
</ElButton>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { pickCompleteApi } from '@/api/factoryOrderNew'
import type { PickCompleteData } from '@/types/api/factoryOrderNew'
const emit = defineEmits<{
success: []
}>()
const visible = ref(false)
const submitLoading = ref(false)
const tableData = ref<PickCompleteData[]>([])
const orderIds = ref<(number | string)[]>([])
const open = async (ids: (number | string)[]) => {
orderIds.value = ids
visible.value = true
try {
const res = await pickCompleteApi(ids)
tableData.value = res.data || []
} catch (_e) {
tableData.value = []
}
}
const handleClose = () => {
tableData.value = []
}
const handleCreateInbound = () => {
ElMessage.info('创建入库单功能待实现')
}
const handleSubmit = async () => {
submitLoading.value = true
try {
ElMessage.success('拣胚完成')
visible.value = false
emit('success')
} catch (_e) {
ElMessage.error('操作失败')
} finally {
submitLoading.value = false
}
}
defineExpose({ open })
</script>
<style scoped lang="scss">
.pick-complete-tip {
font-size: 14px;
color: #606266;
margin-bottom: 8px;
}
.pick-complete-hints {
font-size: 13px;
color: #909399;
margin-bottom: 12px;
line-height: 1.8;
}
</style>
<template>
<ElDialog
v-model="visible"
title="拣胚失败"
width="1400px"
:close-on-click-modal="false"
@close="handleClose"
>
<div class="pick-fail-info">
<span>
您选择了 <strong>{{ orderIds.length }}</strong> 件操作单,如拣胚失败,库位没有实物库存,
<ElButton type="primary" link @click="handleCreateInbound">
建议创建出库单
</ElButton>
,详细库存信息如下:
</span>
</div>
<ElTable :data="tableData" border style="width: 100%; margin-top: 10px">
<ElTableColumn type="index" label="序号" width="60" align="center" />
<ElTableColumn prop="warehouseName" label="仓库名称" min-width="90" />
<ElTableColumn label="SKU图片" width="80" align="center">
<template #default="{ row }">
<el-image
v-if="row.skuImage"
:src="row.skuImage"
style="width: 50px; height: 50px"
fit="contain"
:preview-src-list="[row.skuImage]"
preview-teleported
/>
</template>
</ElTableColumn>
<ElTableColumn prop="productName" label="商品名称" min-width="110" show-overflow-tooltip />
<ElTableColumn prop="styleNo" label="款号" min-width="70" />
<ElTableColumn prop="stockSku" label="库存SKU" min-width="110" />
<ElTableColumn prop="currentStock" label="当前库存数量" min-width="100" align="center" />
<ElTableColumn prop="occupiedQuantity" label="占用数量" min-width="80" align="center" />
<ElTableColumn prop="currentAvailableStock" label="当前可用库存" min-width="100" align="center" />
<ElTableColumn prop="producingQuantity" label="生产中数量" min-width="90" align="center">
<template #default="{ row }">
<span style="color: #e6a23c; font-weight: bold">{{ row.producingQuantity }}</span>
</template>
</ElTableColumn>
<ElTableColumn label="建议出库数量" min-width="100" align="center">
<template #default="{ row }">
<span style="color: #f56c6c; font-weight: bold">{{ row.suggestOutQuantity }}</span>
</template>
</ElTableColumn>
<ElTableColumn label="出库后库存数量" min-width="110" align="center">
<template #default="{ row }">
<span style="color: #f56c6c">{{ row.afterOutStock }}</span>
</template>
</ElTableColumn>
<ElTableColumn prop="afterOutOccupied" label="出库后占用数量" min-width="110" align="center" />
<ElTableColumn prop="afterOutAvailable" label="出库后可用库存" min-width="110" align="center" />
</ElTable>
<template #footer>
<ElButton @click="visible = false">取消</ElButton>
<ElButton
type="primary"
:loading="submitLoading"
@click="handleCreateOutbound"
>
快速创建出库单
</ElButton>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { pickFailApi, createOutboundOrderApi } from '@/api/factoryOrderNew'
import type { PickFailData } from '@/types/api/factoryOrderNew'
const emit = defineEmits<{
success: []
}>()
const visible = ref(false)
const submitLoading = ref(false)
const tableData = ref<PickFailData[]>([])
const orderIds = ref<(number | string)[]>([])
const open = async (ids: (number | string)[]) => {
orderIds.value = ids
visible.value = true
try {
const res = await pickFailApi(ids)
tableData.value = res.data || []
} catch (_e) {
tableData.value = []
}
}
const handleClose = () => {
tableData.value = []
}
const handleCreateInbound = () => {
ElMessage.info('创建出库单功能待实现')
}
const handleCreateOutbound = async () => {
submitLoading.value = true
try {
await createOutboundOrderApi(orderIds.value)
ElMessage.success('创建出库单成功')
visible.value = false
emit('success')
} catch (e: any) {
ElMessage.error(e?.message || '创建出库单失败')
} finally {
submitLoading.value = false
}
}
defineExpose({ open })
</script>
<style scoped lang="scss">
.pick-fail-info {
font-size: 14px;
color: #606266;
line-height: 1.8;
}
</style>
<template>
<ElDialog
v-model="visible"
title="挂起订单"
width="500px"
:close-on-click-modal="false"
@close="handleClose"
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="130px">
<ElFormItem label="挂起原因" prop="reason">
<ElSelect
v-model="form.reason"
placeholder="请选择挂起原因"
style="width: 100%"
>
<ElOption
v-for="item in suspendReasons"
:key="item"
:label="item"
:value="item"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="是否需要客户处理" prop="needCustomerHandle">
<ElSelect
v-model="form.needCustomerHandle"
placeholder="请选择"
style="width: 100%"
>
<ElOption
v-for="item in customerHandleOptions"
:key="item"
:label="item"
:value="item"
/>
</ElSelect>
</ElFormItem>
</ElForm>
<template #footer>
<ElButton @click="visible = false">取消</ElButton>
<ElButton type="primary" :loading="submitLoading" @click="handleSubmit">
确认
</ElButton>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { suspendOrderApi } from '@/api/factoryOrderNew'
const emit = defineEmits<{
success: []
}>()
const visible = ref(false)
const submitLoading = ref(false)
const formRef = ref<FormInstance>()
const orderIds = ref<(number | string)[]>([])
const suspendReasons = ['客户拦截', '地址异常', '素材异常', '其他']
const customerHandleOptions = ['需要客户处理', '无需客户处理']
const form = reactive({
reason: '',
needCustomerHandle: '',
})
const rules: FormRules = {
reason: [{ required: true, message: '请选择挂起原因', trigger: 'change' }],
needCustomerHandle: [
{ required: true, message: '请选择是否需要客户处理', trigger: 'change' },
],
}
const open = (ids: (number | string)[]) => {
orderIds.value = ids
form.reason = ''
form.needCustomerHandle = ''
visible.value = true
}
const handleClose = () => {
formRef.value?.resetFields()
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate()
submitLoading.value = true
try {
await suspendOrderApi(
orderIds.value,
form.reason,
form.needCustomerHandle,
)
ElMessage.success('挂起订单成功')
visible.value = false
emit('success')
} catch (e: any) {
ElMessageBox.alert(e?.message || '挂起订单失败', '挂起订单失败', {
type: 'error',
})
} finally {
submitLoading.value = false
}
}
defineExpose({ open })
</script>
<template>
<div class="waiting-restock">
<div class="restock-filter">
<ElForm :inline="true" :model="filterForm" size="default">
<ElFormItem label="仓库筛选" />
<ElFormItem label="库存SKU">
<ElInput
v-model="filterForm.stockSku"
placeholder="库存SKU"
clearable
style="width: 150px"
/>
</ElFormItem>
<ElFormItem label="款号">
<ElInput
v-model="filterForm.styleNo"
placeholder="款号"
clearable
style="width: 150px"
/>
</ElFormItem>
<ElFormItem>
<ElButton type="primary" @click="handleSearch">查询</ElButton>
<ElButton @click="handleReset">重置</ElButton>
</ElFormItem>
</ElForm>
</div>
<div class="restock-table">
<ElTable v-loading="loading" :data="tableData" border style="width: 100%">
<ElTableColumn type="index" label="序号" width="60" align="center" />
<ElTableColumn prop="warehouseName" label="仓库名称" min-width="100" />
<ElTableColumn label="SKU图片" width="80" align="center">
<template #default="{ row }">
<el-image
v-if="row.skuImage"
:src="row.skuImage"
style="width: 50px; height: 50px"
fit="contain"
:preview-src-list="[row.skuImage]"
preview-teleported
/>
</template>
</ElTableColumn>
<ElTableColumn prop="productName" label="商品名称" min-width="120" show-overflow-tooltip />
<ElTableColumn prop="styleNo" label="款号" min-width="80" />
<ElTableColumn prop="stockSku" label="库存SKU" min-width="120" />
<ElTableColumn prop="shortageQuantity" label="缺货数量" min-width="90" align="center">
<template #default="{ row }">
<span style="color: #f56c6c; font-weight: bold">{{ row.shortageQuantity }}</span>
</template>
</ElTableColumn>
<ElTableColumn prop="availableQuantity" label="可用数量" min-width="90" align="center">
<template #default="{ row }">
<span :style="{ color: (row.availableQuantity || 0) < 0 ? '#f56c6c' : '' }">
{{ row.availableQuantity }}
</span>
</template>
</ElTableColumn>
<ElTableColumn prop="stockQuantity" label="库存数量" min-width="90" align="center" />
<ElTableColumn prop="occupiedQuantity" label="占用数量" min-width="90" align="center" />
<ElTableColumn label="操作" width="120" align="center" fixed="right">
<template #default="{ row }">
<ElButton
type="primary"
link
size="small"
@click="handleRestockCheck(row)"
>
补货校验
</ElButton>
</template>
</ElTableColumn>
</ElTable>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { getRestockListApi, restockCheckApi } from '@/api/factoryOrderNew'
import type { RestockData } from '@/types/api/factoryOrderNew'
const loading = ref(false)
const tableData = ref<RestockData[]>([])
const filterForm = reactive({
stockSku: '',
styleNo: '',
})
const loadData = async () => {
loading.value = true
try {
const res = await getRestockListApi({
stockSku: filterForm.stockSku || undefined,
styleNo: filterForm.styleNo || undefined,
})
tableData.value = res.data || []
} catch (_e) {
tableData.value = []
} finally {
loading.value = false
}
}
const handleSearch = () => {
loadData()
}
const handleReset = () => {
filterForm.stockSku = ''
filterForm.styleNo = ''
loadData()
}
const handleRestockCheck = async (row: RestockData) => {
try {
await restockCheckApi(row.id)
ElMessage.success('补货校验成功')
loadData()
} catch (e: any) {
ElMessage.error(e?.message || '补货校验失败')
}
}
const refresh = () => {
loadData()
}
onMounted(() => {
loadData()
})
defineExpose({ refresh })
</script>
<style scoped lang="scss">
.waiting-restock {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.restock-filter {
flex-shrink: 0;
padding-bottom: 10px;
}
.restock-table {
flex: 1;
overflow: auto;
}
</style>
...@@ -38,7 +38,8 @@ ...@@ -38,7 +38,8 @@
</ElTree> </ElTree>
</div> </div>
<div class="order-content flex-1 flex-column overflow-hidden"> <div class="order-content flex-1 flex-column overflow-hidden">
<div class="header"> <!-- 搜索表单: 批次管理和等待补货有自己的筛选 -->
<div v-if="!isSpecialLayout" class="header">
<div class="header-filter-form"> <div class="header-filter-form">
<ElForm <ElForm
class="search-form" class="search-form"
...@@ -215,14 +216,7 @@ ...@@ -215,14 +216,7 @@
/> />
</ElFormItem> </ElFormItem>
<ElFormItem <ElFormItem
v-if=" v-if="!cardLayoutStatuses.includes(status)"
![
'TO_BE_ARRANGE',
'PICKING',
'TO_BE_REPLENISHMENT',
'IN_PRODUCTION',
].includes(status)
"
label="收件国家" label="收件国家"
> >
<ElSelect <ElSelect
...@@ -318,7 +312,6 @@ ...@@ -318,7 +312,6 @@
></ElOption> ></ElOption>
</ElSelect> </ElSelect>
</ElFormItem> </ElFormItem>
<ElFormItem label="订单来源"> <ElFormItem label="订单来源">
<ElSelect <ElSelect
v-model="searchForm.source" v-model="searchForm.source"
...@@ -369,10 +362,7 @@ ...@@ -369,10 +362,7 @@
></ElOption> ></ElOption>
</ElSelect> </ElSelect>
</ElFormItem> </ElFormItem>
<ElFormItem <ElFormItem v-if="status !== 'CANCELLED'" label="拦截订单">
v-if="status !== 'CANCEL' && status !== 'INTERCEPTED'"
label="拦截订单"
>
<ElSelect <ElSelect
v-model="searchForm.blocking" v-model="searchForm.blocking"
placeholder="请选择" placeholder="请选择"
...@@ -429,25 +419,132 @@ ...@@ -429,25 +419,132 @@
</ElForm> </ElForm>
</div> </div>
</div> </div>
<!-- ====== 操作按钮栏 ====== -->
<div class="operation-list"> <div v-if="!isSpecialLayout" class="operation-list">
<span class="item"> <!-- 待接单 -->
<ElButton type="success" @click="handleConfirmOrder"> <template v-if="status === 'PENDING_ACCEPT'">
确认接单 <ElButton type="success" @click="handleConfirmOrder"
</ElButton> >确认接单</ElButton
</span> >
<ElButton type="danger" @click="handleCancelOrder">取消订单</ElButton>
<ElButton type="primary" @click="handleRefreshProductInfo"
>刷新商品信息</ElButton
>
<ElButton @click="handleTransferOldFlow">转旧流程</ElButton>
</template>
<!-- 待创建物流 -->
<template v-if="status === 'PENDING_LOGISTICS'">
<ElDropdown trigger="click" @command="handleLogisticsCommand">
<ElButton type="primary">
物流接口 <el-icon class="el-icon--right"><ArrowDown /></el-icon>
</ElButton>
<template #dropdown>
<ElDropdownMenu>
<ElDropdownItem command="createLogistic"
>创建物流订单</ElDropdownItem
>
<ElDropdownItem command="getTrackingNumber"
>获取跟踪号</ElDropdownItem
>
<ElDropdownItem command="getPrintOrder"
>获取打印面单</ElDropdownItem
>
<ElDropdownItem command="cancelLogistic"
>取消物流订单</ElDropdownItem
>
</ElDropdownMenu>
</template>
</ElDropdown>
<ElButton type="warning" @click="handleSuspend">挂起</ElButton>
<ElButton @click="handleUpdateCustomsInfo">更新报关信息</ElButton>
</template>
<!-- 待排单/待派单 (卡片布局按钮) -->
<template v-if="status === 'PENDING_SCHEDULE'">
<ElButton type="primary" @click="handleArrange">派单</ElButton>
<ElButton @click="handleDownloadMaterial">下载素材</ElButton>
</template>
<!-- 配货中 (父级) -->
<template v-if="status === 'DISTRIBUTING'">
<ElButton type="warning" @click="handleSuspend">挂起</ElButton>
</template>
<!-- 待拣胚 / 待补胚 -->
<template
v-if="status === 'PENDING_PICK' || status === 'PENDING_REPLENISH'"
>
<ElDropdown trigger="click" @command="handleDtfCommand">
<ElButton type="primary">
DTF排版 <el-icon class="el-icon--right"><ArrowDown /></el-icon>
</ElButton>
<template #dropdown>
<ElDropdownMenu>
<ElDropdownItem command="tiff_42">TIF(40+2cm)</ElDropdownItem>
<ElDropdownItem command="tiff_60">TIF(60cm)</ElDropdownItem>
<ElDropdownItem command="png_42">PNG(40+2cm)</ElDropdownItem>
<ElDropdownItem command="png_60">PNG(60cm)</ElDropdownItem>
</ElDropdownMenu>
</template>
</ElDropdown>
<ElButton @click="handlePrintProductionOrder">打印生产单</ElButton>
<ElButton @click="handlePrintPickOrder">打印拣胚单</ElButton>
<ElButton type="success" @click="handlePickComplete"
>拣胚完成</ElButton
>
<ElButton type="danger" @click="handlePickFail">拣胚失败</ElButton>
</template>
<!-- 生产中 -->
<template v-if="status === 'PRODUCING'">
<ElButton type="warning" @click="handleApplyReplenish"
>申请补胚</ElButton
>
<ElButton type="success" @click="handleProductionComplete"
>生产完成</ElButton
>
<ElButton @click="handleSeedingWall">播种墙配货</ElButton>
</template>
<!-- 待发货 -->
<template v-if="status === 'PENDING_SHIP'">
<ElButton type="primary" @click="handleWeightSort">称重分拣</ElButton>
<ElButton @click="handleDownloadMaterial">下载素材</ElButton>
</template>
<!-- 已完成 -->
<template v-if="status === 'COMPLETED'">
<ElButton @click="handleDownloadMaterial">下载素材</ElButton>
<ElButton type="warning" @click="handleArchiveOrder"
>订单归档</ElButton
>
</template>
<!-- 挂起 -->
<template v-if="status === 'SUSPENDED'">
<ElButton type="primary" @click="handleCancelSuspend"
>取消挂起</ElButton
>
<ElButton @click="handleSyncAddress">同步收货地址</ElButton>
</template>
</div>
<!-- ====== 挂起状态 Tab ====== -->
<div v-if="status === 'SUSPENDED'" class="status-subtabs">
<div
v-for="tab in suspendedTabs"
:key="tab.value"
class="status-subtab"
:class="{ active: suspendedSubTab === tab.value }"
@click="suspendedSubTab = tab.value"
>
{{ tab.label }}
</div>
</div> </div>
<div v-if="status === 'PENDING_ACCEPT'" class="pending-accept-subtabs"> <!-- ====== 待接单子 Tab ====== -->
<div v-if="status === 'PENDING_ACCEPT'" class="status-subtabs">
<div <div
class="pending-accept-subtab" class="status-subtab"
:class="{ active: pendingAcceptSubTab === 'PENDING_ACCEPT' }" :class="{ active: pendingAcceptSubTab === 'PENDING_ACCEPT' }"
@click="pendingAcceptSubTab = 'PENDING_ACCEPT'" @click="pendingAcceptSubTab = 'PENDING_ACCEPT'"
> >
待接单<span> (0) </span> 待接单<span> (0) </span>
</div> </div>
<div <div
class="pending-accept-subtab" class="status-subtab"
:class="{ :class="{
active: pendingAcceptSubTab === 'ACCEPT_FAIL_OUT_OF_STOCK', active: pendingAcceptSubTab === 'ACCEPT_FAIL_OUT_OF_STOCK',
}" }"
...@@ -456,7 +553,34 @@ ...@@ -456,7 +553,34 @@
接单失败-缺货<span> (0) </span> 接单失败-缺货<span> (0) </span>
</div> </div>
</div> </div>
<div class="table-content">
<!-- ====== 布局区域 ====== -->
<!-- 批次管理 -->
<BatchManageTable v-if="status === 'BATCH_MANAGE'" ref="batchManageRef" />
<!-- 等待补货 -->
<WaitingRestockTable
v-if="status === 'WAITING_RESTOCK'"
ref="waitingRestockRef"
/>
<!-- 卡片布局 -->
<div v-if="isCardLayout" class="card-content">
<CardLayout
ref="cardLayoutRef"
:data="(tableData as { id: number | string; [key: string]: unknown }[])"
:total="total"
:current-page="currentPage"
:page-size="pageSize"
@selection-change="handleCardSelectionChange"
@page-size-change="handlePageSizeChange"
@current-page-change="handleCurrentPageChange"
/>
</div>
<!-- 表格布局 -->
<div v-if="isTableLayout" class="table-content">
<splitDiv v-loading="loading" size="55"> <splitDiv v-loading="loading" size="55">
<template #top> <template #top>
<div class="table-list flex-1 overflow-hidden"> <div class="table-list flex-1 overflow-hidden">
...@@ -496,7 +620,9 @@ ...@@ -496,7 +620,9 @@
<el-tab-pane name="log" label="操作日志"> <el-tab-pane name="log" label="操作日志">
<div class="detail-table-content"> <div class="detail-table-content">
<LogList :log-list="logList" /> <LogList :log-list="logList" />
<div class="empty-content">暂无数据</div> <div v-if="!logList.length" class="empty-content">
暂无数据
</div>
</div> </div>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
...@@ -504,6 +630,34 @@ ...@@ -504,6 +630,34 @@
</splitDiv> </splitDiv>
</div> </div>
</div> </div>
<!-- ====== 弹框组件 ====== -->
<ConfirmOrderDialog
ref="confirmOrderDialogRef"
@success="refreshCurrentView"
/>
<CancelOrderDialog
ref="cancelOrderDialogRef"
@success="refreshCurrentView"
/>
<SuspendDialog ref="suspendDialogRef" @success="refreshCurrentView" />
<PickCompleteDialog
ref="pickCompleteDialogRef"
@success="refreshCurrentView"
/>
<PickFailDialog ref="pickFailDialogRef" @success="refreshCurrentView" />
<!-- 复用 podCN 的弹框组件 -->
<CreateLogisticDialog
ref="createLogisticDialogRef"
@refresh-table="refreshCurrentView"
/>
<UpdateCustomDeclarationInfoDialog
v-model="updateCustomsDialogVisible"
:order-selection="selectedRows"
@refresh-table="refreshCurrentView"
/>
<WeightDialog ref="weightDialogRef" @update-list="refreshCurrentView" />
</div> </div>
</template> </template>
...@@ -514,7 +668,7 @@ import { ...@@ -514,7 +668,7 @@ import {
CaretTop, CaretTop,
CaretBottom, CaretBottom,
} from '@element-plus/icons-vue' } from '@element-plus/icons-vue'
import { onMounted, ref, watch } from 'vue' import { computed, onMounted, ref, watch } from 'vue'
import { import {
ElForm, ElForm,
ElFormItem, ElFormItem,
...@@ -535,23 +689,70 @@ import type { ...@@ -535,23 +689,70 @@ import type {
} from '@/types/api/factoryOrderNew' } from '@/types/api/factoryOrderNew'
import platformJson from '../../../json/platform.json' import platformJson from '../../../json/platform.json'
import { import {
confirmOrderApi,
getFactoryOrderNewDetailApi, getFactoryOrderNewDetailApi,
getFactoryOrderNewListApi, getFactoryOrderNewListApi,
getFactoryOrderNewLogApi, getFactoryOrderNewLogApi,
refreshProductInfoApi,
transferOldFlowApi,
cancelSuspendApi,
archiveOrderApi,
printPickOrderApi,
applyReplenishApi,
} from '@/api/factoryOrderNew' } from '@/api/factoryOrderNew'
import { import {
getListCraftApi, getListCraftApi,
getCustomTagListCnApi, getCustomTagListCnApi,
allErpCodeListApi, allErpCodeListApi,
downloadMaterialApi,
composingDesignImages,
printProductionOrderApi,
updateToWaitShipmentApi,
syncReceiverAddress,
getTrackingNumberApi,
getfaceSimplexFileApi,
cancelLogisticsOrderApi,
arrangeFinishApi,
} from '@/api/podCnOrder' } from '@/api/podCnOrder'
import { getUserMarkList } from '@/api/common' import { getUserMarkList } from '@/api/common'
import { getAllCountryApi } from '@/api/logistics' import { getAllCountryApi } from '@/api/logistics'
import { filePath } from '@/api/axios'
import LogisticsWaySelect from '@/views/logistics/components/LogisticsWaySelect' import LogisticsWaySelect from '@/views/logistics/components/LogisticsWaySelect'
import { IAllList } from '@/types/api/podUsOrder' import { IAllList } from '@/types/api/podUsOrder'
import { CraftListData } from '@/types/api/podCnOrder' import { CraftListData } from '@/types/api/podCnOrder'
import ProductTypeFilter from './component/ProductTypeFilter.vue' import ProductTypeFilter from './component/ProductTypeFilter.vue'
import type { ProductTypeGroup } from './component/productTypeFilterTypes' import type { ProductTypeGroup } from './component/productTypeFilterTypes'
import ConfirmOrderDialog from './component/ConfirmOrderDialog.vue'
import CancelOrderDialog from './component/CancelOrderDialog.vue'
import SuspendDialog from './component/SuspendDialog.vue'
import PickCompleteDialog from './component/PickCompleteDialog.vue'
import PickFailDialog from './component/PickFailDialog.vue'
import CardLayout from './component/CardLayout.vue'
import BatchManageTable from './component/BatchManageTable.vue'
import WaitingRestockTable from './component/WaitingRestockTable.vue'
import CreateLogisticDialog from '@/views/order/podCN/components/CreateLogisticDialog.vue'
import UpdateCustomDeclarationInfoDialog from '@/views/order/podCN/components/UpdateCustomDeclarationInfoDialog.vue'
import WeightDialog from '@/views/order/podCN/components/WeightDialog.vue'
// ========== 状态分类 ==========
const cardLayoutStatuses = [
'PENDING_SCHEDULE',
'PENDING_PICK',
'PENDING_REPLENISH',
'PRODUCING',
'PENDING_DISTRIBUTE',
]
const specialLayoutStatuses = ['BATCH_MANAGE', 'WAITING_RESTOCK']
const isCardLayout = computed(() => cardLayoutStatuses.includes(status.value))
const isSpecialLayout = computed(() =>
specialLayoutStatuses.includes(status.value),
)
const isTableLayout = computed(
() => !isCardLayout.value && !isSpecialLayout.value,
)
// ========== 状态树 ==========
const defaultStatusTree: StatusTreeNode[] = [ const defaultStatusTree: StatusTreeNode[] = [
{ {
code: 'ALL', code: 'ALL',
...@@ -574,8 +775,6 @@ const defaultStatusTree: StatusTreeNode[] = [ ...@@ -574,8 +775,6 @@ const defaultStatusTree: StatusTreeNode[] = [
{ code: 'PENDING_DISTRIBUTE', remark: '待配货', count: 0 }, { code: 'PENDING_DISTRIBUTE', remark: '待配货', count: 0 },
], ],
}, },
{ code: 'PRODUCING', remark: '生产中', count: 0 },
{ code: 'PENDING_DISTRIBUTE', remark: '待配货', count: 0 },
{ code: 'PENDING_SHIP', remark: '待发货', count: 0 }, { code: 'PENDING_SHIP', remark: '待发货', count: 0 },
{ code: 'COMPLETED', remark: '已完成', count: 0 }, { code: 'COMPLETED', remark: '已完成', count: 0 },
{ code: 'SUSPENDED', remark: '挂起', count: 0 }, { code: 'SUSPENDED', remark: '挂起', count: 0 },
...@@ -587,9 +786,19 @@ const defaultStatusTree: StatusTreeNode[] = [ ...@@ -587,9 +786,19 @@ const defaultStatusTree: StatusTreeNode[] = [
const statusTree = ref<StatusTreeNode[]>(defaultStatusTree) const statusTree = ref<StatusTreeNode[]>(defaultStatusTree)
const status = ref<string>('PENDING_ACCEPT') const status = ref<string>('PENDING_ACCEPT')
// ========== 子 Tab ==========
const pendingAcceptSubTab = ref<'PENDING_ACCEPT' | 'ACCEPT_FAIL_OUT_OF_STOCK'>( const pendingAcceptSubTab = ref<'PENDING_ACCEPT' | 'ACCEPT_FAIL_OUT_OF_STOCK'>(
'PENDING_ACCEPT', 'PENDING_ACCEPT',
) )
const suspendedTabs = [
{ label: '客户拦截', value: 'CUSTOMER_INTERCEPT' },
{ label: '地址异常', value: 'ADDRESS_EXCEPTION' },
{ label: '其他', value: 'OTHER' },
]
const suspendedSubTab = ref('CUSTOMER_INTERCEPT')
// ========== 搜索 ==========
const treeRef = ref<InstanceType<typeof ElTree>>() const treeRef = ref<InstanceType<typeof ElTree>>()
const searchForm = ref<SearchForm>({}) const searchForm = ref<SearchForm>({})
const dateRange = ref<string[]>([]) const dateRange = ref<string[]>([])
...@@ -612,7 +821,6 @@ const sourceList = [ ...@@ -612,7 +821,6 @@ const sourceList = [
{ name: 'erp推送', id: 'jomall-erp' }, { name: 'erp推送', id: 'jomall-erp' },
{ name: '第三方推送', id: 'third-party' }, { name: '第三方推送', id: 'third-party' },
] ]
const sizes = ['FS', 'XS', 'S', 'M', 'L', 'XL', 'XXL', '3XL', '4XL', '5XL'] const sizes = ['FS', 'XS', 'S', 'M', 'L', 'XL', 'XXL', '3XL', '4XL', '5XL']
const productTypeGroups = ref<ProductTypeGroup[]>([ const productTypeGroups = ref<ProductTypeGroup[]>([
...@@ -648,6 +856,7 @@ const changeReplaceShipment = () => { ...@@ -648,6 +856,7 @@ const changeReplaceShipment = () => {
searchForm.value.shipmentType = '' searchForm.value.shipmentType = ''
} }
// ========== 数据加载 ==========
const getUserMark = async () => { const getUserMark = async () => {
try { try {
const res = await getUserMarkList() const res = await getUserMarkList()
...@@ -686,40 +895,15 @@ const getLogisticsCompanyAllCodelist = async () => { ...@@ -686,40 +895,15 @@ const getLogisticsCompanyAllCodelist = async () => {
/* empty */ /* empty */
} }
} }
interface ProcessTypeData {
label: string const processTypeMap: Record<string, string> = {
value: string TH: '烫画',
ZP: '直喷',
CX: '刺绣',
DK: '雕刻',
BP: '白胚',
QT: '其他',
} }
const processType = ref<ProcessTypeData[]>([
{
label: '烫画',
value: 'TH',
},
{
label: '直喷',
value: 'ZP',
},
{
label: '刺绣',
value: 'CX',
},
{
label: '雕刻',
value: 'DK',
},
{
label: '白胚',
value: 'BP',
},
{
label: '其他',
value: 'QT',
},
])
const processTypeMap = processType.value.reduce((acc, cur) => {
acc[cur.value] = cur.label
return acc
}, {} as Record<string, string>)
const craftList = ref<IAllList[]>([]) const craftList = ref<IAllList[]>([])
const loadCraftList = async () => { const loadCraftList = async () => {
try { try {
...@@ -773,12 +957,16 @@ const pickerOptions = { ...@@ -773,12 +957,16 @@ const pickerOptions = {
], ],
} }
// ========== 表格数据 ==========
const activeTab = ref<'product' | 'log'>('product') const activeTab = ref<'product' | 'log'>('product')
const productList = ref<ProductListData[]>([]) const productList = ref<ProductListData[]>([])
const currentRow = ref<FactoryOrderNewListData | null>(null) const currentRow = ref<FactoryOrderNewListData | null>(null)
const selectedRowIds = ref<(number | string)[]>([]) const selectedRowIds = ref<(number | string)[]>([])
const selectedRows = ref<FactoryOrderNewListData[]>([])
const cardSelectedIds = ref<(number | string)[]>([])
const logList = ref<LogListData[]>([]) const logList = ref<LogListData[]>([])
const mainTableRef = ref() const mainTableRef = ref()
const mainColumns: CustomColumn<FactoryOrderNewListData>[] = [ const mainColumns: CustomColumn<FactoryOrderNewListData>[] = [
{ key: 'orderNumber', prop: 'orderNumber', label: '订单编号', minWidth: 160 }, { key: 'orderNumber', prop: 'orderNumber', label: '订单编号', minWidth: 160 },
{ {
...@@ -794,12 +982,12 @@ const mainColumns: CustomColumn<FactoryOrderNewListData>[] = [ ...@@ -794,12 +982,12 @@ const mainColumns: CustomColumn<FactoryOrderNewListData>[] = [
minWidth: 160, minWidth: 160,
}, },
{ key: 'statusName', prop: 'statusName', label: '订单状态', minWidth: 120 }, { key: 'statusName', prop: 'statusName', label: '订单状态', minWidth: 120 },
{ key: 'status', prop: 'status', label: '客户标签', minWidth: 160 }, {
{ key: 'status', prop: 'status', label: '客户编号', minWidth: 160 }, key: 'customerTags',
{ key: 'status', prop: 'status', label: '物流类型', minWidth: 120 }, prop: 'customerTags',
{ key: 'status', prop: 'status', label: '物流跟踪号', minWidth: 160 }, label: '客户标签',
{ key: 'status', prop: 'status', label: '总克重', minWidth: 120 }, minWidth: 160,
{ key: 'status', prop: 'status', label: '商品总数量', minWidth: 120 }, },
{ {
key: 'logisticsWayName', key: 'logisticsWayName',
prop: 'logisticsWayName', prop: 'logisticsWayName',
...@@ -807,17 +995,19 @@ const mainColumns: CustomColumn<FactoryOrderNewListData>[] = [ ...@@ -807,17 +995,19 @@ const mainColumns: CustomColumn<FactoryOrderNewListData>[] = [
minWidth: 140, minWidth: 140,
}, },
{ {
key: 'logisticsSourceNo',
prop: 'logisticsSourceNo',
label: '物流跟踪号',
minWidth: 160,
},
{ key: 'totalWeight', prop: 'totalWeight', label: '总克重', minWidth: 120 },
{
key: 'totalProductNum', key: 'totalProductNum',
prop: 'totalProductNum', prop: 'totalProductNum',
label: '商品总数量', label: '商品总数量',
minWidth: 120, minWidth: 120,
}, },
{ { key: 'receiverName', prop: 'receiverName', label: '收货人', minWidth: 120 },
key: 'receiverName',
prop: 'receiverName',
label: '收货人',
minWidth: 120,
},
{ {
key: 'receiverPhone', key: 'receiverPhone',
prop: 'receiverPhone', prop: 'receiverPhone',
...@@ -825,8 +1015,8 @@ const mainColumns: CustomColumn<FactoryOrderNewListData>[] = [ ...@@ -825,8 +1015,8 @@ const mainColumns: CustomColumn<FactoryOrderNewListData>[] = [
minWidth: 140, minWidth: 140,
}, },
{ {
key: 'receiverPhone', key: 'receiverCode',
prop: 'receiverPhone', prop: 'receiverCode',
label: '收货人邮编', label: '收货人邮编',
minWidth: 140, minWidth: 140,
}, },
...@@ -859,7 +1049,7 @@ const mainColumns: CustomColumn<FactoryOrderNewListData>[] = [ ...@@ -859,7 +1049,7 @@ const mainColumns: CustomColumn<FactoryOrderNewListData>[] = [
minWidth: 180, minWidth: 180,
}, },
{ {
key:'operation', key: 'operation',
prop: 'operation', prop: 'operation',
label: '操作', label: '操作',
minWidth: 120, minWidth: 120,
...@@ -884,48 +1074,13 @@ const productColumns: CustomColumn<ProductListData>[] = [ ...@@ -884,48 +1074,13 @@ const productColumns: CustomColumn<ProductListData>[] = [
minWidth: 200, minWidth: 200,
showOverflowTooltip: true, showOverflowTooltip: true,
}, },
{ { key: 'variantSku', prop: 'variantSku', label: '变体SKU', minWidth: 160 },
key: 'variantSku', { key: 'stockSku', prop: 'stockSku', label: '库存SKU', minWidth: 160 },
prop: 'variantSku', { key: 'craftCode', prop: 'craftCode', label: '工艺', minWidth: 100 },
label: '变体SKU', { key: 'styleNo', prop: 'styleNo', label: '款号', minWidth: 100 },
minWidth: 160, { key: 'price', prop: 'price', label: '价格', minWidth: 100 },
}, { key: 'quantity', prop: 'quantity', label: '数量', minWidth: 100 },
{ { key: 'weight', prop: 'weight', label: '克重', minWidth: 100 },
key: 'stockSku',
prop: 'stockSku',
label: '库存SKU',
minWidth: 160,
},
{
key: 'craftCode',
prop: 'craftCode',
label: '工艺',
minWidth: 100,
},
{
key: 'styleNo',
prop: 'styleNo',
label: '款号',
minWidth: 100,
},
{
key: 'price',
prop: 'price',
label: '价格',
minWidth: 100,
},
{
key: 'quantity',
prop: 'quantity',
label: '数量',
minWidth: 100,
},
{
key: 'weight',
prop: 'weight',
label: '克重',
minWidth: 100,
},
{ {
key: 'availableQuantity', key: 'availableQuantity',
prop: 'availableQuantity', prop: 'availableQuantity',
...@@ -945,37 +1100,33 @@ const productColumns: CustomColumn<ProductListData>[] = [ ...@@ -945,37 +1100,33 @@ const productColumns: CustomColumn<ProductListData>[] = [
minWidth: 100, minWidth: 100,
}, },
{ {
key:'', key: 'customsNameEnglish',
prop: '', prop: 'customsNameEnglish',
label:'英文报关名称', label: '英文报关名称',
width: 160, width: 160,
}, },
{ {
key:'', key: 'customsNameChinese',
prop: '', prop: 'customsNameChinese',
label:'中文报关名称', label: '中文报关名称',
width: 160, width: 160,
}, },
{ {
key:'', key: 'customsWeight',
prop: '', prop: 'customsWeight',
label:'申报重量(g)', label: '申报重量(g)',
width: 100, width: 100,
}, },
{ {
key:'', key: 'customsValue',
prop: '', prop: 'customsValue',
label:'申报价值($)', label: '申报价值($)',
width: 100, width: 100,
}, },
{ { key: 'remark', prop: 'remark', label: '备注', width: 160 },
key:'',
prop: '',
label:'备注',
width: 160,
},
] ]
// ========== 分页数据 ==========
const { const {
loading, loading,
currentPage, currentPage,
...@@ -996,14 +1147,17 @@ const { ...@@ -996,14 +1147,17 @@ const {
}, },
page, page,
size, size,
status.value === 'ALL' getStatusCodeForQuery(),
? undefined
: status.value === 'PENDING_ACCEPT'
? pendingAcceptSubTab.value
: status.value,
).then((res) => res.data), ).then((res) => res.data),
}) })
const getStatusCodeForQuery = () => {
if (status.value === 'ALL') return undefined
if (status.value === 'PENDING_ACCEPT') return pendingAcceptSubTab.value
if (status.value === 'SUSPENDED') return `SUSPENDED_${suspendedSubTab.value}`
return status.value
}
const search = () => { const search = () => {
onCurrentPageChange(1) onCurrentPageChange(1)
refresh() refresh()
...@@ -1016,12 +1170,64 @@ const reset = () => { ...@@ -1016,12 +1170,64 @@ const reset = () => {
search() search()
} }
// ========== 组件 Ref ==========
const confirmOrderDialogRef = ref<InstanceType<typeof ConfirmOrderDialog>>()
const cancelOrderDialogRef = ref<InstanceType<typeof CancelOrderDialog>>()
const suspendDialogRef = ref<InstanceType<typeof SuspendDialog>>()
const pickCompleteDialogRef = ref<InstanceType<typeof PickCompleteDialog>>()
const pickFailDialogRef = ref<InstanceType<typeof PickFailDialog>>()
const cardLayoutRef = ref<InstanceType<typeof CardLayout>>()
const batchManageRef = ref<InstanceType<typeof BatchManageTable>>()
const waitingRestockRef = ref<InstanceType<typeof WaitingRestockTable>>()
const createLogisticDialogRef = ref()
const updateCustomsDialogVisible = ref(false)
const weightDialogRef = ref()
// ========== 通用工具 ==========
const getSelectedIds = (): (number | string)[] => {
if (isCardLayout.value) return cardSelectedIds.value
return selectedRowIds.value
}
const ensureSelection = (msg = '请先选择订单'): boolean => {
const ids = getSelectedIds()
if (!ids.length) {
ElMessage.warning(msg)
return false
}
return true
}
const refreshCurrentView = () => {
if (isSpecialLayout.value) {
if (status.value === 'BATCH_MANAGE') batchManageRef.value?.refresh()
if (status.value === 'WAITING_RESTOCK') waitingRestockRef.value?.refresh()
return
}
refresh()
if (isCardLayout.value) cardLayoutRef.value?.clearSelection()
}
// ========== 状态切换 ==========
const handleStatusNodeClick = (node: StatusTreeNode) => { const handleStatusNodeClick = (node: StatusTreeNode) => {
if (status.value === node.code) return
status.value = node.code status.value = node.code
if (node.code !== 'PENDING_ACCEPT') { if (node.code !== 'PENDING_ACCEPT') {
pendingAcceptSubTab.value = 'PENDING_ACCEPT' pendingAcceptSubTab.value = 'PENDING_ACCEPT'
} }
// refresh() if (node.code !== 'SUSPENDED') {
suspendedSubTab.value = 'CUSTOMER_INTERCEPT'
}
selectedRowIds.value = []
selectedRows.value = []
cardSelectedIds.value = []
currentRow.value = null
productList.value = []
logList.value = []
if (!isSpecialLayout.value) {
onCurrentPageChange(1)
refresh()
}
} }
const toggleExpand = (node: { expanded?: boolean }) => { const toggleExpand = (node: { expanded?: boolean }) => {
...@@ -1038,6 +1244,11 @@ const handleCurrentPageChange = (page: number) => { ...@@ -1038,6 +1244,11 @@ const handleCurrentPageChange = (page: number) => {
const handleMainSelectionChange = (rows: FactoryOrderNewListData[]) => { const handleMainSelectionChange = (rows: FactoryOrderNewListData[]) => {
selectedRowIds.value = rows.map((row) => row.id) selectedRowIds.value = rows.map((row) => row.id)
selectedRows.value = rows
}
const handleCardSelectionChange = (items: { id: number | string }[]) => {
cardSelectedIds.value = items.map((i) => i.id)
} }
const loadSubTables = async () => { const loadSubTables = async () => {
...@@ -1063,6 +1274,276 @@ const handleMainRowClick = (row: FactoryOrderNewListData) => { ...@@ -1063,6 +1274,276 @@ const handleMainRowClick = (row: FactoryOrderNewListData) => {
loadSubTables() loadSubTables()
} }
// ========== 待接单 操作 ==========
const handleConfirmOrder = () => {
if (!ensureSelection()) return
confirmOrderDialogRef.value?.open(getSelectedIds())
}
const handleCancelOrder = () => {
if (!ensureSelection()) return
cancelOrderDialogRef.value?.open(getSelectedIds())
}
const handleRefreshProductInfo = async () => {
if (!ensureSelection()) return
try {
await refreshProductInfoApi(getSelectedIds())
ElMessage.success('刷新商品信息成功')
refresh()
} catch (e: unknown) {
ElMessage.error((e as Error)?.message || '刷新商品信息失败')
}
}
const handleTransferOldFlow = async () => {
if (!ensureSelection()) return
await ElMessageBox.confirm('确定转旧流程吗?', '提示', { type: 'warning' })
try {
await transferOldFlowApi(getSelectedIds())
ElMessage.success('转旧流程成功')
refresh()
} catch (e: unknown) {
ElMessage.error((e as Error)?.message || '转旧流程失败')
}
}
// ========== 待创建物流 操作 ==========
const handleLogisticsCommand = async (command: string) => {
if (!ensureSelection()) return
const ids = getSelectedIds()
if (command === 'createLogistic') {
createLogisticDialogRef.value?.showDialog(ids)
return
}
const apiMap: Record<
string,
(data: (number | string)[]) => Promise<{ code: number; message?: string }>
> = {
getTrackingNumber: getTrackingNumberApi,
getPrintOrder: getfaceSimplexFileApi,
cancelLogistic: cancelLogisticsOrderApi,
}
const labelMap: Record<string, string> = {
getTrackingNumber: '获取跟踪号',
getPrintOrder: '获取打印面单',
cancelLogistic: '取消物流订单',
}
const api = apiMap[command]
if (!api) return
await ElMessageBox.confirm(`确定${labelMap[command]}吗?`, '提示', {
type: 'warning',
})
try {
const res = await api(ids)
if (command === 'getPrintOrder' && res.message) {
window.open(filePath + res.message, '_blank')
}
ElMessage.success(`${labelMap[command]}成功`)
refresh()
} catch (e: unknown) {
ElMessage.error((e as Error)?.message || `${labelMap[command]}失败`)
}
}
const handleSuspend = () => {
if (!ensureSelection()) return
suspendDialogRef.value?.open(getSelectedIds())
}
const handleUpdateCustomsInfo = () => {
if (!ensureSelection()) return
updateCustomsDialogVisible.value = true
}
// ========== 待排单 操作 ==========
const handleArrange = async () => {
if (!ensureSelection()) return
const ids = getSelectedIds()
await ElMessageBox.confirm('确定派单所选操作单?', '提示', {
type: 'warning',
})
try {
await arrangeFinishApi({ productIdList: ids as number[] })
ElMessage.success('派单成功')
refreshCurrentView()
} catch (e: unknown) {
ElMessage.error((e as Error)?.message || '派单失败')
}
}
const handleDownloadMaterial = async () => {
if (!ensureSelection()) return
const ids = getSelectedIds()
try {
const res = await downloadMaterialApi(ids as number[])
if (res.message) {
const a = document.createElement('a')
a.href = filePath + res.message
a.download = ''
a.click()
}
ElMessage.success('下载素材成功')
} catch (e: unknown) {
ElMessage.error((e as Error)?.message || '下载素材失败')
}
}
// ========== 待拣胚/待补胚 操作 ==========
const handleDtfCommand = async (command: string) => {
if (!ensureSelection()) return
const ids = getSelectedIds()
const [type, widthStr] = command.split('_')
const templateWidth = Number(widthStr)
const url =
status.value === 'PENDING_REPLENISH'
? 'factory/podJomallOrderCn/replenishmentComposingDesignImages'
: 'factory/podJomallOrderProductCn/composingDesignImages'
try {
const res = await composingDesignImages(
url,
ids as number[],
type,
templateWidth,
)
if (type === 'tiff' && res.message) {
window.open(filePath + res.message, '_blank')
}
ElMessage.success('DTF排版成功')
} catch (e: unknown) {
ElMessage.error((e as Error)?.message || 'DTF排版失败')
}
}
const handlePrintProductionOrder = async () => {
if (!ensureSelection()) return
const ids = getSelectedIds()
const url =
status.value === 'PENDING_REPLENISH'
? 'factory/podJomallOrderProductCn/replenishmentPrintProducePdf'
: 'factory/podJomallOrderProductCn/printProducePdf'
try {
const res = await printProductionOrderApi(url, ids as number[])
if (res.message) {
window.open(filePath + res.message, '_blank')
}
} catch (e: unknown) {
ElMessage.error((e as Error)?.message || '打印生产单失败')
}
}
const handlePrintPickOrder = async () => {
if (!ensureSelection()) return
const ids = getSelectedIds()
try {
await printPickOrderApi(ids)
ElMessage.success('打印拣胚单成功')
refreshCurrentView()
} catch (e: unknown) {
ElMessageBox.alert(
(e as Error)?.message || '打印拣胚单失败',
'打印拣胚单失败',
{ type: 'error' },
)
}
}
const handlePickComplete = () => {
if (!ensureSelection()) return
pickCompleteDialogRef.value?.open(getSelectedIds())
}
const handlePickFail = () => {
if (!ensureSelection()) return
pickFailDialogRef.value?.open(getSelectedIds())
}
// ========== 生产中 操作 ==========
const handleApplyReplenish = async () => {
if (!ensureSelection()) return
await ElMessageBox.confirm('确定申请补胚吗?', '提示', { type: 'warning' })
try {
await applyReplenishApi(getSelectedIds())
ElMessage.success('申请补胚成功')
refreshCurrentView()
} catch (e: unknown) {
ElMessageBox.alert(
(e as Error)?.message || '申请补胚失败',
'申请补胚失败',
{ type: 'error' },
)
}
}
const handleProductionComplete = async () => {
if (!ensureSelection()) return
await ElMessageBox.confirm('确定生产完成吗?', '提示', { type: 'warning' })
try {
const params = getSelectedIds().map((id) => ({
id,
podJomallOrderCnId: id,
factorySubOrderNumber: '',
version: 0,
}))
await updateToWaitShipmentApi({ cnUpdateParams: params })
ElMessage.success('生产完成')
refreshCurrentView()
} catch (e: unknown) {
ElMessage.error((e as Error)?.message || '生产完成失败')
}
}
const handleSeedingWall = () => {
ElMessage.info('播种墙配货功能待集成')
}
// ========== 待发货 操作 ==========
const handleWeightSort = () => {
weightDialogRef.value?.open()
}
// ========== 已完成 操作 ==========
const handleArchiveOrder = async () => {
if (!ensureSelection()) return
await ElMessageBox.confirm('确定订单归档吗?', '提示', { type: 'warning' })
try {
await archiveOrderApi(getSelectedIds())
ElMessage.success('订单归档成功')
refresh()
} catch (e: unknown) {
ElMessageBox.alert(
(e as Error)?.message || '订单归档失败',
'订单归档失败',
{ type: 'error' },
)
}
}
// ========== 挂起 操作 ==========
const handleCancelSuspend = async () => {
if (!ensureSelection()) return
try {
await cancelSuspendApi(getSelectedIds())
ElMessage.success('取消挂起成功')
refresh()
} catch (e: unknown) {
ElMessage.error((e as Error)?.message || '取消挂起失败')
}
}
const handleSyncAddress = async () => {
if (!ensureSelection()) return
await ElMessageBox.confirm('确定同步收货地址吗?', '提示', { type: 'warning' })
try {
await syncReceiverAddress(getSelectedIds() as number[])
ElMessage.success('同步收货地址成功')
refresh()
} catch (e: unknown) {
ElMessage.error((e as Error)?.message || '同步收货地址失败')
}
}
// ========== Watchers ==========
watch( watch(
() => pendingAcceptSubTab.value, () => pendingAcceptSubTab.value,
() => { () => {
...@@ -1074,6 +1555,16 @@ watch( ...@@ -1074,6 +1555,16 @@ watch(
) )
watch( watch(
() => suspendedSubTab.value,
() => {
if (status.value === 'SUSPENDED') {
onCurrentPageChange(1)
refresh()
}
},
)
watch(
() => activeTab.value, () => activeTab.value,
() => { () => {
if (currentRow.value) { if (currentRow.value) {
...@@ -1082,27 +1573,7 @@ watch( ...@@ -1082,27 +1573,7 @@ watch(
}, },
) )
const confirmBatchAction = async (message: string) => { // ========== 初始化 ==========
if (!selectedRowIds.value.length) {
ElMessage.warning('请先选择订单')
return false
}
await ElMessageBox.confirm(message, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
return true
}
const handleConfirmOrder = async () => {
const ok = await confirmBatchAction('确认接单所选订单?')
if (!ok) return
await confirmOrderApi(selectedRowIds.value)
ElMessage.success('操作成功')
refresh()
}
onMounted(() => { onMounted(() => {
if (treeRef.value) { if (treeRef.value) {
treeRef.value.setCurrentKey(status.value, true) treeRef.value.setCurrentKey(status.value, true)
...@@ -1178,7 +1649,7 @@ onMounted(() => { ...@@ -1178,7 +1649,7 @@ onMounted(() => {
} }
} }
.pending-accept-subtabs { .status-subtabs {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
...@@ -1187,7 +1658,7 @@ onMounted(() => { ...@@ -1187,7 +1658,7 @@ onMounted(() => {
margin-top: 10px; margin-top: 10px;
} }
.pending-accept-subtab { .status-subtab {
font-size: 14px; font-size: 14px;
line-height: 32px; line-height: 32px;
padding: 0 12px; padding: 0 12px;
...@@ -1203,66 +1674,29 @@ onMounted(() => { ...@@ -1203,66 +1674,29 @@ onMounted(() => {
} }
} }
.right { .operation-list {
flex: 1;
flex-shrink: 0;
background: #fff;
overflow: hidden;
}
.btn-list {
padding: 0 10px 10px;
}
.main-table-wrapper {
height: 100%;
display: flex; display: flex;
flex-direction: column; align-items: center;
min-height: 0; gap: 8px;
flex-wrap: wrap;
padding: 8px 0;
} }
.main-table-scroll { .table-content {
flex: 1; flex: 1;
min-height: 0; margin-top: 10px;
overflow: auto; overflow: hidden;
} :deep(#top) {
.main-table-scroll :deep(.table-view) {
height: 100%;
}
.main-table-pagination {
flex-shrink: 0;
margin: 10px auto 0;
}
.tabs-wrapper {
height: 100%;
:deep(.el-tabs__content) {
height: calc(100% - 40px);
}
:deep(.el-tab-pane) {
height: 100%; height: 100%;
display: flex;
flex-direction: column;
} }
} }
.sub-table-wrapper, .card-content {
.log-table-wrapper {
flex: 1;
overflow: hidden;
}
.table-content {
flex: 1; flex: 1;
margin-top: 10px; margin-top: 10px;
overflow: hidden; overflow: hidden;
:deep(#top) {
height: 100%;
}
} }
.header-filter-form { .header-filter-form {
width: 100%; width: 100%;
min-width: 0; min-width: 0;
...@@ -1284,6 +1718,26 @@ onMounted(() => { ...@@ -1284,6 +1718,26 @@ onMounted(() => {
min-width: 0; min-width: 0;
} }
} }
.tabs-wrapper {
height: 100%;
:deep(.el-tabs__content) {
height: calc(100% - 40px);
}
:deep(.el-tab-pane) {
height: 100%;
display: flex;
flex-direction: column;
}
}
.sub-table-wrapper,
.log-table-wrapper {
flex: 1;
overflow: hidden;
}
</style> </style>
<style lang="scss"> <style lang="scss">
.customize-select-style { .customize-select-style {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment