Commit 5c5b1b42 by qinjianhui

feat: 新订单批次管理修改

parent 253f46ce
<template>
<ElDialog
v-model="visible"
:title="dialogTitle"
width="520px"
:close-on-click-modal="false"
>
<ElForm :model="form">
<ElFormItem
v-if="showAutoSwitch"
label="自动排版(烫画工艺推荐自动排版)"
style="margin-bottom: 10px"
>
<el-switch
v-model="isAuto"
inline-prompt
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
active-text="是"
inactive-text="否"
@change="handleAutoChange"
/>
</ElFormItem>
<template v-if="isAuto || !showAutoSwitch">
<ElFormItem label="排版类型">
<el-radio-group v-model="form.type">
<el-radio label="tiff">tiff</el-radio>
<el-radio label="png">png</el-radio>
</el-radio-group>
</ElFormItem>
<ElFormItem label="排版宽度">
<el-radio-group v-model="form.templateWidth">
<el-radio :value="42">40+2cm</el-radio>
<el-radio :value="60">60cm</el-radio>
</el-radio-group>
</ElFormItem>
</template>
</ElForm>
<template #footer>
<div style="text-align: center">
<ElButton @click="visible = false">取消</ElButton>
<ElButton type="primary" :loading="submitting" @click="handleSubmit">
确定
</ElButton>
</div>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { arrangeFinishApi } from '@/api/podCnOrder'
type OpenPayload = {
productIdList: number[]
title?: string
showAutoSwitch?: boolean
}
const emit = defineEmits<{
success: []
}>()
const visible = ref(false)
const submitting = ref(false)
const payload = ref<OpenPayload | null>(null)
const isAuto = ref(true)
const form = ref<{ type?: string; templateWidth?: number }>({})
const showAutoSwitch = ref(true)
const dialogTitle = ref('排单')
const handleAutoChange = () => {
form.value = {}
}
const open = (p: OpenPayload) => {
payload.value = p
showAutoSwitch.value = p.showAutoSwitch ?? true
dialogTitle.value = p.title || '排单'
isAuto.value = true
form.value = {}
visible.value = true
}
const handleSubmit = async () => {
if (!payload.value?.productIdList?.length) {
return ElMessage.warning('请选择订单')
}
const { templateWidth, type } = form.value
const mustFill = isAuto.value || !showAutoSwitch.value
if (mustFill && (!templateWidth || !type)) {
return ElMessage.warning('排版类型和排版宽度为必选项')
}
if (!templateWidth && type) return ElMessage.warning('请选择排版宽度')
if (templateWidth && !type) return ElMessage.warning('请选择排版类型')
submitting.value = true
try {
await arrangeFinishApi({
productIdList: payload.value.productIdList,
templateWidth: isAuto.value ? templateWidth : undefined,
type: isAuto.value ? type : undefined,
})
ElMessage.success('操作成功')
visible.value = false
emit('success')
} catch (e: unknown) {
ElMessage.error((e as Error)?.message || '排单失败')
} finally {
submitting.value = false
}
}
defineExpose({ open })
</script>
......@@ -13,12 +13,20 @@
/>
</ElFormItem>
<ElFormItem label="创建人">
<ElInput
<ElSelect
v-model="filterForm.creator"
placeholder="创建人"
placeholder="请选择"
clearable
filterable
style="width: 120px"
/>
>
<ElOption
v-for="item in employeeList"
:key="item.id"
:label="item.account"
:value="item.account"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="工艺类型">
<ElSelect
......@@ -30,6 +38,9 @@
<ElOption label="烫画" value="TH" />
<ElOption label="直喷" value="ZP" />
<ElOption label="刺绣" value="CX" />
<ElOption label="雕刻" value="DK" />
<ElOption label="白胚" value="BP" />
<ElOption label="其他" value="QT" />
</ElSelect>
</ElFormItem>
<ElFormItem label="下载状态">
......@@ -98,21 +109,37 @@
</el-tag>
</template>
<template #operation="{ row }">
<ElButton type="primary" link size="small" @click="handleView(row)">
查看
</ElButton>
<ElButton
type="primary"
link
size="small"
@click="handleDownload(row)"
@click="handleDownload()"
>
下载
</ElButton>
<ElButton type="primary" link size="small" @click="handlePrintPick()">
拣货单
</ElButton>
<ElButton
type="primary"
link
size="small"
@click="handlePrintProduction()"
>
生产单
</ElButton>
<ElButton type="warning" link size="small" @click="handleReArrange(row)">
重排
</ElButton>
<ElButton type="danger" link size="small" @click="handleDelete()">
删除
</ElButton>
</template>
</TableView>
</div>
<ArrangeDialog ref="arrangeDialogRef" @success="loadData" />
<ElPagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
......@@ -134,6 +161,9 @@ import { getBatchManageListApi, batchDeleteApi } from '@/api/factoryOrderNew'
import type { BatchManageData } from '@/types/api/factoryOrderNew'
import type { CustomColumn } from '@/types/table'
import TableView from '@/components/TableView.vue'
import ArrangeDialog from './ArrangeDialog.vue'
import { getEmployeeListApi } from '@/api/common'
import type { userData } from '@/types/api/user'
const loading = ref(false)
const tableData = ref<BatchManageData[]>([])
......@@ -141,6 +171,8 @@ const selectedRows = ref<BatchManageData[]>([])
const currentPage = ref(1)
const pageSize = ref(50)
const total = ref(0)
const employeeList = ref<userData[]>([])
const arrangeDialogRef = ref<InstanceType<typeof ArrangeDialog>>()
const columns: CustomColumn<BatchManageData>[] = [
{ key: 'batchNo', prop: 'batchNo', label: '批次号', minWidth: 120 },
......@@ -230,6 +262,16 @@ const loadData = async () => {
}
}
const loadEmployeeList = async () => {
try {
const res = await getEmployeeListApi()
if (res.code !== 200) return
employeeList.value = res.data || []
} catch (e) {
console.error(e)
}
}
const handleSelectionChange = (rows: BatchManageData[]) => {
selectedRows.value = rows
}
......@@ -252,12 +294,30 @@ const handleBatchDelete = async () => {
}
}
const handleView = (row: BatchManageData) => {
ElMessage.info(`查看批次 ${row.batchNo}`)
const handleDownload = () => {
ElMessage.info('接口待提供')
}
const handleDownload = (row: BatchManageData) => {
ElMessage.info(`下载批次 ${row.batchNo}`)
const handlePrintPick = () => {
ElMessage.info('接口待提供')
}
const handlePrintProduction = () => {
ElMessage.info('接口待提供')
}
const handleDelete = () => {
ElMessage.info('接口待提供')
}
const handleReArrange = (row: BatchManageData) => {
// 后端未提供“批次 -> 商品ID列表”映射时,暂用行 id 作为占位
arrangeDialogRef.value?.open({
productIdList: [row.id],
title: '重排',
showAutoSwitch: false,
})
}
const handlePageSizeChange = (size: number) => {
......@@ -277,6 +337,7 @@ const refresh = () => {
}
onMounted(() => {
loadEmployeeList()
loadData()
})
......
......@@ -12,6 +12,7 @@
v-model="form.reason"
placeholder="请选择取消原因"
style="width: 100%"
clearable
>
<ElOption
v-for="item in cancelReasons"
......@@ -23,17 +24,19 @@
</ElFormItem>
</ElForm>
<template #footer>
<ElButton @click="visible = false">取消</ElButton>
<ElButton type="primary" :loading="submitLoading" @click="handleSubmit">
确认
</ElButton>
<div class="dialog-footer" style="text-align: center">
<ElButton @click="visible = false">取消</ElButton>
<ElButton type="primary" :loading="submitLoading" @click="handleSubmit">
确认
</ElButton>
</div>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { cancelOrderWithReasonApi } from '@/api/factoryOrderNew'
......@@ -75,10 +78,8 @@ const handleSubmit = async () => {
ElMessage.success('取消订单成功')
visible.value = false
emit('success')
} catch (e: any) {
ElMessageBox.alert(e?.message || '取消订单失败', '取消订单失败', {
type: 'error',
})
} catch (e: unknown) {
console.error(e)
} finally {
submitLoading.value = false
}
......
......@@ -36,11 +36,24 @@
<div v-if="item.customizedQuantity" class="quantity-badge">
{{ item.customizedQuantity }}
</div>
<Icon name="caozuorizhi" style="width: 28px; height: 28px">
<template #title>
<title>操作日志</title>
</template>
</Icon>
<Icon name="chakanxiangqing">
<template #title>
<title>查看详情</title>
</template>
</Icon>
</template>
<template #info>
<div class="card-info-grid">
<div class="card-info-row full">
<span class="info-value ellipsis" :title="(item.productName as string) || ''">
<span
class="info-value ellipsis"
:title="(item.productName as string) || ''"
>
{{ item.productName }}
</span>
</div>
......
......@@ -58,6 +58,7 @@
:placeholder="placeholder"
readonly
clearable
:style="{ width: width }"
@clear="clearValue"
>
<template #suffix>
......@@ -81,6 +82,7 @@ const props = withDefaults(
options: ProductTypeGroup[]
multiple?: boolean
placeholder?: string
width?: string
}>(),
{
multiple: false,
......
......@@ -124,7 +124,7 @@
:options="productTypeGroups"
:multiple="false"
placeholder="请选择商品类型"
style="width: 140px"
width="140px"
/>
</ElFormItem>
......@@ -661,7 +661,6 @@
</div>
</div>
<!-- ====== 待接单子 Tab ====== -->
<div v-if="status === 'PENDING_ACCEPT'" class="status-subtabs">
<div
class="status-subtab"
......@@ -680,9 +679,6 @@
接单失败-缺货<span> (0) </span>
</div>
</div>
<!-- ====== 布局区域 ====== -->
<!-- 批次管理 -->
<BatchManageTable v-if="status === 'BATCH_MANAGE'" ref="batchManageRef" />
......@@ -692,7 +688,7 @@
ref="waitingRestockRef"
/>
<!-- 卡片布局 -->
<!-- 到派单、配货中子状态 -->
<div v-if="isCardLayout" class="card-content">
<CardLayout
ref="cardLayoutRef"
......@@ -757,8 +753,6 @@
</splitDiv>
</div>
</div>
<!-- ====== 弹框组件 ====== -->
<ConfirmOrderDialog
ref="confirmOrderDialogRef"
@success="refreshCurrentView"
......@@ -774,7 +768,6 @@
/>
<PickFailDialog ref="pickFailDialogRef" @success="refreshCurrentView" />
<!-- 复用 podCN 的弹框组件 -->
<CreateLogisticDialog
ref="createLogisticDialogRef"
@refresh-table="refreshCurrentView"
......@@ -785,6 +778,17 @@
@refresh-table="refreshCurrentView"
/>
<WeightDialog ref="weightDialogRef" @update-list="refreshCurrentView" />
<ArrangeDialog ref="arrangeDialogRef" @success="refreshCurrentView" />
<PodMakeOrder
v-model="podOrderVisible"
:print-order="printOrder"
:warehouse-list="warehouseList"
@set-printer="handlePrinterChange"
@set-warehouse-id="handleWarehouseIdChange"
@refresh="refreshCurrentView"
/>
</div>
</template>
......@@ -838,11 +842,13 @@ import {
getTrackingNumberApi,
getfaceSimplexFileApi,
cancelLogisticsOrderApi,
arrangeFinishApi,
} from '@/api/podCnOrder'
import { getUserMarkList } from '@/api/common'
import { getUserMarkList, loadWarehouseListApi } from '@/api/common'
import { getAllCountryApi } from '@/api/logistics'
import { filePath } from '@/api/axios'
import { OrderData } from '@/types/api/podMakeOrder'
import type { WarehouseListData } from '@/types'
import useLodop, { LODOPObject } from '@/utils/hooks/useLodop'
import LogisticsWaySelect from '@/views/logistics/components/LogisticsWaySelect'
import { IAllList } from '@/types/api/podUsOrder'
import { CraftListData } from '@/types/api/podCnOrder'
......@@ -857,12 +863,13 @@ 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 ArrangeDialog from './component/ArrangeDialog.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'
import PodMakeOrder from '@/views/order/podUs/PodMakeOrder.vue'
// ========== 状态分类 ==========
const cardLayoutStatuses = [
'PENDING_SCHEDULE',
'PENDING_PICK',
......@@ -878,8 +885,6 @@ const isSpecialLayout = computed(() =>
const isTableLayout = computed(
() => !isCardLayout.value && !isSpecialLayout.value,
)
// ========== 状态树 ==========
const defaultStatusTree: StatusTreeNode[] = [
{
code: 'ALL',
......@@ -913,8 +918,6 @@ const defaultStatusTree: StatusTreeNode[] = [
const statusTree = ref<StatusTreeNode[]>(defaultStatusTree)
const status = ref<string>('PENDING_ACCEPT')
// ========== 子 Tab ==========
const pendingAcceptSubTab = ref<'PENDING_ACCEPT' | 'ACCEPT_FAIL_OUT_OF_STOCK'>(
'PENDING_ACCEPT',
)
......@@ -925,7 +928,6 @@ const suspendedTabs = [
]
const suspendedSubTab = ref('CUSTOMER_INTERCEPT')
// ========== 搜索 ==========
const treeRef = ref<InstanceType<typeof ElTree>>()
const searchForm = ref<SearchForm>({})
const dateRange = ref<string[]>([])
......@@ -1306,6 +1308,7 @@ 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 arrangeDialogRef = ref<InstanceType<typeof ArrangeDialog>>()
const createLogisticDialogRef = ref()
const updateCustomsDialogVisible = ref(false)
const weightDialogRef = ref()
......@@ -1483,22 +1486,165 @@ const handleUpdateCustomsInfo = () => {
updateCustomsDialogVisible.value = true
}
// ========== 待排单 操作 ==========
const handleArrange = async () => {
if (!ensureSelection()) return
const ids = getSelectedIds()
await ElMessageBox.confirm('确定派单所选操作单?', '提示', {
type: 'warning',
})
const productIdList = getSelectedIds()
.map((v) => Number(v))
.filter((n) => Number.isFinite(n)) as number[]
arrangeDialogRef.value?.open({ productIdList })
}
const podOrderVisible = ref(false)
const warehouseList = ref<WarehouseListData[]>([])
const loadWarehouseList = async () => {
try {
await arrangeFinishApi({ productIdList: ids as number[] })
ElMessage.success('派单成功')
refreshCurrentView()
} catch (e: unknown) {
ElMessage.error((e as Error)?.message || '派单失败')
const res = await loadWarehouseListApi()
if (res.code !== 200) return
warehouseList.value = res.data
} catch (e) {
console.error(e)
}
}
const sheetPrinter = ref('')
const handlePrinterChange = (value: string) => {
sheetPrinter.value = value
localStorage.setItem('sheetPrinter', JSON.stringify(value))
}
const handleWarehouseIdChange = (value: string) => {
localStorage.setItem('locaclWarehouseId', JSON.stringify(value))
}
const { getCLodop } = useLodop()
const handleSeedingWall = () => {
const lodop = getCLodop(null, null)
if (!lodop) return
sheetPrinter.value = lodop.GET_PRINTER_NAME(0)
podOrderVisible.value = true
}
const printOrder = async (data: OrderData, callback: (status: boolean) => void) => {
const lodop = getCLodop(null, null)
if (!lodop) return
lodop.PRINT_INIT('打印内容')
const printerIndex = lodop.SET_PRINTER_INDEX(sheetPrinter.value)
if (!printerIndex) {
ElMessage.error('打印机设置失败')
callback(false)
return
}
if (data.filePath) {
const strURL = filePath + data.filePath
lodop.ADD_PRINT_PDF(
0,
0,
'100%',
'100%',
/(https):\/\/([^/]+)/i.test(strURL) ? downloadPDF(strURL) : strURL,
)
} else {
lodop.SEND_PRINT_RAWDATA(data.fileData || '')
}
if (lodop.CVERSION) {
const startTime = Date.now()
const jobCode = await lodopCall((lodop) => {
lodop.SET_PRINT_MODE('CATCH_PRINT_STATUS', true)
return lodop.PRINT()
})
// eslint-disable-next-line no-constant-condition
while (true) {
const ok = await lodopCall((lodop) =>
lodop.GET_VALUE('PRINT_STATUS_OK', jobCode),
)
if (ok == '1') {
callback(true)
return
}
const exist = await lodopCall((lodop) =>
lodop.GET_VALUE('PRINT_STATUS_EXIST', jobCode),
)
if (exist == '0') {
callback(true)
return
}
await new Promise((r) => setTimeout(r, 500))
if (Date.now() - startTime >= 30000) {
ElMessage.error('打印超时(30秒)')
callback(false)
return
}
}
} else {
lodop.PRINT()
callback(false)
}
}
let _lodop: LODOPObject | null = null
let _lodopCallback: { [key: string]: (value: string) => void } = {}
const lodopCall = (fn: (lodop: LODOPObject) => string) => {
if (!_lodop) {
_lodop = getCLodop(null, null)
if (!_lodop) return
_lodop.On_Return_Remain = true
_lodop.SET_PRINT_MODE('CATCH_PRINT_STATUS', true)
_lodopCallback = {}
_lodop.On_Return = (id, value) => {
const cb = _lodopCallback[id]
if (!cb) return
delete _lodopCallback[id]
cb(value)
}
}
return new Promise((resolve) => {
if (!_lodop) return
const id = fn(_lodop)
_lodopCallback[id] = resolve
})
}
const downloadPDF = (url: string) => {
if (!/^https?:/i.test(url)) return url
const xhr = new XMLHttpRequest()
let arrybuffer = false
xhr.open('GET', url, false)
if (xhr.overrideMimeType) {
try {
xhr.responseType = 'arraybuffer'
arrybuffer = true
} catch {
xhr.overrideMimeType('text/plain; charset=x-user-defined')
}
}
xhr.send(null)
const data = (xhr.response || (xhr as unknown as { responseBody?: unknown }).responseBody) as
| ArrayBuffer
| string
let dataArray: Uint8Array
if (arrybuffer) {
dataArray = new Uint8Array(data as ArrayBuffer)
} else {
const text = data as string
dataArray = new Uint8Array(text.length)
for (let i = 0; i < dataArray.length; i++) dataArray[i] = text.charCodeAt(i)
}
const digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
let strData = ''
for (let i = 0, ii = dataArray.length; i < ii; i += 3) {
const b1 = dataArray[i] & 0xff
const b2 = dataArray[i + 1] & 0xff
const b3 = dataArray[i + 2] & 0xff
const d1 = b1 >> 2
const d2 = ((b1 & 3) << 4) | (b2 >> 4)
const d3 = i + 1 < ii ? ((b2 & 0xf) << 2) | (b3 >> 6) : 64
const d4 = i + 2 < ii ? b3 & 0x3f : 64
strData += digits.charAt(d1) + digits.charAt(d2) + digits.charAt(d3) + digits.charAt(d4)
}
return 'data:application/pdf;base64,' + strData
}
const handleDownloadMaterial = async () => {
if (!ensureSelection()) return
const ids = getSelectedIds()
......@@ -1585,7 +1731,6 @@ const handlePickFail = () => {
pickFailDialogRef.value?.open(getSelectedIds())
}
// ========== 生产中 操作 ==========
const handleApplyReplenish = async () => {
if (!ensureSelection()) return
await ElMessageBox.confirm('确定申请补胚吗?', '提示', { type: 'warning' })
......@@ -1620,16 +1765,10 @@ const handleProductionComplete = async () => {
}
}
const handleSeedingWall = () => {
ElMessage.info('播种墙配货功能待集成')
}
// ========== 待发货 操作 ==========
const handleWeightSort = () => {
weightDialogRef.value?.open()
}
// ========== 已完成 操作 ==========
const handleArchiveOrder = async () => {
if (!ensureSelection()) return
await ElMessageBox.confirm('确定订单归档吗?', '提示', { type: 'warning' })
......@@ -1646,7 +1785,6 @@ const handleArchiveOrder = async () => {
}
}
// ========== 挂起 操作 ==========
const handleCancelSuspend = async () => {
if (!ensureSelection()) return
try {
......@@ -1706,8 +1844,6 @@ const handleStatusPush = () => {
if (!ensureSelection()) return
ElMessage.info('状态推送功能待集成')
}
// ========== Watchers ==========
watch(
() => pendingAcceptSubTab.value,
() => {
......@@ -1736,8 +1872,6 @@ watch(
}
},
)
// ========== 初始化 ==========
onMounted(() => {
if (treeRef.value) {
treeRef.value.setCurrentKey(status.value, true)
......@@ -1747,6 +1881,7 @@ onMounted(() => {
getCustomTagList()
getLogisticsCompanyAllCodelist()
getReceiverCountryList()
loadWarehouseList()
})
</script>
......
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