Commit 6a68ee79 by qinjianhui

Merge branch 'dev' into 'master'

Dev

See merge request !7
parents 14005c4d ed638dda
import { BasePaginationData, BaseRespData } from '@/types/api'
import {
ProductList,
PodUsOrderListData,
SearchForm,
Tab,
LogListData,
} from '@/types/api/podUsOrder'
import axios from './axios'
export function getOrderTabData() {
return axios.get<never, BaseRespData<Tab[]>>(
'/factory/podJomallOrderUs/findStateGroupList',
)
}
export function getOrderList(
params: SearchForm,
currentPage: number,
pageSize: number,
) {
return axios.post<never, BasePaginationData<PodUsOrderListData[]>>(
'/factory/podJomallOrderUs/list_page',
{
...params,
currentPage,
pageSize,
},
)
}
export function getCardOrderList(
params: SearchForm,
currentPage: number,
pageSize: number,
) {
return axios.post<never, BasePaginationData<ProductList[]>>(
'/factory/podJomallOrderProductUs/list_page',
{
...params,
currentPage,
pageSize,
},
)
}
export function confirmOrderApi(data: number[]) {
return axios.post<never, BaseRespData<never>>(
'factory/podJomallOrderUs/confirmOrders',
data,
)
}
export function updateExceptionOrderApi(data: number[]) {
return axios.post<never, BaseRespData<never>>(
'factory/podJomallOrderUs/updateExceptionOrders',
{
orderIds: data,
},
)
}
export function changeExceptionOrderApi(ids: number[], value: string) {
return axios.post<never, BaseRespData<never>>(
'factory/podJomallOrderUs/exceptionOrders',
{
orderIds: ids,
exceptionReason: value,
},
)
}
export function cancelOrderApi(ids: number[], value: string) {
return axios.post<never, BaseRespData<never>>(
'factory/podJomallOrderUs/cancelOrders',
{
orderIds: ids,
cancelReason: value,
},
)
}
export function getOperationLogApi(id: number) {
return axios.get<never, BaseRespData<LogListData[]>>(
`factory/podJomallOrderUsLog/getPodJomallOrderUsLog?id=${id}`,
)
}
export function getSubOrderBySubOrderNumber(factorySubOrderNumber: string) {
return axios.get<never, BaseRespData<ProductList>>(
'factory/podJomallOrderProductUs/getProductUsByFactorySubOrderNumber',
{
params: {
factorySubOrderNumber,
},
},
)
}
export function downloadMaterialApi(id: Array<number>) {
return axios.post<never, BaseRespData<Array<string>>>(
'factory/podJomallOrderProductUs/downloadDesignImages',
id,
)
}
export function productionQueryApi(id: number, podJomallOrderUsId: number) {
return axios.post<never, BasePaginationData<never>>(
'factory/podJomallOrderProductUs/completeDelivery',
{
id,
podJomallOrderUsId,
},
)
}
......@@ -13,6 +13,7 @@
v-if="selectionable"
type="selection"
width="50"
fixed="left"
header-align="center"
align="center"
></ElTableColumn>
......@@ -20,7 +21,8 @@
v-if="serialNumberable"
label="序号"
type="index"
width="50"
width="60"
fixed="left"
header-align="center"
align="center"
></ElTableColumn>
......
......@@ -19,6 +19,7 @@ import AccountStatementNote from '@/views/AccountStatementNote.vue'
import TypeseetingManagement from '@/views/typesetting/TypesettingManagement.vue'
import PodOrderList from '@/views/order/pod/index.vue'
import PodDeliveryNoteList from '@/views/order/pod/deliveryOrderList.vue'
import PodUsOrderList from '@/views/order/podUs/index.vue'
const router = createRouter({
history: createWebHistory(),
......@@ -50,6 +51,13 @@ const router = createRouter({
component: PodOrderList,
},
{
path: '/pod-us-order/list',
meta: {
title: 'POD订单(US)',
},
component: PodUsOrderList,
},
{
path: '/pod-delivery-note/list',
meta: {
title: 'POD发货单',
......
......@@ -26,6 +26,11 @@ const menu: MenuItem[] = [
id: 7,
label: 'POD订单',
},
{
index: '/pod-us-order/list',
id: 8,
label: 'POD订单(US)',
},
],
},
......
......@@ -114,3 +114,6 @@ img {
.mt-10 {
margin-top: 10px;
}
.mb-10 {
margin-bottom: 10px;
}
export interface Tab {
status?: string
statusName?: string
quantity?: number
}
export interface SearchForm {
timeType: number | null
shopNumber: string
userMark: string
logisticsTracking: string
baseSku: string
factoryOrderNumber: string
sku: string
factorySubOrderNumber: string
status: string
}
export interface PodUsOrderListData {
id: number
thirdOrderNumber?: string
factoryOrderNumber?: string
shopNumber?: string
factoryOnlineId?: number | null
factoryNo?: number | null
factoryCode?: string | null
status?: string
weight?: number | null
totalProductAmount?: number | null
productAmount?: number
carriageAmount?: number | null
totalAmount?: number | null
productNum?: number | null
trackStatus?: string | null
receiverName?: string
receiverPhone?: string
receiverCountry?: string
receiverProvince?: string
receiverCity?: string
receiverDistrict?: string
receiverAddress1?: string
receiverAddress2?: string
receiverPostCode?: string
paymentType?: string
paymentTime?: string
startStockingTime?: string
finishTime?: string
shipmentType?: string
expressSheet?: string
trackingNumber?: string
processNumber?: string
createTime?: string
updateTime?: string
remark?: string | null
userMark?: string
version?: number
productList?: ProductList[]
orderNumber?: string
}
export interface ProductList {
id: number
podJomallOrderUsId: number
thirdSubOrderNumber?: string
factorySubOrderNumber?: string
factoryCode?: string
productName?: string
baseSku?: string
variantSku?: string
productPrice?: number
templatePrice?: number
variantImage?: string
craftPrice?: number
imageAry?: string
designImages?: string
categoryId?: number
categoryName?: string
num?: number
passNum?: number
notPassNum?: number
payAmount?: number
weight?: number | null
diyId?: string
endProductId?: string
customizedQuantity?: number
tagIds?: string
isProduction?: boolean
createTime?: string
updateTime?: string
remark?: string | null
version?: number
subOrderNumber?: string
}
export interface cardImages {
title: string
url: string
sort: number
id?: number
}
export interface LogListData {
id: number
bizId: number
userId: number
employeeName: string
description: string
deleteContent: string
createTime: string
}
export interface PodOrderRes extends ProductList {
expectDeliveryTime?: string | null
thirdOrderNumber?: string | null
startStockingTime?: string | null
factoryOrderNumber?: string | null
userMark?: string | null
craftName?: string | null
craftId?: string | null
shopNumber?: string | null
color?: string | null
size?: string | null
note?: Array<{ prop: string | number; value: string | number }>
imgList: cardImages[]
}
......@@ -57,7 +57,11 @@ export default function usePageList<T>(options: UsePageListOptions<T>) {
loadData()
}
const refresh = () => {
const refresh = (reset?: boolean) => {
if (reset) {
data.value = []
total.value = 0
}
currentPage.value = 1
loadData()
}
......
......@@ -466,7 +466,7 @@ const changeStatus = async () => {
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
setData(TrackingNumber.value)
setData(detail.value.factorySubOrderNumber || '')
})
}
const setData = async (orderNumber: string) => {
......
......@@ -1084,7 +1084,7 @@ import {
OrderData,
ShipmentForm,
} from '@/types/api/order'
import fastProduction from '../fastProduction.vue'
import fastProduction from './fastProduction.vue'
// import CardWrapper from '@/components/CardPods.vue'
import { useValue } from '@/utils/hooks/useValue'
import {
......
<template>
<el-dialog
v-model="dialogVisible"
title="快捷生产"
top="140px"
:fullscreen="true"
:close-on-click-modal="false"
@opened="onOpened"
@close="emit('close')"
>
<div class="detail-div">
<div class="detail-content">
<div class="left">
<div class="left-images">
<el-carousel
v-if="detail?.imgList.length > 0"
style="height: 100%"
:autoplay="false"
indicator-position="none"
>
<el-carousel-item
v-for="(item, index) in detail?.imgList"
:key="index"
style="height: 100%"
>
<div class="left-image">
<b v-show="item?.title && item?.url">
{{ item?.title }}
<span
v-if="item?.id"
style="
text-decoration: underline;
cursor: pointer;
color: blue;
"
>
(DID:{{ item?.id }}
</span>
</b>
<img :src="item.url" alt="" />
</div>
</el-carousel-item>
</el-carousel>
</div>
</div>
<div class="right">
<div class="input">
<el-input
ref="trackingNumberRef"
v-model="TrackingNumber"
:placeholder="placeholderText"
style="width: 660px; margin-right: 10px"
clearable
@keydown.enter="trackCodeInput()"
></el-input>
<el-button type="primary" @click="trackCodeInput()">
查询
</el-button>
</div>
<div class="div-text">
<div class="div-content">
<div :title="String(detail?.userMark)" class="div-item">
<span style="font-size: 18px">客户</span>
<p style="color: red; font-size: 30px">
{{ detail?.userMark }}
</p>
</div>
<div
:title="String(detail?.factoryOrderNumber)"
class="div-item"
style="margin-top: 14px"
>
<span style="font-size: 18px">订单号</span>
<p style="color: red; font-size: 22px">
{{ detail?.factoryOrderNumber }}
</p>
</div>
</div>
</div>
<div class="div-text">
<b>生产单信息</b>
<div class="div-content">
<div :title="detail?.factorySubOrderNumber" class="div-item">
<span>生产单号</span>
<p>
{{ detail?.factorySubOrderNumber }}
</p>
</div>
<div :title="detail?.thirdSubOrderNumber || ''" class="div-item">
<span>第三方生产单号</span>
<p>
{{ detail?.thirdSubOrderNumber }}
</p>
</div>
<div :title="String(detail?.craftName)" class="div-item">
<span>生产工艺</span>
<p>
{{ detail?.craftName }}
</p>
</div>
<div :title="detail?.baseSku" class="div-item">
<span>基版</span>
<p>{{ detail?.baseSku }}</p>
</div>
<div :title="detail?.variantSku" class="div-item">
<span>变体SKU</span>
<p>{{ detail?.variantSku }}</p>
</div>
<div :title="String(detail?.num)" class="div-item">
<span>数量</span>
<p>{{ detail?.num }}</p>
</div>
<div :title="String(detail?.size)" class="div-item">
<span>尺寸</span>
<p>{{ detail?.size }}</p>
</div>
<div :title="detail?.shopNumber ?? ''" class="div-item">
<span>店铺单号</span>
<p>{{ detail?.shopNumber ?? '' }}</p>
</div>
<div :title="detail?.createTime" class="div-item">
<span>创建时间</span>
<p>{{ detail?.createTime }}</p>
</div>
</div>
</div>
<div class="btn">
<div class="btn-sure">
<el-button
style="width: 100%; height: 100%; font-size: 18px"
size="large"
type="success"
@click="changeStatus"
>
生产完成
</el-button>
<div class="check">
<el-checkbox v-model="isAutoSure"> 自动完成上一单 </el-checkbox>
</div>
</div>
<div class="btn-down">
<div class="check">
<el-checkbox v-model="isDownloadImage" size="large">
扫码下载素材
</el-checkbox>
</div>
<el-button
style="width: 100%; height: 100%; font-size: 18px"
type="primary"
@click="handleDownload"
>
下载素材
</el-button>
</div>
</div>
<div
class="div-text"
style="
flex: 1;
flex-shrink: 0;
display: flex;
flex-direction: column;
"
>
<div v-if="detail?.note" style="height: 100%" class="div-content">
<b style="position: absolute; top: -12px">客户留言信息</b>
<div
v-for="(item, index) in detail?.note"
:key="index"
class="div-item"
>
<span>{{ item.prop }}:</span>
<p>
{{ item.value }}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</el-dialog>
</template>
<script setup lang="tsx">
import {
productionQueryApi,
getSubOrderBySubOrderNumber,
downloadMaterialApi,
} from '@/api/podUsOrder'
import { cardImages, PodOrderRes } from '@/types/api/podUsOrder'
import { showConfirm } from '@/utils/ui'
import { filePath } from '@/api/axios'
import { ref, watch, defineProps, defineEmits } from 'vue'
interface HistoryDataItem {
orderNumber: string
finished: boolean
}
const trackingNumberRef = ref()
const historyData = ref<HistoryDataItem[]>([])
const placeholderText = ref('')
const sendNum = ref(0)
const isDownloadImage = ref(false)
const isAutoSure = ref(false)
const detail = ref<PodOrderRes>({
id: -1,
podJomallOrderUsId: -1,
imgList: [] as cardImages[],
})
const dialogVisible = ref(false)
// 通过import动态导入音频文件
const audios = {
weight_warning: new URL('@/assets/audio/weight_warning.mp3', import.meta.url)
.href,
weight_success: new URL('@/assets/audio/weight_success.mp3', import.meta.url)
.href,
weight_repeat: new URL('@/assets/audio/weight_repeat.mp3', import.meta.url)
.href,
weight_search_error: new URL(
'@/assets/audio/weight_search_error.mp3',
import.meta.url,
).href,
weight_search_success: new URL(
'@/assets/audio/weight_search_success.mp3',
import.meta.url,
).href,
}
const TrackingNumber = ref('')
const props = defineProps({
title: {
default: '',
type: String,
},
type: {
default: 0,
type: Number,
},
detailVisible: {
default: false,
type: Boolean,
},
})
const emit = defineEmits(['update:detailVisible', 'close', 'onSuccess'])
watch(
() => props.detailVisible,
(newVal: boolean) => {
dialogVisible.value = newVal
detail.value = { id: -1, podJomallOrderUsId: -1, imgList: [] }
if (newVal) {
const history = localStorage.getItem('historyUsData')
historyData.value = history ? JSON.parse(history) : []
const len = historyData.value
if (len.length > 0) {
confirmQuery(len, 0)
}
placeholderText.value =
'扫描枪输入生产单号,录入下一单本单自动生产完成,最后一单扫两次完成生产'
trackingNumberRef.value && trackingNumberRef.value.focus()
TrackingNumber.value = ''
isAutoSure.value = false
sendNum.value = 0
}
},
)
const confirmQuery = (len: HistoryDataItem[], i: number) => {
const el = len[i]
showConfirm(`生产单号 ${el.orderNumber} 未生产完成,取消则不提醒?`, {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
TrackingNumber.value = el.orderNumber
await trackCodeInput()
await setData(el.orderNumber)
ElMessage.success('生产完成')
if (len[i + 1]) {
confirmQuery(len, i + 1)
}
})
.catch(() => {
const index = historyData.value.findIndex(
(item: HistoryDataItem) => item.orderNumber === el.orderNumber,
)
if (index >= 0) {
historyData.value.splice(index, 1)
localStorage.setItem('historyUsData', JSON.stringify(historyData.value))
}
if (len[i + 1]) {
confirmQuery(len, i + 1)
}
trackingNumberRef.value && trackingNumberRef.value.focus()
})
}
const changeStatus = async () => {
if (!detail.value || Object.keys(detail.value).length <= 1) {
return ElMessage.warning('请扫码生产单号')
}
showConfirm('确定生产完成?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
setData(detail.value.factorySubOrderNumber || '')
})
}
const setData = async (orderNumber: string) => {
if (!detail.value || detail.value?.id === -1) return
try {
const id = detail.value.id
const podJomallOrderUsId = detail.value.podJomallOrderUsId
await productionQueryApi(id, podJomallOrderUsId)
if (orderNumber) {
const index = historyData.value.findIndex(
(el: HistoryDataItem) => el.orderNumber === orderNumber,
)
if (index >= 0) {
// 扫单完成删除
historyData.value.splice(index, 1)
localStorage.setItem('historyUsData', JSON.stringify(historyData.value))
}
}
emit('onSuccess')
playAudio('weight_success')
detail.value = {
id: -1,
podJomallOrderUsId: -1,
imgList: [] as cardImages[],
}
TrackingNumber.value = ''
isAutoSure.value = false
isDownloadImage.value = false
trackingNumberRef.value && trackingNumberRef.value.focus()
} catch (e) {
console.error(e)
detail.value = {
id: -1,
podJomallOrderUsId: -1,
imgList: [] as cardImages[],
}
trackingNumberRef.value && trackingNumberRef.value.focus()
playAudio('weight_search_error')
}
}
const handleDownload = () => {
if (
!detail.value ||
Object.keys(detail.value).length <= 1 ||
detail.value.id == -1
) {
return ElMessage.warning('请扫码生产单号')
}
download()
}
const download = async () => {
if (detail.value && detail.value?.id != -1) {
try {
const id = detail.value.id
if (id !== undefined) {
try {
const res = await downloadMaterialApi([id])
if (res.code !== 200) return
window.open(filePath + res.message)
} catch (e) {
console.error(e)
}
}
} catch (e) {
// showError(e)
console.error(e)
}
}
}
type AudioKey = keyof typeof audios // 创建一个类型,确保 key 只能是 audios 对象的键之一
const playAudio = (key: AudioKey, message?: string) => {
let text = ''
switch (key) {
case 'weight_search_success':
text = ''
break
case 'weight_search_error':
text = '请录入生产单号'
break
case 'weight_success':
text = ''
break
case 'weight_repeat':
text = '称重复录入'
break
default:
text = '请录入跟踪号或重量'
break
}
if (message || text) ElMessage.warning(message || text)
const audio = new Audio()
if (audios[key]) {
audio.src = audios[key] // 获取对应 key 的音频路径
audio.play().catch((err) => console.error('Audio play failed:', err)) // 捕获音频播放失败的错误
} else {
console.error(`No audio found for key: ${key}`)
}
}
const trackCodeInput = async () => {
if (!TrackingNumber.value) {
// ElMessage.warning('请扫描生产单号')
playAudio('weight_search_error')
trackingNumberRef.value && trackingNumberRef.value.focus()
return
}
const item = historyData.value.find(
(el: HistoryDataItem) => el.orderNumber === TrackingNumber.value,
)
if (!item) {
// 记录扫单
historyData.value.push({
orderNumber: TrackingNumber.value,
finished: false,
})
localStorage.setItem('historyUsData', JSON.stringify(historyData.value))
}
const orderNumber = TrackingNumber.value
if (isAutoSure.value) {
await setData(
historyData.value[historyData.value.length - 1].orderNumber || '',
)
}
try {
const res = await getSubOrderBySubOrderNumber(orderNumber)
if (!res.data) {
return ElMessage.error('生产单不存在')
}
const d = JSON.parse(JSON.stringify(res.data))
if (d.note) {
d.note = JSON.parse(d.note)
} else {
d.note = []
}
if (d.imageAry) {
d.imgList = JSON.parse(d.imageAry)
} else {
d.imgList = []
}
detail.value = d
if (isDownloadImage.value) {
download()
}
playAudio('weight_search_success')
trackingNumberRef.value && trackingNumberRef.value.focus()
TrackingNumber.value = ''
} catch (e) {
console.error(e)
trackingNumberRef.value && trackingNumberRef.value.focus()
TrackingNumber.value = ''
}
}
const onOpened = () => {
trackingNumberRef.value && trackingNumberRef.value.focus()
}
</script>
<style lang="scss" scoped>
.sure-btn {
position: absolute;
right: 62px;
top: 14px;
}
.detail-div {
display: flex;
height: 100%;
flex-direction: column;
justify-content: space-between;
.detail-images {
.scroll-list {
background: #ececec;
display: flex;
height: 100px;
width: 100%;
padding: 5px;
.scroll-content {
margin-left: 10px;
overflow-x: auto;
overflow-y: hidden;
flex: 1;
display: flex;
flex-wrap: nowrap;
flex-shrink: 0;
.scroll-item {
height: 100%;
min-width: 100px;
background: white;
margin-right: 5px;
}
}
.img-title {
display: flex;
flex-direction: column;
justify-content: center;
background: white;
padding: 10px;
b {
text-align: center;
color: black;
font-weight: bold;
font-size: 16px;
margin-bottom: 15px;
}
.id {
display: flex;
align-items: center;
padding: 3px 5px;
background: #ececec;
justify-content: center;
img {
width: 15px;
margin-right: 8px;
}
}
}
}
}
.detail-content {
display: flex;
width: 100%;
}
.right {
width: 730px;
height: 100%;
display: flex;
flex-direction: column;
.btn {
margin: 20px 0;
display: flex;
align-items: center;
justify-content: space-between;
height: 50px;
width: 100%;
.btn-sure,
.btn-down {
width: 49%;
position: relative;
.check {
position: absolute;
width: 144px;
height: 100%;
background: transparent;
display: flex;
align-items: center;
justify-content: center;
right: 0;
top: 1px;
}
}
}
.div-text {
.div-content {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: flex-start;
padding: 15px 10px;
box-sizing: border-box;
.div-item {
width: 50%;
margin-bottom: 10px;
display: flex;
align-items: center;
p {
font-weight: 400;
color: black;
}
span {
display: inline-block;
text-align: right;
width: 120px;
}
span::after {
content: ':';
margin: 0 3px;
}
}
}
b {
position: relative;
background: white;
top: 9px;
left: 13px;
padding: 0 10px;
font-size: 18px;
color: black;
z-index: 3;
}
.div-content {
position: relative;
border: 1px solid #ececec;
}
}
.input {
display: flex;
align-items: center;
margin: 30px 0;
}
}
.left {
flex: 1;
flex-shrink: 0;
margin-right: 20px;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
.left-image {
display: flex;
height: 100%;
flex-direction: column;
justify-content: center;
img {
height: auto;
width: 100%;
max-height: 90%;
}
b {
color: black;
font-size: 18px;
margin-bottom: 15px;
}
}
.left-images {
display: flex;
width: 95%;
height: 100%;
flex-direction: column;
b {
color: black;
text-align: center;
margin-bottom: 15px;
}
}
}
}
.left-images {
:deep(.el-carousel__container) {
height: 100%;
}
:deep(.el-dialog__title) {
font-weight: bold;
font-size: 37px;
color: black;
position: relative;
left: 47%;
top: 13px;
}
}
.btn {
position: relative;
:deep(.el-button) {
span {
position: relative;
left: -30px;
}
}
.check {
:deep(.el-checkbox__inner) {
background-color: transparent !important;
border-color: white !important;
width: 12px;
height: 12px;
}
:deep(.el-checkbox__inner::after) {
left: 3px;
}
:deep(.el-checkbox__label) {
padding-left: 5px;
font-size: 12px;
color: white !important;
}
}
}
.warning {
font-size: 18px;
font-weight: bold;
color: #ff9900;
margin-left: 10px;
cursor: pointer;
}
</style>
<template>
<div class="card flex-column h-100 overflow-hidden">
<div class="header-filter-form">
<ElForm :model="searchForm" size="default" inline>
<ElFormItem label="客户">
<el-select
v-model="searchForm.userMark"
clearable
filterable
style="width: 180px"
placeholder="客户"
>
<el-option
v-for="item in userMarkList"
:key="item"
:value="item"
:label="item"
></el-option>
</el-select>
</ElFormItem>
<ElFormItem label="SKU">
<ElInput
v-model.trim="searchForm.sku"
placeholder=" SKU"
clearable
style="width: 180px"
></ElInput>
</ElFormItem>
<ElFormItem label="Base SKU">
<ElInput
v-model.trim="searchForm.baseSku"
placeholder=" Base SKU"
clearable
style="width: 180px"
></ElInput>
</ElFormItem>
<ElFormItem label="物流跟踪号">
<ElInput
v-model.trim="searchForm.logisticsTracking"
placeholder="物流跟踪号"
clearable
style="width: 180px"
></ElInput>
</ElFormItem>
<ElFormItem label="生产单号">
<ElInput
v-model="searchForm.factorySubOrderNumber"
placeholder="生产单号"
clearable
style="width: 180px"
/>
</ElFormItem>
<ElFormItem label="订单号">
<ElInput
v-model="searchForm.factoryOrderNumber"
placeholder="订单号"
clearable
style="width: 180px"
/>
</ElFormItem>
<ElFormItem label="店铺单号">
<ElInput
v-model="searchForm.shopNumber"
placeholder="店铺单号"
clearable
style="width: 180px"
/>
</ElFormItem>
<ElFormItem>
<ElButton type="primary" @click="search">查询</ElButton>
</ElFormItem>
</ElForm>
</div>
<div class="header-filter-tab">
<div class="tabs">
<div
v-for="item in tabsNav"
:key="item.status"
class="tabs-node"
:class="item.status === status ? 'tabs-node_active' : ''"
@click="changeTab(item)"
>
<span class="tabs-node_label">{{ item.statusName }}</span>
<span class="tabs-node_count">{{ `(${item.quantity})` }}</span>
</div>
</div>
</div>
<div class="order-content flex-1 flex-column overflow-hidden mt-10">
<div class="operation-box mb-10">
<span v-if="status === 'TO_BE_CONFIRMED'" class="item">
<ElButton type="success" @click="confirmOrder"> 确认 </ElButton>
</span>
<span v-if="status === 'EXCEPTION'" class="item">
<ElButton type="success" @click="updateOrder"> 更新 </ElButton>
</span>
<span v-if="status === 'TO_BE_CONFIRMED'" class="item">
<ElButton type="warning" @click="changeExceptionOrder">
转为异常单
</ElButton>
</span>
<span v-if="status !== 'IN_PRODUCTION'" class="item">
<ElButton type="danger" @click="cancelOrder">取消</ElButton>
</span>
<span v-if="status !== 'IN_PRODUCTION'" class="item">
<ElButton type="success" @click="handleUpdateRemark">
添加内部标签
</ElButton>
</span>
<span v-if="status === 'IN_PRODUCTION'" class="item">
<ElButton type="warning" @click="onFastProduction">
快捷生产
</ElButton>
</span>
<span class="item">
<ElButton type="primary" @click="downloadMaterial">下载素材</ElButton>
</span>
</div>
<div
v-if="status !== 'IN_PRODUCTION'"
v-loading="loading"
element-loading-text="加载中..."
class="table-wrapper flex-1 flex-column overflow-hidden"
>
<TableView
:columns="tableColumns"
:stripe="true"
:serial-numberable="true"
:selectionable="true"
:paginated-data="tableData"
:cell-style="onCellStyle"
@selection-change="handleSelectionChange"
>
<template #goods="{ row }">
<div class="goods-info-box">
<div class="goods-list">
<div
v-for="item in row.productList"
:key="item"
class="goods-item"
>
<div class="goods-item-img">
<img :src="item.variantImage" alt="商品图片" />
</div>
<div class="goods-item-info">
<div class="goods-item-info-item">
<span class="goods-item-info-item-label">商品名称:</span>
<span class="goods-item-info-item-value">
{{ item.productName }}
</span>
</div>
<div class="goods-item-info-item">
<span class="goods-item-info-item-label">Base SKU:</span>
<span class="goods-item-info-item-value">
{{ item.baseSku }}
</span>
<el-icon class="icon" @click="copy(item.baseSku || '')"
><DocumentCopy
/></el-icon>
</div>
<div class="goods-item-info-item">
<span class="goods-item-info-item-label">变体SKU:</span>
<span class="goods-item-info-item-value">
{{ item.variantSku }}
</span>
<el-icon class="icon" @click="copy(item.variantSku || '')"
><DocumentCopy
/></el-icon>
</div>
<div class="goods-item-info-item">
<span class="goods-item-info-item-label">备注:</span>
<span class="goods-item-info-item-value">
{{ item.remark }}
</span>
<el-icon
class="icon"
style="color: #e6a23c"
@click="handleUpdateRemark(item)"
><EditPen
/></el-icon>
</div>
</div>
<div class="goods-item-info">
<div class="goods-item-info-item">
<span class="goods-item-info-item-label">生产单号:</span>
<span class="goods-item-info-item-value">
{{ item.factorySubOrderNumber }}
</span>
<el-icon
class="icon"
@click="copy(item.factorySubOrderNumber || '')"
><DocumentCopy
/></el-icon>
</div>
<div class="goods-item-info-item">
<span class="goods-item-info-item-label"
>第三方生产单号:</span
>
<span class="goods-item-info-item-value">
{{ item.thirdSubOrderNumber }}
</span>
<el-icon
class="icon"
@click="copy(item.thirdSubOrderNumber || '')"
><DocumentCopy
/></el-icon>
</div>
<div class="goods-item-info-item">
<span class="goods-item-info-item-label">工厂:</span>
<span class="goods-item-info-item-value">
{{ item.factoryCode }}
</span>
</div>
</div>
<div class="goods-item-info">
<div class="goods-item-info-item">
<span class="goods-item-info-item-label">单价:</span>
<span class="goods-item-info-item-value">
{{ item.productPrice }}()
</span>
</div>
<div class="goods-item-info-item">
<span class="goods-item-info-item-label">模板金额:</span>
<span class="goods-item-info-item-value">
{{ item.templatePrice }}()
</span>
</div>
<div class="goods-item-info-item">
<span class="goods-item-info-item-label">付款金额:</span>
<span class="goods-item-info-item-value">
{{ item.payAmount }}()
</span>
</div>
<div class="goods-item-info-item">
<span class="goods-item-info-item-label">数量:</span>
<span class="goods-item-info-item-value">
{{ item.num }}
</span>
</div>
</div>
</div>
</div>
</div>
</template>
<template #orderDetail="{ row }">
<div class="order-detail-box">
<div class="order-detail-item">
<span class="order-detail-item-label">订单号:</span>
<span class="order-detail-item-value">
{{ row.factoryOrderNumber }}
</span>
<el-icon
class="icon"
@click="copy(row.factoryOrderNumber || '')"
><DocumentCopy
/></el-icon>
</div>
<div class="order-detail-item">
<span class="order-detail-item-label">第三方订单号:</span>
<span class="order-detail-item-value">
{{ row.thirdOrderNumber }}
</span>
<el-icon class="icon" @click="copy(row.thirdOrderNumber || '')"
><DocumentCopy
/></el-icon>
</div>
<div class="order-detail-item">
<span class="order-detail-item-label">店铺单号:</span>
<span class="order-detail-item-value">
{{ row.shopNumber }}
</span>
<el-icon class="icon" @click="copy(row.shopNumber || '')"
><DocumentCopy
/></el-icon>
</div>
<div class="order-detail-item">
<span class="order-detail-item-label">收货人:</span>
<span class="order-detail-item-value">
{{ row.receiverName }}
</span>
</div>
<div class="order-detail-item">
<span class="order-detail-item-label">收货人电话:</span>
<span class="order-detail-item-value">
{{ row.receiverPhone }}
</span>
</div>
<div class="order-detail-item">
<span class="order-detail-item-label">收货人邮编:</span>
<span class="order-detail-item-value">
{{ row.receiverPostCode }}
</span>
</div>
<div class="order-detail-item">
<span class="order-detail-item-label">收货地址:</span>
<span class="order-detail-item-value">
{{ row.receiverCountry }}{{ row.receiverProvince
}}{{ row.receiverCity }}{{ row.receiverDistrict }}
</span>
</div>
</div>
</template>
<template #price="{ row }">
<div class="order-price-box">
<div class="order-price-item">
<span class="order-price-item-label">总价:</span>
<span class="order-price-item-value">
{{ row.totalAmount }}()
</span>
</div>
</div>
</template>
<template #time="{ row }">
<div class="order-time-box">
<div class="order-time-item">
<span class="order-time-item-label">创建时间:</span>
<span class="order-time-item-value">
{{ row.createTime }}
</span>
</div>
<div class="order-time-item">
<span class="order-time-item-label">更新时间:</span>
<span class="order-time-item-value">
{{ row.updateTime }}
</span>
</div>
<div class="order-time-item">
<span class="order-time-item-label">支付时间:</span>
<span class="order-time-item-value">
{{ row.paymentTime }}
</span>
</div>
</div>
</template>
<template #innerLabel="{ row }">
<div v-if="row.internalMemoList" class="inner-label-box">
<div
v-for="(item, index) in row.internalMemoList"
:key="index"
class="inner-label-item"
>
<span class="inner-label-item-value">
{{
`${item.operatorTime} ${item.operatorEmployeeName}: ${item.content}`
}}
</span>
</div>
</div>
</template>
<template #operate="{ row }">
<div class="operate-box">
<span class="operate-item">
<ElButton
link
type="primary"
@click="operationLog(row.id, null)"
>
操作日志
</ElButton>
</span>
</div>
</template>
</TableView>
</div>
<div
v-else
v-loading="loading"
element-loading-text="加载中..."
class="card-wrapper flex-1 flex-column overflow-hidden"
>
<div v-if="tableData.length > 0" class="card-list">
<div
v-for="cardItem in tableData as ProductList[]"
:key="cardItem.id"
class="card-list-item"
@click="cardClick(cardItem)"
@mouseleave="handleChangeImages(null, cardItem)"
>
<CommonCard
:card-item="cardItem"
:active="isSelectStatused(cardItem)"
:show-sku="false"
:show-product-info="false"
:image-field="'variantImage'"
@contextmenu.prevent="(v: MouseEvent) => rightClick(v)"
>
<template #bottom_left>
<span
v-if="cardItem?.factorySubOrderNumber"
title="生产单号"
class="factory-sub-order-number"
@click.stop="
copy(String(cardItem?.factorySubOrderNumber || ''))
"
>
{{ cardItem?.factorySubOrderNumber }}
</span>
</template>
<template #operations>
<Icon
name="caozuorizhi"
@click="(e: MouseEvent) => operationLog(cardItem.podJomallOrderUsId, e)"
>
<template #title>
<title>操作日志</title>
</template>
</Icon>
<Icon name="chakanxiangqing">
<template #title>
<title>查看详情</title>
</template>
</Icon>
</template>
<template #images>
<div class="flex-between">
<div v-if="cardItem.imageAry" class="images-position">
<div
v-for="(item, index) in JSON.parse(
cardItem.imageAry || '',
)"
:key="index"
:title="item.title"
class="item-image"
@mousemove="handleChangeImages(item, cardItem)"
>
<img :src="item?.url" height="28" />
</div>
</div>
</div>
</template>
<template #info>
<div class="grid-container">
<div class="grid-item" title="商品名称">
<span class="grid-item-value"
>{{ cardItem?.productName }}
</span>
</div>
<div class="grid-item" title="未生产数量">
<span class="grid-item-label">数量:</span>
<span class="grid-item-value">
{{ cardItem?.notPassNum }}
</span>
</div>
<div class="grid-item">
<span title="Base SKU" class="grid-item-value">
{{ cardItem?.baseSku }}
</span>
</div>
<div class="grid-item">
<span
:title="`第三方生产单号:${cardItem?.thirdSubOrderNumber}`"
class="grid-item-value"
@click.stop="
copy(String(cardItem?.thirdSubOrderNumber || ''))
"
>
{{ cardItem?.thirdSubOrderNumber }}
</span>
</div>
<div class="grid-item">
<span title="Variant SKU" class="grid-item-value">
{{ cardItem?.variantSku }}
</span>
</div>
</div>
</template>
</CommonCard>
</div>
</div>
<div v-else class="empty">暂无数据</div>
</div>
<ElPagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[100, 200, 300, 400, 500]"
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
style="margin: 10px auto 0; text-align: right"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
></ElPagination>
</div>
</div>
<right-menu ref="rightMenuRef" @change="rightChange" />
<el-dialog
v-model="logVisible"
title="操作日志"
width="1000px"
:close-on-click-modal="false"
>
<LogList :log-list="logList" />
</el-dialog>
<FastProduction
v-model:detailVisible="detailVisible"
:current-status="status"
@on-success="handleSuccess"
@close="fastClose"
></FastProduction>
</template>
<script setup lang="ts">
import { getUserMarkList } from '@/api/common'
import {
getCardOrderList,
getOrderList,
getOrderTabData,
confirmOrderApi,
changeExceptionOrderApi,
cancelOrderApi,
getOperationLogApi,
downloadMaterialApi,
updateExceptionOrderApi,
} from '@/api/podUsOrder'
import TableView from '@/components/TableView.vue'
import {
LogListData,
PodUsOrderListData,
ProductList,
SearchForm,
Tab,
cardImages,
} from '@/types/api/podUsOrder'
import usePageList from '@/utils/hooks/usePageList'
import { useValue } from '@/utils/hooks/useValue'
import { showConfirm } from '@/utils/ui'
import { DocumentCopy, EditPen } from '@element-plus/icons-vue'
import { Column } from 'element-plus'
import { computed, onMounted, ref } from 'vue'
import FastProduction from './FastProduction.vue'
import { filePath } from '@/api/axios'
const tabsNav = ref<Tab[]>()
const status = ref('TO_BE_CONFIRMED')
const [searchForm] = useValue<SearchForm>({
timeType: null,
shopNumber: '',
userMark: '',
logisticsTracking: '',
baseSku: '',
factoryOrderNumber: '',
sku: '',
factorySubOrderNumber: '',
status: '',
})
const userMarkList = ref<string[]>([])
const selection = ref<PodUsOrderListData[]>([])
const tableColumns = computed(() => [
{
label: '商品',
prop: 'goods',
slot: 'goods',
minWidth: 800,
},
{
label: '订单详情',
prop: 'orderDetail',
slot: 'orderDetail',
width: 300,
},
{
label: '单价',
slot: 'price',
width: 160,
prop: 'price',
align: 'center',
},
{
label: '时间',
slot: 'time',
width: 300,
prop: 'time',
align: 'center',
},
{
label: '内部便签',
slot: 'innerLabel',
width: 300,
prop: 'innerLabel',
},
{
label: '操作',
slot: 'operate',
width: 180,
align: 'center',
fixed: 'right',
prop: 'operate',
},
])
const rightMenuRef = ref()
const rightClick = (e: MouseEvent) => {
rightMenuRef.value.setPosition({
x: e.clientX,
y: e.clientY,
cardItem: e.clientY,
el: e,
})
}
const handleSelectionChange = (val: PodUsOrderListData[]) => {
selection.value = val
}
const changeTab = (item: Tab) => {
status.value = item.status || ''
selection.value = []
cardSelection.value = []
search(true)
}
const onCellStyle = ({ column }: { column: Column }) => {
if (
column.property === 'orderDetail' ||
column.property === 'price' ||
column.property === 'time' ||
column.property === 'innerLabel' ||
column.property === 'goods' ||
column.property === 'operate'
) {
return { verticalAlign: 'top' }
}
}
const loadTabData = async () => {
try {
const res = await getOrderTabData()
tabsNav.value = res.data
} catch (error) {
// showError(error)
}
}
const getUserMark = async () => {
try {
const res = await getUserMarkList()
userMarkList.value = res.data
} catch (error) {
//showError(error)
}
}
const {
loading,
currentPage,
pageSize,
total,
data: tableData,
refresh: search,
onCurrentPageChange: handleCurrentChange,
onPageSizeChange: handleSizeChange,
} = usePageList({
query: (page, pageSize) => {
if (status.value !== 'IN_PRODUCTION') {
return getOrderList(
{
...searchForm.value,
status: status.value,
},
page,
pageSize,
).then((res) => res.data) as never
} else {
return getCardOrderList(
{
...searchForm.value,
status: status.value,
},
page,
pageSize,
).then((res) => res.data) as never
}
},
})
const copy = (text: string) => {
navigator.clipboard.writeText(text)
ElMessage.success('复制成功')
}
const handleUpdateRemark = (item: PodUsOrderListData) => {
console.log(item)
}
const confirmOrder = async () => {
if (selection.value.length === 0) {
return ElMessage.warning('请选择数据')
}
try {
await showConfirm('确定确认吗?', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
const ids = selection.value.map((item) => item.id)
try {
const res = await confirmOrderApi(ids)
if (res.code !== 200) return
ElMessage.success('操作成功')
search()
loadTabData()
} catch (e) {
console.error(e)
}
}
const updateOrder = async () => {
if (selection.value.length === 0) {
return ElMessage.warning('请选择数据')
}
try {
await showConfirm('确定更新吗?', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
const ids = selection.value.map((item) => item.id)
try {
const res = await updateExceptionOrderApi(ids)
if (res.code !== 200) return
ElMessage.success('操作成功')
search()
loadTabData()
} catch (e) {
console.error(e)
}
}
const changeExceptionOrder = async () => {
if (selection.value.length === 0) {
return ElMessage.warning('请选择数据')
}
const orderIds = selection.value.map((item) => item.id)
ElMessageBox.prompt('请填写异常原因', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
inputPattern: /^.{1,2000}$/,
inputErrorMessage: '请输入异常原因',
}).then(async ({ value }) => {
try {
const res = await changeExceptionOrderApi(orderIds, value)
if (res.code !== 200) return
ElMessage.success('操作成功')
search()
loadTabData()
} catch (e) {
console.error(e)
}
})
}
const cancelOrder = async () => {
if (selection.value.length === 0) {
return ElMessage.warning('请选择数据')
}
const orderIds = selection.value.map((item) => item.id)
ElMessageBox.prompt('请填写取消原因', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
inputPattern: /^.{1,2000}$/,
inputErrorMessage: '请输入取消原因',
}).then(async ({ value }) => {
try {
const res = await cancelOrderApi(orderIds, value)
if (res.code !== 200) return
ElMessage.success('操作成功')
search()
loadTabData()
} catch (e) {
console.error(e)
}
})
}
const cardSelection = ref<ProductList[]>([])
const cardClick = (data: ProductList) => {
const status = isSelectStatused(data)
if (status) {
cardSelection.value = cardSelection.value.filter(
(item: ProductList) => item.id !== data.id,
)
} else {
cardSelection.value.push(data as ProductList)
}
}
const isSelectStatused = (data: ProductList) => {
const index = cardSelection.value.findIndex(
(item: ProductList) => item.id === data.id,
)
return index !== -1
}
const rightChange = async (code: string) => {
if (code === 'check_all') {
cardSelection.value = JSON.parse(JSON.stringify(tableData.value))
} else if (code === 'clear_check') {
cardSelection.value = []
} else if (code === 'copy_code') {
const str = cardSelection.value
.map((item) => item.factorySubOrderNumber)
.join()
navigator.clipboard.writeText(str)
ElMessage.success('复制成功')
}
}
const currentImage = ref('')
const handleChangeImages = (item: cardImages | null, cardItem: ProductList) => {
currentImage.value = item?.url || ''
cardItem.variantImage =
item?.url || JSON.parse(cardItem.imageAry || '')[0].url
}
const detailVisible = ref(false)
const onFastProduction = () => {
detailVisible.value = true
}
// 下载稿件
const downloadMaterial = async () => {
let selectedIds = []
if (status.value === 'IN_PRODUCTION') {
selectedIds = cardSelection.value.map(
(item: ProductList) => item.podJomallOrderUsId,
)
} else {
selectedIds = selection.value.map((item: PodUsOrderListData) => item.id)
}
if (selectedIds.length === 0) {
return ElMessage({
message: '请选择订单',
type: 'warning',
offset: window.innerHeight / 2,
})
}
try {
const res = await downloadMaterialApi(selectedIds)
if (res.code !== 200) return
window.open(filePath + res.message)
} catch (e) {
// showError(e)
console.error(e)
}
}
const logList = ref<LogListData[]>([])
const logVisible = ref(false)
const operationLog = async (id: number, e: MouseEvent | null) => {
e && e.stopPropagation()
try {
const res = await getOperationLogApi(id)
if (res.code !== 200) return
logList.value = res.data
logVisible.value = true
} catch (e) {
console.error(e)
}
}
const handleSuccess = () => {
loadTabData()
search()
}
const fastClose = () => {
loadTabData()
detailVisible.value = false
}
onMounted(() => {
loadTabData()
getUserMark()
})
</script>
<style lang="scss" scoped>
.header-filter-form {
:deep(.el-form-item) {
margin-right: 14px;
margin-bottom: 10px;
}
}
.tabs {
display: flex;
align-items: center;
gap: 10px;
border-bottom: 1px solid #f0f0f0;
height: 42px;
.tabs-node {
padding: 10px;
font-size: 14px;
color: #000;
cursor: pointer;
height: 40px;
}
.tabs-node:hover {
color: #409eff;
}
.tabs-node.tabs-node_active {
color: #409eff;
border-bottom: 2px solid #409eff;
font-weight: 600;
}
}
.goods-item {
display: grid;
grid-template-columns: 100px 1fr minmax(180px, 1fr) 140px;
gap: 20px;
.goods-item-img {
width: 100px;
height: 100px;
img {
width: 100%;
}
}
&:not(:last-child) {
padding-bottom: 10px;
margin-bottom: 10px;
border-bottom: 1px solid #eee;
}
}
.goods-item-info-item {
display: flex;
align-items: center;
gap: 6px;
.goods-item-info-item-value {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.order-detail-item {
display: flex;
align-items: center;
gap: 6px;
.order-detail-item-value {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.card-list {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-template-rows: max-content;
gap: 10px;
height: 100%;
overflow-y: auto;
}
.empty {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #999;
}
.card-list-item {
cursor: pointer;
}
.images-position {
display: flex;
gap: 5px;
.item-image {
cursor: pointer;
border: 1px solid #eee;
}
}
.grid-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
font-size: 12px;
margin-top: 10px;
}
.factory-sub-order-number {
font-size: 12px;
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment