feat: 逆向管理开发

This commit is contained in:
zc 2025-12-04 00:20:53 +08:00
parent 89936bd954
commit 23384b307a
3 changed files with 736 additions and 8 deletions

View File

@ -197,27 +197,26 @@ export default [
modifyTime: '2024-06-19 17:56:01', modifyTime: '2024-06-19 17:56:01',
tag: null, tag: null,
childList: [] childList: []
} },
/*
{ {
id: 45, id: 45,
resourceName: '客户详情', resourceName: '逆向管理',
resourceType: 1, resourceType: 1,
resourceCode: null, resourceCode: null,
path: '/customer/userInfo/detail/index', path: '/order/refund/index',
pid: 43, pid: 43,
resourceDesc: null, resourceDesc: null,
tenantId: 2, tenantId: 2,
icon: '', icon: '',
isCache: 1, isCache: 1,
visible: 1, visible: 0,
sort: '2', sort: '2',
createTime: '2024-06-21 16:55:54', createTime: '2024-06-21 16:55:54',
modifyTime: '2024-06-24 17:10:34', modifyTime: '2024-06-24 17:10:34',
tag: null, tag: null,
childList: [] childList: []
}, }
{ /*{
id: 55, id: 55,
resourceName: '授信记录', resourceName: '授信记录',
resourceType: 1, resourceType: 1,

View File

@ -39,6 +39,8 @@
<span class="text-xs text-[#666]">默认</span> <span class="text-xs text-[#666]">默认</span>
<el-switch <el-switch
v-model="item.isDefault" v-model="item.isDefault"
:active-value="1"
:inactive-value="0"
size="small" size="small"
@change="handleIsDefaultChange(item, list)" @change="handleIsDefaultChange(item, list)"
/> />
@ -83,7 +85,7 @@ const handleExceed = () => {
const handleIsDefaultChange = (item: any, list: any[]) => { const handleIsDefaultChange = (item: any, list: any[]) => {
const isDefault = item.isDefault const isDefault = item.isDefault
list.forEach((child) => { list.forEach((child) => {
child.isDefault = false child.isDefault = 0
}) })
item.isDefault = isDefault item.isDefault = isDefault
} }

View File

@ -0,0 +1,727 @@
<template>
<div class="order-list-page">
<el-card class="status-card" shadow="never">
<div class="status-tabs">
<el-tabs v-model="activeTab" @tab-change="onTabClick">
<el-tab-pane
v-for="tab in tabs"
:key="tab.name"
:name="tab.name"
:label="`${tab.label} ${tab.count ? `(${tab.count})` : ''}`"
/>
</el-tabs>
</div>
<!-- <div class="status-shortcut">
<div
v-for="shortcut in statusShortcuts"
:key="shortcut.label"
class="status-shortcut__item"
>
<span class="label">{{ shortcut.label }}</span>
<span v-if="typeof shortcut.count !== 'undefined'" class="count">{{
`(${shortcut.count})`
}}</span>
</div>
</div> -->
<el-form :inline="true" class="filter-form">
<el-form-item label="商品ID">
<el-input
v-model="filters[activeTab as keyof typeof filters].productId"
placeholder="请输入商ID"
clearable
/>
</el-form-item>
<el-form-item label="商品名称">
<el-input
v-model="filters[activeTab as keyof typeof filters].productName"
placeholder="请输入商品名称"
clearable
/>
</el-form-item>
<el-form-item label="订单号">
<el-input
v-model="filters[activeTab as keyof typeof filters].tradeOrderIds"
placeholder="请输入订单号"
clearable
/>
</el-form-item>
<el-form-item label="创建时间排序">
<el-select
v-model="filters[activeTab as keyof typeof filters].createTimestampSort"
style="width: 180px"
>
<el-option
v-for="option in sortOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</el-form-item>
<el-form-item label="订单时间">
<el-date-picker
v-model="filters[activeTab as keyof typeof filters].orderRange"
type="datetimerange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="handleGetOrderList(filters[activeTab as keyof typeof filters])"
>查询</el-button
>
<el-button @click="resetFilters">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="order-table-card" shadow="never">
<div class="order-table-header">
<div class="col goods">商品</div>
<div class="col total text-center">总计</div>
<!-- <div class="col delivery">发货</div> -->
<div class="col status text-center">状态</div>
<div class="col actions text-center">操作</div>
</div>
<div class="order-list">
<div v-for="order in orders" :key="order.tradeOrderId" class="order-item">
<div class="order-top">
<div class="top-left">
<el-checkbox v-model="order.checked" />
<span class="platform">{{ order.platform }}</span>
<el-divider direction="vertical" />
<!-- <el-link :underline="false" type="primary">{{ order.summary }}</el-link> -->
</div>
<div class="top-right">
<span class="seller">{{ order.buyerWeixin }}</span>
<el-divider direction="vertical" />
<span
class="order-meta"
style="text-decoration: underline"
@click="handleViewOrderDetail(order.tradeOrderId)"
>订单号{{ order.tradeOrderId }}</span
>
<el-divider direction="vertical" />
<span class="order-meta">创建时间{{ order.createTimestamp }}</span>
</div>
</div>
<div v-for="(packageData, packageIndex) in order.packageList" :key="packageIndex">
<div class="order-content">
<div class="col goods">
<div
v-for="item in packageData.vvTradeOrderLineDOList"
:key="item.id"
class="goods-item"
>
<el-image class="goods-image" :src="item.productMainImageUrl" fit="cover">
</el-image>
<div class="goods-info">
<div class="title">{{ item.productName }}</div>
<div class="sub">
<span>{{ item.skuInfo }}</span>
<!-- <span>卖家SKU{{ item.sku }}</span> -->
</div>
</div>
<div class="goods-price">{{ item.chinesePromotionPrice }} x {{ item.num }}</div>
</div>
</div>
<div class="col total">
<div class="amount">
{{
packageData.vvTradeOrderLineDOList.reduce((acc: number, it: any) => {
return acc + it.promotionPrice * it.num
}, 0)
}}
</div>
</div>
<div class="col status">
<div class="goods-status-item flex-1 flex justify-center items-center">
<div class="status-label">{{ packageData.vvTradeOrderLineDOList[0].status }}</div>
</div>
<!-- <ul class="status-detail">
<li>AWB{{ order.awb }}</li>
<li>揽收{{ order.pickupStatus }}</li>
<li>装袋{{ order.bagStatus }}</li>
</ul> -->
</div>
<!-- <div class="col delivery">
<div class="delivery-type">{{ order.delivery.type }}</div>
<div class="delivery-warehouse">签收仓库{{ order.delivery.warehouse }}</div>
</div> -->
<div class="col actions">
<div
v-for="(orderAction, orderActionIndex) in packageData.orderActionList"
:key="orderActionIndex"
:class="[
'goods-actions-item flex flex-col gap-2 justify-center items-center',
orderActionIndex ? '!ml-2' : ''
]"
>
<el-button
class="ml-0!"
:type="
{
'/mm/logistics/query': 'primary',
'/mm/order/shipped': 'success',
'/mm/order/unShipping': 'danger',
'/mm/order/toShipping': 'warning'
}[orderAction.interfaceUri]
"
plain
size="small"
@click="onButtonClick(orderAction.interfaceUri, packageData)"
>{{ orderAction.desc.replace('admin', '').replace('按钮', '') }}</el-button
>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="pagination">
<el-pagination
background
layout="prev, pager, next, jumper"
:page-size="20"
:total="summary.totalOrders"
/>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
type SortWay = 'create_desc' | 'create_asc' | 'pickup_desc' | 'pickup_asc'
interface OrderItem {
id: number
productName: string
// sku: string
num: number
promotionPrice: string
skuInfo: string
status: string
orderActionList: OrderAction[]
productMainImageUrl: string
chinesePromotionPrice: string
}
interface PackageItem {
orderActionList: OrderAction[]
vvTradeOrderLineDOList: OrderItem[]
trackNumber: number
}
interface OrderAction {
desc: string
buttonName: string
interfaceUri: string
}
interface Order {
platform: string
// warehouse: string
buyerWeixin: string
// summary: string
tradeOrderId: string
createTimestamp: string
allPrice: string
status: string
/*
awb: string
pickupStatus: string
bagStatus: string
pickupTime: string
delivery: {
type: string
warehouse: string
} */
packageList: PackageItem[]
checked: boolean
}
const tabs = reactive([
{ name: 'all', label: '全部' },
{ name: 'wait_shipping', label: '待发货', count: '' },
{ name: 'shipping', label: '已发货' },
{ name: 'shipped', label: '已投递' },
{ name: 'delivered', label: '确认收货' },
{ name: 'apply_cancel', label: '买家取消' },
{ name: 'cancel', label: '卖家取消' },
{ name: 'close', label: '已关闭' },
{ name: 'delete', label: '已删除' },
{ name: 'refund', label: '已退款' }
])
const summary = reactive({
totalOrders: 0
})
const orders = ref<Order[]>([])
const filters = reactive({
all: {
productId: '',
productName: '',
tradeOrderIds: '',
orderRange: [] as string[] | [],
createTimestampSort: 'create_DESC' as SortWay
},
wait_shipping: {
productId: '',
productName: '',
tradeOrderIds: '',
orderRange: [] as string[] | [],
createTimestampSort: 'create_DESC' as SortWay
},
close: {
productId: '',
productName: '',
tradeOrderIds: '',
orderRange: [] as string[] | [],
createTimestampSort: 'create_DESC' as SortWay
},
shipping: {
productId: '',
productName: '',
tradeOrderIds: '',
orderRange: [] as string[] | [],
createTimestampSort: 'create_DESC' as SortWay
},
shipped: {
productId: '',
productName: '',
tradeOrderIds: '',
orderRange: [] as string[] | [],
createTimestampSort: 'create_DESC' as SortWay
},
delivered: {
productId: '',
productName: '',
tradeOrderIds: '',
orderRange: [] as string[] | [],
createTimestampSort: 'create_DESC' as SortWay
},
apply_cancel: {
productId: '',
productName: '',
tradeOrderIds: '',
orderRange: [] as string[] | [],
createTimestampSort: 'create_DESC' as SortWay
},
cancel: {
productId: '',
productName: '',
tradeOrderIds: '',
orderRange: [] as string[] | [],
createTimestampSort: 'create_DESC' as SortWay
},
refund: {
productId: '',
productName: '',
tradeOrderIds: '',
orderRange: [] as string[] | [],
createTimestampSort: 'create_DESC' as SortWay
},
delete: {
productId: '',
productName: '',
tradeOrderIds: '',
orderRange: [] as string[] | [],
createTimestampSort: 'create_DESC' as SortWay
}
})
const sortOptions = [
{ label: '创建时间:最新在前', value: 'create_DESC' },
{ label: '创建时间:最早在前', value: 'create_ASC' },
{ label: '修改时间:最新在前', value: 'modify_DESC' },
{ label: '修改时间:最早在前', value: 'modify_ASC' }
]
const activeTab = ref(tabs[0].name)
const resetFilters = () => {
filters[activeTab.value as keyof typeof filters] = {
productName: '',
productId: '',
tradeOrderIds: '',
orderRange: [] as string[] | [],
createTimestampSort: 'create_DESC' as SortWay
}
}
const onTabClick = () => {
handleGetOrderList()
}
const onButtonClick = (interfaceUri: string, packageData: PackageItem) => {
console.warn('----- my data is packageData: ', packageData)
/* if ('/mm/order/toShipping' === interfaceUri) {
//
handlePack(packageData.vvTradeOrderLineDOList)
} else if ('/mm/logistics/query' === interfaceUri) {
//
handleViewLogistics(packageData)
} else {
const apiMap = {
'/mm/order/unShipping': 'unpackOrder', //
'/mm/order/shipped': 'finishDeliver', //
'/mm/order/cancel': 'cancelOrder' //
}
handleMessageBox({
msg: '是否确认该操作?',
success:
api.order[apiMap[interfaceUri as keyof typeof apiMap] as keyof typeof api.order].post!,
data: { id: orderLine.id }
}).then(() => {
handleGetOrderList()
})
} */
}
const orderStatusMap = {
create: '已创建',
wait_pay: '待支付',
wait_shipping: '待发货',
shipping: '已发货',
shipped: '已投递',
delivered: '已妥投',
apply_cancel: '买家申请取消',
cancel: '卖家同意取消',
refund: '已退款',
close: '买家关闭订单'
}
const router = useRouter()
const handleViewOrderDetail = (id: number) => {
router.push({ path: '/order/list/detail', query: { id } })
}
const handleGetOrderList = async (searchData: any = {}) => {
const search = {
...searchData,
productId: searchData.productId || undefined,
productName: searchData.productName || undefined,
tradeOrderIds: searchData.tradeOrderIds || undefined
}
if (search.createTimestampSort?.startsWith('create_')) {
search.createTimestampSort = search.createTimestampSort.split('_')[1]
} else if (search.createTimestampSort?.startsWith('modify_')) {
search.modifyTimestampSort = search.createTimestampSort.split('_')[1]
delete search.createTimestampSort
}
const res = await api.order.getReverseList.post!<any>({
...search,
minCreateTimestamp: search.orderRange?.[0],
maxCreateTimestamp: search.orderRange?.[1],
tradeOrderIds: search.tradeOrderIds?.split(','),
status: activeTab.value === 'all' ? undefined : activeTab.value,
orderRange: undefined,
pageNum: 1,
pageSize: 20
})
orders.value = res.data.rows.map((item: any) => {
item.platform = '购de着小程序'
item.allPrice = '¥' + item.allPrice
item.tradeOrderId = item.id
item.packageList = (item.vvReverseOrderLineEntities || []).map((packageData: any) => {
packageData.vvTradeOrderLineDOList = packageData.vvTradeOrderLineDOList.map((item: any) => {
return {
...item,
status: orderStatusMap[item.status as keyof typeof orderStatusMap],
chinesePromotionPrice: '¥' + item.promotionPrice.toFixed(2),
skuInfo: JSON.parse(item.skuInfo)
.map((it: any) => `${it.propertyName}:${it.propertyValue}`)
.join(',')
}
})
return packageData
})
/* item.vvTradeOrderLineList = item.packageList.map((childOrder: any) => {
return {
...childOrder,
status: orderStatusMap[childOrder.status as keyof typeof orderStatusMap],
promotionPrice: '¥' + childOrder.promotionPrice.toFixed(2),
skuInfo: JSON.parse(childOrder.skuInfo)
.map((it: any) => `${it.propertyName}:${it.propertyValue}`)
.join(','),
checked: false
}
}) */
return item
})
summary.totalOrders = res.data.total
if (activeTab.value === 'wait_shipping') {
tabs.find((item) => item.name === 'wait_shipping')!.count = res.data.total
}
}
handleGetOrderList()
</script>
<style scoped lang="scss">
.order-list-page {
display: flex;
flex-direction: column;
gap: 16px;
background-color: #f5f7fa;
padding: 16px;
.status-card {
.status-shortcut {
display: flex;
gap: 24px;
margin-bottom: 12px;
padding: 0 4px;
font-size: 13px;
color: #606266;
&__item {
display: inline-flex;
align-items: center;
gap: 4px;
cursor: pointer;
.label {
color: #303133;
}
.count {
color: #606266;
}
}
}
.filter-form {
display: flex;
flex-wrap: wrap;
gap: 8px 16px;
align-items: center;
:deep(.el-form-item__label) {
font-weight: 500;
color: #303133;
}
}
}
.order-table-card {
padding: 0 0 16px;
.order-table-header {
display: grid;
grid-template-columns: 46% 12% 16% 1fr;
padding: 12px 24px;
background: #f9fafc;
border-bottom: 1px solid #ebeef5;
font-size: 13px;
color: #909399;
.col:first-child {
padding-left: 44px;
}
}
.order-list {
display: flex;
flex-direction: column;
gap: 12px;
padding: 16px 0;
}
.order-item {
border: 1px solid #ebeef5;
border-radius: 6px;
background: #fff;
overflow: hidden;
.order-top {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 24px;
background: #fafafa;
border-bottom: 1px solid #f0f2f5;
font-size: 13px;
color: #606266;
.top-left,
.top-right {
display: flex;
align-items: center;
gap: 8px;
}
.platform {
font-weight: 600;
color: #303133;
}
.seller {
color: #303133;
}
.order-meta {
color: #909399;
}
}
.order-content {
display: grid;
grid-template-columns: 46% 12% 16% 1fr;
padding: 0;
font-size: 13px;
color: #606266;
border-bottom: 1px solid #ebeef5;
.goods-item + .goods-item {
border-top: 1px solid #f0f2f5;
}
.goods-status-item {
width: 100%;
}
.goods-status-item + .goods-status-item {
border-top: 1px solid #f0f2f5;
}
.goods {
display: flex;
flex-direction: column;
.goods-item {
display: flex;
gap: 12px;
border-right: 1px solid #f0f2f5;
border-radius: 6px;
padding: 12px;
align-items: center;
flex: 1;
.goods-image {
width: 60px;
height: 60px;
border-radius: 6px;
overflow: hidden;
}
.image-fallback {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background: #f2f3f5;
color: #909399;
font-size: 12px;
}
.goods-info {
flex: 1;
.title {
font-size: 14px;
font-weight: 500;
color: #303133;
margin-bottom: 6px;
}
.sub {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 12px;
color: #909399;
}
}
.goods-price {
font-weight: 600;
color: #303133;
}
}
}
.total {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 4px;
.amount {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.remark {
font-size: 12px;
color: #909399;
}
}
.delivery {
display: flex;
flex-direction: column;
justify-content: center;
gap: 4px;
.delivery-type {
font-weight: 600;
color: #303133;
}
.delivery-warehouse {
font-size: 12px;
color: #909399;
}
}
.status {
border-left: 1px solid #f0f2f5;
display: flex;
flex-direction: column;
justify-content: center;
.status-label {
font-weight: 600;
color: #303133;
}
.status-detail {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 4px;
font-size: 12px;
color: #909399;
}
}
.actions {
border-left: 1px solid #f0f2f5;
display: flex;
align-items: center;
justify-content: center;
padding: 8px 0;
}
}
}
.pagination {
display: flex;
justify-content: flex-end;
padding: 0 24px;
margin-top: 8px;
}
}
}
</style>