Commit 455434be by linjinhong

Merge remote-tracking branch 'origin/master' into linjinhong

parents d614938c 03336ad7
......@@ -3,8 +3,54 @@
"version": "0.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@ampproject/remapping": {
"packages": {
"": {
"name": "factory_front",
"version": "0.0.0",
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.6.7",
"bignumber.js": "^9.3.0",
"dayjs": "^1.11.13",
"element-plus": "^2.6.0",
"lodash-es": "^4.17.21",
"pinia": "^2.1.7",
"splitpanes": "^3.1.5",
"vue": "^3.4.19",
"vue-dompurify-html": "^5.1.0",
"vue-router": "^4.3.0",
"vue-tsc": "^2.1.10"
},
"devDependencies": {
"@types/splitpanes": "^2.2.6",
"@typescript-eslint/eslint-plugin": "^7.1.1",
"@typescript-eslint/parser": "^7.1.1",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^4.1.0",
"@vue/eslint-config-typescript": "^12.0.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-vue": "^9.22.0",
"sass": "^1.71.1",
"typescript": "^5.2.2",
"unplugin-auto-import": "^0.17.5",
"unplugin-icons": "^0.18.5",
"unplugin-vue-components": "^0.26.0",
"vite": "^5.1.4"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
"version": "1.2.6",
"resolved": "https://registry.npmmirror.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
"integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
......@@ -1801,7 +1847,15 @@
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"binary-extensions": {
"node_modules/bignumber.js": {
"version": "9.3.0",
"resolved": "https://registry.npmmirror.com/bignumber.js/-/bignumber.js-9.3.0.tgz",
"integrity": "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==",
"engines": {
"node": "*"
}
},
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
......
......@@ -14,6 +14,7 @@
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.6.7",
"bignumber.js": "^9.3.0",
"dayjs": "^1.11.13",
"element-plus": "^2.6.0",
"lodash-es": "^4.17.21",
......
......@@ -4,11 +4,16 @@
</el-config-provider>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { ref, computed, onMounted } from 'vue'
import zhCn from 'element-plus/es/locale/lang/zh-cn.mjs'
import en from 'element-plus/es/locale/lang/en.mjs'
const language = ref('zh-cn')
const locale = computed(() => (language.value === 'zh-cn' ? zhCn : en))
// 在组件挂载时清空 localStorage
onMounted(() => {
localStorage.removeItem('socket_connect')
})
</script>
<style lang="scss">
body {
......
......@@ -60,7 +60,13 @@ export function getFilePath() {
import.meta.env.VITE_API_BASE_URL + import.meta.env.VITE_API_BASE_UPLOAD_URL
)
}
export function getWsUrl() {
if (location.protocol === 'https:') {
return 'wss://' + location.host
} else {
return 'ws://' + location.host
}
}
export const filePath = getFilePath()
export default axios
import { BasePaginationData, BaseRespData } from '@/types/api'
import { BasePaginationData, BaseRespData, RejectParams } from '@/types/api'
import axios from './axios'
import { PaymentForm } from '@/types/api/index.ts'
import {
LogListData,
OrderData,
......@@ -12,11 +13,15 @@ import {
InspectionData,
} from '@/types/api/order'
import {
apiSubmitPodOrderForm,
DeliveryNoteData,
DeliveryNoteSearchForm,
DetailForm,
LogListsData,
ProductionOrder,
ShipmentOrderDetailData,
updatePriceForm,
IUpdatePrice,
} from '@/types/api/deliveryNote'
import {
AccountStatementNote,
......@@ -78,7 +83,16 @@ export function reCreateScriptUrlApi(id: number) {
},
)
}
export function apiGetCraftGroup(id?: number | string) {
return axios.get<never, BaseRespData<never>>(
'pod/podReconciliation/getCraftGroup',
{
params: {
id,
},
},
)
}
// 打印生产单
export function printOrder(ids: number[]) {
return axios.post<never, BaseRespData<never>>(
......@@ -129,7 +143,27 @@ export function qaFinishedApi(data: InspectionData[]) {
data,
)
}
export function apiSetCraftData({
id,
craftTotalPrice,
recNumber,
craftPriceList,
}: {
id: number | undefined
craftTotalPrice: number | undefined
recNumber:string|undefined
craftPriceList: IUpdatePrice[]
}) {
return axios.post<never, BaseRespData<never>>(
'pod/podReconciliation/setCraftData',
{
id,
recNumber,
craftTotalPrice,
craftPriceList,
},
)
}
// 发货保存
export function saveOrder(
sumbitSendOutList: ShipmentOrderRes[],
......@@ -251,6 +285,21 @@ export function customJomallReconciliation(
)
}
export function podReconciliationList(
data: AccountStatementNoteSearchForm,
currentPage: number,
pageSize: number,
) {
return axios.post<never, BasePaginationData<AccountStatementNote>>(
'pod/podReconciliation/list',
{
...data,
currentPage,
pageSize,
},
)
}
export function printDeliveryNote(data: string[], userMark?: string) {
return axios.post<never, BaseRespData<never>>(
'factory/customJomallShipment/printInvoiceStatistics',
......@@ -302,6 +351,17 @@ export function getReconciliationAmount(id?: number) {
)
}
export function podReconciliation(id?: number) {
return axios.get<never, BaseRespData<CountStatus[]>>(
'pod/podReconciliation/groupCount',
{
params: {
id,
},
},
)
}
export function getCustomJomallReconciliationById(id?: number) {
return axios.get<never, BaseRespData<OrderData>>(
'factory/customJomallReconciliation/getById',
......@@ -337,17 +397,97 @@ export function exportExcelApi(ids: string) {
},
})
}
export function exportPodExcelApi(ids: string) {
return axios.get<never, BaseRespData<never>>(
'pod/podReconciliation/exportExcel',
{
params: {
ids,
},
},
)
}
export function getShipmentDetailsById(data: DetailForm) {
return axios.post<never, BasePaginationData<BillOrderDetailData>>(
'reconciliation/getItemList',
data,
)
}
export function apiupdateByPodExcel(formData: never) {
return axios.post('pod/podReconciliation/updateByExcel', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
}
export function apiRejectedPodReconciliation(params?: RejectParams) {
return axios.post<never, BaseRespData<never>>(
'pod/podReconciliation/rejectedLocal',
params,
)
}
export function getPodShipmentDetailsById(data: DetailForm) {
return axios.post<never, BasePaginationData<BillOrderDetailData>>(
'pod/podReconciliation/getItemList',
data,
)
}
export function apiLogList(id?: number) {
return axios.get<never, BaseRespData<LogListsData[]>>(
'pod/podReconciliation/getLog',
{ params: { id } },
)
}
export function apiItemLogList(id?: number) {
return axios.get<never, BaseRespData<LogListsData[]>>(
'pod/podReconciliation/getItemLog',
{ params: { id } },
)
}
export function apiSubmitPodOrder(params: apiSubmitPodOrderForm) {
return axios.get<never, BaseRespData<ShipmentOrderDetailData>>(
'pod/podReconciliation/create',
{ params },
)
}
export function apiPodBillSubmit(ids: number[]) {
return axios.post<never, BaseRespData<ShipmentOrderDetailData>>(
'pod/podReconciliation/submission',
{ ids },
)
}
export function apiPodUpdatePrice(data: updatePriceForm) {
return axios.get<never, BaseRespData<never>>(
'pod/podReconciliation/updatePrice',
{ params: data },
)
}
export function updateRecPrice(data: updatePriceForm) {
return axios.get<never, BaseRespData<never>>(
'pod/podReconciliation/updateRecPrice',
{ params: data },
)
}
export function getLogListApi(id?: number) {
return axios.get<never, BaseRespData<LogList[]>>('reconciliation/getLog', {
params: { id },
})
}
export function getPodLogListApi(id?: number) {
return axios.get<never, BaseRespData<LogList[]>>(
'pod/podReconciliation/getLog',
{
params: { id },
},
)
}
export function auditOrderApi(url: string, data: string) {
return axios.get(url, {
params: {
......@@ -369,6 +509,37 @@ export function getShipmentOrderDetailById(id?: number | string) {
export function confirmOrderApi(data: ConfirmOrderForm) {
return axios.post<never, BaseRespData<never>>('reconciliation/confirm', data)
}
export function confirmPodOrderApi(data: ConfirmOrderForm) {
return axios.post<never, BaseRespData<never>>(
'pod/podReconciliation/confirm',
data,
)
}
export function apiRejectionOfReview({
ids,
description,
recNumbers,
}: {
ids: string
recNumbers: string
description: string
}) {
return axios.post<never, BaseRespData<never>>(
'pod/podReconciliation/rejectionOfReview',
{
ids,
description,
recNumbers,
},
)
}
export function apiBillPodPayment(data: PaymentForm) {
return axios.post<never, BaseRespData<never>>(
'pod/podReconciliation/payment',
data,
)
}
export function rejectOrderApi({
ids,
description,
......
import { BasePaginationData, BaseRespData } from '@/types/api'
import {
ProductList,
PodUsOrderListData,
SearchForm,
Tab,
LogListData,
} from '@/types/api/podUsOrder'
import axios from './axios'
import { PodMakeOrderData } from '@/types/api/podMakeOrder'
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: number[]) {
return axios.post<never, BaseRespData<never>>(
'factory/podJomallOrderProductUs/downloadDesignImages',
id,
)
}
export function productionQueryApi(id: number, podJomallOrderUsId: number) {
return axios.post<never, BasePaginationData<never>>(
'factory/podJomallOrderProductUs/completeDelivery',
{
id,
podJomallOrderUsId,
},
)
}
export function printProductionOrderApi(orderIds: number[]) {
return axios.post<never, BaseRespData<string>>(
'factory/podJomallOrderUs/printProducePdf',
orderIds,
)
}
export function getOrderDetailById(id: number) {
return axios.get<never, BaseRespData<ProductList>>(
`factory/podJomallOrderProductUs/getProductUsById?id=${id}`,
)
}
export function getPackingDataApi(
code: string,
factoryNo: number,
box: number | null,
) {
return axios.get<never, BaseRespData<PodMakeOrderData>>(
'/factory/podJomallOrderUs/getPodBoxDetailsBySkuOrNo',
{
params: {
podJomallUsNo: code,
box,
factoryNo,
},
},
)
}
export function getPodBoxListApi(factoryNo: number | string) {
return axios.get<never, BaseRespData<PodMakeOrderData[]>>(
'factory/podJomallOrderUs/getPodBoxOrderDetails',
{
params: { factoryNo },
},
)
}
export function submitInspectionApi(
data: { id: number; version?: number }[],
boxIndex: number | null,
) {
return axios.post<never, BaseRespData<never>>(
`factory/podJomallOrderUs/podPrintOrderComplete?box=${boxIndex}`,
{
orderParamList: data,
},
)
}
export function clearBoxApi(factoryNo: number, box: number | null) {
return axios.get<never, BaseRespData<never>>(
'factory/podJomallOrderUs/delPodBoxOrderDetailsByBox',
{
params: { factoryNo, box },
},
)
}
export function clearAllBoxApi() {
return axios.get<never, BaseRespData<never>>(
'factory/podJomallOrderUs/delPodBoxOrderDetails',
)
}
export function updateRemarkApi(id: number, content: string) {
return axios.post<never, BaseRespData<never>>(
'factory/podJomallOrderUs/addRemark',
{ id, content },
)
}
......@@ -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>
......
......@@ -16,9 +16,11 @@ import { getToken } from '@/api/axios'
import UserPage from '@/views/UserPage.vue'
import DeliveryNotePage from '@/views/DeliveryNotePage.vue'
import AccountStatementNote from '@/views/AccountStatementNote.vue'
import PodBillOrder from '@/views/podBillOrder/index.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 +52,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发货单',
......@@ -80,9 +89,15 @@ const router = createRouter({
{
path: '/account/statement-note',
meta: {
title: '对账单',
title: '定制对账单',
},
component: AccountStatementNote,
},{
path: '/account/pod-bill-order',
meta: {
title: 'POD对账单',
},
component: PodBillOrder,
},
{
path: '/typesetting-management/list',
......
......@@ -26,13 +26,30 @@ const menu: MenuItem[] = [
id: 7,
label: 'POD订单',
},
{
index: '/pod-us-order/list',
id: 8,
label: 'POD订单(US)',
},
],
},
{
index: '/account/statement-note',
index: '11',
id: 3,
label: '对账单',
children: [
{
index: '/account/statement-note',
id: 1,
label: '定制对账单',
},
{
index: '/account/pod-bill-order',
id: 2,
label: 'POD对账单',
},
],
},
{
index: '2',
......
import { defineStore } from 'pinia'
import {
OrderData,
PodMakeOrderData,
ProductList,
} from '@/types/api/podMakeOrder'
import { getPodBoxListApi } from '@/api/podUsOrder'
export interface OrderStoreState {
podBoxList?: PodMakeOrderData[]
podBoxIndex?: number | null
socketConnect?: string
}
const useOrderStore = defineStore('order', {
state: () => {
return {
podBoxList: [],
podBoxIndex: null,
socketConnect: '',
} as OrderStoreState
},
actions: {
async setPodBoxList(content: {
boxList: PodMakeOrderData[] | OrderData | null
factoryNo: number | string
box?: number
data?: OrderData
}) {
const { factoryNo, boxList, box, data } = content
if (Array.isArray(boxList)) {
this.podBoxList = boxList
} else {
const index = this.podBoxList?.findIndex((item) => item.box === box)
if (index === -1) {
try {
const res = await getPodBoxListApi(factoryNo)
const boxList = res.data.map((item) => {
if (res.data) {
const productList = item?.data?.productList || []
const pickingNumber = productList.reduce(
(prev: number, item1: ProductList) => {
if (item1.count) {
return prev + item1.count
}
return prev
},
0,
)
if (item.data) {
item.data.pickingNumber = pickingNumber
}
}
return item
})
this.podBoxList = boxList
this.podBoxIndex = box
} catch (error) {
console.error(error)
}
} else if (box !== 0 && box !== undefined) {
const arr = this.podBoxList
if (arr) {
arr[box - 1] = {
box,
data: data || boxList || null,
}
}
console.log('222arr1111', arr)
this.podBoxList = arr
this.podBoxIndex = box
}
}
},
// 清空所有箱子
clearPodBoxList() {
this.podBoxList = []
},
// 设置当前箱子
setPodBox(box: number) {
this.podBoxIndex = box
},
// 清空当前箱子
clearPodBox() {
this.podBoxIndex = null
},
setSocketConnect(connect: string) {
this.socketConnect = connect
},
},
})
export default useOrderStore
......@@ -114,3 +114,6 @@ img {
.mt-10 {
margin-top: 10px;
}
.mb-10 {
margin-bottom: 10px;
}
......@@ -10,17 +10,26 @@ export interface AccountStatementNoteSearchForm {
status?: number | string | null
dateRange?: string[]
billNumber?: string
subOrderNumber?: string
shipmentNumber?: string
recNumber?: string
orderNumber?: string
factory_status?: string
erp_status?: string
startTime?: string
start_time?: string
endTime?: string
end_time?: string
orderNumber?: string
}
export interface AccountStatementNote {
create_time?: string
product_total_amount?: number
craft_total_amount?: number
pass_num?: number
factory_code?: string
total_amount?: string | number
actual_amount?: string | number
num?: number
end_time?: string
rec_number?: string
......@@ -35,6 +44,9 @@ export interface AccountStatementNote {
id?: number
erp_total_amount?: number
dataVersion: ''
water_list?: {
url: string | undefined
}[]
}
export interface BillOrderDetailData {
create_time?: string
......@@ -59,11 +71,13 @@ export interface BillOrderDetailData {
export interface LogList {
create_time?: string
employee_id?: number
info_id?: number
description?: string
id: string
diy_id: number
employee_account?: string
employee_name?: string
employee_id?: string
description?: string
}
export interface ItemList {
......@@ -85,5 +99,5 @@ export interface ItemList {
export interface ConfirmOrderForm {
pass?: number | null
description?: string
ids?:string
ids?: string
}
......@@ -12,6 +12,11 @@ export interface DeliveryNoteSearchForm {
status?: number | null
}
export interface IUpdatePrice {
process: string
base_price: string | number
add_price: string | number
}
export interface DeliveryNoteData {
id: number
billNumber?: string
......@@ -182,11 +187,49 @@ export interface DbFactory {
authorize_number?: number
status?: number
}
export interface LogListsData {
id: number
diy_id: number
employee_account?: string
employee_name?: string
employee_id?: string
description?: string
create_time?: string
}
export interface LogListData {
id?: string | number | null
infoId?: number
employeeId?: number
employeeAccount?: string
description?: string
createTime?: string
}
export interface DetailForm {
billNumber?: string
orderNumber?:string
orderNumber?: string
baseSku?: string
shipmentNumber?: string
process?: string
supplierItemNo?: string
dateRange?: string[]
endTime?: string
startTime?: string
subOrderNumber?: string
rows?: number
page?: number
pageSize?: number
infoId?: number
}
export interface BillForm {
timeRange: [string, string] | []
}
export interface apiSubmitPodOrderForm {
startDate?: string
endDate?: string
}
export interface updatePriceForm {
ids?: string
infoId?: number
price?: string
}
export interface BaseRespData<D> {
code: number
message?: string
......@@ -7,12 +6,14 @@ export interface BaseRespData<D> {
export interface PaginationData<D> {
page?: PaginationData<D>
craftPrice?: string
costPrice?: string
total: number
size: number
current: number
records: D[]
}
export interface Statistics<D> {
export interface Statistics<D> {
sumNotPassNum: number
sumPassNum: number
sumShipmentNum: number
......@@ -28,3 +29,21 @@ export interface Statistics<D> {
export type BasePaginationData<D> = BaseRespData<PaginationData<D>>
export type StatisticsData<D> = BaseRespData<Statistics<D>>
export interface baseRes {
code: number
msg: string
}
export interface PaymentForm {
waterList: string
id?: number | string
actualAmount?: number | string
payableAmount?: number | string
recNumber?: number | string
}
export interface RejectParams {
id: number
description?: string
ids?: string
pass?: number
}
......@@ -81,6 +81,7 @@ export interface ProductList {
subOrderNumber?: string
shopNumber?: string
material?: string
materialPrice?: number|null
count?: number
baseSku?: string
erpSubOrderNumber?: string
......
export interface PodMakeOrderData {
data: OrderData | null
warehouseId?: number
box?: number
addDate?: string
}
export interface OrderData {
shipmentsNote?: string
filePath?: string
fileData?: string
logisticsWayName?: string
orderStatus?: number
salesPlatform?: string
logisticsCompanyId?: number
blocked?: boolean
logisticsCompanyName?: string
logisticsWayNameId?: number
shopNumber?: string
id?: number
shopId?: number
systemWeight?: number
trackingNumber?: string
productList?: ProductList[]
purchaseNumber?: number
pickingNumber?: number
printResult?: string
remark?: string
version?: number
}
export interface ProductList {
warehouseSkuImage: string
warehouseSku: string
orderItemId: string
warehouseSkuName: string
productMark: string
podJomallNo: string
id: number
mainSku: string
imageAry: string
purchaseNumber: number
count?: number
power?: boolean
variantImage?: string
}
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
customizedQuantity: string
startTime: string | null
endTime: string | null
}
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
craftName?: string | null
lanshouAddress?: string | null
shopNumber?: string | null
factoryOrderNumber?: string | null
}
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[]
}
......@@ -101,6 +101,7 @@ export interface LogList {
employeeId: number
description: string
createdTime: string
createTime?: string
}
export interface ImageListData {
......
import { onMounted } from 'vue'
export interface LODOPObject extends HTMLObjectElement {
VERSION: string;
CVERSION: string;
SET_LICENSES: (companyName: string, licenseA: string, licenseB: string, licenseC: string) => void;
GET_PRINTER_COUNT: () => number;
GET_PRINTER_NAME: (index: number) => string;
PRINT_INIT: (printName: string) => void;
SET_PRINTER_INDEX: (printerName: string) => void | string | number | boolean;
SET_PRINT_MODE: (mode: string, value: boolean) => void;
On_Return: (id: string, value: string) => void;
On_Return_Remain: boolean;
SEND_PRINT_RAWDATA: (data: string) => void;
ADD_PRINT_PDF: (x: number, y: number, width: string, height: string, url: string) => void;
GET_VALUE: (valueType: string, valueIndex: string | unknown) => string;
PRINT: () => string;
PRINTA: () => string;
}
export interface CLodopObject {
CVERSION: string;
GET_STATUS: () => number;
GET_VALUE: (valueType: string, valueIndex: string | unknown) => string;
}
declare global {
interface Window {
LODOP: new () => LODOPObject;
CLODOP: CLodopObject;
getCLodop: () => LODOPObject;
}
}
export default function useLodop() {
let CLodopJsState: 'loading' | 'complete' | null = null
let CLodopIsLocal = false
let CreatedOKLodopObject: LODOPObject | null = null
// 检查是否需要使用 CLODOP
const checkNeedCLodop = (): boolean => {
try {
const ua = navigator.userAgent
if (ua.match(/Windows\sPhone/i)) return true
if (ua.match(/iPhone|iPod|iPad/i)) return true
if (ua.match(/Android/i)) return true
if (ua.match(/Edge\D?\d+/i)) return true
const verTrident = ua.match(/Trident\D?\d+/i)
const verIE = ua.match(/MSIE\D?\d+/i)
let verOPR = ua.match(/OPR\D?\d+/i)
let verFF = ua.match(/Firefox\D?\d+/i)
const x64 = ua.match(/x64/i)
if (!verTrident && !verIE && x64) return true
else if (verFF) {
verFF = verFF[0].match(/\d+/)
if ((verFF && parseInt(verFF[0]) >= 41) || x64) return true
} else if (verOPR) {
verOPR = verOPR[0].match(/\d+/)
if (verOPR && parseInt(verOPR[0]) >= 32) return true
} else if (!verTrident && !verIE) {
let verChrome = ua.match(/Chrome\D?\d+/i)
if (verChrome) {
verChrome = verChrome[0].match(/\d+/)
if (verChrome && parseInt(verChrome[0]) >= 41) return true
}
}
return false
} catch (err) {
return true
}
}
const needCLodop = checkNeedCLodop()
const showMsg = (msg: string) => {
ElMessageBox.alert(msg, '提示', {
dangerouslyUseHTMLString: true,
closeOnClickModal: false,
center: true,
})
}
// 初始化 LODOP
const initLodop = () => {
if (CLodopJsState == 'loading' || CLodopJsState == 'complete') return
CLodopJsState = 'loading'
const head =
document.head ||
document.getElementsByTagName('head')[0] ||
document.documentElement
const JS1 = document.createElement('script')
const JS2 = document.createElement('script')
if (window.location.protocol == 'https:') {
JS1.src = 'https://localhost.lodop.net:8443/CLodopfuncs.js'
JS2.src = 'https://localhost.lodop.net:8444/CLodopfuncs.js'
} else {
JS1.src = 'http://localhost:8000/CLodopfuncs.js'
JS2.src = 'http://localhost:18000/CLodopfuncs.js'
}
JS1.onload = JS2.onload = function () {
CLodopJsState = 'complete'
}
JS1.onerror = JS2.onerror = function () {
CLodopJsState = 'complete'
}
head.insertBefore(JS1, head.firstChild)
head.insertBefore(JS2, head.firstChild)
CLodopIsLocal = !!(JS1.src + JS2.src).match(/\/\/localho|\/\/127.0.0./i)
}
const getCLodop = (oOBJECT: LODOPObject | null, oEMBED: LODOPObject | null): LODOPObject | null => {
const strFontTag = '<font _#_>打印控件'
const strLodopInstall =
strFontTag +
'未安装!点击这里<a href=http://www.c-lodop.com/download/CLodop_Setup_for_Win64NT_6.623EN.zip target=_self>执行安装</a>'
const strLodopUpdate =
strFontTag +
'需要升级!点击这里<a href=install_lodop32.exe target=_self>执行升级</a>'
const strLodop64Install =
strFontTag +
'未安装!点击这里<a href=install_lodop64.exe target=_self>执行安装</a>'
const strLodop64Update =
strFontTag +
'需要升级!点击这里<a href=install_lodop64.exe target=_self>执行升级</a>'
const strCLodopInstallA =
'<font >Web打印服务CLodop未安装启动,点击下列按钮执行安装<br><a href=http://www.c-lodop.com/download/lodop6.226_Clodop6.623.zip target=_self>安装32位</a> <a href=http://www.c-lodop.com/download/CLodop_Setup_for_Win64NT_6.623EN.zip target=_blank>安装64位</a><br>'
const strCLodopInstallB =
'(若此前已安装过,可点这里直接<a href=CLodop.protocol:setup target=_self>再次启动</a>)'
const strCLodopUpdate =
'<font >Web打印服务CLodop需升级!点击这里<a href=CLodop_Setup_for_Win32NT.exe target=_self>执行升级</a>'
const strLodop7FontTag = '<font >Web打印服务Lodop7'
const strLodop7HrefX86 =
'点击这里<a href=https://www.c-lodop.com/download/Lodop7.105_Linux_X86_64_CN.deb.zip target=_self>下载安装</a>(下载后解压,点击lodop文件开始执行)'
const strLodop7HrefARM =
'点击这里<a href=https://www.c-lodop.com/download/Lodop7.105_Linux_ARM64_CN.deb.zip target=_self>下载安装</a>(下载后解压,点击lodop文件开始执行)'
const strLodop7Install_X86 =
strLodop7FontTag + '未安装启动,' + strLodop7HrefX86
const strLodop7Install_ARM =
strLodop7FontTag + '未安装启动,' + strLodop7HrefARM
const strLodop7Update_X86 = strLodop7FontTag + '需升级,' + strLodop7HrefX86
const strLodop7Update_ARM = strLodop7FontTag + '需升级,' + strLodop7HrefARM
const strInstallOK = ',成功后请刷新本页面或重启浏览器。</font>'
let LODOP: LODOPObject | null = null
try {
const isWinIE =
/MSIE/i.test(navigator.userAgent) ||
/Trident/i.test(navigator.userAgent)
const isWinIE64 = isWinIE && /x64/i.test(navigator.userAgent)
const isLinuxX86 =
/Linux/i.test(navigator.userAgent) && /x86_64/i.test(navigator.userAgent)
const isLinuxARM =
/Linux/i.test(navigator.userAgent) && /aarch64/i.test(navigator.userAgent)
if (needCLodop || isLinuxX86 || isLinuxARM) {
try {
LODOP = window.getCLodop()
} catch (err) {
console.error(err)
}
if (!LODOP && CLodopJsState !== 'complete') {
if (CLodopJsState == 'loading')
alert('网页还没下载完毕,请稍等一下再操作.')
else alert('未曾加载Lodop主JS文件,请先调用loadCLodop过程.')
return null
}
let strAlertMessage
if (!LODOP) {
if (isLinuxX86) strAlertMessage = strLodop7Install_X86
else if (isLinuxARM) strAlertMessage = strLodop7Install_ARM
else
strAlertMessage =
strCLodopInstallA + (CLodopIsLocal ? strCLodopInstallB : '')
showMsg(strAlertMessage + strInstallOK)
return null
} else {
if (isLinuxX86 && LODOP.CVERSION < '7.0.4.3')
strAlertMessage = strLodop7Update_X86
else if (isLinuxARM && LODOP.CVERSION < '7.0.4.3')
strAlertMessage = strLodop7Update_ARM
else if (window.CLODOP.CVERSION < '4.1.6.1')
strAlertMessage = strCLodopUpdate
if (strAlertMessage) showMsg(strAlertMessage + strInstallOK)
}
} else {
//==如果页面有Lodop插件就直接使用,否则新建:==
if (oOBJECT || oEMBED) {
if (isWinIE) LODOP = oOBJECT
else LODOP = oEMBED
} else if (!CreatedOKLodopObject) {
LODOP = document.createElement('object') as unknown as LODOPObject
LODOP.setAttribute('width', '0')
LODOP.setAttribute('height', '0')
LODOP.setAttribute(
'style',
'position:absolute;left:0px;top:-100px;width:0px;height:0px;',
)
if (isWinIE)
LODOP.setAttribute(
'classid',
'clsid:2105C259-1E0C-4534-8141-A753534CB4CA',
)
else LODOP.setAttribute('type', 'application/x-print-lodop')
document.documentElement.appendChild(LODOP)
CreatedOKLodopObject = LODOP
} else LODOP = CreatedOKLodopObject
//==Lodop插件未安装时提示下载地址:==
if (!LODOP || !LODOP.VERSION) {
showMsg(
(isWinIE64 ? strLodop64Install : strLodopInstall) + strInstallOK,
)
return null
}
if (LODOP.VERSION < '6.2.2.6') {
showMsg(
(isWinIE64 ? strLodop64Update : strLodopUpdate) + strInstallOK,
)
}
}
if (location.host.indexOf('jomalls.com') !== -1) {
LODOP.SET_LICENSES('', '90D9E6991E35971D8FC86B525F637C8A9F9', '', '')
} else {
LODOP.SET_LICENSES('', 'A9F23BF819622E36A8775E717685989E9FC', '', '')
}
return LODOP
} catch (err) {
alert('getLodop出错:' + err)
}
return null
}
onMounted(() => {
initLodop()
})
return {
getCLodop,
}
}
......@@ -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()
}
......
import { getWsUrl } from '../api/axios'
import useOrderStore from '../store/order'
interface NotificationOptions {
body: string
icon: string
data: string
requireInteraction: boolean
}
export interface WebSocketMessage {
code?: string
content?: string
type?: string
data?: unknown
[key: string]: string | unknown | undefined
}
interface InitOptions {
account: string
factoryNo: string
}
type MessageCallback = (
data: WebSocketMessage,
sendSystemMessage: (msg: string) => void,
) => void
type OpenCallback = () => void
function sendSystemMessage(msg: string): void {
if (window.Notification && Notification.permission === 'granted') {
const notificationOptions: NotificationOptions = {
body: msg,
icon: './favicon.ico',
data: 'I like peas.',
requireInteraction: true,
}
const n = new Notification('消息通知', notificationOptions)
n.onclick = (event: Event) => {
event.preventDefault()
window.open('/home', '_blank')
}
setTimeout(() => {
n.close()
}, 10000)
}
}
const showReconnectingMsg = (): void => {
ElMessageBox.alert('消息服务已断开,正在重新连接,请稍候', {
showClose: true,
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
}
class Im {
private socket: WebSocket | null = null
private _wsUrl: string = ''
// private userId: string = ''
private _onMessageCallback?: MessageCallback
private _onOpenCallback?: OpenCallback
private _heartbeatTimer: number | null = null
private _heartbeatTimeoutTimer: number | null = null
private _reconnectingTimer: number | null = null
private num: number = 0
constructor() {
this._onWsOpen = this._onWsOpen.bind(this)
this._onWsMessage = this._onWsMessage.bind(this)
this._onWsClose = this._onWsClose.bind(this)
this._onWsError = this._onWsError.bind(this)
}
private _onWsOpen(): void {
console.log('服务器连接成功')
localStorage.setItem('socket_connect', 'online')
this._onOpenCallback?.()
this._startHeartbeat()
}
private _onWsMessage(event: MessageEvent): void {
let data: WebSocketMessage = {}
if (typeof event.data === 'string') {
try {
data = JSON.parse(event.data)
} catch (error) {
data = {}
}
}
this._onHeartbeatMessage()
this._onMessageCallback?.(data, sendSystemMessage)
}
private _onWsClose(): void {
console.log('服务器关闭')
this._destroyWebSocket(true)
}
private _onWsError(): void {
console.log('连接出错')
this._destroyWebSocket(true)
}
private _sendHeartbeat(): void {
if (!this.socket) return
this.send({ code: 'HEALTH' })
if (this._heartbeatTimeoutTimer) {
window.clearTimeout(this._heartbeatTimeoutTimer)
}
this._heartbeatTimeoutTimer = window.setTimeout(() => {
this._destroyWebSocket(true)
}, 5 * 1000)
}
private _onHeartbeatMessage(): void {
console.log('心跳')
useOrderStore().setSocketConnect('online')
if (this._heartbeatTimeoutTimer) {
window.clearTimeout(this._heartbeatTimeoutTimer)
}
}
private _startHeartbeat(): void {
this._stopHeartbeat()
this._sendHeartbeat()
this._heartbeatTimer = window.setInterval(
() => this._sendHeartbeat(),
10 * 1000,
)
}
private _stopHeartbeat(): void {
if (this._heartbeatTimer) {
window.clearInterval(this._heartbeatTimer)
}
if (this._heartbeatTimeoutTimer) {
window.clearTimeout(this._heartbeatTimeoutTimer)
}
}
private _scheduleReconnect(): void {
if (!this.num) this.num = 0
this.num++
if (this.num > 5) {
ElMessageBox.alert('尝试重连消息服务失败,请刷新重试')
return
}
showReconnectingMsg()
this._reconnectingTimer = window.setTimeout(() => {
this._createWebSocket()
}, 2000)
}
private _destroyWebSocket(reconnect?: boolean): void {
if (!this.socket) return
this._stopHeartbeat()
if (this._reconnectingTimer) {
window.clearTimeout(this._reconnectingTimer)
}
this.socket.removeEventListener('open', this._onWsOpen)
this.socket.removeEventListener('message', this._onWsMessage)
this.socket.removeEventListener('close', this._onWsClose)
this.socket.removeEventListener('error', this._onWsError)
this.socket.close(1000)
this.socket = null
localStorage.removeItem('socket_connect')
useOrderStore().setSocketConnect('offline')
if (reconnect) this._scheduleReconnect()
}
private _createWebSocket(): void {
if (!this._wsUrl) return
const socket = new WebSocket(this._wsUrl)
socket.addEventListener('open', this._onWsOpen)
socket.addEventListener('message', this._onWsMessage)
socket.addEventListener('close', this._onWsClose)
socket.addEventListener('error', this._onWsError)
this.socket = socket
}
init(
options: InitOptions,
msgfunc?: MessageCallback,
openfunc?: OpenCallback,
): Promise<void> {
return new Promise((resolve, reject) => {
const { account, factoryNo } = options
const socket_connect = localStorage.getItem('socket_connect')
if (socket_connect === 'online') {
resolve()
return
}
if (!window.WebSocket) {
reject(new Error('WebSocket is not supported'))
return
}
this._onMessageCallback = msgfunc
this._onOpenCallback = () => {
openfunc?.()
resolve()
}
this._destroyWebSocket()
this._wsUrl = `${getWsUrl()}/ws/websocket/${factoryNo}/${account}`
this._createWebSocket()
})
}
send(options: WebSocketMessage): void {
this.socket?.send(JSON.stringify(options))
}
close(): void {
this._destroyWebSocket()
}
}
export default new Im()
......@@ -163,6 +163,15 @@
item.price || '--'
}}</span>
</div>
<div
v-if="item.materialPrice && item.materialPrice > 0"
class="order-list-expand_item_info_title"
>
<span class="order-list-expand_item_label">材质价格:</span>
<span class="order-list-expand_item_value">{{
item.materialPrice
}}</span>
</div>
<div class="order-list-expand_item_info_title font-bold">
<span class="order-list-expand_item_label">购买数:</span>
<span class="order-list-expand_item_value">{{ item.num || 0 }}</span>
......
......@@ -97,10 +97,10 @@
{{ detail?.factorySubOrderNumber }}
</p>
</div>
<div :title="detail?.thirdOrderNumber || ''" class="div-item">
<div :title="detail?.thirdSubOrderNumber || ''" class="div-item">
<span>第三方生产单号</span>
<p>
{{ detail?.thirdOrderNumber }}
{{ detail?.thirdSubOrderNumber }}
</p>
</div>
<div :title="String(detail?.process)" class="div-item">
......@@ -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="fastKey === 'detail' ? '查看详情' : '快捷生产'"
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 v-if="fastKey === 'fastProduction'" 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
:style="{
visibility: fastKey === 'fastProduction' ? 'visible' : 'hidden',
}"
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"
size="large"
@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({
type: {
default: 0,
type: Number,
},
detailVisible: {
default: false,
type: Boolean,
},
detailData: {
default: null,
type: Object,
},
fastKey: {
default: '',
type: String,
},
})
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 && props.fastKey === 'fastProduction') {
confirmQuery(len, 0)
}
placeholderText.value =
'扫描枪输入生产单号,录入下一单本单自动生产完成,最后一单扫两次完成生产'
trackingNumberRef.value && trackingNumberRef.value.focus()
TrackingNumber.value = ''
isAutoSure.value = false
sendNum.value = 0
}
},
)
watch(
() => props.detailData,
(newVal) => {
detail.value = {
id: -1,
podJomallOrderUsId: -1,
imgList: [],
}
if (newVal && Object.keys(newVal).length > 0) {
const d = JSON.parse(JSON.stringify(newVal))
if (d.note) {
d.note = JSON.parse(d.note)
} else {
d.note = []
}
if (d.imageAry) {
d.imgList = d.imageAry
} else {
d.imgList = []
}
detail.value = d
}
},
{ deep: true },
)
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 = ''
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%;
object-fit: contain;
}
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>
<ElDialog
v-model="visible"
title="POD打单"
fullscreen
:close-on-click-modal="false"
:close-on-press-escape="false"
:before-close="handleClose"
style="top: 60px"
modal-class="pod-make-order-dialog"
@opened="handleOpened"
@close="onClose"
>
<template #title>
<div class="title">
<span>POD打单</span>
<span v-if="socketConnect === 'online'" class="online">[在线]</span>
<span v-else class="offline">[离线]</span>
</div>
</template>
<div class="pod-make-order-content">
<div class="left-content">
<div class="head-form">
<div class="form-item">
<ElSelect
v-model="sheetPrinter"
placeholder="请选择打印机"
style="width: 200px"
@change="handlePrinterChange"
>
<ElOption
v-for="item in printDeviceList"
:key="item"
:label="item"
:value="item"
/>
</ElSelect>
</div>
<div class="form-item" style="flex: 1">
<ElInput
ref="productionOrderRef"
v-model="productionOrder"
placeholder="请输入生产单号"
clearable
style="width: 100%"
@keyup.enter="handleSearch"
/>
</div>
<div class="form-item">
<ElButton type="primary" @click="handleSearch">查询</ElButton>
</div>
<div class="form-item">
<ElButton type="danger" @click="clearAllBox">清空所有箱子</ElButton>
</div>
</div>
<div class="basic-info">
<div class="basic-info-item">
<span>物流跟踪号:</span>
<span>{{ podOrderDetailsData?.trackingNumber }}</span>
</div>
<div class="basic-info-item">
<span>店铺单号:</span>
<span>{{ podOrderDetailsData?.shopNumber }}</span>
</div>
<div class="basic-info-item">
<span>商品总数量:</span>
<span>{{ podOrderDetailsData?.purchaseNumber }}</span>
</div>
<div class="basic-info-item">
<span>拣货数量:</span>
<span>{{ podOrderDetailsData?.pickingNumber }}</span>
</div>
<!-- <div class="basic-info-item">
<span>物流公司:</span>
<span></span>
</div>
<div class="basic-info-item">
<span>物流方式:</span>
<span></span>
</div> -->
<div class="basic-info-item">
<span>发货备注:</span>
<span>{{ podOrderDetailsData?.remark }}</span>
</div>
</div>
<div class="table-content">
<TableView
ref="tableRef"
:paginated-data="podOrderDetailsData?.productList || []"
:columns="podOrderDetailsColumns"
highlight-current-row
@row-click="handleRowClick"
@current-change="handleCurrentChange"
>
<template #image="{ row }">
<img :src="row.variantImage" alt="" />
</template>
<template #verifyResult="{ row }">
<el-icon
v-if="row.power"
style="color: #00cc00; font-size: 24px; font-weight: 900"
><Check
/></el-icon>
</template>
</TableView>
</div>
</div>
<div class="middle-content">
<div class="box-top">
<div class="box-top-item">
<span class="box-top-item-box-index">
{{ boxIndex }}
</span>
<span class="box-top-item-box-index-text">号箱</span>
<span style="font-size: 30px">放入第</span>
<div class="box-top-item-box-index-number">
{{ podOrderDetailsData?.pickingNumber }}
</div>
<span style="font-size: 30px">件商品</span>
</div>
<div class="box-top-item-status">
<span
v-if="
podOrderDetailsData?.pickingNumber &&
podOrderDetailsData?.purchaseNumber &&
podOrderDetailsData?.pickingNumber ===
podOrderDetailsData?.purchaseNumber
"
>
<span v-if="podOrderDetailsData?.printResult">
面单{{ renderPrintResult(podOrderDetailsData?.printResult) }}
</span>
<span v-else>面单打印中。。。</span>
</span>
<span v-else>验货中。。。</span>
</div>
<div class="box-top-item-btn">
<ElButton
type="primary"
@click="podOrderDetailsData && print(podOrderDetailsData, true)"
>手动打印</ElButton
>
<ElButton type="success" @click="handlePrintFinish"
>打单完成</ElButton
>
<ElButton type="danger" @click="handleClearBox">清空箱子</ElButton>
</div>
<div
v-if="
podOrderDetailsData?.productList?.length &&
podOrderDetailsData?.productList?.length > 1
"
class="multiple-title"
>
<span class="multiple-title-text"></span>
</div>
</div>
<div class="order-image">
<img :src="coverImage" alt="" />
</div>
</div>
<div class="right-content">
<div class="box-list">
<div
v-for="(item, index) in podBoxList"
:key="item.box"
class="box-list-item"
:class="{
active: item.box && boxIndex == item.box,
isNull: !item.data,
badge: handleProduct(item.data || {}),
}"
@click="handleBoxClick(item)"
>
<span style="font-weight: bold" :title="index + 1 + '号箱'">
{{ index + 1 }}
</span>
<span v-if="item.data" class="number">
{{ item.data.pickingNumber }}/{{ item.data.purchaseNumber }}
</span>
</div>
</div>
</div>
</div>
</ElDialog>
</template>
<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue'
import useLodop from '@/utils/hooks/useLodop'
import TableView from '@/components/TableView.vue'
import type { WebSocketMessage } from '@/utils/websocket'
import {
OrderData,
PodMakeOrderData,
ProductList,
} from '@/types/api/podMakeOrder'
import useOrderStore from '@/store/order'
import {
clearBoxApi,
clearAllBoxApi,
getPackingDataApi,
getPodBoxListApi,
submitInspectionApi,
} from '@/api/podUsOrder'
import useUserStore from '@/store/user'
import { Check } from '@element-plus/icons-vue'
import socket from '@/utils/websocket'
const { getCLodop } = useLodop()
const props = defineProps<{
modelValue: boolean
printOrder: (data: OrderData, callback: (status: boolean) => void) => void
}>()
const emit = defineEmits(['update:modelValue', 'set-printer', 'refresh'])
const visible = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
},
})
const printDeviceList = ref<string[]>([])
const sheetPrinter = ref<string>('')
const productionOrder = ref<string>('')
const podOrderDetailsData = ref<OrderData>()
const podOrderDetailsColumns = computed(() => [
{
label: '图片',
prop: 'image',
width: 60,
slot: 'image',
align: 'center',
fixed: 'left',
},
{
label: 'base SKU',
prop: 'baseSku',
width: 140,
align: 'center',
},
{
label: 'variant SKU',
prop: 'variantSku',
width: 140,
align: 'center',
},
{
label: '商品名称',
prop: 'productName',
},
{
label: '生产单号',
prop: 'podJomallUsNo',
width: 150,
align: 'center',
},
{
label: '购买数量',
prop: 'purchaseNumber',
width: 90,
align: 'center',
},
{
label: '拣货数量',
prop: 'count',
width: 90,
align: 'center',
},
{
label: '验证结果',
slot: 'verifyResult',
width: 90,
align: 'center',
},
])
const boxChange = ref<boolean>(false)
const boxIndex = ref<number | null>(null)
const orderStore = useOrderStore()
const isLock = ref<boolean>(false)
const renderLock = ref<boolean>(false)
const productionOrderRef = ref()
const socketConnect = computed(() => orderStore.socketConnect)
const podBoxList = computed(() => orderStore.podBoxList)
const coverImage = ref<string>('')
let currentCode = ''
const tableRef = ref()
watch(visible, async (value: boolean) => {
if (value) {
podOrderDetailsData.value = {}
currentCode = ''
if (userStore.user?.factory.id) {
try {
await socket.init(
{
account: userStore.user?.account.toString(),
factoryNo: userStore.user?.factory.id.toString(),
},
messageChange,
)
socket.send({
code: 'STARTORDER',
factoryNo: userStore.user?.factory.id,
})
} catch (error) {
console.error(error)
}
}
initOrderDetailBox()
initPrintDevice()
} else {
if (userStore.user?.factory.id) {
socket.send({
code: 'ENDORDER',
factoryNo: userStore.user?.factory.id,
})
socket.close()
}
}
})
watch(boxIndex, (value: number | null) => {
if (value) {
console.log('boxIndex', value, boxChange.value)
const bool = !boxChange.value
boxChange.value = false
renderItemBox(bool)
}
})
watch(
podBoxList,
(value) => {
if (value) {
const item = value.find((item) => item.box === podBoxIndex.value)
console.log('podBoxList', value, podBoxIndex.value, item)
if (item?.data) {
renderItemBox(true)
} else {
if (boxIndex.value === podBoxIndex.value) {
podOrderDetailsData.value = {}
boxIndex.value = null
}
}
}
},
{ deep: true },
)
const podBoxIndex = computed(() => orderStore.podBoxIndex)
const renderItemBox = (bool: boolean) => {
if (
!podBoxList.value ||
podBoxList.value.length === 0 ||
!boxIndex.value ||
(bool && boxIndex.value !== podBoxIndex.value)
)
return
if (renderLock.value) return
renderLock.value = true
let boxItem = podBoxList.value.find((item) => item.box === boxIndex.value)
if (!boxItem) boxItem = { data: { productList: [] } }
const { data } = boxItem
if (!data) {
renderLock.value = false
currentCode = ''
podOrderDetailsData.value = {}
return
}
const { productList = [] } = data
const pickingNumber = productList.reduce((prev, product) => {
return prev + (product.count || 0)
}, 0)
data.pickingNumber = pickingNumber
coverImage.value = productList[0].variantImage || ''
for (const product of productList) {
if (product.count === product.purchaseNumber) {
product.power = true
}
}
if (currentCode) {
for (const product of productList) {
if (product.podJomallNo === currentCode) {
coverImage.value = product.variantImage || ''
nextTick(() => {
tableRef.value?.setCurrentRow(product)
})
break
}
}
currentCode = ''
}
podOrderDetailsData.value = data
if (productList.every((item) => item.power)) {
print(data)
}
renderLock.value = false
}
const messageChange = (data: WebSocketMessage) => {
if (!data) return
const { code, ...more } = data
if (code === 'POD_PRINT_ORDER') {
try {
if (typeof more.txt === 'string') {
console.log(
'%conWebSocketMessage',
'font-size: 20px; color: red;',
JSON.parse(more.txt),
)
}
} catch (e) {
console.error(e)
}
setPodBoxList(more)
}
}
const setPodBoxList = (data: WebSocketMessage) => {
const obj = data.txt
if (obj && typeof obj === 'string') {
console.log('obj', obj)
const parsedData = JSON.parse(obj)
console.log('parsedData', parsedData)
useOrderStore().setPodBoxList(parsedData)
}
}
const initPrintDevice = () => {
const lodop = getCLodop(null, null)
if (!lodop) return
const arr = []
// 获取打印机数量
const length = lodop.GET_PRINTER_COUNT()
for (let i = 0; i < length; i++) {
// 根据设备序号获取设备名
const name = lodop.GET_PRINTER_NAME(i)
arr.push(name)
}
console.log('arr', arr, lodop.GET_PRINTER_NAME(1))
// 获取默认打印机
sheetPrinter.value = lodop.GET_PRINTER_NAME(0)
printDeviceList.value = arr
}
const handleProduct = (val: OrderData) => {
if (val && val.productList && val.productList.length > 0) {
return val.productList?.some((item) => {
return item.productMark == 'normal'
})
} else {
return false
}
}
const handleSearch = () => {
const code = productionOrder.value
if (!code) {
ElMessage.warning('请输入生产单号')
productionOrderRef.value.focus()
return
}
if (isLock.value) {
ElMessage.warning('请稍后再试')
productionOrderRef.value.focus()
return
}
productionOrder.value = ''
isLock.value = true
const everyPower = podOrderDetailsData.value?.productList?.every(
(item) => item.power,
)
if (everyPower) {
/**
* printSuccess 打印成功
* printFail 打印失败
* printIng 打印中
* notPrintSuccess 未能获取打印状态
*/
if (podOrderDetailsData.value?.printResult === 'printSuccess') {
submitInspection(() => {
getPackingData(code)
})
} else {
ElMessage.warning('未获取到打印结果')
isLock.value = false
}
} else {
getPackingData(code)
}
}
const userStore = useUserStore()
const getPackingData = async (code: string) => {
const loading = ElLoading.service({
fullscreen: true,
})
currentCode = code
try {
const factoryNo = userStore.user?.factory.id
if (!factoryNo) {
isLock.value = false
productionOrder.value = ''
return
}
const res = await getPackingDataApi(code, factoryNo, boxIndex.value)
if (res.code !== 200) {
ElMessage.warning(res.message)
isLock.value = false
productionOrder.value = ''
return
}
const { box } = res.data
if (box) {
boxIndex.value = box
}
} catch (error) {
console.log(error)
} finally {
isLock.value = false
productionOrder.value = ''
loading.close()
productionOrderRef.value.focus()
}
}
// 提交打单
const submitInspection = async (callback: () => void) => {
const factoryNo = userStore.user?.factory.id
if (!factoryNo) {
return
}
const data = podOrderDetailsData.value?.id
? [
{
id: podOrderDetailsData.value.id,
version: podOrderDetailsData.value?.version,
},
]
: []
try {
const res = await submitInspectionApi(data, boxIndex.value)
if (res.code !== 200) return
ElMessage.warning(res.message)
isLock.value = false
coverImage.value = ''
podOrderDetailsData.value = {}
productionOrderRef.value.focus()
callback && callback()
} catch (error) {
isLock.value = false
productionOrderRef.value.focus()
console.error(error)
}
}
const isBillLading = ref<boolean>(false)
const initOrderDetailBox = async () => {
const factoryNo = userStore.user?.factory.id
if (!factoryNo) {
return
}
const loading = ElLoading.service({
fullscreen: true,
})
try {
const res = await getPodBoxListApi(factoryNo)
if (res.code !== 200) {
ElMessage.warning(res.message)
return
}
orderStore.setPodBoxList({
boxList: res.data,
factoryNo,
})
const boxList = res.data.map((item) => {
if (item.data) {
if (!item.data.filePath) {
isBillLading.value = true
} else {
isBillLading.value = false
}
const productList = item?.data?.productList || []
const pickingNumber = productList.reduce((prev, product) => {
if (product.count === product.purchaseNumber) {
product.power = true
}
if (product.count) {
return prev + product.count
}
return prev
}, 0)
item.data.pickingNumber = pickingNumber
}
return item
})
podOrderDetailsData.value =
boxList.find((item) => item.data)?.data || undefined
boxIndex.value = boxList.find((item) => item.data)?.box || null
coverImage.value =
boxList.find((item) => item.data)?.data?.productList?.[0]?.variantImage ||
''
if (
podOrderDetailsData.value &&
podOrderDetailsData.value.pickingNumber ===
podOrderDetailsData.value.purchaseNumber
) {
podOrderDetailsData.value.printResult = 'notPrintSuccess'
}
const pickFinished = boxList.filter((item) => {
return item.data?.productList?.every((product) => product.power)
})
const boxs = pickFinished.map((item) => item.box)
if (boxs.length > 0) {
nextTick(async () => {
try {
await ElMessageBox.alert(
`检测到${boxs.join(',')}号箱验货完成,请及时处理`,
'提示',
{
confirmButtonText: '确定',
},
)
productionOrderRef.value.focus()
} catch (error) {
productionOrderRef.value.focus()
console.error(error)
}
})
}
renderLock.value = false
productionOrder.value = ''
isLock.value = false
productionOrderRef.value.focus()
} catch (error) {
console.error(error)
} finally {
loading.close()
}
}
const renderPrintResult = (printResult: string) => {
switch (printResult) {
case 'printSuccess':
return '打印成功'
case 'printFail':
return '打印失败'
case 'printIng':
return '打印中'
case 'notPrintSuccess':
return '未能获取打印状态'
default:
return '未知'
}
}
const handleOpened = () => {
productionOrderRef.value.focus()
}
const handleClose = (done: () => void) => {
nextStep(() => {
done()
})
}
const onClose = () => {
emit('refresh')
}
// 下一步
const nextStep = async (callback: () => void) => {
const everyPicked = podOrderDetailsData.value?.productList?.every(
(item) => item.count === item.purchaseNumber,
)
if (
everyPicked &&
(podOrderDetailsData.value?.printResult === 'printSuccess' ||
podOrderDetailsData.value?.printResult === 'notPrintSuccess')
) {
try {
await ElMessageBox.alert(
'当前订单验货完成并打印面单成功,是否转至已完成',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
},
)
submitInspection(() => {
callback && callback()
})
} catch (error) {
productionOrderRef.value.focus()
console.error(error)
}
} else {
callback && callback()
}
}
const handleBoxClick = (item: PodMakeOrderData) => {
const { box, data } = item
isBillLading.value = !data?.filePath
nextStep(() => {
if (!data) {
ElMessage.warning('暂无数据')
return
}
boxIndex.value = box || null
boxChange.value = true
productionOrderRef.value.focus()
})
}
const handleClearBox = async () => {
try {
await ElMessageBox.alert('确定清空当前箱子吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
})
} catch {
return
}
const factoryNo = userStore.user?.factory.id
if (!factoryNo) {
return
}
try {
const res = await clearBoxApi(factoryNo, boxIndex.value || null)
if (res.code !== 200) {
ElMessage.warning(res.message)
return
}
ElMessage.success('清空成功')
orderStore.setPodBoxList({
boxList: null,
factoryNo,
box: boxIndex.value || undefined,
})
boxIndex.value = null
podOrderDetailsData.value = {}
coverImage.value = ''
productionOrderRef.value.focus()
} catch (error) {
productionOrderRef.value.focus()
console.error(error)
}
}
const handlePrintFinish = async () => {
try {
await ElMessageBox.alert('确定打单完成吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
})
submitInspection(() => {
productionOrderRef.value.focus()
})
} catch {
productionOrderRef.value.focus()
return
}
}
const handlePrinterChange = (value: string) => {
emit('set-printer', value)
}
const print = (data: OrderData, forcePrint = false) => {
const _boxIndex = boxIndex.value
if (!forcePrint && data.printResult) return
props.printOrder(data, (status: boolean) => {
const item = podBoxList.value?.find((item) => item.box === _boxIndex)
if (item && item.data) {
if (status) {
item.data.printResult = 'printSuccess'
} else {
item.data.printResult = 'printFail'
}
}
const factoryNo = userStore.user?.factory.id
if (!factoryNo) return
orderStore.setPodBoxList({
boxList: item ? item.data : null,
factoryNo,
box: _boxIndex || undefined,
})
})
}
const clearAllBox = async () => {
try {
await ElMessageBox.alert('确定清空所有箱子吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
})
} catch {
productionOrderRef.value.focus()
return
}
try {
const res = await clearAllBoxApi()
if (res.code !== 200) return
orderStore.setPodBoxList({
boxList: res.data,
factoryNo: userStore.user?.factory.id || '',
})
productionOrderRef.value.focus()
podOrderDetailsData.value = {}
coverImage.value = ''
boxIndex.value = null
} catch (error) {
productionOrderRef.value.focus()
console.error(error)
}
}
const handleRowClick = () => {
productionOrderRef.value.focus()
}
const handleCurrentChange = (row: ProductList) => {
if (row) {
coverImage.value = row.variantImage || ''
}
}
</script>
<style scoped lang="scss">
.title {
display: flex;
align-items: center;
gap: 10px;
}
.online {
color: green;
font-size: 14px;
font-weight: 600;
}
.offline {
color: red;
font-size: 14px;
font-weight: 600;
}
.pod-make-order-content {
display: flex;
height: 100%;
overflow: hidden;
gap: 10px;
}
.left-content {
display: flex;
flex-direction: column;
gap: 10px;
width: calc(100% - 900px - 20px);
}
.head-form {
display: flex;
align-items: center;
gap: 10px;
}
.table-content {
flex: 1;
overflow: hidden;
}
.middle-content {
width: 500px;
display: flex;
flex-direction: column;
gap: 5px;
}
.right-content {
width: 400px;
}
.basic-info {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
.basic-info-item {
display: flex;
align-items: center;
span:last-child {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.box-top {
position: relative;
border: 1px solid #ddd;
background: rgb(50 50 50);
border-radius: 5px;
padding: 10px 15px;
box-sizing: border-box;
font-size: 16px;
color: #fff;
overflow: hidden;
}
.multiple-title {
position: absolute;
top: 0;
right: 0;
&::after {
position: absolute;
top: -31px;
right: -30px;
content: '';
border-width: 30px;
border-style: solid;
border-color: transparent transparent red transparent;
transform: rotate(45deg);
}
.multiple-title-text {
position: absolute;
z-index: 2;
color: #fff;
left: -20px;
}
}
.box-top-item {
display: flex;
height: 80px;
align-items: center;
margin-bottom: 10px;
}
.box-top-item-box-index {
font-size: 60px;
color: red;
text-align: center;
width: 120px;
font-weight: 600;
}
.box-top-item-box-index-text {
margin-right: 15px;
font-size: 30px;
}
.box-top-item-box-index-number {
font-size: 60px;
color: red;
display: inline-block;
text-align: center;
width: 90px;
font-weight: 600;
}
.box-top-item-status {
margin-bottom: 15px;
}
.order-image {
flex: 1;
overflow: hidden;
border: 1px solid #ddd;
background: #eee;
}
.box-list {
display: grid;
grid-template-columns: repeat(7, 50px);
gap: 8px;
position: relative;
}
.box-list-item {
position: relative;
border: 1px solid #ddd;
cursor: pointer;
font-size: 18px;
line-height: 30px;
height: 46px;
text-align: center;
box-sizing: border-box;
border-radius: 5px;
&:not(.isNull) {
background: #819aff;
color: #fff;
}
&.active {
background: #ff9900;
color: #fff;
}
&.badge::after {
position: absolute;
content: '';
right: 4px;
top: 4px;
width: 7px;
height: 7px;
border-radius: 50%;
background-color: red;
}
.number {
position: absolute;
bottom: 3px;
right: 0;
left: 0;
text-align: center;
font-size: 12px;
line-height: 14px;
color: #ddd;
}
}
</style>
<style lang="scss">
.pod-make-order-dialog {
.el-dialog {
display: flex;
flex-direction: column;
height: calc(100% - 60px);
}
.el-dialog__body {
flex: 1;
overflow: hidden;
}
}
</style>
<template>
<div class="card flex-column h-100 overflow-hidden">
<div class="header-filter-form">
<ElForm :model="searchForm" size="default" inline>
<ElFormItem>
<el-select
v-model="searchForm.timeType"
style="width: 100px; margin-right: 5px"
clearable
placeholder="时间类型"
>
<el-option :value="1" label="创建时间"></el-option>
<el-option :value="2" label="确认时间"></el-option>
<el-option :value="3" label="完成时间"></el-option>
</el-select>
<el-date-picker
v-model="timeRange"
:default-time="[
new Date(0, 0, 0, 0, 0, 0),
new Date(0, 0, 0, 23, 59, 59),
]"
placeholder="收货人"
value-format="YYYY-MM-DD HH:mm:ss"
type="datetimerange"
style="width: 280px"
:shortcuts="pickerOptions.shortcuts"
start-placeholder="开始时间"
end-placeholder="结束时间"
clearable
>
</el-date-picker>
</ElFormItem>
<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 label="定制类型">
<ElSelect
v-model="searchForm.customizedQuantity"
placeholder="定制类型"
clearable
style="width: 180px"
>
<ElOption label="单面" value="single" />
<ElOption label="多面" value="multiple" />
</ElSelect>
</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_ORDER'" class="item">
<ElButton type="success" @click="updateOrder"> 更新 </ElButton>
</span>
<span v-if="status === 'TO_BE_CONFIRMED'" class="item">
<ElButton type="primary" @click="printProductionOrder">
打印生产单
</ElButton>
</span>
<span v-if="status === 'TO_BE_CONFIRMED'" class="item">
<ElButton type="warning" @click="changeExceptionOrder">
转为异常单
</ElButton>
</span>
<span
v-if="status === 'TO_BE_CONFIRMED' || status === 'EXCEPTION_ORDER'"
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 v-if="status === 'WAIT_SHIPMENT'" class="item">
<ElButton type="warning" @click="printPodOrder"> POD打单 </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"
:title="item.remark"
>
{{ 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"
:title="item.factorySubOrderNumber"
>
{{ 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"
:title="item.thirdSubOrderNumber"
>
{{ 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">{{
status === 'TO_BE_CONFIRMED'
? '未生产数量:'
: status === 'EXCEPTION_ORDER'
? '数量:'
: '已生产数量:'
}}</span>
<span class="goods-item-info-item-value">
{{
status === 'TO_BE_CONFIRMED'
? item.notPassNum
: status === 'EXCEPTION_ORDER'
? item.num
: item.passNum
}}
</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"
:title="row.factoryOrderNumber"
>
{{ 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"
:title="row.thirdOrderNumber"
>
{{ 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" :title="row.shopNumber">
{{ 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" :title="row.receiverName">
{{ row.receiverName }}
</span>
</div>
<div class="order-detail-item">
<span class="order-detail-item-label">收货人电话:</span>
<span
class="order-detail-item-value"
:title="row.receiverPhone"
>
{{ row.receiverPhone }}
</span>
</div>
<div class="order-detail-item">
<span class="order-detail-item-label">收货人邮编:</span>
<span
class="order-detail-item-value"
:title="row.receiverPostCode"
>
{{ row.receiverPostCode }}
</span>
</div>
<div class="order-detail-item">
<span class="order-detail-item-label">收货地址:</span>
<span
class="order-detail-item-value"
:title="row.lanshouAddress"
>
{{ row.lanshouAddress }}
</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.startStockingTime }}
</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 class="order-time-item">
<span class="order-time-item-label">完成时间:</span>
<span class="order-time-item-value">
{{ row.finishTime }}
</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" @click="openDetail(cardItem.id)">
<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" title="工艺">
<span class="grid-item-label">工艺:</span>
<span class="grid-item-value">
{{ cardItem?.craftName }}
</span>
</div>
<div class="grid-item">
<span title="Variant SKU" class="grid-item-value">
{{ cardItem?.variantSku }}
</span>
</div>
<div class="grid-item" title="店铺单号">
<span class="grid-item-value">
{{ cardItem?.shopNumber }}
</span>
</div>
<div class="grid-item" title="订单号">
<span class="grid-item-value">
{{ cardItem?.factoryOrderNumber }}
</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>
</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"
:detail-data="detailData"
:fast-key="fastKey"
@on-success="handleSuccess"
@close="fastClose"
></FastProduction>
<PodMakeOrder
v-model="podOrderVisible"
:print-order="printOrder"
@set-printer="handlePrinterChange"
@refresh="onFastRefresh"
/>
</template>
<script setup lang="ts">
import { getUserMarkList } from '@/api/common'
import {
getCardOrderList,
getOrderList,
getOrderTabData,
confirmOrderApi,
changeExceptionOrderApi,
cancelOrderApi,
getOperationLogApi,
downloadMaterialApi,
updateExceptionOrderApi,
printProductionOrderApi,
getOrderDetailById,
updateRemarkApi,
} 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'
import PodMakeOrder from './PodMakeOrder.vue'
import { OrderData } from '@/types/api/podMakeOrder'
import useLodop, { LODOPObject } from '@/utils/hooks/useLodop'
import dayjs from 'dayjs'
declare global {
interface Window {
ActiveXObject: {
new (type: string): XMLHttpRequest
}
VBS_BinaryToArray: {
(data: unknown): { toArray(): number[] }
}
}
interface XMLHttpRequest {
responseBody?: unknown
}
}
const tabsNav = ref<Tab[]>()
const status = ref('TO_BE_CONFIRMED')
const detailData = ref({})
const [searchForm] = useValue<SearchForm>({
timeType: null,
shopNumber: '',
userMark: '',
logisticsTracking: '',
baseSku: '',
factoryOrderNumber: '',
sku: '',
factorySubOrderNumber: '',
status: '',
customizedQuantity: '',
startTime: null,
endTime: null,
})
const userMarkList = ref<string[]>([])
const selection = ref<PodUsOrderListData[]>([])
const pickerOptions = {
shortcuts: [
{
text: '今日',
value: () => getDateRange(0),
},
{
text: '昨天',
value: () => getDateRange(1),
},
{
text: '最近7天',
value: () => getDateRange(6),
},
{
text: '最近14天',
value: () => getDateRange(13),
},
{
text: '最近30天',
value: () => getDateRange(29),
},
{
text: '本周',
value: () => getWeekRange(0),
},
{
text: '上周',
value: () => getWeekRange(1),
},
{
text: '本月',
value: () => getMonthRange(0),
},
{
text: '上月',
value: () => getMonthRange(1),
},
{
text: '历史',
value: () => {
const end = dayjs().endOf('day').toDate()
const start = dayjs('2000-01-01').startOf('day').toDate()
return [start, end]
},
},
],
}
const timeRange = ref<string[]>([])
const getDateRange = (days = 0, type: 'past' | 'future' = 'past') => {
const end = dayjs()
const start =
type === 'past' ? end.subtract(days, 'day') : end.add(days, 'day')
return [start.startOf('day').toDate(), end.endOf('day').toDate()]
}
const getMonthRange = (months = 0, type: 'past' | 'future' = 'past') => {
const now = dayjs()
const start =
type === 'past' ? now.subtract(months, 'month') : now.add(months, 'month')
return [start.startOf('month').toDate(), start.endOf('month').toDate()]
}
const getWeekRange = (weeks = 0, type: 'past' | 'future' = 'past') => {
const now = dayjs()
const start =
type === 'past' ? now.subtract(weeks, 'week') : now.add(weeks, 'week')
return [start.startOf('week').toDate(), start.endOf('week').toDate()]
}
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: 'left',
},
{
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,
startTime:
timeRange.value && timeRange.value.length > 0
? timeRange.value[0]
: null,
endTime:
timeRange.value && timeRange.value.length > 0
? timeRange.value[1]
: null,
status: status.value,
},
page,
pageSize,
).then((res) => res.data) as never
} else {
return getCardOrderList(
{
...searchForm.value,
startTime:
timeRange.value && timeRange.value.length > 0
? timeRange.value[0]
: null,
endTime:
timeRange.value && timeRange.value.length > 0
? timeRange.value[1]
: null,
status: status.value,
},
page,
pageSize,
).then((res) => res.data) as never
}
},
})
const copy = (text: string) => {
navigator.clipboard.writeText(text)
ElMessage.success('复制成功')
}
const handleUpdateRemark = async (item: ProductList) => {
ElMessageBox.prompt('请输入备注', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
inputPattern: /^.{1,2000}$/,
inputErrorMessage: '请输入备注',
}).then(async ({ value }) => {
try {
const res = await updateRemarkApi(item.id, value)
if (res.code !== 200) return
ElMessage.success('操作成功')
search()
} catch (e) {
console.error(e)
}
})
}
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 printProductionOrder = async () => {
if (selection.value.length === 0) {
return ElMessage.warning('请选择数据')
}
const orderIds = selection.value.map((item) => item.id)
try {
const res = await printProductionOrderApi(orderIds)
if (res.code !== 200) return
ElMessage.success('操作成功')
window.open(filePath + res.message)
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 fastKey = ref('')
const onFastProduction = () => {
detailVisible.value = true
fastKey.value = 'fastProduction'
detailData.value = {}
}
// 下载稿件
const downloadMaterial = async () => {
let selectedIds = []
if (status.value === 'IN_PRODUCTION') {
selectedIds = cardSelection.value
.map((item: ProductList) => item.id)
.filter((id): id is number => id !== undefined)
} else {
selectedIds = selection.value
.map((item: PodUsOrderListData) => item.productList)
.flat()
.map((e) => e?.id)
.filter((id): id is number => id !== undefined)
}
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
}
const podOrderVisible = ref(false)
const printPodOrder = async () => {
const lodop = getCLodop(null, null)
if (!lodop) return
sheetPrinter.value = lodop.GET_PRINTER_NAME(0)
podOrderVisible.value = true
}
const sheetPrinter = ref('')
const handlePrinterChange = (value: string) => {
sheetPrinter.value = value
}
const { getCLodop } = useLodop()
const printOrder = async (
data: OrderData,
callback: (status: boolean) => void,
) => {
const lodop = getCLodop(null, null)
if (!lodop) return
lodop.PRINT_INIT('打印内容')
const printerIndex = lodop.SET_PRINTER_INDEX(sheetPrinter.value)
console.log(
'printerIndex',
printerIndex,
`sheetPrinter.value:${sheetPrinter.value}`,
)
if (!printerIndex) {
ElMessage.error('打印机设置失败')
callback && callback(false)
return
}
if (data.filePath) {
const strURL = filePath + data.filePath
console.log('strURL', strURL, data)
lodop.ADD_PRINT_PDF(
0,
0,
'100%',
'100%',
/(https):\/\/([^/]+)/i.test(strURL) ? downloadPDF(strURL) : strURL,
)
} else {
lodop.SEND_PRINT_RAWDATA(data.fileData || '')
}
console.log('lodop.CVERSION', lodop.CVERSION)
if (lodop.CVERSION) {
const startTime = Date.now()
const jobCode = await lodopCall((lodop) => {
lodop.SET_PRINT_MODE('CATCH_PRINT_STATUS', true)
return lodop.PRINT()
})
console.log('jobCode', jobCode)
// eslint-disable-next-line no-constant-condition
while (true) {
const ok = await lodopCall((lodop) =>
lodop.GET_VALUE('PRINT_STATUS_OK', jobCode),
)
console.log('PRINT_STATUS_OK:', jobCode, `ok: ${ok}`)
if (ok == 1) {
console.log('打印成功')
// 打印状态:成功
callback && callback(true)
return
}
// 如果打印状态表示未成功或者返回了空,继续获取任务是否存在
const exist = await lodopCall((lodop) =>
lodop.GET_VALUE('PRINT_STATUS_EXIST', jobCode),
)
console.log(
'[LODOP] PRINT_STATUS OK,EXIST',
jobCode,
`ok:${ok}`,
`exist:${exist}`,
`jobCode:${jobCode}`,
`(${Date.now() - startTime}ms)`,
)
if (exist == 0) {
console.log('任务不存在了', `exist:${exist}`)
// 任务不存在了
callback && callback(true)
return
}
await new Promise((r) => setTimeout(r, 500))
if (Date.now() - startTime >= 30000) {
console.log('打印超时(30秒)')
ElMessage.error('打印超时(30秒)')
callback && callback(false)
return
}
}
} else {
lodop.PRINT()
callback && callback(false)
}
}
let _lodop: LODOPObject | null = null
let _lodopCallback: { [key: string]: (value: string) => void } = {}
const lodopCall = (fn: (lodop: LODOPObject) => string) => {
if (!_lodop) {
_lodop = getCLodop(null, null)
if (!_lodop) return
_lodop.On_Return_Remain = true
_lodop.SET_PRINT_MODE('CATCH_PRINT_STATUS', true)
_lodopCallback = {}
_lodop.On_Return = (id, value) => {
console.log('lodopCall return', id, JSON.stringify(value))
const cb = _lodopCallback[id]
if (!cb) return
delete _lodopCallback[id]
cb(value)
}
}
return new Promise((resolve) => {
if (!_lodop) return
const id = fn(_lodop)
console.log('lodopCall', id)
_lodopCallback[id] = resolve
})
}
const 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
}
const openDetail = async (id: number) => {
try {
const res = await getOrderDetailById(id)
if (res.code == 200) {
if (res.data.imageAry) {
res.data.imageAry = JSON.parse(res.data.imageAry as string)
}
detailData.value = res.data || {}
detailVisible.value = true
fastKey.value = 'detail'
}
} catch (e) {
//showError(e)
}
}
const onFastRefresh = () => {
loadTabData()
search()
}
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>
<template>
<div class="page card h-100 flex-gap-10 overflow-hidden flex">
<div class="left">
<ElTree
ref="treeRef"
default-expand-all
:expand-on-click-node="false"
:default-expanded-keys="[]"
:highlight-current="true"
node-key="code"
:data="treeData"
:props="{ children: 'children', label: 'remark' }"
@node-click="nodeClick"
>
<template #default="{ data }">
<div class="tree-node">
<div class="tree-node-label">{{ data.remark }}</div>
<div v-if="data.count || data.count === 0" class="tree-node-count">
{{ `(${data.count})` }}
</div>
</div>
</template>
</ElTree>
</div>
<div class="right">
<!-- <pending-reconciliation></pending-reconciliation> -->
<div class="delivery-note-page flex-column card h-100 overflow-hidden">
<splitDiv size="50">
<template #top>
<div class="header-filter-form">
<ElForm :model="searchForm" inline>
<ElFormItem label="账期(发货时间)">
<div style="display: flex">
<el-date-picker
v-model="dateRange"
:default-time="[
new Date(0, 0, 0, 0, 0, 0),
new Date(0, 0, 0, 23, 59, 59),
]"
type="datetimerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
unlink-panels
clearable
style="width: 260px"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</div>
</ElFormItem>
<ElFormItem style="margin-right: 10px" label="对账单号">
<ElInput
v-model="searchForm.recNumber"
clearable
placeholder="对账单号"
style="width: 160px"
/>
</ElFormItem>
<ElFormItem style="margin-right: 10px" label="发货单号">
<ElInput
v-model="searchForm.shipmentNumber"
clearable
placeholder="发货单号"
style="width: 160px"
/>
</ElFormItem>
<ElFormItem style="margin-right: 10px" label="订单号">
<ElInput
v-model="searchForm.orderNumber"
clearable
placeholder="订单号"
style="width: 160px"
/>
</ElFormItem>
<ElFormItem>
<ElButton type="primary" @click="search">查询</ElButton>
</ElFormItem>
<ElFormItem>
<ElButton @click="reset">重置</ElButton>
</ElFormItem>
</ElForm>
</div>
<div class="btn-list">
<!-- <el-button
v-if="nodeId === 10"
type="primary"
@click="confirmOrder()"
>
确认
</el-button> -->
<el-button
v-if="nodeId === 15"
type="warning"
@click="rejectOrder"
>
驳回
</el-button>
<el-button type="success" @click="exportExcel"> 导出</el-button>
<el-button
v-if="nodeId === 30"
type="danger"
@click="auditOrder('archive')"
>
归档
</el-button>
</div>
<div
class="delivery-note-content flex-1 flex-column overflow-hidden"
>
<div class="delivery-note-list flex-1 overflow-hidden">
<ElTable
ref="singleTableRef"
highlight-current-row
:data="tableData"
show-summary
:summary-method="getSummaries"
default-expand-all
size="small"
style="width: 100%; height: 100%"
border
@current-change="rowClick"
@selection-change="handleSelectionChange"
>
<el-table-column
type="selection"
width="70"
header-align="center"
align="center"
/>
<el-table-column
label="序号"
type="index"
width="80"
align="center"
header-align="center"
></el-table-column>
<el-table-column
label="对账单号"
prop="rec_number"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="账期(发货时间)"
header-align="center"
align="center"
min-width="340"
>
<template #default="scope">
{{ scope.row.start_time }} - {{ scope.row.end_time }}
</template>
</el-table-column>
<el-table-column
label="总发货(件)"
header-align="center"
prop="num"
width="100"
align="center"
show-overflow-tooltip
>
</el-table-column>
<el-table-column
label="成本总价(¥)"
header-align="center"
prop="product_total_amount"
width="130"
align="center"
show-overflow-tooltip
>
</el-table-column>
<el-table-column
label="工艺总价(¥)"
header-align="center"
prop="craft_total_amount"
width="130"
align="center"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="物流总价(¥)"
header-align="center"
prop="carriage_total_amount"
width="130"
align="center"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="应付金额(¥)"
header-align="center"
prop="total_amount"
width="130"
align="center"
show-overflow-tooltip
>
</el-table-column>
<el-table-column
label="实付金额(¥)"
header-align="center"
prop="actual_amount"
width="130"
align="center"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="水单"
header-align="center"
min-width="140"
align="center"
show-overflow-tooltip
>
<template #default="{ row }">
<div
v-if="row.water_list"
style="display: flex; gap: 2px; align-items: center"
>
<div
v-for="item in (typeof row.water_list === 'string'
? row.water_list
: ''
).split(',')"
:key="item"
style="width: 30px"
>
<ImageView :src="item" />
</div>
</div>
</template>
</el-table-column>
<el-table-column
label="创建时间"
prop="create_time"
width="180"
header-align="center"
align="center"
show-overflow-tooltip
/>
<el-table-column
label="操作"
width="100"
header-align="center"
align="center"
fixed="right"
>
<template #default="{ row }">
<div>
<el-button
v-if="row.status === 10"
type="success"
size="small"
@click="confirmOrder(row)"
>
确认
</el-button>
</div>
<div>
<el-button
v-if="row.status === 20"
type="success"
size="small"
@click="onPayment(row)"
>付款
</el-button>
</div>
</template>
</el-table-column>
</ElTable>
</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>
</template>
<template #bottom>
<el-tabs v-model="tabsValue" @tab-click="tabsClick">
<el-tab-pane name="0" label="对账单详情">
<el-form :model="detailForm" inline>
<!-- <el-form-item label="发货时间">
<el-date-picker
v-model="detailForm.dateRange"
:default-time="[
new Date(0, 0, 0, 0, 0, 0),
new Date(0, 0, 0, 23, 59, 59),
]"
type="datetimerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
unlink-panels
clearable
style="width: 260px"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item> -->
<el-form-item label="底胚">
<el-input
v-model="detailForm.baseSku"
placeholder="请输入底胚"
clearable
style="width: 130px"
/>
</el-form-item>
<el-form-item label="工艺简称">
<el-input
v-model="detailForm.process"
placeholder="请输入工艺简称"
clearable
style="width: 130px"
/>
</el-form-item>
<el-form-item label="供应商货号">
<el-input
v-model="detailForm.supplierItemNo"
placeholder="请输入供应商货号"
clearable
style="width: 130px"
/>
</el-form-item>
<el-form-item label="发货单号">
<el-input
v-model="detailForm.shipmentNumber"
placeholder="请输入发货单号"
clearable
style="width: 130px"
/>
</el-form-item>
<el-form-item label="订单号">
<el-input
v-model="detailForm.orderNumber"
placeholder="请输入订单号"
clearable
style="width: 130px"
/>
</el-form-item>
<el-form-item label="生产单号">
<el-input
v-model="detailForm.subOrderNumber"
placeholder="请输入订单号"
clearable
style="width: 130px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="searchDetail"
>查询
</el-button>
</el-form-item>
<el-form-item>
<el-button
v-if="[0, 1].some((id) => id === Number(nodeId))"
type="primary"
@click="priceModification(1)"
>修改成本价格
</el-button>
</el-form-item>
<el-form-item>
<el-button
v-if="[0, 1].some((id) => id === Number(nodeId))"
type="warning"
@click="modifyProcessPrices"
>修改工艺价格
</el-button>
</el-form-item>
<el-form-item>
<el-button
v-if="[0, 1].some((id) => id === Number(nodeId))"
type="primary"
@click="priceModification(4)"
>修改物流价格
</el-button>
</el-form-item>
</el-form>
<div class="table-wrap">
<ElTable
size="small"
:data="detailList"
height="100%"
border
@selection-change="handleDetailSelectionChange"
>
<el-table-column
type="selection"
width="50"
header-align="center"
align="center"
/>
<ElTableColumn
show-overflow-tooltip
width="60"
align="center"
label="序号"
type="index"
/>
<el-table-column
label="生产单号"
prop="factory_sub_order_number"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="店铺单号"
prop="shop_number"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="客户"
prop="user_mark"
header-align="center"
align="center"
min-width="80"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="订单号"
prop="factory_order_number"
header-align="center"
align="center"
min-width="130"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="底胚"
prop="base_sku"
header-align="center"
align="center"
min-width="150"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="发货数(件)"
prop="shipment_num"
header-align="center"
align="center"
min-width="100"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="成本单价(¥)"
prop="cost_price"
header-align="center"
align="center"
min-width="120"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="工艺简称"
prop="process"
header-align="center"
align="center"
min-width="90"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="设计面数"
prop="face_count"
header-align="center"
align="center"
min-width="90"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="工艺单价(¥)"
prop="craft_price"
header-align="center"
align="center"
min-width="120"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="物流价格(¥)"
prop="carriage_amount"
header-align="center"
align="center"
min-width="120"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="发货单号"
prop="shipment_number"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="供应商货号"
prop="supplier_item_no"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="产品名称"
prop="product_name"
header-align="center"
align="center"
min-width="180"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="订单来源"
prop="namespace"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="变体SKU"
prop="variant_sku"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="工艺简称"
prop="process"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="工艺全称"
prop="process_name"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="重量"
prop="weight"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="商品备注"
prop="remark"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="模版类型"
header-align="center"
width="140"
align="center"
show-overflow-tooltip
>
<template #default="{ row }">
{{ row.diy_type == 0 ? '公模' : '私模' }}
</template>
</el-table-column>
<el-table-column
label="设计面数"
prop="face_count"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="送货方式"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
>
<template #default="{ row }">
{{ row.shipping_way == 1 ? '送货上门' : '快递' }}
</template>
</el-table-column>
<el-table-column
label="运费(¥)"
prop="carriage_amount"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="物流名称"
prop="carriage_name"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="物流跟踪号"
prop="logistics_tracking"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="揽收-姓名"
prop="lanshou_name"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="揽收-电话"
prop="lanshou_phone"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="揽收-区域"
prop="lanshou_region"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="揽收-地址"
prop="lanshou_address"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="揽收-邮政编码"
prop="lanshou_post"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="创建时间"
prop="create_time"
header-align="center"
align="center"
min-width="180"
show-overflow-tooltip
></el-table-column>
<!-- <el-table-column
label="更新时间"
prop="update_time"
header-align="center"
align="center"
min-width="180"
show-overflow-tooltip
></el-table-column> -->
</ElTable>
</div>
<ElPagination
v-model:current-page="detailPager.page"
v-model:page-size="detailPager.rows"
:page-sizes="[100, 200, 300, 400, 500]"
background
layout="total, sizes, prev, pager, next, jumper"
:total="detailPager.total"
style="margin: 10px auto 0; text-align: right"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
></ElPagination>
</el-tab-pane>
<el-tab-pane name="1" label="操作日志">
<ul
style="
color: #333;
font-size: 12px;
height: 100%;
overflow: auto;
"
>
<li
v-for="(item, index) in logList"
:key="index"
style="display: flex"
>
<span style="display: inline-block">
{{ item.createTime }}
</span>
<span style="margin: 0 5px 0 20px">{{
item.employeeAccount
}}</span>
<span style="display: inline-block">{{
item.description
}}</span>
</li>
</ul>
</el-tab-pane>
</el-tabs>
</template>
</splitDiv>
</div>
</div>
</div>
<!-- 付款 -->
<el-dialog
v-model="paymentDialogVisible"
title="付款"
width="30%"
:close-on-click-modal="false"
>
<el-form ref="paymentFormRef" :model="paymentForm" label-width="100px">
<el-form-item label="应付金额" prop="amount">
<el-input
v-model="paymentForm.payableAmount"
placeholder="请输入付款金额"
disabled
clearable
/>
</el-form-item>
<el-form-item
label="实付金额"
prop="actualAmount"
:rules="[
{
required: true,
message: '请输入实付金额',
},
{
type: 'number',
message: '实付金额需要为数字',
},
]"
>
<el-input
v-model.number="paymentForm.actualAmount"
placeholder="请输入实付金额"
clearable
/>
</el-form-item>
<el-form-item
label="水单"
prop="waterList"
:rules="[
{
required: true,
message: '请上传水单',
trigger: 'blur',
},
]"
>
<div v-loading="uploadLoading" class="avatar-uploader-icon">
<div class="img-list">
<template v-if="paymentForm.waterList.length">
<div
v-for="(item, index) in paymentForm.waterList"
:key="index"
class="img-item"
>
<img :src="item.url" style="width: 100%; height: 100%" />
<el-icon
v-if="paymentForm.waterList.length"
class="close-bill"
@click="paymentForm.waterList.splice(index, 1)"
>
<CircleClose />
</el-icon>
</div>
</template>
<div class="img-item">
<el-icon style="width: 100%; height: 100%" @click="toUpload">
<Plus />
</el-icon>
</div>
</div>
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="paymentDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handlePayment">确定</el-button>
</template>
</el-dialog>
<ElDialog
v-model="confirmOrderVisible"
title="确认对账单"
width="70%"
:close-on-click-modal="false"
>
<el-row>
<el-col :span="6"> 对账单号:{{ currentRow?.rec_number }} </el-col>
<el-col :span="12">
账期(发货时间) {{ currentRow?.start_time }} -
{{ currentRow?.end_time }}
</el-col>
<el-col :span="6">
成本总价() {{ currentRow?.product_total_amount }}
</el-col>
</el-row>
<el-row style="margin: 10px 0">
<el-col :span="6">
工艺总价(){{ currentRow?.craft_total_amount }}
</el-col>
<el-col :span="6">
物流总价() {{ currentRow?.carriage_total_amount }}
</el-col>
<el-col :span="6"> 应付金额(){{ currentRow?.total_amount }} </el-col>
<el-col :span="6"> 实付金额(){{ currentRow?.actual_amount }} </el-col>
</el-row>
<el-row style="margin: 10px 0">
<el-col :span="6" v-if="currentRow?.water_list">
水单:
<span
v-for="item in (typeof currentRow?.water_list === 'string'
? currentRow.water_list
: ''
).split(',')"
:key="item"
style="width: 30px"
>
<ImageView :src="item" />
</span>
</el-col>
<el-col :span="6"> 总发货() {{ currentRow?.num }} </el-col>
<el-col :span="6"> 创建时间: {{ currentRow?.create_time }} </el-col>
</el-row>
<div></div>
<ElForm ref="auditFormRef" :model="auditForm" label-width="80px">
<ElFormItem
label="驳回原因"
prop="description"
:rules="[{ required: true, message: '请输入驳回原因' }]"
>
<ElInput v-model="auditForm.description" type="textarea" />
</ElFormItem>
</ElForm>
<template #footer>
<span class="dialog-footer">
<!-- <el-button @click="confirmOrderVisible = false">取消</el-button> -->
<el-button type="success" @click="submitConfirmOrder(1)"
>确认</el-button
>
<el-button type="danger" @click="submitConfirmOrder(2)">驳回</el-button>
</span>
</template>
</ElDialog>
<el-dialog
v-model="processPriceDialogVisible"
title="修改工艺价格"
width="50%"
:close-on-click-modal="false"
>
<el-table :data="craftPriceList" border style="width: 100%" height="330px">
<el-table-column
prop="process"
label="工艺简称"
width="260"
align="center"
/>
<el-table-column prop="base_price" label="基础价格(¥)" align="center">
<template #default="scope">
<el-input
v-model="scope.row.base_price"
placeholder="请输入基础价格"
clearable
oninput="value=value.replace(/[^\-?\d.]/g,'')"
style="width: 100%"
/>
</template>
</el-table-column>
<el-table-column prop="add_price" label="加价(¥)" align="center">
<template #default="scope">
<el-input
v-model="scope.row.add_price"
placeholder="请输入加价"
clearable
oninput="value=value.replace(/[^\-?\d.]/g,'')"
style="width: 100%"
/>
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="processPriceDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitPodPrice">确定</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ElMessage, ElTable, ElTree, TableColumnCtx } from 'element-plus'
import splitDiv from '@/components/splitDiv/splitDiv.vue'
import { CircleClose, Plus } from '@element-plus/icons-vue'
// import pendingReconciliation from './pendingReconciliation.vue'
import usePageList from '@/utils/hooks/usePageList'
import { useValue } from '@/utils/hooks/useValue'
import BigNumber from 'bignumber.js'
import {
apiBillPodPayment,
auditOrderApi,
confirmPodOrderApi,
apiRejectionOfReview,
exportPodExcelApi,
getPodLogListApi,
getPodShipmentDetailsById,
podReconciliation,
podReconciliationList,
updateRecPrice,
apiGetCraftGroup,
apiSetCraftData,
} from '@/api/order'
import { nextTick, onMounted, ref, watch } from 'vue'
import 'element-plus/dist/index.css'
import {
AccountStatementNote,
AccountStatementNoteSearchForm,
CountStatus,
ItemList,
} from '@/types/api/billOrder'
import { DetailForm, LogListData, IUpdatePrice } from '@/types/api/deliveryNote'
import ImageView from '@/components/ImageView.vue'
// import { showConfirm } from '@/utils/ui'
import { uploadImageApi } from '@/api/common'
// import { getUserMarkList } from '@/api/auth.ts'
interface Tree {
remark?: string
count?: number
code?: number
children?: Tree[]
}
interface SummaryMethodProps<T = AccountStatementNote> {
columns: TableColumnCtx<T>[]
data: T[]
}
interface PaymentForm {
waterList: {
url: string | undefined
}[]
id?: number | string
actualAmount?: number | string
payableAmount?: number | string
}
const logList = ref<LogListData[]>([])
// const nameSpaceList = ref<string[]>([])
const treeData = ref<CountStatus[]>()
const [searchForm, resetSearchForm] = useValue<AccountStatementNoteSearchForm>(
{},
)
const paymentFormRef = ref()
const uploadLoading = ref(false)
const dateRange = ref<string[]>([])
const selections = ref<AccountStatementNote[]>([])
const detailSelections = ref<ItemList[]>([])
const detailList = ref<ItemList[]>([])
const tabsValue = ref<string>('0')
const singleTableRef = ref<InstanceType<typeof ElTable>>()
const currentRow = ref<AccountStatementNote | null>(null)
const paymentDialogVisible = ref(false)
const paymentForm = ref<PaymentForm>({
waterList: [],
id: '',
actualAmount: '',
payableAmount: '',
})
const getSelectionsProperty = (property: keyof AccountStatementNote) => {
return selections.value
.map((el: AccountStatementNote) => el[property])
.join(',')
}
const nodeId = ref<number | string>(0)
const treeRef = ref<InstanceType<typeof ElTree>>()
const {
currentPage,
pageSize,
total,
data: tableData,
refresh: search,
onCurrentPageChange: handleCurrentChange,
onPageSizeChange: handleSizeChange,
} = usePageList({
query: (page, pageSize) =>
podReconciliationList(
{
...searchForm.value,
status: nodeId.value === -1 ? null : nodeId.value,
startTime: dateRange.value && dateRange.value[0],
endTime: dateRange.value && dateRange.value[1],
},
page,
pageSize,
).then((res) => res.data),
})
watch(
() => tableData.value,
() => {
;(tableData.value as AccountStatementNote[]).forEach((item) => {
item.total_amount = computedPrice(item)
})
},
{ immediate: true, deep: true },
)
const reset = () => {
dateRange.value = []
resetSearchForm()
}
const priceModification = (type: 1 | 3 | 4) => {
if (detailSelections.value.length === 0) {
ElMessage.warning('至少选择一条对账单')
return
}
const pricePrompts: Record<1 | 3 | 4, { title: string; param: string }> = {
1: { title: '修改工厂价格', param: 'costPrice' },
3: { title: '修改工艺价格', param: 'craftPrice' },
4: { title: '修改物流价格', param: 'carriageAmount' },
}
const promptConfig = pricePrompts[type]
if (!promptConfig) {
ElMessage.error('无效的操作类型')
return
}
ElMessageBox.prompt(promptConfig.title, {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /\d+/,
inputErrorMessage: '请输入数字',
}).then(async ({ value }: { value: string }) => {
const price = parseFloat(value)
if (isNaN(price) || price < 0) {
ElMessage.error('价格必须为大于等于0的数字')
return
}
await updateRecPrice({
ids: detailSelections.value.map((item: ItemList) => item.id).join(),
...{ [promptConfig.param]: price },
infoId: currentRow.value?.id,
})
ElMessage.success('操作成功')
detailSelections.value = []
search()
searchDetail()
})
}
const onPayment = async (item: AccountStatementNote) => {
currentRow.value = item
paymentForm.value = {
payableAmount: item.total_amount,
actualAmount: '',
waterList: [],
}
await nextTick()
paymentFormRef.value && paymentFormRef.value.clearValidate()
paymentDialogVisible.value = true
}
const handlePayment = async () => {
try {
await paymentFormRef.value?.validate()
} catch {
return
}
if (Number(paymentForm.value.actualAmount) <= 0) {
return ElMessage.warning('实付金额需大于等于0')
}
await apiBillPodPayment({
...paymentForm.value,
waterList: paymentForm.value.waterList
.map((item: { url: string | undefined }) => item.url)
.join(','),
id: currentRow.value?.id,
recNumber: currentRow.value?.rec_number,
})
ElMessage.success('付款成功')
paymentDialogVisible.value = false
search()
await getTreeNum()
}
const handleWaterBill = async (e: Event) => {
const files = (e.target as HTMLInputElement).files || []
const request = []
uploadLoading.value = true
for (const file of files) {
const formData = new FormData()
formData.append('file', file)
formData.append('businessType', 'other')
request.push(uploadImageApi(formData))
}
const resList = []
try {
const res = await Promise.all(request)
for (const item of res) {
const { filePath, code } = item
if (code === 500) {
ElMessageBox.alert('上传失败', '系统提示', { type: 'error' })
return
} else if (code === 404) {
ElMessageBox.alert('接口异常', '系统提示', { type: 'error' })
return
} else if (code === 403) {
ElMessageBox.alert('权限不足', '系统提示', { type: 'error' })
return
}
resList.push({ url: filePath })
}
paymentForm.value.waterList = resList
} catch (error) {
console.log(error)
} finally {
uploadLoading.value = false
}
}
const getTreeNum = async () => {
try {
const res = await podReconciliation()
treeData.value = res.data
await nextTick(() => {
treeRef.value!.setCurrentKey(nodeId.value, true)
})
} catch (e) {
console.error(e)
}
}
const rowClick = (row: AccountStatementNote) => {
if (!row) {
currentRow.value = null
}
currentRow.value = row
tabsClick()
}
const tabsClick = async () => {
if (!currentRow.value) {
detailList.value = []
logList.value = []
return
}
await nextTick()
if (tabsValue.value === '0') {
await searchDetail()
} else {
await getLogList()
}
}
const computedPrice = (row: AccountStatementNote) => {
return new BigNumber(row.product_total_amount || 0)
.plus(new BigNumber(row.carriage_total_amount || 0))
.plus(new BigNumber(row.craft_total_amount || 0))
.toString()
}
const craftPriceList = ref<IUpdatePrice[]>([])
const processPriceDialogVisible = ref(false)
const modifyProcessPrices = async () => {
const res = await apiGetCraftGroup(currentRow.value?.id)
const result = (res.data as string[]).map((item) => ({
process: item,
base_price: '',
add_price: '',
}))
craftPriceList.value = result || []
processPriceDialogVisible.value = true
}
const submitPodPrice = async () => {
const filteredList = craftPriceList.value.filter(
(item: IUpdatePrice) => item.base_price && item.add_price,
)
if (filteredList.length > 0) {
try {
const res = await apiSetCraftData({
id: currentRow.value?.id,
craftTotalPrice:currentRow.value?.craft_total_amount,
recNumber: currentRow.value?.rec_number,
craftPriceList: filteredList,
})
if (res.code !== 200) return
ElMessage.success('修改成功')
processPriceDialogVisible.value = false
search()
await getTreeNum()
} catch (error) {
console.error(error)
}
} else {
ElMessage.error('至少提交一条数据')
}
}
const toUpload = () => {
const input = document.createElement('input')
input.style.display = 'none'
input.type = 'file'
input.multiple = true
input?.click()
input.onchange = function (e: Event) {
handleWaterBill(e)
}
}
const handleSelectionChange = (v: AccountStatementNote[]) => {
selections.value = v
}
const handleDetailSelectionChange = (v: ItemList[]) => {
detailSelections.value = v
}
const getSummaries = (param: SummaryMethodProps) => {
const { columns, data } = param
const sums: string[] = []
columns.forEach(
(column: TableColumnCtx<AccountStatementNote>, index: number) => {
if (index === 0) {
sums[index] = '合计'
return
}
const values = data.map((item: AccountStatementNote) => {
return Number(item[column.property as keyof AccountStatementNote])
})
if (!values.every((value: number) => !isNaN(value))) {
return
}
sums[index] = values
.reduce((prev: BigNumber, curr: number) => {
const value = new BigNumber(curr)
return prev.plus(value) // 直接处理 BigNumber 类型
}, new BigNumber(0))
.toString()
},
)
return sums
}
const confirmOrderVisible = ref<boolean>(false)
const auditForm = ref({
description: '',
})
const auditFormRef = ref()
const auditOrder = (key: string) => {
let url = ''
let text = ''
switch (key) {
case 'pay':
url = 'pod/podReconciliation/payment'
text = '确认付款'
break
case 'archive':
url = 'pod/podReconciliation/archiving'
text = '确认归档'
break
}
if (selections.value.length === 0) {
return ElMessage.warning('请选择要操作的数据')
}
ElMessageBox.confirm(`${text}对账单?`, '重要提示', {
confirmButtonText: '确定',
type: 'warning',
}).then(async () => {
const ids = selections.value
.map((el: AccountStatementNote) => el.id)
.join(',')
await auditOrderApi(url, ids)
ElMessage.success('操作成功')
search()
await getTreeNum()
})
}
const rejectOrder = () => {
if (selections.value.length === 0) {
return ElMessage.warning('请选择要操作的数据')
}
ElMessageBox.prompt('请输入驳回原因', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
inputPattern: /.+/,
customClass: 'reject',
inputErrorMessage: '请输入驳回原因',
inputPlaceholder: '驳回原因',
}).then(async ({ value }: { value: string }) => {
const ids = getSelectionsProperty('id')
const recNumbers = getSelectionsProperty('rec_number')
try {
await apiRejectionOfReview({
ids: ids,
description: value,
recNumbers: recNumbers,
})
ElMessage.success('操作成功')
search()
await getTreeNum()
} catch (e) {
console.error(e)
}
})
}
const confirmOrder = async (item: AccountStatementNote) => {
currentRow.value = item
confirmOrderVisible.value = true
}
interface ConfirmOrderPayload {
ids: string
description?: string
pass?: number
}
const submitConfirmOrder = async (type: number) => {
// const ids = selections.value
// .map((el: AccountStatementNote) => el.id)
// .join(',')
let apiPayload: ConfirmOrderPayload = { ids: `${currentRow.value?.id}` }
if (type === 1) {
// 确认操作
await confirmPodOrderApi(apiPayload)
} else {
// 驳回操作
try {
await auditFormRef.value?.validate()
} catch {
return // 验证失败,终止操作
}
apiPayload = {
...apiPayload,
description: auditForm.value.description,
pass: 0,
}
await confirmPodOrderApi(apiPayload)
}
confirmOrderVisible.value = false
ElMessage.success('操作成功')
search()
await getTreeNum()
if (singleTableRef.value) {
singleTableRef.value!.setCurrentRow(currentRow.value)
}
}
watch(
() => tableData.value,
() => {
if (tableData.value && tableData.value.length > 0) {
if (singleTableRef.value) {
singleTableRef.value!.setCurrentRow(tableData.value[0])
currentRow.value = (tableData.value as never)[0]
}
} else {
detailList.value = []
}
},
{ immediate: true },
)
const nodeClick = (data: Tree) => {
nodeId.value = data.code ?? ''
// if (data.code !== 0) {
search()
// }
}
const detailForm = ref({} as DetailForm)
const detailPager = ref({
page: 1,
rows: 100,
total: 0,
})
const searchDetail = async () => {
try {
const res = await getPodShipmentDetailsById({
...detailForm.value,
// startTime: detailForm.value.dateRange && detailForm.value.dateRange[0],
// endTime: detailForm.value.dateRange && detailForm.value.dateRange[1],
page: detailPager.value.page,
rows: detailPager.value.rows,
infoId: currentRow.value?.id,
})
detailList.value = res.data.records || []
detailPager.value.total = res.data.total
} catch (e) {
console.error(e)
}
}
const getLogList = async () => {
try {
const res = await getPodLogListApi(currentRow.value?.id)
logList.value = res.data
} catch (e) {
console.error(e)
}
}
const exportExcel = async () => {
if (selections.value.length !== 1) {
return ElMessage.warning('请选择一条数据')
}
const ids = selections.value
.map((el: AccountStatementNote) => el.id)
.join(',')
try {
const res = await exportPodExcelApi(ids)
window.open(res.data)
} catch (e) {
console.error(e)
}
}
onMounted(() => {
getTreeNum()
})
</script>
<style lang="scss" scoped>
.img-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.img-item {
font-size: 28px;
color: #8c939d;
width: 58px;
height: 58px;
text-align: center;
border: 1px solid #d9d9d9;
cursor: pointer;
position: relative;
.close-bill {
position: absolute;
font-size: 14px;
top: -8px;
right: -8px;
color: #666;
}
}
.header-filter-form {
:deep(.el-form-item) {
margin-right: 14px;
margin-bottom: 10px;
}
}
$border: solid 1px #ddd;
.send-order-list {
display: grid;
grid-template-columns: 2fr 1fr;
border-left: $border;
border-top: $border;
}
.send-order-column {
padding: 10px 16px;
line-height: 1.5;
}
.send-order-header {
font-weight: bold;
text-align: center;
background-color: #f8f8f9;
}
.send-order-product-item {
display: flex;
justify-content: space-between;
gap: 20px;
&:not(:first-child) {
border-top: $border;
padding: 10px 0;
}
.send-order-prop-list {
flex: 1;
}
}
.send-order-product-image {
width: 100px;
}
.dialog-footer {
display: flex;
justify-content: center;
}
.delivery-note-list {
:deep(.vertical-align-top) {
vertical-align: top;
}
}
.left {
width: 160px;
:deep(.el-tree-node__content) {
height: 30px;
line-height: 30px;
}
:deep(.el-tree-node__label) {
font-size: 13px;
cursor: pointer;
display: inline-block;
width: 100%;
color: black !important;
padding: 3px 7px;
}
:deep(.el-tree-node__expand-icon) {
display: none;
}
:deep(.is-current) {
.tree-node-label,
.tree-node-count {
background-color: #ecf5ff;
color: #409eff !important;
}
.el-tree-node__children {
.tree-node-label,
.tree-node-count {
background-color: transparent !important;
color: black !important;
}
}
}
}
.tree-node {
display: flex;
color: #333;
font-weight: 500;
}
.right {
flex: 1;
flex-shrink: 0;
background: white;
overflow: hidden;
}
::v-deep(.el-tree-node__label) {
font-size: 13px;
color: rgb(96, 98, 102);
cursor: pointer;
}
::v-deep(.el-tree-node__expand-icon) {
display: none;
}
::v-deep(.el-tree-node__label) {
display: inline-block;
width: 100%;
color: black !important;
padding: 3px 7px;
}
::v-deep(.is-current) {
.el-tree-node__label {
background-color: #ecf5ff;
color: #409eff !important;
}
.el-tree-node__children {
.el-tree-node__label {
background-color: transparent !important;
color: black !important;
}
}
}
::v-deep(.splitpanes__pane) {
display: flex;
flex-direction: column;
}
::v-deep(.splitpanes--horizontal > .splitpanes__splitter) {
min-height: 5px;
margin-top: 10px;
}
.draw-line {
width: 100%;
height: 5px;
background: #eff3f6;
}
.btn-list {
margin-bottom: 10px;
}
::v-deep(.el-tree-node) {
cursor: pointer;
margin-bottom: 5px;
}
::v-deep(.el-tree-node__label) {
font-size: 14px;
}
::v-deep(.el-tabs) {
display: flex;
flex-direction: column;
height: 100%;
.el-tabs__content {
flex: 1;
flex-shrink: 0;
overflow: hidden;
.el-tab-pane {
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
}
}
.delivery-note-page {
:deep(#top) {
height: 100%;
}
}
::v-deep(.el-form--inline .el-form-item) {
margin-right: 10px;
}
</style>
<script setup lang="ts">
import { ElTable } from 'element-plus'
import usePageList from '@/utils/hooks/usePageList.ts'
import {
getPodShipmentDetailsById,
apiPodUpdatePrice,
apiItemLogList,
} from '@/api/order.ts'
import { useValue } from '@/utils/hooks/useValue.ts'
import { AccountStatementNote, AccountStatementNoteSearchForm } from '@/types/api/billOrder.ts'
import { ref } from 'vue'
import { LogListsData } from '@/types/api/deliveryNote.ts'
const [searchForm, resetSearchForm] = useValue<AccountStatementNoteSearchForm>(
{},
)
const logList = ref<LogListsData[]>([])
const craftPrice = ref<string | undefined>('')
const costPrice = ref<string | undefined>('')
const logDialogVisible = ref<boolean>(false)
const viewTheLog = async (id: number) => {
const res = await apiItemLogList(id)
logList.value = res.data || []
logDialogVisible.value = true
}
const selections = ref<AccountStatementNote[]>([])
const dateRange = ref<string[]>([])
const {
currentPage,
pageSize,
total,
data: tableData,
refresh: search,
onCurrentPageChange: handleCurrentChange,
onPageSizeChange: handleSizeChange,
} = usePageList({
query: (page, pageSize) =>
getPodShipmentDetailsById(
{
...searchForm.value,
page,
pageSize,
startTime: dateRange.value && dateRange.value[0],
endTime: dateRange.value && dateRange.value[1],
},
).then((res) => {
craftPrice.value = res.data?.craftPrice
costPrice.value = res.data?.costPrice
return res.data
}) as never,
})
const handleSelectionChange = (v: AccountStatementNote[]) => {
selections.value = v
}
const reset = () => {
dateRange.value = []
resetSearchForm()
}
const priceModification = (type: 1 | 3 | 4) => {
if (selections.value.length === 0) {
ElMessage.warning('至少选择一条对账单')
return
}
const pricePrompts: Record<1 | 3 | 4, { title: string; param: string }> =
{
1: { title: '修改工厂价格', param: 'costPrice' },
3: { title: '修改工艺价格', param: 'craftPrice' },
4: { title: '修改物流价格', param: 'carriageAmount' },
}
const promptConfig = pricePrompts[type]
if (!promptConfig) {
ElMessage.error('无效的操作类型')
return
}
ElMessageBox.prompt(promptConfig.title, {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /\d+/,
inputErrorMessage: '请输入数字',
}).then(async ({ value }) => {
const price = parseFloat(value)
if (isNaN(price) || price < 0) {
ElMessage.error('价格必须为大于等于0的数字')
return
}
await apiPodUpdatePrice({
ids: selections.value.map(item => item.id).join(),
...{ [promptConfig.param]: price },
})
ElMessage.success('操作成功')
selections.value = []
search()
})
}
</script>
<template>
<el-dialog v-model="logDialogVisible" title="操作日志" width="30%">
<ul style="color: #333; font-size: 12px; height: 100%; overflow: auto">
<li v-for="(item, index) in logList" :key="index" style="display: flex">
<span style="display: inline-block; width: 23%">
{{ item.create_time }}
</span>
<div style="display: inline-block; width: 80%">
<span style="word-break: normal"> {{ item.employee_name }}:</span>
<span v-html="item.description"></span>
</div>
</li>
</ul>
</el-dialog>
<div class="delivery-note-page flex-column card h-100 overflow-hidden">
<div class="header-filter-form">
<ElForm :model="searchForm" inline>
<ElFormItem style="margin-right: 10px" label="发货时间">
<el-date-picker
v-model="dateRange"
:default-time="[
new Date(0, 0, 0, 0, 0, 0),
new Date(0, 0, 0, 23, 59, 59),
]"
type="datetimerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
unlink-panels
clearable
style="width: 260px"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</ElFormItem>
<ElFormItem style="margin-right: 10px" label="发货单号">
<ElInput
v-model="searchForm.shipmentNumber"
clearable
placeholder="发货单号"
style="width: 130px"
/>
</ElFormItem>
<ElFormItem style="margin-right: 10px" label="订单号">
<ElInput
v-model="searchForm.orderNumber"
clearable
placeholder="订单号"
style="width: 130px"
/>
</ElFormItem>
<ElFormItem style="margin-right: 10px" label="生产单号">
<ElInput
v-model="searchForm.subOrderNumber"
clearable
placeholder="生产单号"
style="width: 130px"
/>
</ElFormItem>
<ElFormItem>
<ElButton type="primary" @click="search">查询</ElButton>
</ElFormItem>
<ElFormItem>
<ElButton @click="reset">重置</ElButton>
</ElFormItem>
</ElForm>
</div>
<div class="btn-list" style="margin-bottom: 5px">
<el-button
type="primary"
@click="priceModification(1)"
>修改工厂价格
</el-button
>
<el-button type="warning" @click="priceModification(3)"
>修改工艺价格
</el-button
>
<el-button type="primary" @click="priceModification(4)"
>修改物流价格
</el-button
>
</div>
<div
class="delivery-note-content flex-1 flex-column overflow-hidden"
>
<div class="delivery-note-list flex-1 overflow-hidden">
<ElTable
ref="singleTableRef"
highlight-current-row
:data="tableData"
show-summary
default-expand-all
size="small"
style="width: 100%; height: 100%"
border
@selection-change="handleSelectionChange"
>
<el-table-column
type="selection"
width="70"
header-align="center"
align="center"
/>
<el-table-column
label="序号"
type="index"
width="80"
align="center"
header-align="center"
></el-table-column>
<el-table-column
label="发货单号"
prop="shipment_number"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="订单号"
prop="factory_order_number"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="发货数(件)"
prop="shipment_num"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="工厂价格(¥)"
prop="cost_price"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="工艺价格(¥)"
prop="craft_price"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="物流价格(¥)"
prop="carriage_amount"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="生产单号"
prop="factory_sub_order_number"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="店铺单号"
prop="shop_number"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="供应商货号"
prop="supplier_item_no"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="产品名称"
prop="product_name"
header-align="center"
align="center"
min-width="180"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="工厂编码"
prop="factory_code"
header-align="center"
align="center"
width="100"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="订单来源"
prop="namespace"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="客户"
prop="user_mark"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<!-- <el-table-column
label="ERP订单号"
prop="erp_order_number"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="erp子单号"
prop="erp_sub_order_number"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column> -->
<el-table-column
label="baseSKU"
prop="base_sku"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="变体SKU"
prop="variant_sku"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="工艺简称"
prop="process"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="工艺全称"
prop="process_name"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="重量"
prop="weight"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="商品备注"
prop="remark"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="模版类型"
header-align="center"
width="140"
align="center"
show-overflow-tooltip
>
<template #default="{ row }">
{{ row.diy_type == 0 ? '公模' : '私模' }}
</template>
</el-table-column>
<el-table-column
label="设计面数"
prop="face_count"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="送货方式"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
>
<template #default="{ row }">
{{ row.shipping_way == 1 ? '送货上门' : '快递' }}
</template>
</el-table-column
>
<el-table-column
label="运费(¥)"
prop="carriage_amount"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="物流名称"
prop="carriage_name"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="物流跟踪号"
prop="logistics_tracking"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="揽收-姓名"
prop="lanshou_name"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="揽收-电话"
prop="lanshou_phone"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="揽收-区域"
prop="lanshou_region"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="揽收-地址"
prop="lanshou_address"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="揽收-邮政编码"
prop="lanshou_post"
header-align="center"
align="center"
min-width="160"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="创建时间"
prop="create_time"
header-align="center"
align="center"
min-width="180"
show-overflow-tooltip
></el-table-column>
<!-- <el-table-column
label="付款时间"
prop="payment_time"
header-align="center"
align="center"
min-width="180"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="确认时间"
prop="start_stocking_time"
header-align="center"
align="center"
min-width="180"
show-overflow-tooltip
></el-table-column> -->
<el-table-column
label="更新时间"
prop="update_time"
header-align="center"
align="center"
min-width="180"
show-overflow-tooltip
></el-table-column>
<el-table-column
label="操作"
width="100"
header-align="center"
align="center"
fixed="right"
>
<template #default="{ row }">
<div>
<el-button type="primary" link @click="viewTheLog(row.id)"
>操作日志
</el-button
>
</div>
</template>
</el-table-column>
</ElTable>
</div>
<div class="pagination" style="display: flex;align-items:center;justify-content: center">
<div style="display:flex; position: relative;left: 200px;color: rgb(255, 153, 0);font-weight: bold" class="total-price">
<div style="display:flex;margin-right: 10px" class="total">
<div class="total-title">
工艺总价(¥):
</div>
<div class="total-price">
{{ craftPrice }}元
</div>
</div>
<div style="display:flex;" class="total">
<div class="total-title">
工厂价格(¥):
</div>
<div class="total-price">
{{ costPrice }}元
</div>
</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>
</div>
</template>
<style scoped lang="scss">
</style>
......@@ -239,7 +239,7 @@
<div v-for="(item, index) in logList" :key="index" class="log-item">
<div class="icon-left">
<Icon name="a-2labadianji3x" />
<span class="time">{{ item.createdTime }}</span
<span class="time">{{ item.createTime }}</span
>&emsp;
</div>
<span class="description" :title="item.description">
......@@ -385,7 +385,7 @@ const operationLog = async (data: TypesettingListData) => {
try {
const res = await getTypesettingLogByIdApi(data.id)
res.data.forEach((item) => {
item.createdTime = dayjs(item.createdTime).format('YYYY-MM-DD HH:mm:ss')
item.createTime = dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss')
})
logList.value = res.data
logVisible.value = true
......
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