Commit 00ff1ac5 by wusiyi

feat: 新增物流跟踪页面

parent e90edaec
......@@ -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,
)
}
......@@ -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>
......@@ -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: '仓库管理',
......
......@@ -61,6 +61,11 @@ const menu: MenuItem[] = [
id: 7,
label: '分拣配置',
},
{
index: '/logistics/logisticsTracking',
id: 8,
label: '物流跟踪',
},
],
},
{
......
<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: 10px;
}
.table-flex {
flex: 1;
flex-shrink: 0;
overflow: hidden;
display: flex;
}
.right-table {
flex: 1;
margin-left: 10px;
flex-shrink: 0;
}
.left-table {
height: 100%;
display: flex;
width: 100%;
flex-direction: column;
.pagination {
display: flex;
margin-top: 10px;
justify-content: center;
}
.table-container {
flex: 1;
flex-shrink: 0;
overflow: hidden;
}
}
}
.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;
}
.tree-node {
display: flex;
color: #333;
font-weight: 500;
}
: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;
}
}
}
}
.right {
flex: 1;
flex-shrink: 0;
background: white;
overflow: hidden;
}
</style>
......@@ -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
}
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