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