Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
F
factory_front
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
qinjianhui
factory_front
Commits
00ff1ac5
Commit
00ff1ac5
authored
Aug 27, 2025
by
wusiyi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 新增物流跟踪页面
parent
e90edaec
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
633 additions
and
3 deletions
+633
-3
src/api/logistics.ts
+39
-0
src/components/TableView.vue
+85
-3
src/router/index.ts
+7
-0
src/router/menu.ts
+5
-0
src/views/logistics/logisticsTracking.vue
+486
-0
src/views/logistics/types/logistics.ts
+11
-0
No files found.
src/api/logistics.ts
View file @
00ff1ac5
...
...
@@ -4,6 +4,8 @@ import {
LogisticsMethod
,
LogisticsMethodList
,
UpdateLogisticsMethodStatus
,
LogisticsTrackingParams
,
LogisticsTrackingTree
,
}
from
'@/views/logistics/types/logistics'
import
{
AddDeclarationRuleObj
}
from
'@/views/logistics/types/declarationRule'
import
{
LogisticsQuotation
}
from
'@/views/logistics/types/logisticsQuotation'
...
...
@@ -473,3 +475,40 @@ export function deleteSortingApi(ids: string) {
},
)
}
// 物流跟踪 获取菜单树
export
function
logisticStatusList
()
{
return
axios
.
get
<
never
,
BaseRespData
<
LogisticsTrackingTree
[]
>>
(
'factory/logisticsTracking/statusList'
,
)
}
// 物流跟踪 分页
export
function
logisticsTrackingPage
(
params
:
LogisticsTrackingParams
)
{
return
axios
.
post
<
never
,
BasePaginationData
<
ILogisticsCompany
>>
(
'/factory/logisticsTracking/list_page'
,
params
,
)
}
// 物流跟踪 批量注册
export
function
logisticsTrackingBatchRegister
(
params
:
{
idList
:
number
[]
selectAll
:
boolean
})
{
return
axios
.
post
<
never
,
BaseRespData
<
never
>>
(
'/factory/logisticsTracking/batchRegister'
,
params
,
)
}
// 物流跟踪 批量同步
export
function
logisticsTrackingBatchPushOrder
(
params
:
{
idList
:
number
[]
selectAll
:
boolean
})
{
return
axios
.
post
<
never
,
BaseRespData
<
never
>>
(
'/factory/logisticsTracking/batchPushOrder'
,
params
,
)
}
src/components/TableView.vue
View file @
00ff1ac5
...
...
@@ -8,6 +8,8 @@
header-align=
"center"
height=
"100%"
v-bind=
"attrs"
:class=
"internalIsMore ? 'is-more-active' : ''"
@
header-click=
"handleTableHeaderClick"
>
<ElTableColumn
v-if=
"selectionable"
...
...
@@ -16,7 +18,8 @@
fixed=
"left"
header-align=
"center"
align=
"center"
></ElTableColumn>
>
</ElTableColumn>
<ElTableColumn
v-if=
"serialNumberable"
label=
"序号"
...
...
@@ -25,6 +28,7 @@
fixed=
"left"
header-align=
"center"
align=
"center"
:class=
"isMore ? 'is-more-active' : ''"
></ElTableColumn>
<template
v-for=
"column in columns"
:key=
"column.key"
>
<ElTableColumn
...
...
@@ -60,16 +64,26 @@
</div>
</template>
<
script
setup
lang=
"tsx"
generic=
"T"
>
import
{
type
Slot
,
useAttrs
,
useSlots
,
type
PropType
,
shallowRef
}
from
'vue'
import
{
type
Slot
,
useAttrs
,
useSlots
,
type
PropType
,
shallowRef
,
ref
,
nextTick
,
}
from
'vue'
import
type
{
CustomColumn
}
from
'@/types/table'
import
RenderColumn
from
'./RenderColumn.vue'
import
{
ElTable
}
from
'element-plus'
import
type
{
TableInstance
}
from
'element-plus'
const
tableRef
=
shallowRef
<
TableInstance
>
()
const
internalIsMore
=
ref
(
false
)
const
selectionHeaderClickCount
=
ref
(
0
)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
defineProps
({
const
props
=
defineProps
({
paginatedData
:
{
type
:
Array
,
required
:
true
,
...
...
@@ -90,6 +104,10 @@ defineProps({
type
:
Boolean
,
default
:
false
,
},
isMore
:
{
type
:
Boolean
,
default
:
false
,
},
})
const
attrs
=
useAttrs
()
...
...
@@ -110,12 +128,51 @@ const toggleAllSelection = () => {
tableRef
.
value
?.
toggleAllSelection
()
}
const
handleTableHeaderClick
=
(
column
:
{
type
?:
string
},
event
:
Event
)
=>
{
if
(
column
.
type
===
'selection'
&&
props
.
isMore
)
{
event
.
preventDefault
()
event
.
stopPropagation
()
selectionHeaderClickCount
.
value
++
const
count
=
selectionHeaderClickCount
.
value
const
actions
=
{
1
:
()
=>
selectAllRows
(
true
),
2
:
()
=>
selectAllRows
(
true
,
true
),
3
:
()
=>
selectAllRows
(
false
,
false
),
}
if
(
actions
[
count
as
keyof
typeof
actions
])
{
actions
[
count
as
keyof
typeof
actions
]()
}
}
}
const
selectAllRows
=
(
select
:
boolean
,
setInternalIsMore
?:
boolean
)
=>
{
nextTick
(()
=>
{
if
(
tableRef
.
value
&&
props
.
paginatedData
.
length
>
0
)
{
props
.
paginatedData
.
forEach
((
row
)
=>
{
tableRef
.
value
?.
toggleRowSelection
(
row
,
select
)
})
}
if
(
setInternalIsMore
!==
undefined
)
{
internalIsMore
.
value
=
setInternalIsMore
}
if
(
!
select
&&
setInternalIsMore
===
false
)
{
selectionHeaderClickCount
.
value
=
0
}
})
}
defineExpose
({
tableRef
,
setCurrentRow
,
toggleRowSelection
,
clearSelection
,
toggleAllSelection
,
internalIsMore
,
})
</
script
>
...
...
@@ -125,4 +182,29 @@ defineExpose({
overflow
:
hidden
;
position
:
relative
;
}
::v-deep
.is-more-active
{
.el-table__header
.el-table-column--selection
.cell
{
height
:
auto
!important
;
padding
:
0px
0
!important
;
overflow
:
visible
!important
;
.el-checkbox
{
position
:
relative
;
transform
:
translateY
(
-7px
);
&::after
{
content
:
'All'
;
color
:
#262626
;
font-size
:
12px
;
position
:
absolute
;
top
:
100%
;
left
:
50%
;
transform
:
translateX
(
-50%
);
line-height
:
1
;
white-space
:
nowrap
;
}
}
}
}
</
style
>
src/router/index.ts
View file @
00ff1ac5
...
...
@@ -236,6 +236,13 @@ const router = createRouter({
component
:
()
=>
import
(
'@/views/logistics/sortingConfiguration.vue'
),
},
{
path
:
'/logistics/logisticsTracking'
,
meta
:
{
title
:
'物流跟踪'
,
},
component
:
()
=>
import
(
'@/views/logistics/logisticsTracking.vue'
),
},
{
path
:
'/warehouse/manage'
,
meta
:
{
title
:
'仓库管理'
,
...
...
src/router/menu.ts
View file @
00ff1ac5
...
...
@@ -61,6 +61,11 @@ const menu: MenuItem[] = [
id
:
7
,
label
:
'分拣配置'
,
},
{
index
:
'/logistics/logisticsTracking'
,
id
:
8
,
label
:
'物流跟踪'
,
},
],
},
{
...
...
src/views/logistics/logisticsTracking.vue
0 → 100644
View file @
00ff1ac5
<
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=
"status"
:data=
"treeData"
:props=
"
{ children: 'children', label: 'name' }"
@node-click="nodeClick"
>
<template
#
default=
"
{ data }">
<div
class=
"tree-node"
>
<div
class=
"tree-node-label"
>
{{
data
.
name
}}
</div>
<div
v-if=
"data.num || data.num === 0"
class=
"tree-node-count"
>
{{
`(${data.num
}
)`
}}
<
/div
>
<
/div
>
<
/template
>
<
/ElTree
>
<
/div
>
<
div
class
=
"right"
>
<
split
-
div
>
<
template
#
top
>
<
el
-
card
>
<
el
-
form
inline
:
model
=
"searchForm"
>
<
el
-
form
-
item
label
=
"店铺单号"
>
<
el
-
input
v
-
model
=
"searchForm.shopNumber"
style
=
"width: 180px"
placeholder
=
"请输入店铺单号"
clearable
><
/el-input
>
<
/el-form-item
>
<
el
-
form
-
item
label
=
"物流跟踪号"
>
<
el
-
input
v
-
model
=
"searchForm.trackNumber"
style
=
"width: 180px"
placeholder
=
"请输入物流跟踪号"
clearable
><
/el-input
>
<
/el-form-item
>
<
el
-
form
-
item
>
<
el
-
button
type
=
"primary"
@
click
=
"getData"
>
查询
<
/el-button
>
<
el
-
button
v
-
if
=
"nodeId === 0 || nodeId === 11"
type
=
"success"
@
click
=
"batchRegister"
>
批量注册
<
/el-button
>
<
el
-
button
v
-
if
=
"nodeId !== -1 && nodeId !== 0 && nodeId !== 11"
type
=
"warning"
@
click
=
"batchPushOrder"
>
批量同步
<
/el-button
>
<
/el-form-item
>
<
/el-form
>
<
/el-card
>
<
/template
>
<
template
#
bottom
>
<
el
-
card
style
=
"height: 100%"
>
<
div
class
=
"manage"
>
<
div
class
=
"table-flex"
>
<
div
class
=
"left-table"
>
<
div
class
=
"table-container"
>
<
TableView
ref
=
"tableRef"
:
columns
=
"tableColumns"
:
serial
-
numberable
=
"true"
:
selectionable
=
"true"
:
paginated
-
data
=
"leftData"
:
is
-
more
=
"true"
highlight
-
current
-
row
@
selection
-
change
=
"handleSelectionChange"
>
<
template
#
orderStatus
=
"{ row
}
"
>
<
div
>
{{
getStatus
(
row
.
orderStatus
)
}}
<
/div
>
<
/template
>
<
template
#
shipmentType
=
"{ row
}
"
>
{{
[
'自有物流'
,
'工厂物流'
][
row
.
shipmentType
]
}}
<
/template
>
<
/TableView
>
<
/div
>
<
div
class
=
"pagination"
>
<
el
-
pagination
v
-
model
:
current
-
page
=
"pagination.currentPage"
v
-
model
:
page
-
size
=
"pagination.pageSize"
:
page
-
sizes
=
"[50, 100, 150, 200]"
layout
=
"total, sizes, prev, pager, next, jumper"
:
total
=
"pagination.total"
@
size
-
change
=
"handleSizeChange"
@
current
-
change
=
"handleCurrentChange"
/>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/el-card
>
<
/template
>
<
/split-div
>
<
/div
>
<
/div
>
<
/template
>
<
script
setup
lang
=
"ts"
>
import
{
nextTick
,
ref
,
onMounted
,
computed
}
from
'vue'
import
{
ElMessage
,
ElTree
}
from
'element-plus'
import
SplitDiv
from
'@/components/splitDiv/splitDiv.vue'
import
TableView
from
'@/components/TableView.vue'
import
{
ILogisticsCompany
,
ILogisticsCompanyData
,
logisticsTrackingPage
,
logisticStatusList
,
logisticsTrackingBatchRegister
,
logisticsTrackingBatchPushOrder
,
}
from
'@/api/logistics.ts'
import
{
getOrderTabData
}
from
'@/api/podUsOrder'
import
{
LogisticsTrackingTree
}
from
'@/views/logistics/types/logistics'
import
{
Tab
}
from
'@/types/api/podUsOrder'
interface
SearchForm
{
shopNumber
:
string
trackNumber
:
string
|
number
}
const
searchForm
=
ref
<
SearchForm
>
({
shopNumber
:
''
,
trackNumber
:
''
,
}
)
const
treeData
=
ref
<
LogisticsTrackingTree
[]
>
()
const
treeRef
=
ref
<
InstanceType
<
typeof
ElTree
>>
()
const
tableRef
=
ref
<
{
internalIsMore
?:
boolean
}
>
()
const
nodeId
=
ref
<
number
>
(
-
1
)
const
selections
=
ref
<
ILogisticsCompany
[]
>
([])
const
leftData
=
ref
<
ILogisticsCompany
[]
>
([])
const
pagination
=
ref
<
ILogisticsCompanyData
>
({
pageSize
:
50
,
currentPage
:
1
,
total
:
0
,
}
)
// 表格列配置
const
tableColumns
=
computed
(()
=>
{
return
[
{
label
:
'订单号'
,
prop
:
'factoryOrderNumber'
,
width
:
160
,
align
:
'center'
,
}
,
{
label
:
'第三方订单号'
,
prop
:
'thirdOrderNumber'
,
width
:
240
,
align
:
'center'
,
}
,
{
label
:
'店铺单号'
,
prop
:
'shopNumber'
,
width
:
160
,
align
:
'center'
,
}
,
{
label
:
'订单状态'
,
prop
:
'orderStatus'
,
slot
:
'orderStatus'
,
width
:
160
,
align
:
'center'
,
}
,
{
label
:
'物流类型'
,
slot
:
'shipmentType'
,
prop
:
'shipmentType'
,
width
:
120
,
align
:
'center'
,
}
,
{
label
:
'物流跟踪号'
,
prop
:
'trackingNumber'
,
width
:
200
,
align
:
'center'
,
showOverflowTooltip
:
true
,
}
,
{
label
:
'总克重(g)'
,
prop
:
'weight'
,
width
:
100
,
align
:
'center'
,
}
,
{
label
:
'生产端'
,
prop
:
'productionClient'
,
width
:
100
,
align
:
'center'
,
}
,
{
label
:
'发货仓库'
,
prop
:
'warehouseName'
,
width
:
120
,
align
:
'center'
,
}
,
{
label
:
'物流方式'
,
prop
:
'logisticsWayName'
,
width
:
120
,
align
:
'center'
,
}
,
{
label
:
'收货人'
,
prop
:
'receiverName'
,
width
:
150
,
align
:
'center'
,
showOverflowTooltip
:
true
,
}
,
{
label
:
'收货人电话'
,
prop
:
'receiverPhone'
,
width
:
140
,
align
:
'center'
,
showOverflowTooltip
:
true
,
}
,
{
label
:
'收货人邮编'
,
prop
:
'receiverPostCode'
,
width
:
140
,
align
:
'center'
,
}
,
{
label
:
'收货地址'
,
prop
:
'lanshouAddress'
,
width
:
500
,
align
:
'center'
,
showOverflowTooltip
:
true
,
}
,
]
}
)
// 获取菜单树
const
getTree
=
async
()
=>
{
try
{
const
res
=
await
logisticStatusList
()
treeData
.
value
=
res
.
data
await
nextTick
(()
=>
{
treeRef
.
value
!
.
setCurrentKey
(
nodeId
.
value
,
true
)
}
)
}
catch
(
e
)
{
console
.
error
(
e
)
}
}
// 列表查询
async
function
getData
()
{
const
res
=
await
logisticsTrackingPage
({
trackingStatus
:
nodeId
.
value
,
shopNumber
:
searchForm
.
value
.
shopNumber
,
trackNumber
:
searchForm
.
value
.
trackNumber
,
}
)
leftData
.
value
=
res
.
data
.
records
pagination
.
value
.
total
=
res
.
data
.
total
}
// 批量注册
const
batchRegister
=
async
()
=>
{
if
(
selections
.
value
.
length
===
0
)
{
ElMessage
.
warning
(
'请选择要注册的订单'
)
return
}
try
{
await
ElMessageBox
.
confirm
(
`确定批量注册?`
,
'重要提示'
,
{
confirmButtonText
:
'确定'
,
type
:
'warning'
,
}
)
}
catch
(
error
)
{
return
}
const
loading
=
ElLoading
.
service
({
fullscreen
:
true
,
text
:
'操作中...'
,
background
:
'rgba(0, 0, 0, 0.3)'
,
}
)
try
{
await
logisticsTrackingBatchRegister
({
idList
:
tableRef
.
value
?.
internalIsMore
?
[]
:
selections
.
value
.
map
((
d
)
=>
d
.
id
),
selectAll
:
tableRef
.
value
?.
internalIsMore
||
false
,
}
)
loading
.
close
()
selections
.
value
=
[]
ElMessage
.
success
(
'操作成功'
)
getData
()
}
catch
(
err
)
{
console
.
log
(
err
)
}
finally
{
loading
.
close
()
}
}
// 批量同步
const
batchPushOrder
=
async
()
=>
{
if
(
selections
.
value
.
length
===
0
)
{
ElMessage
.
warning
(
'请选择要同步的订单'
)
return
}
try
{
await
ElMessageBox
.
confirm
(
`确定批量同步?`
,
'重要提示'
,
{
confirmButtonText
:
'确定'
,
type
:
'warning'
,
}
)
}
catch
(
error
)
{
return
}
const
loading
=
ElLoading
.
service
({
fullscreen
:
true
,
text
:
'操作中...'
,
background
:
'rgba(0, 0, 0, 0.3)'
,
}
)
try
{
await
logisticsTrackingBatchPushOrder
({
idList
:
selections
.
value
.
map
((
d
)
=>
d
.
id
),
selectAll
:
false
,
}
)
loading
.
close
()
selections
.
value
=
[]
ElMessage
.
success
(
'操作成功'
)
getData
()
}
catch
(
err
)
{
console
.
log
(
err
)
}
finally
{
loading
.
close
()
}
}
const
tabsNav
=
ref
<
Tab
[]
>
([])
const
loadTabData
=
async
()
=>
{
try
{
const
res
=
await
getOrderTabData
()
tabsNav
.
value
=
res
.
data
.
filter
((
el
)
=>
el
.
status
!==
'BATCH_DOWNLOAD'
)
}
catch
(
error
)
{
// showError(error)
}
}
function
getStatus
(
status
:
string
)
{
const
item
=
tabsNav
.
value
.
find
((
el
)
=>
el
.
status
===
status
)
if
(
item
)
{
return
item
.
statusName
}
return
''
}
const
nodeClick
=
(
data
:
LogisticsTrackingTree
)
=>
{
nodeId
.
value
=
data
.
status
??
0
getData
()
}
const
handleSelectionChange
=
(
data
:
ILogisticsCompany
[])
=>
{
selections
.
value
=
data
}
const
handleSizeChange
=
(
pageSize
:
number
)
=>
{
pagination
.
value
.
pageSize
=
pageSize
getData
()
}
const
handleCurrentChange
=
(
currentPage
:
number
)
=>
{
pagination
.
value
.
currentPage
=
currentPage
getData
()
}
onMounted
(()
=>
{
getTree
()
getData
()
loadTabData
()
}
)
<
/script
>
<
style
scoped
lang
=
"scss"
>
.
el
-
card
{
::
v
-
deep
(.
el
-
card__body
)
{
height
:
100
%
;
}
}
.
manage
{
height
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
.
header
{
margin
-
bottom
:
10
px
;
}
.
table
-
flex
{
flex
:
1
;
flex
-
shrink
:
0
;
overflow
:
hidden
;
display
:
flex
;
}
.
right
-
table
{
flex
:
1
;
margin
-
left
:
10
px
;
flex
-
shrink
:
0
;
}
.
left
-
table
{
height
:
100
%
;
display
:
flex
;
width
:
100
%
;
flex
-
direction
:
column
;
.
pagination
{
display
:
flex
;
margin
-
top
:
10
px
;
justify
-
content
:
center
;
}
.
table
-
container
{
flex
:
1
;
flex
-
shrink
:
0
;
overflow
:
hidden
;
}
}
}
.
left
{
width
:
160
px
;
:
deep
(.
el
-
tree
-
node__content
)
{
height
:
30
px
;
line
-
height
:
30
px
;
}
:
deep
(.
el
-
tree
-
node__label
)
{
font
-
size
:
13
px
;
cursor
:
pointer
;
display
:
inline
-
block
;
width
:
100
%
;
color
:
black
!
important
;
padding
:
3
px
7
px
;
}
:
deep
(.
el
-
tree
-
node__expand
-
icon
)
{
display
:
none
;
}
.
tree
-
node
{
display
:
flex
;
color
:
#
333
;
font
-
weight
:
500
;
}
:
deep
(.
is
-
current
)
{
.
tree
-
node
-
label
,
.
tree
-
node
-
count
{
background
-
color
:
#
ecf5ff
;
color
:
#
409
eff
!
important
;
}
.
el
-
tree
-
node__children
{
.
tree
-
node
-
label
,
.
tree
-
node
-
count
{
background
-
color
:
transparent
!
important
;
color
:
black
!
important
;
}
}
}
}
.
right
{
flex
:
1
;
flex
-
shrink
:
0
;
background
:
white
;
overflow
:
hidden
;
}
<
/style
>
src/views/logistics/types/logistics.ts
View file @
00ff1ac5
...
...
@@ -49,3 +49,14 @@ interface ruleRefObj {
ruleId
:
string
|
number
ruleName
:
string
|
number
}
export
interface
LogisticsTrackingTree
{
name
:
string
status
:
number
num
:
number
}
export
interface
LogisticsTrackingParams
{
trackNumber
?:
number
|
string
shopNumber
?:
string
|
number
trackingStatus
?:
number
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment