Commit b6d40292 by qinjianhui

Merge branch 'dev_new_pod_order' into 'release_v2.21.0'

Dev new pod order

See merge request !186
parents 652bd4a8 d52d379f
---
description
glob
alwaysApply: true
---
## 技术栈
Vue3 + Element Plus + Pinia + Axios + TypeScript + Vite
## UI 相关编码要求
- 当有上下两个子表时,使用 `src/components/splitDiv/splitDiv.vue` 组件,使用时注意传递对应的 props 和插槽
- 表格相关布局时,使用 `src/components/TableView.vue` 组件,使用时注意传递对应的 props 和插槽
- `ElDialog`组件中`footer`插槽都放在中间位置
- 写入表格`columns`时,如果需要对齐,使用`align`属性,数字相关的列, `align`属性值为`right`,长度一致的列, `align`属性值为`center`, 长度不一致的列, `align`属性值为`left`
## 接口相关
- 所有接口统一放在 `src/api/`目录下,当模块多时,可按模块创建子目录,例如 `src/api/factoryOrderNew/xxx1`、`src/api/factoryOrderNew/xxx2`、`src/api/factoryOrderNew/xxx3`
## TS 类型定义相关
- 所有类型定义统一放在 `src/types/`目录下,当模块多时,可按模块创建子目录,例如 `src/types/factoryOrderNew/xxx1`、`src/types/factoryOrderNew/xxx2`、`src/types/factoryOrderNew/xxx3`
## 代码编写原则
- 只编写解决问题所需的最少代码
- 避免冗余实现和过渡设计
- 实现一个复杂功能时,需要合理拆分组件,使每个文件的代码更易维护
- 封装组件时,要考虑组件的复用性
...@@ -55,6 +55,12 @@ ...@@ -55,6 +55,12 @@
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe605;</span>
<div class="name">提示</div>
<div class="code-name">&amp;#xe605;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe62e;</span> <span class="icon iconfont">&#xe62e;</span>
<div class="name">查看详情</div> <div class="name">查看详情</div>
<div class="code-name">&amp;#xe62e;</div> <div class="code-name">&amp;#xe62e;</div>
...@@ -132,9 +138,9 @@ ...@@ -132,9 +138,9 @@
<pre><code class="language-css" <pre><code class="language-css"
>@font-face { >@font-face {
font-family: 'iconfont'; font-family: 'iconfont';
src: url('iconfont.woff2?t=1729077025378') format('woff2'), src: url('iconfont.woff2?t=1774496872018') format('woff2'),
url('iconfont.woff?t=1729077025378') format('woff'), url('iconfont.woff?t=1774496872018') format('woff'),
url('iconfont.ttf?t=1729077025378') format('truetype'); url('iconfont.ttf?t=1774496872018') format('truetype');
} }
</code></pre> </code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3> <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
...@@ -161,6 +167,15 @@ ...@@ -161,6 +167,15 @@
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib"> <li class="dib">
<span class="icon iconfont icon-tishi"></span>
<div class="name">
提示
</div>
<div class="code-name">.icon-tishi
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-chakanxiangqing"></span> <span class="icon iconfont icon-chakanxiangqing"></span>
<div class="name"> <div class="name">
查看详情 查看详情
...@@ -279,6 +294,14 @@ ...@@ -279,6 +294,14 @@
<li class="dib"> <li class="dib">
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-tishi"></use>
</svg>
<div class="name">提示</div>
<div class="code-name">#icon-tishi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-chakanxiangqing"></use> <use xlink:href="#icon-chakanxiangqing"></use>
</svg> </svg>
<div class="name">查看详情</div> <div class="name">查看详情</div>
......
@font-face { @font-face {
font-family: "iconfont"; /* Project id 4462827 */ font-family: "iconfont"; /* Project id 4462827 */
src: url('iconfont.woff2?t=1729077025378') format('woff2'), src: url('iconfont.woff2?t=1774496872018') format('woff2'),
url('iconfont.woff?t=1729077025378') format('woff'), url('iconfont.woff?t=1774496872018') format('woff'),
url('iconfont.ttf?t=1729077025378') format('truetype'); url('iconfont.ttf?t=1774496872018') format('truetype');
} }
.iconfont { .iconfont {
...@@ -13,6 +13,10 @@ ...@@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-tishi:before {
content: "\e605";
}
.icon-chakanxiangqing:before { .icon-chakanxiangqing:before {
content: "\e62e"; content: "\e62e";
} }
......
...@@ -6,6 +6,13 @@ ...@@ -6,6 +6,13 @@
"description": "", "description": "",
"glyphs": [ "glyphs": [
{ {
"icon_id": "3833188",
"name": "提示",
"font_class": "tishi",
"unicode": "e605",
"unicode_decimal": 58885
},
{
"icon_id": "22718987", "icon_id": "22718987",
"name": "查看详情", "name": "查看详情",
"font_class": "chakanxiangqing", "font_class": "chakanxiangqing",
......
...@@ -242,10 +242,7 @@ export function productionQueryApi(id: number, podJomallOrderCnId: number) { ...@@ -242,10 +242,7 @@ export function productionQueryApi(id: number, podJomallOrderCnId: number) {
) )
} }
export function printProductionOrderApi(url: string, orderIds: number[]) { export function printProductionOrderApi(url: string, orderIds: number[]) {
return axios.post<never, BaseRespData<string>>( return axios.post<never, BaseRespData<string>>(url, orderIds)
url,
orderIds,
)
} }
export function printPrintOrderApi(orderIds: number[]) { export function printPrintOrderApi(orderIds: number[]) {
return axios.post<never, BaseRespData<string>>( return axios.post<never, BaseRespData<string>>(
...@@ -630,11 +627,15 @@ export function updateToWaitShipmentApi(params: { ...@@ -630,11 +627,15 @@ export function updateToWaitShipmentApi(params: {
} }
//创建物流订单 //创建物流订单
export function createLogisticsOrdersApi(orderIdList: (string | number)[], logisticsWayId: number | null) { export function createLogisticsOrdersApi(
return axios.post<never, BaseRespData<never>>( url: string,
`factory/podJomallOrderCn/createLogisticsOrders`, orderIdList: (string | number)[],
{ orderIdList, logisticsWayId }, logisticsWayId: number | null,
) ) {
return axios.post<never, BaseRespData<never>>(url, {
orderIdList,
logisticsWayId,
})
} }
// 获取跟踪号 // 获取跟踪号
...@@ -747,13 +748,16 @@ export function allErpCodeListApi() { ...@@ -747,13 +748,16 @@ export function allErpCodeListApi() {
'/logisticsCompany/allErpCodeList', '/logisticsCompany/allErpCodeList',
) )
} }
export function updateCustomDeclarationInfoApi({ export function updateCustomDeclarationInfoApi(
params, url: string,
ids, {
}: { params,
params: CustomDeclarationInfoForm ids,
ids: string }: {
}) { params: CustomDeclarationInfoForm
ids: string
},
) {
const formData = new FormData() const formData = new FormData()
formData.append('ids', ids) formData.append('ids', ids)
Object.keys(params).forEach((key) => { Object.keys(params).forEach((key) => {
...@@ -763,13 +767,9 @@ export function updateCustomDeclarationInfoApi({ ...@@ -763,13 +767,9 @@ export function updateCustomDeclarationInfoApi({
} }
}) })
return axios.post<never, BaseRespData<never>>( return axios.post<never, BaseRespData<never>>(url, formData, {
'factory/podJomallOrderCn/batchUpdateCustomsClearanceInfo', headers: { 'content-type': 'multipart/form-data' },
formData, })
{
headers: { 'content-type': 'multipart/form-data' },
},
)
} }
// 标记缺货/移除缺货共用 // 标记缺货/移除缺货共用
export function updateProductOutOfStockApi(params: { export function updateProductOutOfStockApi(params: {
......
...@@ -179,10 +179,7 @@ export function productionQueryApi(id: number, podJomallOrderUsId: number) { ...@@ -179,10 +179,7 @@ export function productionQueryApi(id: number, podJomallOrderUsId: number) {
) )
} }
export function printProductionOrderApi(url: string, orderIds: number[]) { export function printProductionOrderApi(url: string, orderIds: number[]) {
return axios.post<never, BaseRespData<string>>( return axios.post<never, BaseRespData<string>>(url, orderIds)
url,
orderIds,
)
} }
export function printNormalProductionOrderApi(orderIds: number[]) { export function printNormalProductionOrderApi(orderIds: number[]) {
return axios.post<never, BaseRespData<string>>( return axios.post<never, BaseRespData<string>>(
...@@ -232,66 +229,59 @@ export function getOrderDetailById(id: number) { ...@@ -232,66 +229,59 @@ export function getOrderDetailById(id: number) {
) )
} }
export function getPackingDataApi( export function getPackingDataApi(
code: string, url: string,
factoryNo: number, params: {
box: number | null, podJomallUsNo?: string
warehouseId: number | string, box?: number | null
factoryNo?: number
warehouseId?: number | string
},
) { ) {
return axios.get<never, BaseRespData<PodMakeOrderData>>( return axios.get<never, BaseRespData<PodMakeOrderData>>(url, {
'/factory/podJomallOrderUs/local/putPackingSafe', params,
{ })
params: {
podJomallUsNo: code,
box,
factoryNo,
warehouseId,
},
},
)
} }
export function getPodBoxListApi( export function getPodBoxListApi(
url: string,
factoryNo: number | string | undefined, factoryNo: number | string | undefined,
warehouseId: number | string, warehouseId: number | string,
) { ) {
return axios.get<never, BaseRespData<PodMakeOrderData[]>>( return axios.get<never, BaseRespData<PodMakeOrderData[]>>(url, {
'factory/podJomallOrderUs/local/getPodBoxOrderDetails', params: { factoryNo, warehouseId },
{ })
params: { factoryNo, warehouseId },
},
)
} }
export function submitInspectionApi( export function submitInspectionApi(
orderId: number | undefined, url: string,
params: {
orderId?: number | undefined
},
boxIndex: number | null, boxIndex: number | null,
warehouseId: number | string, warehouseId: number | string,
) { ) {
return axios.post<never, BaseRespData<never>>( return axios.post<never, BaseRespData<never>>(
`factory/podJomallOrderUs/podPrintOrderComplete?box=${boxIndex}&warehouseId=${warehouseId}`, `${url}?box=${boxIndex}&warehouseId=${warehouseId}`,
{ params,
orderId,
},
) )
} }
export function clearBoxApi( export function clearBoxApi(
url: string,
factoryNo: number, factoryNo: number,
box: number | null, box: number | null,
warehouseId: number | string, warehouseId: number | string,
) { ) {
return axios.get<never, BaseRespData<never>>( return axios.get<never, BaseRespData<never>>(url, {
'factory/podJomallOrderUs/local/delPodBoxOrderDetailsByBox', params: { factoryNo, box, warehouseId },
{ })
params: { factoryNo, box, warehouseId },
},
)
} }
export function clearAllBoxApi(warehouseId: string | number, factoryNo: string | number | undefined) { export function clearAllBoxApi(
return axios.get<never, BaseRespData<never>>( url: string,
'factory/podJomallOrderUs/local/delPodBoxOrderDetails', warehouseId: string | number,
{ factoryNo: string | number | undefined,
params: { warehouseId, factoryNo }, ) {
}, return axios.get<never, BaseRespData<never>>(url, {
) params: { warehouseId, factoryNo },
})
} }
export function updateRemarkApi(id: number, content: string) { export function updateRemarkApi(id: number, content: string) {
return axios.post<never, BaseRespData<never>>( return axios.post<never, BaseRespData<never>>(
......
<template> <template>
<div class="commodity-card" :class="{ active: active }"> <div class="commodity-card" :class="{ active: active }">
<div class="commodity-card-image"> <div class="commodity-card-image">
<div class="before"></div> <div class="before" :style="{ paddingTop: paddingTop }"></div>
<div class="image"> <div class="image">
<img :src="mainImageSrc" /> <img :src="mainImageSrc" />
</div> </div>
...@@ -110,6 +110,7 @@ interface Props { ...@@ -110,6 +110,7 @@ interface Props {
imageField?: string imageField?: string
imageListField?: string imageListField?: string
imagePathField?: string imagePathField?: string
paddingTop?: string
} }
// 定义默认值 // 定义默认值
...@@ -122,6 +123,7 @@ const props = withDefaults(defineProps<Props>(), { ...@@ -122,6 +123,7 @@ const props = withDefaults(defineProps<Props>(), {
imageField: 'variantImage', imageField: 'variantImage',
imageListField: 'imageList', imageListField: 'imageList',
imagePathField: 'imagePath', imagePathField: 'imagePath',
paddingTop: '120%',
}) })
// 获取主图片源 // 获取主图片源
...@@ -217,7 +219,6 @@ const copy = (text: string) => { ...@@ -217,7 +219,6 @@ const copy = (text: string) => {
.before { .before {
height: 0; height: 0;
padding-top: 120%;
} }
.image { .image {
......
<template> <template>
<i <i
v-if="unicodeIcon" v-if="unicodeIcon"
class="erpIconfont erp unicode-icon" class="iconfont factory unicode-icon"
v-html="unicodeIcon" v-html="unicodeIcon"
></i> ></i>
<svg v-else class="svg-icon erp" aria-hidden="true"> <svg v-else class="svg-icon factory" aria-hidden="true">
<slot name="title"></slot> <slot name="title"></slot>
<use :xlink:href="svgIcon"></use> <use :xlink:href="svgIcon"></use>
</svg> </svg>
...@@ -19,7 +19,7 @@ const props = defineProps({ ...@@ -19,7 +19,7 @@ const props = defineProps({
} }
}) })
const unicodeIcon = computed(() => { const unicodeIcon = computed(() => {
if (props.name.match(/^x[a-f0-9]{4}$/)) { if (props.name.match(/^x[a-f0-9]{4,}$/)) {
return `&#${props.name};` return `&#${props.name};`
} }
return undefined return undefined
......
...@@ -32,9 +32,12 @@ defineProps({ ...@@ -32,9 +32,12 @@ defineProps({
.log-list { .log-list {
height: 500px; height: 500px;
overflow: auto; overflow: auto;
font-size: 14px;
} }
.log-item { .log-item {
line-height: 26px; line-height: 26px;
white-space: pre-wrap;
word-break: break-all;
} }
.log-item div:not(:last-child) { .log-item div:not(:last-child) {
margin-right: 6px; margin-right: 6px;
......
...@@ -32,39 +32,42 @@ ...@@ -32,39 +32,42 @@
</template> </template>
</ElTableColumn> </ElTableColumn>
</template> </template>
<ElTableColumn v-else header-align="center" v-bind="col"> <template v-else>
<template #header="{ column, $index }"> <ElTableColumn
<component v-if="col.showColumn !== false"
:is="() => col.headerRender(column, $index)" header-align="center"
v-if="col.headerRender" v-bind="col"
/> >
<slot <template #header="{ column, $index }">
v-else-if="col.headerSlot" <component
:name="col.headerSlot" :is="() => col.headerRender(column, $index)"
:column="column" v-if="col.headerRender"
:index="$index" />
></slot> <slot
<span v-else>{{ column.label }}</span> v-else-if="col.headerSlot"
</template> :name="col.headerSlot"
<template #default="{ row, $index }"> :column="column"
<template v-if="col.formatDate"> :index="$index"
<span>{{ col.formatDate(row) }}</span> ></slot>
<span v-else>{{ column.label }}</span>
</template> </template>
<!-- 如果传递按钮数组,就展示按钮组 END--> <template #default="{ row, $index }">
<!-- render函数 (START) 使用内置的component组件可以支持h函数渲染和txs语法--> <template v-if="col.formatDate">
<component <span>{{ col.formatDate(row) }}</span>
:is="() => col.render(row, $index)" </template>
v-if="col.render" <!-- 如果传递按钮数组,就展示按钮组 END-->
/> <!-- render函数 (START) 使用内置的component组件可以支持h函数渲染和txs语法-->
<slot <component :is="() => col.render(row, $index)" v-if="col.render" />
v-else-if="col.slot" <slot
:name="col.slot" v-else-if="col.slot"
:row="row" :name="col.slot"
:index="$index" :row="row"
></slot> :index="$index"
<span v-else>{{ row[col.prop!] }}</span> ></slot>
</template> <span v-else>{{ row[col.prop!] }}</span>
</ElTableColumn> </template>
</ElTableColumn>
</template>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { Column } from 'element-plus' import type { Column } from 'element-plus'
......
import { ElMessage } from 'element-plus' import useLodop from '@/utils/hooks/useLodop.ts'
import useLodop, { LODOPObject } from '@/utils/hooks/useLodop.ts' import { printWithLodop } from '@/utils/lodopPrinter'
const { getCLodop } = useLodop() const { getCLodop } = useLodop()
const lodopCall = async (fn: (lodop: LODOPObject) => string) => {
if (!window._lodop) {
window._lodop = getCLodop(null, null)
if (window._lodop) {
window._lodop.On_Return_Remain = true
window._lodop.SET_PRINT_MODE('CATCH_PRINT_STATUS', true)
window._lodopCallback = {}
window._lodop.On_Return = function (id, value) {
const cb = window._lodopCallback[id]
if (!cb) return
delete window._lodopCallback[id]
cb(value)
}
}
}
return new Promise((resolve) => {
let id
if (window._lodop) {
id = fn(window._lodop)
}
window._lodopCallback[id || 0] = resolve
})
}
function downloadPDF(url: string) {
if (!/^https?:/i.test(url)) return url
let xhr,
arrybuffer = false,
dataArray = null
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest()
} else {
xhr = new window.ActiveXObject('MSXML2.XMLHTTP')
}
xhr.open('GET', url, false) //同步方式
if (xhr.overrideMimeType)
try {
xhr.responseType = 'arraybuffer'
arrybuffer = true
} catch (err) {
xhr.overrideMimeType('text/plain; charset=x-user-defined')
}
xhr.send(null)
const data = xhr.response || xhr.responseBody
if (typeof Uint8Array !== 'undefined') {
if (arrybuffer) {
dataArray = new Uint8Array(data)
} else {
dataArray = new Uint8Array(data.length)
for (let i = 0; i < dataArray.length; i++) {
dataArray[i] = data.charCodeAt(i)
}
}
} else {
dataArray = window.VBS_BinaryToArray(data).toArray() //兼容IE低版本
}
const digits =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
let strData = ''
for (let i = 0, ii = dataArray.length; i < ii; i += 3) {
if (isNaN(dataArray[i])) break
const b1 = dataArray[i] & 0xff,
b2 = dataArray[i + 1] & 0xff,
b3 = dataArray[i + 2] & 0xff
const d1 = b1 >> 2,
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.substring(d1, d1 + 1) +
digits.substring(d2, d2 + 1) +
digits.substring(d3, d3 + 1) +
digits.substring(d4, d4 + 1)
}
return strData
}
export const print = async (url: string,printDevice:string) => { export const print = async (url: string,printDevice:string) => {
const lodop = getCLodop(null, null) //调用getLodop获取LODOP对象 await printWithLodop({
if (!lodop) return getCLodop: () => getCLodop(null, null),
lodop.PRINT_INIT('打印内容') printer: printDevice,
// SET_PRINTER_INDEX 指定打印设备 data: { filePath: url },
const setPrintRes = lodop.SET_PRINTER_INDEX(printDevice) callback: (_status: boolean) => {},
if (!setPrintRes) { baseUrl: '',
ElMessage.warning('设置面单打印机出错') })
return
}
lodop.ADD_PRINT_PDF(0, 0, '100%', '100%', downloadPDF(url))
console.log(lodop)
if (lodop.CVERSION) {
const startTime = Date.now()
const jobCode = await lodopCall((lodop: LODOPObject) => {
lodop.SET_PRINT_MODE('CATCH_PRINT_STATUS', true)
return lodop.PRINT()
})
console.log('[LODOP] job ' + jobCode)
// eslint-disable-next-line no-constant-condition
while (true) {
const ok = await lodopCall((lodop: LODOPObject) =>
lodop.GET_VALUE('PRINT_STATUS_OK', jobCode),
)
if (ok == 1) {
// 打印状态:成功
return
}
// 如果打印状态表示未成功或者返回了空,继续获取任务是否存在
const exist = await lodopCall((lodop: LODOPObject) =>
lodop.GET_VALUE('PRINT_STATUS_EXIST', jobCode),
)
console.log(
'[LODOP] PRINT_STATUS OK,EXIST',
jobCode,
ok,
exist,
`(${Date.now() - startTime}ms)`,
)
if (exist == 0) {
// 任务不存在了
return
}
await new Promise((r) => setTimeout(r, 500))
if (Date.now() - startTime >= 30000) {
ElMessage.error('打印超时(30秒)')
return
}
}
} else {
lodop.PRINT()
}
} }
...@@ -78,12 +78,18 @@ const playAudio = (key: AudioKey, message?: string) => { ...@@ -78,12 +78,18 @@ const playAudio = (key: AudioKey, message?: string) => {
console.error(`No audio found for key: ${key}`) console.error(`No audio found for key: ${key}`)
} }
} }
const getOrderFrom = (factorySubOrderNumber: string): string => {
if (/^CN/i.test(factorySubOrderNumber)) return 'CN'
if (/^US/i.test(factorySubOrderNumber)) return 'US'
if (/^OP/i.test(factorySubOrderNumber)) return 'NEWP'
return ''
}
const getPrintData = async () => { const getPrintData = async () => {
if(!searchData.value) return if(!searchData.value) return
const user = JSON.parse(localStorage.getItem('user') || '{}') const orderFrom = getOrderFrom(searchData.value.factorySubOrderNumber || '')
const data = await printProductionQrCode( const data = await printProductionQrCode(
searchData.value.id, searchData.value.id,
user.factory?.countryCode.toLowerCase(), orderFrom,
) )
// const path = 'http://10.168.1.209/local_files/factory' + data.message // const path = 'http://10.168.1.209/local_files/factory' + data.message
const path = filePath + data.message const path = filePath + data.message
......
...@@ -99,6 +99,14 @@ const router = createRouter({ ...@@ -99,6 +99,14 @@ const router = createRouter({
import('@/views/order/podUsSchedulingRules/index.vue'), import('@/views/order/podUsSchedulingRules/index.vue'),
}, },
{ {
path: '/order/factory-order-new',
meta: {
title: '工厂订单(NEW)',
},
component: () =>
import('@/views/order/factoryOrderNew/index.vue'),
},
{
path: '/pod-cn-order/orderTracking', path: '/pod-cn-order/orderTracking',
meta: { meta: {
title: 'POD(CN)订单跟踪', title: 'POD(CN)订单跟踪',
......
...@@ -146,6 +146,11 @@ const menu: MenuItem[] = [ ...@@ -146,6 +146,11 @@ const menu: MenuItem[] = [
id: 11, id: 11,
label: 'POD(US)排单规则', label: 'POD(US)排单规则',
}, },
{
index: '/order/factory-order-new',
id: 12,
label: '工厂订单(NEW)',
},
], ],
}, },
{ {
......
...@@ -20,6 +20,7 @@ const useOrderStore = defineStore('order', { ...@@ -20,6 +20,7 @@ const useOrderStore = defineStore('order', {
}, },
actions: { actions: {
async setPodBoxList(content: { async setPodBoxList(content: {
url: string
boxList: PodMakeOrderData[] | OrderData | null boxList: PodMakeOrderData[] | OrderData | null
factoryNo: number | string | undefined factoryNo: number | string | undefined
fromUser: number fromUser: number
...@@ -27,10 +28,10 @@ const useOrderStore = defineStore('order', { ...@@ -27,10 +28,10 @@ const useOrderStore = defineStore('order', {
box?: number box?: number
data?: OrderData data?: OrderData
}) { }) {
console.log(content,'content') console.log(content, 'content')
const { factoryNo, warehouseId, boxList, box, data,fromUser } = content const { url, factoryNo, warehouseId, boxList, box, data, fromUser } = content
if (Array.isArray(boxList)) { if (Array.isArray(boxList)) {
boxList.forEach(item => { boxList.forEach((item) => {
item.fromUser = fromUser item.fromUser = fromUser
}) })
this.podBoxList = boxList this.podBoxList = boxList
...@@ -38,7 +39,7 @@ const useOrderStore = defineStore('order', { ...@@ -38,7 +39,7 @@ const useOrderStore = defineStore('order', {
const index = this.podBoxList?.findIndex((item) => item.box === box) const index = this.podBoxList?.findIndex((item) => item.box === box)
if (index === -1) { if (index === -1) {
try { try {
const res = await getPodBoxListApi(factoryNo, warehouseId) const res = await getPodBoxListApi(url, factoryNo, warehouseId)
const boxList = res.data.map((item) => { const boxList = res.data.map((item) => {
item.fromUser = fromUser item.fromUser = fromUser
if (res.data) { if (res.data) {
......
export interface StatusTreeNode {
status: string
statusName: string
quantity: number
children: StatusTreeNode[] | null
}
export interface SearchForm {
platform?: string
craftCode?: string | string[]
thirdSkuCode?: string
supplierProductNo?: string
batchArrangeNumber?: string
orderNumber?: string
customerOrderNumber?: string
shopOrderNumber?: string
productMark?: string | number | (string | number)[]
multi?: boolean | null
timeType?: number | null
startTime?: string | null
endTime?: string | null
logisticsWayCode?: string
receiverCountry?: string | string[]
factoryOrderNumber?: string
userMark?: string
sku?: string
trackingNumber?: string
replaceShipment?: number | string
shipmentType?: number | string
tagsIdArr?: string[]
/** list_page 查询:客户标签 id,逗号分隔(由 tagsIdArr 转换) */
tagsId?: string
source?: string
size?: string
logisticsCompanyCode?: string
interceptStatus?: boolean
standardDesignImage?: number
productMarkList?: string[]
podCustomizedQuantity?: string
cpCustomizedQuantity?: string
/** list_page 排序字段,需与 order 同时传递 */
prop?: string
/** list_page 排序方向:asc 正序,desc 倒序 */
order?: 'asc' | 'desc'
}
export interface FactoryOrderNewListData {
id: number
factoryOrderNumber?: string
factoryNo?: number
factoryCode?: string
factoryCountryCode?: string
warehouseId?: number
warehouseName?: string
namespace?: string
status?: string
platform?: string
shopNumber?: string
weight?: number
productAmount?: number
totalAmount?: number
productNum?: number
shipmentType?: number
expressSheet?: string
trackingNumber?: string
processNumber?: string
userMark?: string
receiverName?: string
receiverPhone?: string
receiverCountry?: string
receiverProvince?: string
receiverCity?: string
receiverDistrict?: string
receiverAddress1?: string
receiverAddress2?: string
receiverPostCode?: string
trackStatus?: number
prepaidAmount?: number
pause?: boolean
serviceAmount?: number
mixed?: boolean
tagsId?: string
source?: string
logisticsCompanyName?: string
logisticsCompanyCode?: string
selfOwned?: boolean
createTime?: string
updateTime?: string
version?: number
totalCustomsWeight?: number
totalCustomsValue?: number
statusName?: string
thirdOrderNumber?: string
}
export interface ProductListData {
id: number
podOrderId: number
factoryCode?: string
productName?: string
baseSku?: string
variantSku?: string
variantImage?: string
variant_image?: string
productMark?: string
diyId?: string
templateType?: number
customTemplateItemId?: number
endProductId?: string
craftSource?: number
craftCode?: string
craftName?: string
craftType?: string
customizedQuantity: number
categoryId?: number
categoryName?: string
productPrice?: number
templatePrice?: number
craftPrice?: number
imageAry?: string
designImages?: string
trimDesignImages?: string
sizeType?: number
num?: number
passNum?: number
notPassNum?: number
payAmount?: number
weight?: number
createTime?: string
updateTime?: string
version?: number
remark?: string
}
export interface operateOrderListData {
id: number
operationNo?: string
podOrderId?: number
podOrderNo?: string
podOrderProductId?: number
baseSku?: string
variantSku?: string
variantImage?: string
thirdSpuCode?: string
thirdSkuCode?: string
supplierProductNo?: string
productName?: string
productMark?: string
customizedQuantity?: number
quantity?: number
isReplenishment?: boolean
totalProductionNum?: number
status?: string
outOfStock?: boolean
arrange?: boolean
version?: number
createTime?: string
updateTime?: string
factoryNo?: number
factoryCode?: string
factoryCountryCode?: string
warehouseId?: number
namespace?: string
statusName?: string
[key: string]: unknown
}
export interface LogListData {
id: number
bizId?: number | string
employeeName?: string
description?: string
createTime?: string
}
export interface BatchManageData {
id: number
factoryId?: number
batchArrangeNum?: string
billType?: string
url?: string
tiffUrl?: string
syntheticStatus?: boolean
downloadStatus?: boolean
productNum?: number
materialNum?: number
craftType?: string
employeeAccount?: string
employeeId?: number
createTime?: string
failReason?: string
failTime?: string
automaticComposing?: boolean
composingParam?: string
printProductOrder?: boolean
printPickOrder?: boolean
prnUrl?: string
prnDownloadStatus?: string
enableArrange?: boolean
standardDesignImage?: number
type?: string
}
export interface RestockData {
id: number
factoryId: number
warehouseId: number
warehouseName?: string
locationId?: number
locationCode?: string
warehouseSku?: string
customSku?: string
skuName?: string
unit: string | null
occupyInventory?: number
freezeInventory?: number
inventory?: number
upperLimit?: number
floorLimit?: number
productNo?: string
customerName?: string
userMark?: string
createTime?: string
updateTime?: string
productItem?: string
usableInventory?: number
image?: string
price?: number
costPrice?: number | null
skuImage?: string | null
currencyCode?: string
currencyName?: string
sumOccupyInventory?: number
thirdSkuCode?: string
}
export interface PickCompleteData {
id: number
warehouseId: number
warehouseName?: string
skuImage?: string
productName?: string
supplierProductNo?: string
thirdSkuCode?: string
selectedQuantity?: number
availableInventory?: number
inventory?: number
producingQuantity?: number
occupyInventory?: number
pickingStatus?: string
availableOrderIds?: number[]
allOrderIds: number[]
freezeInventory?: number
}
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
}
export interface ResultInfoDataItem {
id: string | number
status?: boolean
factoryOrderNumber?: string
message?: string
}
export interface StatusTreeNode {
status: string
statusName: string
quantity: number
children: StatusTreeNode[] | null
}
export interface SearchForm {
platform?: string
craftCode?: string | string[]
thirdSkuCode?: string
supplierProductNo?: string
batchArrangeNumber?: string
orderNumber?: string
customerOrderNumber?: string
shopOrderNumber?: string
productMark?: string | number | (string | number)[]
multi?: boolean | null
timeType?: number | null
startTime?: string | null
endTime?: string | null
logisticsWayCode?: string
receiverCountry?: string | string[]
factoryOrderNumber?: string
userMark?: string
sku?: string
trackingNumber?: string
replaceShipment?: number | string
shipmentType?: number | string
tagsIdArr?: string[]
/** list_page 查询:客户标签 id,逗号分隔(由 tagsIdArr 转换) */
tagsId?: string
source?: string
size?: string
logisticsCompanyCode?: string
interceptStatus?: boolean
shopNumber?: string
newStandard?: number
productMarkList?: string[]
/** list_page:POD 商品单面/多面,与 productMarkList 中 pod 联动 */
podCustomizedQuantity?: string
/** list_page:一件定制局部印单面/多面,与 productMarkList 中 custom_part 联动 */
cpCustomizedQuantity?: string
operationNo?: string
/** list_page 排序字段,需与 order 同时传递 */
prop?: string
/** list_page 排序方向:asc 正序,desc 倒序 */
order?: 'asc' | 'desc'
}
export interface FactoryOrderNewListData {
id: number
factoryOrderNumber?: string
factoryNo?: number
factoryCode?: string
factoryCountryCode?: string
warehouseId?: number
warehouseName?: string
namespace?: string
status?: string
platform?: string
shopNumber?: string
weight?: number
productAmount?: number
totalAmount?: number
productNum?: number
shipmentType?: number
replaceShipment?: number
expressSheet?: string
trackingNumber?: string
processNumber?: string
userMark?: string
receiverName?: string
receiverPhone?: string
receiverCountry?: string
receiverProvince?: string
receiverCity?: string
receiverDistrict?: string
receiverAddress1?: string
receiverAddress2?: string
receiverPostCode?: string
rfcNumber?: string
trackStatus?: number
prepaidAmount?: number
pause?: boolean
serviceAmount?: number
mixed?: boolean
tagsId?: string
source?: string
logisticsCompanyName?: string
logisticsCompanyCode?: string
selfOwned?: boolean
createTime?: string
updateTime?: string
version?: number
totalCustomsWeight?: number
totalCustomsValue?: number
statusName?: string
thirdOrderNumber?: string
customTagList?: { id: string; name: string }[]
}
export interface ProductListData {
id: number
podOrderId: number
factoryCode?: string
productName?: string
baseSku?: string
variantSku?: string
variantImage?: string
variant_image?: string
productMark?: string
diyId?: string
templateType?: number
customTemplateItemId?: number
endProductId?: string
craftSource?: number
craftCode?: string
craftName?: string
craftType?: string
customizedQuantity: number
categoryId?: number
categoryName?: string
productPrice?: number
templatePrice?: number
craftPrice?: number
imageAry?: string
designImages?: string
trimDesignImages?: string
sizeType?: number
num?: number
passNum?: number
notPassNum?: number
payAmount?: number
weight?: number
inventory?: number
occupyInventory?: number
freezeInventory?: number
createTime?: string
updateTime?: string
version?: number
remark?: string
}
export interface operateOrderListData {
id: number
operationNo?: string
podOrderId?: number
podOrderNo?: string
podOrderProductId?: number
baseSku?: string
variantSku?: string
variantImage?: string
thirdSpuCode?: string
thirdSkuCode?: string
supplierProductNo?: string
productName?: string
productMark?: string
customizedQuantity?: number
quantity?: number
isReplenishment?: boolean
totalProductionNum?: number
status?: string
outOfStock?: boolean
arrange?: boolean
version?: number
createTime?: string
updateTime?: string
factoryNo?: number
factoryCode?: string
factoryCountryCode?: string
warehouseId?: number
namespace?: string
statusName?: string
[key: string]: unknown
}
export interface ExportParams extends SearchForm {
idList?: number[]
exportAll: boolean
orderTracking?: boolean
}
export interface LogListData {
id: number
bizId?: number | string
employeeName?: string
description?: string
createTime?: string
}
export interface BatchManageData {
id: number
factoryId?: number
batchArrangeNum?: string
billType?: string
url?: string
tiffUrl?: string
syntheticStatus?: boolean
downloadStatus?: boolean
productNum?: number
materialNum?: number
craftType?: string
employeeAccount?: string
employeeId?: number
createTime?: string
failReason?: string
failTime?: string
automaticComposing?: boolean
composingParam?: string
printProductOrder?: boolean
printPickOrder?: boolean
prnUrl?: string
prnDownloadStatus?: string
enableArrange?: boolean
standardDesignImage?: 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
warehouseId?: number
warehouseName?: string
skuImage?: string
productName?: string
supplierProductNo?: string
thirdSkuCode?: string
selectedQuantity?: number
availableInventory?: number
inventory?: number
producingQuantity?: number
occupyInventory?: number
pickingStatus?: string
availableOrderIds?: number[]
allOrderIds: number[]
}
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
}
...@@ -56,6 +56,9 @@ export interface ProductList { ...@@ -56,6 +56,9 @@ export interface ProductList {
podJomallCnNo?: string podJomallCnNo?: string
thirdSkuCode?: string thirdSkuCode?: string
previewImgs?: { sort?: string | number; title?: string; url: string }[] previewImgs?: { sort?: string | number; title?: string; url: string }[]
operationNos?: string
variantSku?: string
productName?: string
} }
export interface LogisticBill { export interface LogisticBill {
......
import type { Column as ElColumn } from 'element-plus/lib/components/index.js' import type { Column as ElColumn } from 'element-plus/lib/components/index.js'
interface Column<D> extends Omit<ElColumn, 'width'> { interface Column<D> extends Omit<ElColumn, 'width'> {
/** 为 false 时隐藏该列;未设置时默认显示 */
showColumn?: boolean
width?: number | string width?: number | string
minWidth?: number | string minWidth?: number | string
key?: string key?: string
......
...@@ -47,7 +47,7 @@ declare global { ...@@ -47,7 +47,7 @@ declare global {
interface Window { interface Window {
LODOP: new () => LODOPObject LODOP: new () => LODOPObject
_lodop: LODOPObject | null _lodop: LODOPObject | null
_lodopCallback: { [key: string]: Function } _lodopCallback: { [key: string]: (value: string) => void }
CLODOP: CLodopObject CLODOP: CLodopObject
getCLodop: () => LODOPObject getCLodop: () => LODOPObject
} }
......
...@@ -26,12 +26,15 @@ export default function usePageList<T>(options: UsePageListOptions<T>) { ...@@ -26,12 +26,15 @@ export default function usePageList<T>(options: UsePageListOptions<T>) {
sumTotalPrice: 0, sumTotalPrice: 0,
sumNotPassNum: 0, sumNotPassNum: 0,
}) })
/** 防止快速切换查询条件时,旧请求晚返回覆盖新数据 */
const loadRequestId = ref(0)
const loadData = async () => { const loadData = async () => {
const { query } = options const { query } = options
const myId = ++loadRequestId.value
try { try {
loading.value = true loading.value = true
// Promise 链式调用
const res = await query(currentPage.value, pageSize.value) const res = await query(currentPage.value, pageSize.value)
if (myId !== loadRequestId.value) return
if (statistics.value) { if (statistics.value) {
totalData.value = res as never totalData.value = res as never
total.value = res.page!.total total.value = res.page!.total
...@@ -45,10 +48,13 @@ export default function usePageList<T>(options: UsePageListOptions<T>) { ...@@ -45,10 +48,13 @@ export default function usePageList<T>(options: UsePageListOptions<T>) {
data.value = res.records data.value = res.records
} }
} catch (error) { } catch (error) {
if (myId !== loadRequestId.value) return
console.error(error) console.error(error)
// showError(error) // showError(error)
} finally { } finally {
loading.value = false if (myId === loadRequestId.value) {
loading.value = false
}
} }
} }
......
import { ElMessage } from 'element-plus'
import type { LODOPObject } from '@/utils/hooks/useLodop'
type LodopGetter = () => LODOPObject | null
export function downloadPDF(url: string): string {
if (!/^https?:/i.test(url)) return url
const xhr = new XMLHttpRequest()
let arrayBuffer = false
xhr.open('GET', url, false)
if (xhr.overrideMimeType) {
try {
xhr.responseType = 'arraybuffer'
arrayBuffer = 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 (arrayBuffer) {
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
}
export function createLodopCaller(getCLodop: LodopGetter) {
let lodop: LODOPObject | null = null
let lodopCallback: { [key: string]: (value: string) => void } = {}
return (fn: (lodop: LODOPObject) => string) => {
if (!lodop) {
lodop = getCLodop()
if (!lodop) return Promise.resolve('')
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<string>((resolve) => {
if (!lodop) {
resolve('')
return
}
const id = fn(lodop)
lodopCallback[id] = resolve
})
}
}
interface PrintData {
filePath?: string
fileData?: string
}
interface PrintWithLodopOptions {
getCLodop: LodopGetter
printer: string
data: PrintData
callback?: (success: boolean) => void
timeout?: number
baseUrl?: string
}
export async function printWithLodop(options: PrintWithLodopOptions) {
const {
getCLodop,
printer,
data,
callback,
timeout = 30000,
baseUrl = '',
} = options
const lodop = getCLodop()
if (!lodop) return
const lodopCall = createLodopCaller(getCLodop)
lodop.PRINT_INIT('打印内容')
const printerIndex = lodop.SET_PRINTER_INDEX(printer)
if (!printerIndex) {
ElMessage.error('打印机设置失败')
callback?.(false)
return
}
if (data.filePath) {
const strURL = /^https?:/i.test(data.filePath)
? data.filePath
: baseUrl + 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((instance) => {
instance.SET_PRINT_MODE('CATCH_PRINT_STATUS', true)
return instance.PRINT()
})
let pending = true
while (pending) {
const ok = await lodopCall((instance) =>
instance.GET_VALUE('PRINT_STATUS_OK', jobCode),
)
if (ok == '1' || ok == '1.0') {
callback?.(true)
return
}
const exist = await lodopCall((instance) =>
instance.GET_VALUE('PRINT_STATUS_EXIST', jobCode),
)
if (exist == '0' || exist == '0.0') {
callback?.(true)
return
}
await new Promise((r) => setTimeout(r, 500))
if (Date.now() - startTime >= timeout) {
ElMessage.error(`打印超时(${Math.floor(timeout / 1000)}秒)`)
callback?.(false)
pending = false
}
}
return
}
lodop.PRINT()
callback?.(false)
}
...@@ -85,9 +85,12 @@ import { ...@@ -85,9 +85,12 @@ import {
} from '@/api/logistics' } from '@/api/logistics'
import { createLogisticsOrdersApi } from '@/api/podCnOrder' import { createLogisticsOrdersApi } from '@/api/podCnOrder'
import { FactoryOrderNewListData } from '@/types/api/factoryOrderNew'
const createLogisticDialogVisible = ref(false) const createLogisticDialogVisible = ref(false)
const props = defineProps<{
newOrderSelection?: FactoryOrderNewListData[],
}>()
const isAutoMatch = ref(false) const isAutoMatch = ref(false)
const logisticCompanyList = ref<ILogisticsCompany[]>([]) const logisticCompanyList = ref<ILogisticsCompany[]>([])
...@@ -129,7 +132,11 @@ const selectLogisticCompany = async (item: ILogisticsCompany) => { ...@@ -129,7 +132,11 @@ const selectLogisticCompany = async (item: ILogisticsCompany) => {
} }
const confirmDialog = async () => { const confirmDialog = async () => {
await createLogisticsOrdersApi(orderIdList.value, logisticsWayId.value) const url =
props.newOrderSelection && props.newOrderSelection.length > 0
? 'factory/podOrder/createLogisticsOrders'
: 'factory/podJomallOrderCn/createLogisticsOrders'
await createLogisticsOrdersApi(url, orderIdList.value, logisticsWayId.value)
.then((res) => { .then((res) => {
emits('show-result', res.data) emits('show-result', res.data)
}) })
......
...@@ -381,6 +381,7 @@ const coverImage = ref<string>('') ...@@ -381,6 +381,7 @@ const coverImage = ref<string>('')
let currentCode = '' let currentCode = ''
const tableRef = ref() const tableRef = ref()
watch(visible, async (value: boolean) => { watch(visible, async (value: boolean) => {
console.log('visible', value)
if (value) { if (value) {
podOrderDetailsData.value = {} podOrderDetailsData.value = {}
currentCode = '' currentCode = ''
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
<div style="margin: 15px 0"></div> <div style="margin: 15px 0"></div>
<el-checkbox-group v-model="selectedList" @change="checkChange"> <el-checkbox-group v-model="selectedList" @change="checkChange">
<div style="display: block" v-for="(item, index) in list" :key="index"> <div style="display: block" v-for="(item, index) in list" :key="index">
<el-checkbox :value="item"> <el-checkbox :value="item" style="user-select: text;">
{{ '工厂订单号:' + item.factoryOrderNumber + ' ' + item.message }} {{ '工厂订单号:' + item.factoryOrderNumber + ' ' + item.message }}
</el-checkbox> </el-checkbox>
</div> </div>
...@@ -53,17 +53,11 @@ ...@@ -53,17 +53,11 @@
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { copyText } from '@/utils/index' import { copyText } from '@/utils/index'
import { ResultInfoDataItem } from '@/types/api/order/common';
interface IList {
id: string | number
shopNumber?: string
factoryOrderNumber?: string
message: string
status: boolean
}
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
list: IList[] list: ResultInfoDataItem[]
}>(), }>(),
{ {
list: () => [], list: () => [],
...@@ -73,7 +67,7 @@ const props = withDefaults( ...@@ -73,7 +67,7 @@ const props = withDefaults(
const resultDialog = ref(false) const resultDialog = ref(false)
const isIndeterminate = ref(false) const isIndeterminate = ref(false)
const checkAll = ref(false) const checkAll = ref(false)
const selectedList = ref<IList[]>([]) const selectedList = ref<ResultInfoDataItem[]>([])
let key = '' let key = ''
// 显示弹窗 // 显示弹窗
const showDialog = (type?: string) => { const showDialog = (type?: string) => {
...@@ -161,7 +155,7 @@ defineExpose({ ...@@ -161,7 +155,7 @@ defineExpose({
showDialog, showDialog,
}) })
const emits = defineEmits<{ const emits = defineEmits<{
(e: 'confirm', data: IList[]): void (e: 'confirm', data: ResultInfoDataItem[]): void
}>() }>()
</script> </script>
......
...@@ -57,6 +57,7 @@ ...@@ -57,6 +57,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { updateCustomDeclarationInfoApi } from '@/api/podCnOrder' import { updateCustomDeclarationInfoApi } from '@/api/podCnOrder'
import { FactoryOrderNewListData } from '@/types/api/factoryOrderNew'
import { import {
CustomDeclarationInfoForm, CustomDeclarationInfoForm,
PodCnOrderListData, PodCnOrderListData,
...@@ -67,7 +68,8 @@ defineOptions({ ...@@ -67,7 +68,8 @@ defineOptions({
}) })
const props = defineProps<{ const props = defineProps<{
modelValue: boolean modelValue: boolean
orderSelection: PodCnOrderListData[] orderSelection: FactoryOrderNewListData[] | PodCnOrderListData[]
isNewOrder?: boolean
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void (e: 'update:modelValue', value: boolean): void
...@@ -101,8 +103,16 @@ const closeDialog = async () => { ...@@ -101,8 +103,16 @@ const closeDialog = async () => {
visible.value = false visible.value = false
} }
const submit = async () => { const submit = async () => {
const url = props.isNewOrder
? 'factory/podOrder/batchUpdateCustomsClearanceInfo'
: 'factory/podJomallOrderCn/batchUpdateCustomsClearanceInfo'
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try { try {
const res = await updateCustomDeclarationInfoApi({ const res = await updateCustomDeclarationInfoApi(url, {
params: form.value, params: form.value,
ids: props.orderSelection.map((item) => item.id).join(','), ids: props.orderSelection.map((item) => item.id).join(','),
}) })
...@@ -112,6 +122,8 @@ const submit = async () => { ...@@ -112,6 +122,8 @@ const submit = async () => {
emit('refreshTable') emit('refreshTable')
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} finally {
loading.close()
} }
} }
defineExpose({ defineExpose({
...@@ -119,7 +131,7 @@ defineExpose({ ...@@ -119,7 +131,7 @@ defineExpose({
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.dialog-footer { .dialog-footer {
text-align: center; text-align: center;
} }
</style> </style>
...@@ -22,7 +22,16 @@ ...@@ -22,7 +22,16 @@
placeholder="" placeholder=""
> >
<el-option label="跟踪号" value="trackingNumber"></el-option> <el-option label="跟踪号" value="trackingNumber"></el-option>
<el-option label="交运单号" value="shopNumber"></el-option> <el-option
v-if="!isNewOrder"
label="交运单号"
value="shopNumber"
></el-option>
<el-option
v-if="isNewOrder"
label="客户交运单号"
value="shopNumber"
></el-option>
</el-select> </el-select>
<input <input
ref="weighInput" ref="weighInput"
...@@ -33,7 +42,7 @@ ...@@ -33,7 +42,7 @@
weight.weightInput weight.weightInput
? selectType === 'trackingNumber' ? selectType === 'trackingNumber'
? '请输入跟踪号' ? '请输入跟踪号'
: '请输入交运单号' : isNewOrder ? '请输入客户交运单号' : '请输入交运单号'
: '请输入重量' : '请输入重量'
" "
@keyup.enter="weightChange" @keyup.enter="weightChange"
...@@ -110,10 +119,11 @@ interface ILogisticsList { ...@@ -110,10 +119,11 @@ interface ILogisticsList {
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import weight from '../components/weigh.js' import weight from '../components/weigh.js'
import { import {
listByNoApi, listByNoApi as defaultListByNoApi,
orderWeighingApi, orderWeighingApi as defaultOrderWeighingApi,
allErpCodeListApi, allErpCodeListApi,
} from '@/api/podCnOrder' } from '@/api/podCnOrder'
import type { BaseRespData } from '@/types/api'
// import { logisticsCompanyAllCodelist } from '@/api/logistics.ts' // import { logisticsCompanyAllCodelist } from '@/api/logistics.ts'
import CustomizeTable from '@/components/VxeTable.tsx' import CustomizeTable from '@/components/VxeTable.tsx'
import { TableColumn } from '@/components/VxeTable' import { TableColumn } from '@/components/VxeTable'
...@@ -196,6 +206,41 @@ const tableConfig = ref<TableColumn[]>([ ...@@ -196,6 +206,41 @@ const tableConfig = ref<TableColumn[]>([
}, },
}, },
]) ])
const props = withDefaults(
defineProps<{
/** 默认 podCN:`factory/podJomallOrderCn/listByNo` */
listByNoApi?: typeof defaultListByNoApi
/** 默认 podCN:`factory/podJomallOrderCn/orderWeighing` */
orderWeighingApi?: (payload: unknown) => Promise<BaseRespData<never>>
/**
* 提交称重时的请求体构造;默认 `{ podCnWeighingParams: rows }`,
* 若后端字段与 podCN 分叉时可单独传入。
*/
buildOrderWeighingPayload?: (rows: IpodCnWeighingParams[]) =>
| {
podCnWeighingParams: { id?: string; outWarehouseWeight?: string }[]
}
| {
podOrderWeighingParams: { id?: string; outWarehouseWeight?: string }[]
}
/** 是否是新订单 */
isNewOrder?: boolean
}>(),
{
isNewOrder: false,
},
)
const resolveListByNoApi = () => props.listByNoApi ?? defaultListByNoApi
const resolveOrderWeighingApi = () =>
(props.orderWeighingApi ?? defaultOrderWeighingApi) as (
payload: unknown,
) => Promise<BaseRespData<never>>
const resolveWeighingPayload = (rows: IpodCnWeighingParams[]) =>
(props.buildOrderWeighingPayload?.(rows) ?? {
podCnWeighingParams: rows,
}) as unknown
const emits = defineEmits<{ const emits = defineEmits<{
(e: 'updateList'): void (e: 'updateList'): void
}>() }>()
...@@ -259,7 +304,7 @@ const weightChange = async () => { ...@@ -259,7 +304,7 @@ const weightChange = async () => {
params.logisticsCompanyCode = logisticsCompanyCode.value params.logisticsCompanyCode = logisticsCompanyCode.value
weight.check( weight.check(
noValue, noValue,
listByNoApi, resolveListByNoApi(),
params, params,
(arr) => { (arr) => {
tableData.value = [...arr] tableData.value = [...arr]
...@@ -302,18 +347,20 @@ const weightGet = async () => { ...@@ -302,18 +347,20 @@ const weightGet = async () => {
background: 'rgba(0, 0, 0, 0.3)', background: 'rgba(0, 0, 0, 0.3)',
}) })
try { try {
orderWeighingApi({ podCnWeighingParams: tableData.value }).then((res) => { resolveOrderWeighingApi()(resolveWeighingPayload(tableData.value)).then(
if (res.code === 200) { (res) => {
weight.clear() if (res.code === 200) {
ElMessage.success('保存称重分拣成功') weight.clear()
handleClose() ElMessage.success('保存称重分拣成功')
emits('updateList') handleClose()
} else { emits('updateList')
ElMessage.error(res.message) } else {
handleClose() ElMessage.error(res.message)
emits('updateList') handleClose()
} emits('updateList')
}) }
},
)
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} finally { } finally {
...@@ -323,6 +370,7 @@ const weightGet = async () => { ...@@ -323,6 +370,7 @@ const weightGet = async () => {
const open = () => { const open = () => {
isweight.value = true isweight.value = true
weight.stashToCache()
tableData.value = [] tableData.value = []
selectType.value = 'trackingNumber' selectType.value = 'trackingNumber'
logisticsCompanyCode.value = '' logisticsCompanyCode.value = ''
......
<script setup lang="ts"> <script setup lang="ts">
import { batchCheckPrintPodCn, batchCheckPrintPodUs } from '@/api/podCnOrder.ts' import { batchCheckPrintPodCn, batchCheckPrintPodUs } from '@/api/podCnOrder.ts'
import { batchCheckPrintPodOrder } from '@/api/factoryOrderNew.ts'
import { InterWarehousePage } from '@/types/api/warehouse.ts' import { InterWarehousePage } from '@/types/api/warehouse.ts'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { factoryWarehouseInventoryPrint } from '@/api/warehouse.ts' import { factoryWarehouseInventoryPrint } from '@/api/warehouse.ts'
...@@ -9,14 +10,16 @@ import { PrintData } from '@/types/api/podOrder.ts' ...@@ -9,14 +10,16 @@ import { PrintData } from '@/types/api/podOrder.ts'
const showPrintDialog = ref(false) const showPrintDialog = ref(false)
const printData = ref<PrintData[]>([]) const printData = ref<PrintData[]>([])
const open = async (type: number,ids:string) => { const apiMap: Record<number, (ids: string) => Promise<{ data: PrintData[] }>> = {
let res 1: batchCheckPrintPodCn,
2: batchCheckPrintPodUs,
3: batchCheckPrintPodOrder,
}
const open = async (type: number, ids: string) => {
printData.value = [] printData.value = []
if (type === 1) { const api = apiMap[type] ?? batchCheckPrintPodCn
res = await batchCheckPrintPodCn(ids) const res = await api(ids)
}else{
res= await batchCheckPrintPodUs(ids)
}
showPrintDialog.value = true showPrintDialog.value = true
printData.value = res.data printData.value = res.data
} }
......
...@@ -55,6 +55,7 @@ type AudioFiles = Record<AudioKey, string> ...@@ -55,6 +55,7 @@ type AudioFiles = Record<AudioKey, string>
class Weigh extends Lock { class Weigh extends Lock {
public weightInput: boolean public weightInput: boolean
public list: WeighItem[] public list: WeighItem[]
public cachedList: WeighItem[]
public selectType: string public selectType: string
private audios: AudioFiles private audios: AudioFiles
private audioElements: Map<AudioKey, HTMLAudioElement> private audioElements: Map<AudioKey, HTMLAudioElement>
...@@ -64,6 +65,7 @@ class Weigh extends Lock { ...@@ -64,6 +65,7 @@ class Weigh extends Lock {
this.weightInput = true this.weightInput = true
this.selectType = 'trackingNumber' this.selectType = 'trackingNumber'
this.list = [] this.list = []
this.cachedList = []
this.audios = { this.audios = {
weight_warning: new URL( weight_warning: new URL(
'@/assets/audio/weight_warning.mp3', '@/assets/audio/weight_warning.mp3',
...@@ -102,22 +104,32 @@ class Weigh extends Lock { ...@@ -102,22 +104,32 @@ class Weigh extends Lock {
clear(): void { clear(): void {
this.list = [] this.list = []
this.cachedList = []
this.weightInput = true this.weightInput = true
this.selectType = 'trackingNumber' this.selectType = 'trackingNumber'
} }
stashToCache(): void {
if (this.list.length) {
this.cachedList = [...this.list]
}
this.list = []
this.weightInput = true
}
updatedList(data: WeighItem[]) { updatedList(data: WeighItem[]) {
this.weightInput = true this.weightInput = true
this.list = [...data] this.list = [...data]
} }
// 去重逻辑优化
private deduplicate( private deduplicate(
value: string, value: string,
callback?: (list: WeighItem[]) => void, callback?: (list: WeighItem[]) => void,
): boolean { ): boolean {
const existingIndex = this.list.findIndex( const existingIndex = this.list.findIndex(
(item) => item.trackingNumber === value, (item) =>
item.trackingNumber === value ||
(this.selectType === 'shopNumber' && item.shopNumber === value),
) )
if (existingIndex !== -1) { if (existingIndex !== -1) {
...@@ -233,12 +245,23 @@ class Weigh extends Lock { ...@@ -233,12 +245,23 @@ class Weigh extends Lock {
this.playAudio('weight_warning', '请录入重量') this.playAudio('weight_warning', '请录入重量')
return return
} }
// 去重检查
if (this.deduplicate(value, callback)) { if (this.deduplicate(value, callback)) {
return return
} }
const cachedItem = this.cachedList.find(
(item) =>
(this.selectType === 'trackingNumber' && item.trackingNumber === value) ||
(this.selectType === 'shopNumber' && item.shopNumber === value),
)
if (cachedItem) {
this.list = [cachedItem, ...this.list]
this.weightInput = !!cachedItem.outWarehouseWeight
callback?.(this.list)
this.playAudio('weight_search_success')
return
}
try { try {
const response = await apiCall(params) const response = await apiCall(params)
console.log(211, response) console.log(211, response)
...@@ -250,20 +273,6 @@ class Weigh extends Lock { ...@@ -250,20 +273,6 @@ class Weigh extends Lock {
return return
} }
// const waitWeighingList = data.filter(
// (el) => el.status === 'WAIT_WEIGHING',
// )
// if (waitWeighingList.length === 0) {
// this.playAudio(
// 'weight_search_error',
// `必须是待称重状态的订单下的${
// this.selectType === 'trackingNumber' ? '跟踪号' : '店铺单号'
// }才能使用`,
// )
// return
// }
if (this.list?.length) { if (this.list?.length) {
const firstLogisticsCode = data[0]?.logisticsCompanyCode const firstLogisticsCode = data[0]?.logisticsCompanyCode
......
<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.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" @click="handleSubmit"> 确定 </ElButton>
</div>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BaseRespData } from '@/types/api'
type OpenPayload = {
productIdList?: number[]
title?: string
showAutoSwitch?: boolean
id?: number
}
const emit = defineEmits<{
success: []
}>()
const props = defineProps<{
submitApi: (params: {
productIdList?: number[]
templateWidth?: number
type?: string
}) => Promise<BaseRespData<never>>
}>()
const visible = 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 } = form.value
const mustFill = isAuto.value || !showAutoSwitch.value
if (mustFill && !templateWidth) {
return ElMessage.warning('请选择排版宽度')
}
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await props.submitApi({
productIdList: payload.value.productIdList,
templateWidth: isAuto.value ? templateWidth : undefined,
type: isAuto.value ? 'png' : undefined,
})
if (res.code !== 200) return
ElMessage.success(res.message)
visible.value = false
emit('success')
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
defineExpose({ open })
</script>
<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%"
clearable
>
<ElOption
v-for="item in cancelReasons"
:key="item"
:label="item"
:value="item"
/>
</ElSelect>
</ElFormItem>
</ElForm>
<template #footer>
<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 } 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: unknown) {
console.error(e)
} finally {
submitLoading.value = false
}
}
defineExpose({ open })
</script>
<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>
<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 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: [
data: {
factoryOrderNumber?: string
message?: string
id: number | string
status?: boolean
}[],
]
}>()
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()
const warehouseName = warehouseList.value.find(
(item) => item.id === form.warehouseId,
)?.name
submitLoading.value = true
try {
const res = await confirmOrderWithWarehouseApi(
orderIds.value,
form.warehouseId,
warehouseName || '',
)
if (res.code !== 200) return
visible.value = false
emit('success', res.data)
} catch (e) {
console.error(e)
} finally {
submitLoading.value = false
}
}
defineExpose({ open })
</script>
<template>
<ElDialog
v-model="visible"
:title="dialogTitle"
width="1400px"
:close-on-click-modal="false"
@close="handleClose"
>
<div class="pick-fail-info">
<span>
您选择了
<strong>{{ orderIds.length }}</strong>
件操作单,如拣胚失败,库位没有实物库存,
<strong style="color: #f56c6c"> 建议创建出库单 </strong>
,详细库存信息如下:
</span>
</div>
<div class="table-view">
<TableView
:paginated-data="tableData"
:columns="columns"
selectionable
serial-numberable
@selection-change="handleSelectionChange"
>
<template #skuImage="{ 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>
<template #producingQuantity="{ row }">
<span style="color: #e6a23c; font-weight: bold">{{
row.producingQuantity
}}</span>
</template>
</TableView>
</div>
<template #footer>
<div class="dialog-footer" style="text-align: center">
<span class="item">
<ElButton @click="visible = false">取消</ElButton>
</span>
<span class="item">
<ElButton type="primary" @click="handleCreateOutbound">
快速创建出库单
</ElButton>
</span>
</div>
</template>
</ElDialog>
<CreateOutboundDialog
ref="createOutboundDialogRef"
@success="
() => {
visible = false
emit('success')
}
"
/>
</template>
<script setup lang="tsx">
import { ref } from 'vue'
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'
import BigNumber from 'bignumber.js'
import { pickCompleteByIdsDataApi } from '@/api/factoryOrderNew'
import type { PickCompleteData } from '@/types/api/factoryOrderNew'
import TableView from '@/components/TableView.vue'
import _ from 'lodash'
import CreateOutboundDialog from './CreateOutboundDialog.vue'
/** 当前可用库存 = 当前库存数量 − 占用 − 冻结 */
function getCurrentAvailableInventory(row: PickCompleteData): BigNumber {
return new BigNumber(row.inventory ?? 0)
.minus(row.occupyInventory ?? 0)
.minus(row.freezeInventory ?? 0)
}
/** 建议出库数量 = 当前库存 − 生产中数量 */
function getSuggestOutQuantity(row: PickCompleteData): BigNumber {
return new BigNumber(row.inventory ?? 0).minus(row.producingQuantity ?? 0)
}
/** 出库后库存数量 = 当前库存 − 建议出库数量 */
function getAfterOutStock(row: PickCompleteData): BigNumber {
return new BigNumber(row.inventory ?? 0).minus(getSuggestOutQuantity(row))
}
/** 出库后可用库存 = 出库后库存 − 占用 − 冻结 */
function getAfterOutAvailable(row: PickCompleteData): BigNumber {
return getAfterOutStock(row)
.minus(row.occupyInventory ?? 0)
.minus(row.freezeInventory ?? 0)
}
const emit = defineEmits<{
success: []
}>()
const visible = ref(false)
const tableData = ref<PickCompleteData[]>([])
const orderIds = ref<(number | string)[]>([])
const dialogTitle = ref('拣胚失败')
const selections = ref<PickCompleteData[]>([])
const columns = [
{
key: 'warehouseName',
prop: 'warehouseName',
label: '仓库名称',
minWidth: 120,
},
{
key: 'skuImage',
label: 'SKU图片',
width: 100,
align: 'center',
slot: 'skuImage',
},
{
key: 'productName',
prop: 'productName',
label: '商品名称',
minWidth: 160,
showOverflowTooltip: true,
},
{
prop: 'supplierProductNo',
label: '款号',
width: 140,
align: 'center',
},
{
key: 'thirdSkuCode',
prop: 'thirdSkuCode',
label: '库存SKU',
minWidth: 160,
},
{
prop: 'inventory',
label: '当前库存数量',
minWidth: 120,
align: 'right',
},
{
prop: 'occupyInventory',
label: '占用数量',
minWidth: 90,
align: 'right',
},
{
key: 'currentAvailableInventory',
prop: 'currentAvailableInventory',
label: '当前可用库存',
minWidth: 120,
align: 'right',
render: (row: PickCompleteData) => {
return <span>{getCurrentAvailableInventory(row).toNumber()}</span>
},
},
{
prop: 'producingQuantity',
label: '生产中数量',
minWidth: 110,
align: 'right',
slot: 'producingQuantity',
},
{
key: 'suggestOutQuantity',
prop: 'suggestOutQuantity',
label: '建议出库数量',
minWidth: 120,
align: 'right',
render: (row: PickCompleteData) => {
return (
<span style="color: #f56c6c; font-weight: bold">
{getSuggestOutQuantity(row).toNumber()}
</span>
)
},
},
{
key: 'afterOutStock',
prop: 'afterOutStock',
label: '出库后库存数量',
minWidth: 130,
align: 'right',
render: (row: PickCompleteData) => {
return (
<span style="color: #e6a23c; font-weight: bold">
{getAfterOutStock(row).toNumber()}
</span>
)
},
},
{
prop: 'occupyInventory',
label: '出库后占用数量',
minWidth: 130,
align: 'right',
},
{
prop: 'afterOutAvailable',
label: '出库后可用库存',
minWidth: 130,
align: 'right',
render: (row: PickCompleteData) => {
return (
<span style="color: #f56c6c; font-weight: bold">
{getAfterOutAvailable(row).toNumber()}
</span>
)
},
},
]
const handleSelectionChange = (selection: PickCompleteData[]) => {
selections.value = selection
}
const open = async (
ids: (number | string)[],
options?: { title?: string; submitType?: string },
) => {
dialogTitle.value = options?.title || '拣胚失败'
orderIds.value = ids
visible.value = true
const loading = ElLoading.service({
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await pickCompleteByIdsDataApi(ids)
tableData.value = res.data?.pickingSituationList || []
} catch (_e) {
tableData.value = []
} finally {
loading.close()
}
}
const handleClose = () => {
tableData.value = []
}
const createOutboundDialogRef = ref<InstanceType<
typeof CreateOutboundDialog
> | null>(null)
const handleCreateOutbound = () => {
if (selections.value.length === 0) {
ElMessage.warning('请至少选择一条数据')
return
}
const warehouseIds = _.uniq(
selections.value
.map((item) => item.warehouseId)
.filter((id) => id !== undefined && id !== null),
)
if (warehouseIds.length !== 1) {
ElMessage.warning('请选择相同仓库的库存SKU!')
return
}
const warehouseId = warehouseIds[0] as number | string
const firstSelection = selections.value[0] as PickCompleteData & {
warehouseName?: string
}
const warehouseName = firstSelection.warehouseName
const items = selections.value
.map((item) => {
const row = item as PickCompleteData & {
thirdSkuCode?: string
inventory?: number
producingQuantity?: number
}
return {
thirdSkuCode: row.thirdSkuCode || '',
suggestOutQuantity: getSuggestOutQuantity(row).toNumber(),
}
})
.filter((item) => item.suggestOutQuantity !== 0)
if (items.length === 0) {
ElMessageBox.alert('建议出库数量=0,无法快速创建出库单!', '提示', {
type: 'warning',
})
return
}
createOutboundDialogRef.value?.open({
warehouseId,
warehouseName,
items,
})
}
defineExpose({ open })
</script>
<style scoped lang="scss">
.pick-fail-info {
font-size: 14px;
color: #606266;
line-height: 1.8;
}
.table-view {
height: 600px;
}
</style>
<template>
<ElPopover
placement="bottom-start"
:width="420"
trigger="click"
:teleported="false"
popper-class="product-type-filter-popper"
>
<div class="ptf-panel">
<div v-for="group in options" :key="String(group.value)" class="ptf-group">
<div class="ptf-group-header">
<div class="ptf-group-title">{{ group.label }}</div>
<div class="ptf-group-actions">
<ElCheckbox
v-if="isMulti"
:model-value="isGroupAllChecked(group)"
:indeterminate="isGroupIndeterminate(group)"
@change="toggleGroupAll(group)"
/>
<template v-if="isMulti">
<span class="ptf-select-all" @click="toggleGroupAll(group)">全选</span>
</template>
<template v-else>
<ElCheckbox
:model-value="isGroupAllChecked(group)"
:indeterminate="isGroupIndeterminate(group)"
@change="toggleGroupAll(group)"
/>
<span class="ptf-select-all" @click="toggleGroupAll(group)">全选</span>
</template>
</div>
</div>
<div class="ptf-items">
<div
v-for="item in group.children"
:key="String(item.value)"
class="ptf-item"
:class="{ active: isSelected(item.value) }"
@click="handleItemClick(item.value)"
>
<ElCheckbox
:model-value="isSelected(item.value)"
@change="() => handleItemClick(item.value)"
@click.stop
/>
<span class="ptf-item-label">{{ item.label }}</span>
</div>
</div>
</div>
</div>
<template #reference>
<ElInput
class="ptf-trigger"
:model-value="displayText"
:placeholder="placeholder"
readonly
clearable
:style="{ width: width }"
@clear="clearValue"
>
<template #suffix>
<el-icon class="ptf-arrow"><ArrowDown /></el-icon>
</template>
</ElInput>
</template>
</ElPopover>
</template>
<script setup lang="ts">
/** 选项 value 与接口查询字段的对应关系见 `../utils/productMarkQuery.ts`(productMarkList / podCustomizedQuantity / cpCustomizedQuantity)。 */
import { computed } from 'vue'
import { ArrowDown } from '@element-plus/icons-vue'
import type { ProductTypeGroup, ProductTypeValue } from './productTypeFilterTypes'
type Model = ProductTypeValue | ProductTypeValue[] | null | undefined
const props = withDefaults(
defineProps<{
modelValue: Model
options: ProductTypeGroup[]
multiple?: boolean
placeholder?: string
width?: string
}>(),
{
multiple: false,
placeholder: '请选择',
},
)
const emit = defineEmits<{
(e: 'update:modelValue', v: Model): void
}>()
const isMulti = computed(() => props.multiple || Array.isArray(props.modelValue))
const valueArray = computed<ProductTypeValue[]>(() => {
if (!isMulti.value) return []
const v = props.modelValue
return Array.isArray(v) ? v : v == null ? [] : [v]
})
const labelMap = computed(() => {
const map = new Map<ProductTypeValue, string>()
for (const g of props.options) {
map.set(g.value, g.label)
for (const c of g.children) {
map.set(c.value, c.label)
}
}
return map
})
const displayText = computed(() => {
const v = props.modelValue
if (isMulti.value) {
const arr = valueArray.value
if (!arr.length) return ''
return arr.map((x) => labelMap.value.get(x) ?? String(x)).join(',')
}
if (v == null || Array.isArray(v)) return ''
return labelMap.value.get(v) ?? String(v)
})
const isSelected = (v: ProductTypeValue) => {
if (isMulti.value) return valueArray.value.includes(v)
const cur = props.modelValue
return !Array.isArray(cur) && cur != null && cur === v
}
const selectSingle = (v: ProductTypeValue) => {
emit('update:modelValue', v)
}
const handleItemClick = (v: ProductTypeValue) => {
if (!isMulti.value) {
selectSingle(v)
return
}
const next = new Set(valueArray.value)
if (next.has(v)) next.delete(v)
else next.add(v)
emit('update:modelValue', Array.from(next))
}
const isGroupAllChecked = (group: ProductTypeGroup) => {
if (!isMulti.value) return false
const set = new Set(valueArray.value)
return group.children.length > 0 && group.children.every((c) => set.has(c.value))
}
const isGroupIndeterminate = (group: ProductTypeGroup) => {
if (!isMulti.value) return false
const set = new Set(valueArray.value)
const checkedCount = group.children.filter((c) => set.has(c.value)).length
return checkedCount > 0 && checkedCount < group.children.length
}
const toggleGroupAll = (group: ProductTypeGroup) => {
if (!group.children?.length) return
const nextAll = group.children.map((c) => c.value)
if (!isMulti.value) {
emit('update:modelValue', nextAll)
return
}
const set = new Set(valueArray.value)
const allChecked = isGroupAllChecked(group)
for (const c of group.children) {
if (allChecked) set.delete(c.value)
else set.add(c.value)
}
emit('update:modelValue', Array.from(set))
}
const clearValue = () => {
emit('update:modelValue', isMulti.value ? [] : null)
}
</script>
<style scoped lang="scss">
.ptf-panel {
padding: 6px 0;
max-height: 360px;
overflow: auto;
:deep(.el-checkbox) {
margin-right: 2px !important;
}
}
.ptf-group {
border: 1px solid #e5e6eb;
border-top: 0;
}
.ptf-group:first-child {
border-top: 1px solid #e5e6eb;
}
.ptf-group-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
background: #fff;
}
.ptf-group-title {
font-size: 14px;
font-weight: 600;
color: #1f2329;
}
.ptf-group-actions {
display: inline-flex;
align-items: center;
gap: 10px;
color: #1f2329;
user-select: none;
}
.ptf-select-all {
cursor: pointer;
font-size: 13px;
}
.ptf-items {
background: #f5f6f7;
padding: 6px 0;
}
.ptf-item {
display: inline-flex;
align-items: center;
gap: 16px;
padding: 0px 12px;
box-sizing: border-box;
cursor: pointer;
}
.ptf-item-label {
font-size: 14px;
color: #1f2329;
}
.ptf-item.active .ptf-item-label {
color: #409eff;
}
.ptf-trigger :deep(.el-input__wrapper) {
cursor: pointer;
}
.ptf-arrow {
color: #909399;
}
</style>
<style lang="scss">
.product-type-filter-popper {
padding: 0 !important;
}
</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="140px">
<ElFormItem label="挂起原因" prop="pauseReason">
<ElSelect
v-model="form.pauseReason"
placeholder="请选择挂起原因"
style="width: 100%"
clearable
>
<ElOption
v-for="item in suspendReasons"
:key="item"
:label="item.label"
:value="item.value"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="是否需要客户处理" prop="pauseType">
<ElSelect
v-model="form.pauseType"
placeholder="请选择"
style="width: 100%"
clearable
>
<ElOption
v-for="item in customerHandleOptions"
:key="item"
:label="item.label"
:value="item.value"
/>
</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 } 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 = [
{ label: '客户拦截', value: 1 },
{ label: '地址异常', value: 2 },
{ label: '素材异常', value: 3 },
{ label: '其他', value: 4 },
]
const customerHandleOptions = [
{ label: '需要客户处理', value: 'CUSTOMER' },
{ label: '无需客户处理', value: 'FACTORY' },
]
const form = reactive({
pauseReason: '',
pauseType: '',
})
const rules: FormRules = {
pauseReason: [{ required: true, message: '请选择挂起原因', trigger: 'change' }],
pauseType: [
{ required: true, message: '请选择是否需要客户处理', trigger: 'change' },
],
}
const open = (ids: (number | string)[]) => {
orderIds.value = ids
form.pauseReason = ''
form.pauseType = ''
visible.value = true
}
const handleClose = () => {
formRef.value?.resetFields()
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate()
submitLoading.value = true
try {
const res = await suspendOrderApi(orderIds.value, form.pauseReason, form.pauseType)
if (res.code !== 200) return
ElMessage.success('挂起订单成功')
visible.value = false
emit('success')
} catch (e) {
console.error(e)
} finally {
submitLoading.value = false
}
}
defineExpose({ open })
</script>
<template>
<div class="waiting-restock">
<div class="restock-filter">
<ElForm
:inline="true"
:model="filterForm"
size="default"
class="search-form"
>
<ElFormItem label="库存SKU">
<ElInput
v-model="filterForm.warehouseSku"
placeholder="库存SKU"
clearable
style="width: 150px"
/>
</ElFormItem>
<ElFormItem label="款号">
<ElInput
v-model="filterForm.productNo"
placeholder="款号"
clearable
style="width: 150px"
/>
</ElFormItem>
<ElFormItem>
<ElButton type="primary" @click="handleSearch">查询</ElButton>
<ElButton @click="handleReset">重置</ElButton>
</ElFormItem>
</ElForm>
</div>
<div v-loading="loading" class="restock-table">
<TableView :paginated-data="data" :columns="columns" serial-numberable>
<template #operation="{ row }">
<ElButton
type="primary"
link
size="small"
@click="handleRestockCheck(row)"
>
补货校验
</ElButton>
</template>
</TableView>
</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: 10px auto 0"
@size-change="onPageSizeChange"
@current-change="onCurrentPageChange"
/>
</div>
</template>
<script setup lang="tsx">
import { reactive } from 'vue'
import { ElMessage } from 'element-plus'
import { getRestockListApi, restockCheckApi } from '@/api/factoryOrderNew'
import type { RestockData } from '@/types/api/factoryOrderNew'
import TableView from '@/components/TableView.vue'
import usePageList from '@/utils/hooks/usePageList'
const filterForm = reactive({
warehouseSku: '',
productNo: '',
})
const {
loading,
currentPage,
pageSize,
total,
data,
onCurrentPageChange,
onPageSizeChange,
refresh,
} = usePageList<RestockData>({
initPageSize: 50,
query: async (current, size) => {
const res = await getRestockListApi(
{
warehouseSku: filterForm.warehouseSku || undefined,
productNo: filterForm.productNo || undefined,
},
current,
size,
)
return res.data
},
})
const columns = [
{
prop: 'warehouseName',
label: '仓库名称',
width: 160,
},
{
label: 'SKU图片',
width: 100,
align: 'center',
render: (row: RestockData) => {
return (
<el-image
src={row.image ?? ''}
style={{ width: '50px', height: '50px' }}
previewSrcList={[row.image ?? '']}
previewTeleported
/>
)
},
},
{
prop: 'skuName',
label: '商品名称',
minWidth: 140,
showOverflowTooltip: true,
},
{ prop: 'productNo', label: '款号', width: 130, align: 'center' },
{
prop: 'warehouseSku',
label: '库存SKU',
width: 200,
align: 'center',
},
{
prop: 'usableInventory',
label: '缺货数量',
width: 90,
align: 'right',
render: (row: RestockData) => {
const v = row.usableInventory ?? 0
return (
<span style="color: #f56c6c; font-weight: bold">{Math.abs(v)}</span>
)
},
},
{
label: '可用数量',
prop: 'usableInventory',
width: 90,
align: 'right',
},
{
prop: 'inventory',
label: '库存数量',
width: 90,
align: 'right',
},
{
prop: 'occupyInventory',
label: '占用数量',
width: 90,
align: 'right',
},
{
key: 'operation',
label: '操作',
width: 120,
align: 'center',
fixed: 'right',
slot: 'operation',
},
]
const handleSearch = () => {
refresh()
}
const handleReset = () => {
filterForm.warehouseSku = ''
filterForm.productNo = ''
refresh()
}
const handleRestockCheck = async (row: RestockData) => {
try {
await ElMessageBox.confirm('确定补货校验吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
const res = await restockCheckApi(
row.id,
row.warehouseSku ?? '',
row.warehouseId,
)
if (res.code !== 200) return
ElMessage.success('补货校验成功')
refresh()
} catch (e) {
console.error(e)
}
}
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;
}
.search-form {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0;
min-width: 0;
:deep(.el-form-item) {
margin-right: 10px;
margin-bottom: 10px;
}
:deep(.el-form-item__content) {
min-width: 0;
}
}
</style>
export type ProductTypeValue = string | number
export interface ProductTypeItem {
label: string
value: ProductTypeValue
}
export interface ProductTypeGroup {
label: string
/** 单选时允许直接选择分组本身 */
value: ProductTypeValue
children: ProductTypeItem[]
}
import { ElLoading, ElMessage, ElMessageBox } from 'element-plus'
import type { BaseRespData } from '@/types/api'
interface BatchActionOptions<T = unknown> {
getIds: () => (number | string)[]
api: (ids: (number | string)[]) => Promise<BaseRespData<T>>
confirmText?: string
successText?: string
refreshTree?: boolean
onSuccess?: (res: BaseRespData<T>) => void | Promise<void>
onAfter?: () => void | Promise<void>
}
interface UseOrderBatchActionsOptions {
getIds: () => (number | string)[]
refreshCurrentView: (options?: { isRefreshTree?: boolean }) => void | Promise<void>
}
export function useOrderBatchActions(options: UseOrderBatchActionsOptions) {
const { getIds, refreshCurrentView } = options
const ensureSelection = (msg = '请先选择订单'): boolean => {
const ids = getIds()
if (!ids.length) {
ElMessage.warning(msg)
return false
}
return true
}
const executeBatchAction = async <T = unknown>(action: BatchActionOptions<T>) => {
const ids = action.getIds()
if (!ids.length) {
ElMessage.warning('请先选择订单')
return
}
if (action.confirmText) {
try {
await ElMessageBox.confirm(action.confirmText, '提示', { type: 'warning' })
} catch {
return
}
}
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await action.api(ids)
if (res.code !== 200) return
if (action.successText) ElMessage.success(action.successText)
if (action.onSuccess) await action.onSuccess(res)
await refreshCurrentView({ isRefreshTree: !!action.refreshTree })
if (action.onAfter) await action.onAfter()
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
return {
ensureSelection,
executeBatchAction,
}
}
import { ref } from 'vue'
import { getListCraftApi, allErpCodeListApi } from '@/api/podCnOrder'
import { getUserMarkList, loadWarehouseListApi } from '@/api/common'
import { getAllCountryApi } from '@/api/logistics'
import type { ProductTypeGroup } from '../component/productTypeFilterTypes'
import {
PRODUCT_TYPE_CUSTOM_PART_MULTIPLE,
PRODUCT_TYPE_CUSTOM_PART_SINGLE,
PRODUCT_TYPE_POD_MULTIPLE,
PRODUCT_TYPE_POD_SINGLE,
} from '../utils/productMarkQuery'
import type { IAllList } from '@/types/api/podUsOrder'
import type { CraftListData } from '@/types/api/podCnOrder'
import type { WarehouseListData } from '@/types'
import { getCustomTagListPodOrderApi } from '@/api/factoryOrderNew'
interface LogisticsCodeItem {
code: string
basicsName: string
apiData: unknown
}
const processTypeMap: Record<string, string> = {
TH: '烫画',
ZP: '直喷',
CX: '刺绣',
DK: '雕刻',
BP: '白胚',
QT: '其他',
}
export function useOrderDictionaries() {
const userMarkList = ref<string[]>([])
const receiverCountryList = ref<{ countryCode: string; nameCn: string }[]>([])
const customTagList = ref<{ id: string; name: string }[]>([])
const allCodelist = ref<LogisticsCodeItem[]>([])
const craftList = ref<IAllList[]>([])
const warehouseList = ref<WarehouseListData[]>([])
const sourceList = [
{ name: 'erp推送', id: 'jomall-erp' },
{ name: '第三方推送', id: 'third-party' },
]
const sizes = ['FS', 'XS', 'S', 'M', 'L', 'XL', 'XXL', '3XL', '4XL', '5XL']
const productTypeGroups = ref<ProductTypeGroup[]>([
{
label: '普品',
value: 'normal',
children: [{ label: '普品', value: 'normal' }],
},
{
label: '胚衣',
value: 'custom_normal',
children: [{ label: '胚衣', value: 'custom_normal' }],
},
{
label: 'POD商品',
value: 'pod',
children: [
{ label: '单面', value: PRODUCT_TYPE_POD_SINGLE },
{ label: '多面', value: PRODUCT_TYPE_POD_MULTIPLE },
],
},
{
label: '一件定制局部印',
value: 'custom_part',
children: [
{ label: '单面', value: PRODUCT_TYPE_CUSTOM_PART_SINGLE },
{ label: '多面', value: PRODUCT_TYPE_CUSTOM_PART_MULTIPLE },
],
},
])
const getUserMark = async () => {
try {
const res = await getUserMarkList()
userMarkList.value = res.data
} catch (_e) {
/* empty */
}
}
const getReceiverCountryList = async () => {
try {
const res = await getAllCountryApi()
if (res.code !== 200) return
receiverCountryList.value = res.data
} catch (_e) {
/* empty */
}
}
const getCustomTagList = async () => {
try {
const res = await getCustomTagListPodOrderApi()
if (res.code !== 200) return
customTagList.value = res.data
} catch (_e) {
/* empty */
}
}
const getLogisticsCompanyAllCodelist = async () => {
try {
const res = await allErpCodeListApi()
if (res.code !== 200) return
allCodelist.value = res.data
} catch (_e) {
/* empty */
}
}
const loadCraftList = async () => {
try {
const res = await getListCraftApi()
if (res.code !== 200) return
const data: CraftListData[] = res.data
craftList.value = data.map((item) => ({
id: item.craftCode,
name: item.craftName,
warehouseName: processTypeMap[item.craftType] ?? '其他',
}))
} catch (_e) {
/* empty */
}
}
const loadWarehouseList = async () => {
try {
const res = await loadWarehouseListApi()
if (res.code !== 200) return
warehouseList.value = res.data
} catch (e) {
console.error(e)
}
}
const loadAllDictionaries = async () => {
await Promise.all([
loadCraftList(),
getUserMark(),
getCustomTagList(),
getLogisticsCompanyAllCodelist(),
getReceiverCountryList(),
loadWarehouseList(),
])
}
return {
userMarkList,
receiverCountryList,
customTagList,
allCodelist,
craftList,
warehouseList,
sourceList,
sizes,
productTypeGroups,
loadCraftList,
getUserMark,
getCustomTagList,
getLogisticsCompanyAllCodelist,
getReceiverCountryList,
loadWarehouseList,
loadAllDictionaries,
}
}
import { nextTick, ref, watch, type Ref } from 'vue'
import type { TabsPaneContext } from 'element-plus'
import usePageList from '@/utils/hooks/usePageList'
import {
getFactoryOrderNewDetailApi,
getFactoryOrderNewListApi,
getFactoryOrderNewLogApi,
} from '@/api/factoryOrderNew'
import type { SearchForm } from '@/types/api/factoryOrderNew'
import type {
FactoryOrderNewListData,
LogListData,
ProductListData,
operateOrderListData,
} from '@/types/api/order/factoryOrderNew'
interface UseOrderListAndDetailOptions {
status: Ref<string>
isCardLayout: Ref<boolean>
isTableLayout: Ref<boolean>
getQueryPayload: () => Record<string, unknown>
getListPageAcceptedSubStatus: () => number | undefined
suspendedSubTab: Ref<number>
}
export function useOrderListAndDetail(options: UseOrderListAndDetailOptions) {
const {
status,
isCardLayout,
isTableLayout,
getQueryPayload,
getListPageAcceptedSubStatus,
suspendedSubTab,
} = options
const subLoading = ref(false)
const activeTab = ref<'product' | 'log'>('product')
const productList = ref<ProductListData[]>([])
const logList = ref<LogListData[]>([])
const tableRef = ref()
const currentRow = ref<FactoryOrderNewListData | null>(null)
const selectedRowIds = ref<(number | string)[]>([])
const selectedRows = ref<FactoryOrderNewListData[]>([])
const cardSelectList = ref<operateOrderListData[]>([])
const listSortProp = ref<string | null>(null)
const listSortOrder = ref<'asc' | 'desc' | null>(null)
const buildListQueryBody = (): SearchForm => {
const base = { ...getQueryPayload() } as SearchForm
if (listSortProp.value && listSortOrder.value) {
return {
...base,
prop: listSortProp.value,
order: listSortOrder.value,
}
}
return base
}
const {
loading,
currentPage,
pageSize,
total,
data: tableData,
onCurrentPageChange,
onPageSizeChange,
refresh: refreshTableList,
} = usePageList<FactoryOrderNewListData>({
query: (page, size) => {
const isSuspend = status.value === 'SUSPEND'
return getFactoryOrderNewListApi(
buildListQueryBody(),
page,
size,
status.value === 'ALL' ? undefined : status.value,
getListPageAcceptedSubStatus(),
isSuspend ? suspendedSubTab.value : undefined,
).then(async (res) => {
const records = res.data.records || []
await nextTick(() => {
tableRef.value.setCurrentRow(records[0])
currentRow.value = (records[0] as never) || null
})
return res.data
})
},
})
const getSelectedIds = (): (number | string)[] => {
if (isCardLayout.value) return cardSelectList.value.map((item) => item.id)
return selectedRowIds.value
}
const clearTableState = () => {
selectedRowIds.value = []
selectedRows.value = []
cardSelectList.value = []
productList.value = []
logList.value = []
currentRow.value = null
listSortProp.value = null
listSortOrder.value = null
}
const SORTABLE_COLUMN_PROPS = [
'createTime',
'startStockingTime',
'finishTime',
] as const
/** 表格列 prop → 后端 list_page 排序字段(如 MyBatis 列别名) */
const LIST_SORT_PROP_BY_COLUMN: Record<
(typeof SORTABLE_COLUMN_PROPS)[number],
string
> = {
createTime: 'po.create_time',
startStockingTime: 'po.start_stocking_time',
finishTime: 'po.finish_time',
}
const handleMainTableSortChange = (data: {
prop?: string
order: string | null
}) => {
const { prop, order } = data
if (
!prop ||
!SORTABLE_COLUMN_PROPS.includes(
prop as (typeof SORTABLE_COLUMN_PROPS)[number],
)
) {
return
}
const sortProp = LIST_SORT_PROP_BY_COLUMN[prop as keyof typeof LIST_SORT_PROP_BY_COLUMN]
if (order === 'ascending') {
listSortProp.value = sortProp
listSortOrder.value = 'asc'
} else if (order === 'descending') {
listSortProp.value = sortProp
listSortOrder.value = 'desc'
} else {
listSortProp.value = null
listSortOrder.value = null
}
refreshTableList()
}
const handleMainSelectionChange = (rows: FactoryOrderNewListData[]) => {
selectedRowIds.value = rows.map((row) => row.id)
selectedRows.value = rows
}
const handleCardSelectionChange = (items: operateOrderListData[]) => {
cardSelectList.value = items
}
const getOrderDetailsById = async (tabName?: 'product' | 'log') => {
if (!currentRow.value) {
productList.value = []
logList.value = []
return
}
const id = currentRow.value.id
// const isSuspend = status.value === 'SUSPEND'
const effectiveTab = tabName ?? activeTab.value
subLoading.value = true
try {
if (effectiveTab === 'product') {
productList.value = []
// const productRes = isSuspend
// ? await getSuspendDetailApi(id)
// : await getFactoryOrderNewDetailApi(id)
const productRes = await getFactoryOrderNewDetailApi(id)
if (productRes.code !== 200) return
productList.value = Array.isArray(productRes.data)
? productRes.data
: []
} else {
logList.value = []
// const logRes = isSuspend
// ? await getSuspendLogApi(id)
// : await getFactoryOrderNewLogApi(id)
const logRes = await getFactoryOrderNewLogApi(id)
if (logRes.code !== 200) return
logList.value = Array.isArray(logRes.data) ? logRes.data : []
}
} catch (e) {
console.error(e)
} finally {
subLoading.value = false
}
}
watch(currentRow, (row) => {
if (!row) {
productList.value = []
logList.value = []
return
}
if (!isTableLayout.value) return
void getOrderDetailsById()
})
const handleRowClick = (row: FactoryOrderNewListData) => {
const isSameRow = currentRow.value?.id === row.id
currentRow.value = row
if (isSameRow) {
getOrderDetailsById()
}
}
const handleTabClick = (tab: TabsPaneContext) => {
if (!currentRow.value) return
const name = tab?.props?.name as 'product' | 'log' | undefined
void getOrderDetailsById(name)
}
return {
subLoading,
activeTab,
productList,
logList,
tableRef,
currentRow,
selectedRowIds,
selectedRows,
cardSelectList,
loading,
currentPage,
pageSize,
total,
tableData,
onCurrentPageChange,
onPageSizeChange,
refreshTableList,
getSelectedIds,
clearTableState,
handleMainSelectionChange,
handleCardSelectionChange,
getOrderDetailsById,
handleRowClick,
handleTabClick,
handleMainTableSortChange,
}
}
import { nextTick, ref } from 'vue'
import type { SearchForm } from '@/types/api/order/factoryOrderNew'
import { normalizeProductMarkListForQuery } from '../utils/productMarkQuery'
export function useOrderSearchForm(refreshCurrentView: () => void) {
const searchForm = ref<SearchForm>({})
const dateRange = ref<string[]>([])
const searchVisible = ref(false)
const pickerOptions = {
shortcuts: [
{
text: '今日',
value: () => {
const start = new Date()
start.setHours(0, 0, 0, 0)
const end = new Date()
end.setHours(23, 59, 59, 999)
return [start, end]
},
},
{
text: '最近7天',
value: () => {
const end = new Date()
end.setHours(23, 59, 59, 999)
const start = new Date()
start.setDate(start.getDate() - 6)
start.setHours(0, 0, 0, 0)
return [start, end]
},
},
{
text: '最近30天',
value: () => {
const end = new Date()
end.setHours(23, 59, 59, 999)
const start = new Date()
start.setDate(start.getDate() - 29)
start.setHours(0, 0, 0, 0)
return [start, end]
},
},
],
}
const toggleMulti = (val: boolean) => {
if (searchForm.value.multi === val) {
setTimeout(() => {
if (searchForm.value.multi === val) {
searchForm.value.multi = undefined
}
}, 0)
}
}
const changeReplaceShipment = () => {
searchForm.value.shipmentType = ''
}
const getQueryPayload = () => {
const { productMarkList: rawProductMarks, tagsIdArr, ...rest } =
searchForm.value
const markQuery = normalizeProductMarkListForQuery(rawProductMarks)
const tagsId =
Array.isArray(tagsIdArr) && tagsIdArr.length > 0
? tagsIdArr.join(',')
: undefined
return {
...rest,
...markQuery,
...(tagsId !== undefined ? { tagsId } : {}),
startTime: dateRange.value?.[0] || null,
endTime: dateRange.value?.[1] || null,
}
}
const reset = () => {
searchForm.value = {}
dateRange.value = []
nextTick(() => {
refreshCurrentView()
})
}
return {
searchForm,
dateRange,
searchVisible,
pickerOptions,
toggleMulti,
changeReplaceShipment,
getQueryPayload,
reset,
}
}
import { computed, ref, nextTick } from 'vue'
import {
getPodOrderAcceptedStatisticsApi,
getPodOrderStateGroupListApi,
getSuspendStatisticsApi,
} from '@/api/factoryOrderNew'
import type { StatusTreeNode } from '@/types/api/order/factoryOrderNew'
interface UseOrderStatusTreeOptions {
getQueryPayload: () => Record<string, unknown>
currentPage: { value: number }
pageSize: { value: number }
onClearTableState: () => void
onRefreshCurrentView: () => void
}
export function useOrderStatusTree(options: UseOrderStatusTreeOptions) {
const {
getQueryPayload,
currentPage,
pageSize,
onClearTableState,
onRefreshCurrentView,
} = options
const cardLayoutStatuses = [
'PENDING_SCHEDULE',
'PENDING_PICK',
'PENDING_REPLENISH',
'IN_PRODUCTION',
'PENDING_PACKING',
]
const specialLayoutStatuses = ['BATCH_MANAGE', 'AWAITING_RESTOCK']
const statusTree = ref<StatusTreeNode[]>()
const status = ref<string>('PENDING_RECEIVE')
const pendingAcceptSubTab = ref<
'PENDING_RECEIVE' | 'ACCEPT_FAIL_OUT_OF_STOCK'
>('PENDING_RECEIVE')
const pendingAcceptCounts = ref<{
acceptedOutOfStockCount?: number
pendingCount?: number
totalCount?: number
}>({
pendingCount: 0,
acceptedOutOfStockCount: 0,
})
const suspendedTabs = ref([
{ label: '客户拦截', key: 'customerInterceptCount', value: 1, count: 0 },
{ label: '地址异常', key: 'addressExceptionCount', value: 2, count: 0 },
{ label: '素材异常', key: 'materialExceptionCount', value: 3, count: 0 },
{ label: '其他', key: 'otherReasonCount', value: 4, count: 0 },
])
const suspendedSubTab = ref(1)
const treeRef = ref()
const isCardLayout = computed(() => cardLayoutStatuses.includes(status.value))
const isSpecialLayout = computed(() =>
specialLayoutStatuses.includes(status.value),
)
const isTableLayout = computed(
() => !isCardLayout.value && !isSpecialLayout.value,
)
const getListPageAcceptedSubStatus = () =>
status.value === 'PENDING_RECEIVE'
? pendingAcceptSubTab.value === 'PENDING_RECEIVE'
? 0
: 2
: undefined
const getPendingReceiveCounts = async () => {
try {
const res = await getPodOrderAcceptedStatisticsApi(
getQueryPayload(),
currentPage.value,
pageSize.value,
status.value,
getListPageAcceptedSubStatus(),
)
if (res.code !== 200) return
pendingAcceptCounts.value = res.data || {
pendingCount: 0,
acceptedOutOfStockCount: 0,
}
} catch (e) {
console.error(e)
}
}
const getSuspendCounts = async () => {
try {
const res = await getSuspendStatisticsApi(
getQueryPayload(),
currentPage.value,
pageSize.value,
suspendedSubTab.value,
)
if (res.code !== 200 || !res.data) return
const data = res.data || {}
suspendedTabs.value = suspendedTabs.value.map((tab) => ({
...tab,
count: data[String(tab.key)] ?? 0,
}))
} catch (e) {
console.error(e)
}
}
const loadStatusTreeCounts = async () => {
try {
const res = await getPodOrderStateGroupListApi()
statusTree.value = [
{ status: 'ALL', statusName: '全部', quantity: 0, children: res.data },
]
nextTick(() => {
treeRef.value?.setCurrentKey(status.value, true)
})
} catch (e) {
console.error(e)
}
}
const handleStatusNodeClick = (node: StatusTreeNode) => {
status.value = node.status
if (node.status !== 'PENDING_RECEIVE') {
pendingAcceptSubTab.value = 'PENDING_RECEIVE'
}
if (node.status !== 'SUSPEND') {
suspendedSubTab.value = 1
}
onClearTableState()
if (!isSpecialLayout.value) {
onRefreshCurrentView()
}
if (node.status === 'SUSPEND') {
void getSuspendCounts()
}
}
const handlePendingAcceptTabClick = (
tab: 'PENDING_RECEIVE' | 'ACCEPT_FAIL_OUT_OF_STOCK',
refreshTableList: () => void,
) => {
if (pendingAcceptSubTab.value === tab) return
pendingAcceptSubTab.value = tab
refreshTableList()
}
const toggleExpand = (node: { expanded?: boolean }) => {
node.expanded = !node.expanded
}
return {
cardLayoutStatuses,
specialLayoutStatuses,
statusTree,
status,
pendingAcceptSubTab,
pendingAcceptCounts,
suspendedTabs,
suspendedSubTab,
treeRef,
isCardLayout,
isSpecialLayout,
isTableLayout,
getListPageAcceptedSubStatus,
getPendingReceiveCounts,
getSuspendCounts,
loadStatusTreeCounts,
handleStatusNodeClick,
handlePendingAcceptTabClick,
toggleExpand,
}
}
# 新订单:
## 待接单、待创建物流、配货中、待发货、已完成、已取消、已归档
- 查询条件
- 数量: 当点击同一个字段时,取消选中状态,**例如: 当前状态在多件,再点击一次多件,取消选中状态**
- 主表格操作列
- 待接单状态下添加`排单按钮`,和批量排单的接口一致,区别是这里只能操作一条数据
- 子表格备注列
- 可修改备注,在值的后面加一个编辑图标,如图一所示
- 点击编辑图标后,弹出一个`prompt` 输入框,提示,请输入备注
- 点击确定后,调用`factory/podOrder/updateRemark` 接口,参数为 `id``remark`
- 接口返回成功后,刷新当前行数据
- 待派单状态下的两个tab可以点击
- 点击待接单后,给接口`factory/podOrder/list_page`传入`subStatus``PENDING_RECEIVE`
- 点击接单失败-缺货后,给接口`factory/podOrder/list_page`传入`subStatus``ACCEPT_FAIL_OUT_OF_STOCK`
- `factory/podOrder/list_page`会返回对应状态下的数据,其中`pendingCount`为待接单tab下的数量,`acceptFailOutOfStockCount`为接单失败-缺货tab下的数量
- 点击接单失败-缺货tab后,将`确认接单`按钮修改为`重新接单`,注意:只修改文字,操作列也同步修改
- 待发货子表格加一个操作列
- 操作列添加`申请补胚`按钮
- 点击“申请补胚”按钮后,会调用接口`factory/podOrder/applyForReplenish`,参数为 `id`
- 调用接口成功后,会返回和卡片布局一样的数据结构(这里暂时还不清楚,可占位),然后弹出和待排单、待补胚、生产中、待配货状态下布局一样的卡片布局弹框,如图二所示(页面中紫色字体无需处理)
- 点击确定后,调用接口`factory/podOrder/applyForReplenish`,参数为 `id`
- 接口调用成功后,关闭申请补胚弹框,刷新列表
/** POD 商品子项,与 useOrderDictionaries 中 options 一致 */
export const PRODUCT_TYPE_POD_SINGLE = 'pod_single'
export const PRODUCT_TYPE_POD_MULTIPLE = 'pod_multiple'
/** 一件定制局部印子项 */
export const PRODUCT_TYPE_CUSTOM_PART_SINGLE = 'custom_part_single'
export const PRODUCT_TYPE_CUSTOM_PART_MULTIPLE = 'custom_part_multiple'
export interface ProductMarkQueryResult {
productMarkList?: string[]
podCustomizedQuantity?: string
cpCustomizedQuantity?: string
}
export function normalizeProductMarkListForQuery(
raw: string[] | undefined,
): ProductMarkQueryResult {
if (!raw?.length) {
return {}
}
const set = new Set(raw)
const marks: string[] = []
if (set.has('normal')) marks.push('normal')
if (set.has('custom_normal')) marks.push('custom_normal')
if (
set.has(PRODUCT_TYPE_POD_SINGLE) ||
set.has(PRODUCT_TYPE_POD_MULTIPLE)
) {
marks.push('pod')
}
if (
set.has(PRODUCT_TYPE_CUSTOM_PART_SINGLE) ||
set.has(PRODUCT_TYPE_CUSTOM_PART_MULTIPLE)
) {
marks.push('custom_part')
}
const podS = set.has(PRODUCT_TYPE_POD_SINGLE)
const podM = set.has(PRODUCT_TYPE_POD_MULTIPLE)
const partS = set.has(PRODUCT_TYPE_CUSTOM_PART_SINGLE)
const partM = set.has(PRODUCT_TYPE_CUSTOM_PART_MULTIPLE)
const podQty =
podS && podM ? undefined : podS ? 'single' : podM ? 'multiple' : undefined
const partQty =
partS && partM ? undefined : partS ? 'single' : partM ? 'multiple' : undefined
const result: ProductMarkQueryResult = {}
if (marks.length) result.productMarkList = marks
if (podQty !== undefined) result.podCustomizedQuantity = podQty
if (partQty !== undefined) result.cpCustomizedQuantity = partQty
return result
}
<script setup lang="ts"> <script setup lang="ts">
import { defineModel } from 'vue'
import { updateAddressApi } from '@/api/podCnOrder.ts' import { updateAddressApi } from '@/api/podCnOrder.ts'
import {AddressInfo} from '@/types/api/podCnOrder.ts' import type { AddressInfo } from '@/types/api/podCnOrder.ts'
const emits = defineEmits(['success']) const emits = defineEmits(['success'])
defineProps<{ const props = defineProps<{
countryList: { countryCode: string }[] countryList: { countryCode: string; nameCn?: string }[]
submitAddressApi?: (data: AddressInfo) => Promise<unknown>
}>() }>()
const visible = defineModel<boolean>('visible') const visible = defineModel<boolean>('visible')
const form = defineModel<AddressInfo>('form', { const form = defineModel<AddressInfo>('form', {
...@@ -42,7 +42,8 @@ const rules = { ...@@ -42,7 +42,8 @@ const rules = {
const submitForm = async () => { const submitForm = async () => {
formRef?.value.validate(async (valid: boolean) => { formRef?.value.validate(async (valid: boolean) => {
if (valid) { if (valid) {
await updateAddressApi(form.value as never) const save = props.submitAddressApi ?? updateAddressApi
await save(form.value as never)
visible.value = false visible.value = false
emits('success') emits('success')
await ElMessageBox.alert( await ElMessageBox.alert(
...@@ -87,7 +88,7 @@ const submitForm = async () => { ...@@ -87,7 +88,7 @@ const submitForm = async () => {
<el-option <el-option
v-for="it in countryList" v-for="it in countryList"
:key="it.countryCode" :key="it.countryCode"
:label="it.countryCode" :label="it.nameCn || it.countryCode"
:value="it.countryCode" :value="it.countryCode"
></el-option> ></el-option>
</el-select> </el-select>
......
...@@ -1047,8 +1047,8 @@ ...@@ -1047,8 +1047,8 @@
<div class="header-filter-tab"> <div class="header-filter-tab">
<div class="tabs"> <div class="tabs">
<div <div
v-for="item in tabsNav" v-for="(item, tabIndex) in tabsNav"
:key="item.status" :key="`${tabIndex}-${item.status ?? ''}`"
class="tabs-node" class="tabs-node"
:class="item.status === status ? 'tabs-node_active' : ''" :class="item.status === status ? 'tabs-node_active' : ''"
@click="changeTab(item)" @click="changeTab(item)"
...@@ -2079,6 +2079,7 @@ ...@@ -2079,6 +2079,7 @@
</div> </div>
<div <div
v-else v-else
:key="status"
v-loading="loading" v-loading="loading"
element-loading-text="加载中..." element-loading-text="加载中..."
class="card-wrapper flex-1 flex-column overflow-hidden" class="card-wrapper flex-1 flex-column overflow-hidden"
...@@ -2945,7 +2946,7 @@ ...@@ -2945,7 +2946,7 @@
/> />
</template> </template>
<script setup lang="tsx"> <script setup lang="tsx">
import WeightDialog from './components/WeightDialog.vue' import WeightDialog from '@/views/order/components/WeightDialog.vue'
import { getUserMarkList } from '@/api/common' import { getUserMarkList } from '@/api/common'
import LogisticsWaySelect from '../../logistics/components/LogisticsWaySelect.tsx' import LogisticsWaySelect from '../../logistics/components/LogisticsWaySelect.tsx'
import PrintWarehouseSkuTag from '../components/printWarehouseSkuTag.vue' import PrintWarehouseSkuTag from '../components/printWarehouseSkuTag.vue'
...@@ -3019,7 +3020,7 @@ import { loadWarehouseListApi, getEmployeeListApi } from '@/api/common' ...@@ -3019,7 +3020,7 @@ import { loadWarehouseListApi, getEmployeeListApi } from '@/api/common'
// import { logisticsCompanyAllCodelist } from '@/api/logistics.ts' // import { logisticsCompanyAllCodelist } from '@/api/logistics.ts'
import { BaseRespData } from '@/types/api' import { BaseRespData } from '@/types/api'
import ChangeWayDialog from './components/ChangeWayDialog.vue' import ChangeWayDialog from './components/ChangeWayDialog.vue'
import CreateLogisticDialog from './components/CreateLogisticDialog.vue' import CreateLogisticDialog from '../components/CreateLogisticDialog.vue'
import UpdateAddress from './components/updateAddress.vue' import UpdateAddress from './components/updateAddress.vue'
import { useEnterKeyTrigger } from '@/utils/hooks/useEnterKeyTrigger.ts' import { useEnterKeyTrigger } from '@/utils/hooks/useEnterKeyTrigger.ts'
...@@ -3047,16 +3048,16 @@ import { showConfirm } from '@/utils/ui' ...@@ -3047,16 +3048,16 @@ import { showConfirm } from '@/utils/ui'
import { DocumentCopy, EditPen } from '@element-plus/icons-vue' import { DocumentCopy, EditPen } from '@element-plus/icons-vue'
import { Column, ElFormItem, ElMessage } from 'element-plus' import { Column, ElFormItem, ElMessage } from 'element-plus'
import { computed, onMounted, ref, nextTick, reactive } from 'vue' import { computed, onMounted, ref, nextTick, reactive } from 'vue'
import FastProduction from './FastProduction.vue' import FastProduction from '@/views/order/components/FastProduction.vue'
import { filePath } from '@/api/axios' import { filePath } from '@/api/axios'
import PodMakeOrder from './PodMakeOrder.vue' import PodMakeOrder from '@/views/order/components/PodMakeOrder.vue'
import PodDistributionOrder from './PodDistributionOrder.vue' import PodDistributionOrder from './PodDistributionOrder.vue'
import SuperPodMakeOrder from './SuperPodMakeOrder.vue' import SuperPodMakeOrder from './SuperPodMakeOrder.vue'
import { OrderData } from '@/types/api/podMakeOrder' import { OrderData } from '@/types/api/podMakeOrder'
import useLodop, { LODOPObject } from '@/utils/hooks/useLodop' import useLodop, { LODOPObject } from '@/utils/hooks/useLodop'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import RightClickMenu from '@/components/RightClickMenu.vue' import RightClickMenu from '@/components/RightClickMenu.vue'
import ResultInfo from './components/ResultInfo.vue' import ResultInfo from '../components/ResultInfo.vue'
import { isArray, isString } from '@/utils/validate' import { isArray, isString } from '@/utils/validate'
import platformJson from '../../../json/platform.json' import platformJson from '../../../json/platform.json'
// import useUserStore from '@/store/user' // import useUserStore from '@/store/user'
...@@ -3066,7 +3067,7 @@ import { ...@@ -3066,7 +3067,7 @@ import {
type NavigationGuardNext, type NavigationGuardNext,
type RouteLocationNormalized, type RouteLocationNormalized,
} from 'vue-router' } from 'vue-router'
import UpdateCustomDeclarationInfoDialog from './components/UpdateCustomDeclarationInfoDialog.vue' import UpdateCustomDeclarationInfoDialog from '../components/UpdateCustomDeclarationInfoDialog.vue'
import { userData } from '@/types/api/user.ts' import { userData } from '@/types/api/user.ts'
declare global { declare global {
...@@ -4845,9 +4846,9 @@ const cancelOrder = async () => { ...@@ -4845,9 +4846,9 @@ const cancelOrder = async () => {
} }
const cardSelection = ref<ProductList[]>([]) const cardSelection = ref<ProductList[]>([])
const cardClick = (data: ProductList) => { const cardClick = (data: ProductList) => {
const status = isSelectStatused(data) const selected = isSelectStatused(data)
if (status) { if (selected) {
cardSelection.value = cardSelection.value.filter( cardSelection.value = cardSelection.value.filter(
(item: ProductList) => item.id !== data.id, (item: ProductList) => item.id !== data.id,
) )
......
import { computed, ref, type Ref } from 'vue'
import BigNumber from 'bignumber.js'
import { ElMessage } from 'element-plus'
import { getBySkuAndUserMarkApi } from '@/api/warehouse'
import type {
InterProductList,
InterWarehouseDetail,
InterskuList,
} from '@/types/api/warehouse'
interface UserMark {
userId: number
userMark: string
userName: string
}
interface UseReceiptProductDialogOptions {
editForm: Ref<InterWarehouseDetail>
otherPurchaseData: Ref<InterProductList[]>
userMark: Ref<number>
batchUserMark: Ref<number>
importUserMark: Ref<number>
selectSku: Ref<string>
userMarkList: Ref<UserMark[]>
}
export function useReceiptProductDialog(options: UseReceiptProductDialogOptions) {
const {
editForm,
otherPurchaseData,
userMark,
batchUserMark,
importUserMark,
selectSku,
userMarkList,
} = options
const skuData = ref<InterskuList[]>([])
const setCostPrice = (item: InterProductList) => {
if (item.costPrice !== 0 && !item.costPrice) {
ElMessage.warning('商品成本价为空,请完善商品成本价')
return
}
const buyStored = item.buyStored ?? 0
const costPrice = item.costPrice ?? 0
const amount = new BigNumber(buyStored).multipliedBy(costPrice).toFixed(2)
item.totalPrice = Number(amount)
}
const getUserMarkText = (
customerId: number,
mode: 'query' | 'create' = 'query',
): string | null => {
const item = userMarkList.value.find((e) => e.userId === customerId)
if (customerId === 0) {
return mode === 'query' ? null : ''
}
return item?.userMark || null
}
const batchAddCommodity = async (
sku: string,
type: '1' | '2' | '3',
): Promise<InterskuList[]> => {
if (!editForm.value.warehouseId) {
ElMessage.error('请选择仓库')
return []
}
try {
let userValue: number = 0
if (type === '1') {
userValue = userMark.value
} else if (type === '2') {
userValue = batchUserMark.value
} else if (type === '3') {
userValue = importUserMark.value
}
const user = getUserMarkText(userValue, 'query')
const res = await getBySkuAndUserMarkApi(editForm.value.warehouseId, sku, user)
const arr: InterskuList[] = res.data || []
const ids: Record<string, boolean> = {}
for (const item of otherPurchaseData.value) {
if (item.warehouseSku !== undefined) {
ids[item.warehouseSku] = true
}
}
return arr.filter((currentItem: InterskuList) => {
return currentItem.sku === undefined || !ids[currentItem.sku]
})
} catch (e) {
console.error(e)
return []
}
}
const selectbySku = async () => {
if (!editForm.value.warehouseId) return ElMessage.error('请选择仓库')
try {
const user = getUserMarkText(userMark.value, 'query')
const res = await getBySkuAndUserMarkApi(
editForm.value.warehouseId,
selectSku.value,
user,
)
skuData.value = res.data || []
} catch (e) {
console.error(e)
}
}
const skudblclick = (val: InterskuList) => {
const {
locationCode = '',
costPrice = null,
productNo = '',
warehouseSku = '',
productName = '',
skuImage = '',
locationId = null,
currencyName = '',
currencyCode = null,
} = val || {}
const lastItem =
otherPurchaseData.value[otherPurchaseData.value.length - 1] || null
if (lastItem && lastItem.currencyName) {
if (!currencyName || currencyName !== lastItem.currencyName) {
ElMessage.error('添加的商品币种需一致')
return
}
}
const item = userMarkList.value.find((e) => e.userId === userMark.value)
otherPurchaseData.value = [
...JSON.parse(JSON.stringify(otherPurchaseData.value)),
{
skuImage,
warehouseSku,
customerId: userMark.value !== 0 ? userMark.value : null,
customerName: userMark.value !== 0 ? item?.userName || '' : null,
userMark: userMark.value !== 0 ? item?.userMark : null,
skuName: productName,
productNo,
locationCode: locationCode ?? '',
locationId: locationId ?? null,
costPrice,
buyStored: null,
totalPrice: null,
currencyName,
currencyCode,
},
]
const skuSet = new Set(
otherPurchaseData.value.map((item: InterProductList) => item.warehouseSku),
)
skuData.value = skuData.value.filter((item: InterskuList) => !skuSet.has(item.sku))
}
const filterSkuData = computed(() => {
const skuList = otherPurchaseData.value.map((el) => el.warehouseSku)
const item = userMarkList.value.find((u) => u.userId === userMark.value)
return skuData.value
.filter((el) => !skuList.includes(el.warehouseSku))
.map((e) => {
return {
...e,
userMark: userMark.value === 0 ? '' : item?.userMark,
}
})
})
return {
skuData,
setCostPrice,
batchAddCommodity,
selectbySku,
skudblclick,
filterSkuData,
}
}
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