feat:卖家管理开发

This commit is contained in:
zc 2025-11-21 21:43:28 +08:00
parent f1f3b01081
commit 930f48bfa3
8 changed files with 324 additions and 20 deletions

View File

@ -16,6 +16,7 @@
"@wangeditor/editor": "^5.1.23", "@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12", "@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.6.8", "axios": "^1.6.8",
"element-china-area-data": "^6.1.0",
"element-plus": "^2.11.5", "element-plus": "^2.11.5",
"lz-utils-lib": "^1.0.51", "lz-utils-lib": "^1.0.51",
"pinia": "^2.1.7", "pinia": "^2.1.7",

View File

@ -14,7 +14,14 @@ const order = {
*/ */
getUserReviews: ['/comment/list'], // 获取用户评论 getUserReviews: ['/comment/list'], // 获取用户评论
agreeComment: ['/comment/agree'], // 同意 agreeComment: ['/comment/agree'], // 同意
rejectComment: ['/comment/reject'] // 拒绝 rejectComment: ['/comment/reject'], // 拒绝
/**
*
*/
getSellerList: ['/seller/list'], // 卖家列表
updateSeller: ['/seller/insertOrUpdate'], // 添加卖家
updateAddress: ['/warehouse/insertOrUpdate'] // 添加仓库
} }
export default order export default order

View File

@ -452,13 +452,13 @@ export default [
childList: [] childList: []
} */ } */
] ]
} },
/* { {
id: 60, id: 60,
resourceName: '渠道管理', resourceName: '卖家管理',
resourceType: 0, resourceType: 0,
resourceCode: null, resourceCode: null,
path: '/channel', path: '/seller-manage',
pid: 0, pid: 0,
resourceDesc: null, resourceDesc: null,
tenantId: 2, tenantId: 2,
@ -472,10 +472,10 @@ export default [
childList: [ childList: [
{ {
id: 61, id: 61,
resourceName: '渠道配置', resourceName: '卖家列表',
resourceType: 1, resourceType: 1,
resourceCode: null, resourceCode: null,
path: '/channel/config/index', path: '/seller-manage/list/index',
pid: 60, pid: 60,
resourceDesc: null, resourceDesc: null,
tenantId: 2, tenantId: 2,
@ -489,7 +489,8 @@ export default [
childList: [] childList: []
} }
] ]
}, }
/*
{ {
id: 110, id: 110,
resourceName: '产品管理', resourceName: '产品管理',

View File

@ -266,13 +266,13 @@ interface Order {
const tabs = reactive([ const tabs = reactive([
{ name: 'all', label: '全部' }, { name: 'all', label: '全部' },
{ name: 'wait_shipping', label: '待发货', count: 3544 }, { name: 'wait_shipping', label: '待发货', count: '' },
{ name: 'shipping', label: '已发货' }, { name: 'shipping', label: '已发货' },
{ name: 'shipped', label: '已投递' }, { name: 'shipped', label: '已投递' },
{ name: 'delivered', label: '已妥投' }, { name: 'delivered', label: '确认收货' },
{ name: 'apply_cancel', label: '买家取消' }, { name: 'apply_cancel', label: '买家取消' },
{ name: 'cancel', label: '卖家取消' }, { name: 'cancel', label: '卖家取消' },
{ name: 'close', label: '已取消' }, { name: 'close', label: '已关闭' },
{ name: 'delete', label: '已删除' }, { name: 'delete', label: '已删除' },
{ name: 'refund', label: '已退款' } { name: 'refund', label: '已退款' }
]) ])
@ -285,7 +285,7 @@ const statusShortcuts = reactive([
]) */ ]) */
const summary = reactive({ const summary = reactive({
totalOrders: 3544 totalOrders: 0
}) })
const orders = ref<Order[]>([]) const orders = ref<Order[]>([])
@ -590,6 +590,10 @@ const handleGetOrderList = async (searchData: any = {}) => {
}) */ }) */
return item return item
}) })
summary.totalOrders = res.data.total
if (activeTab.value === 'wait_shipping') {
tabs.find((item) => item.name === 'wait_shipping')!.count = res.data.total
}
} }
handleGetOrderList() handleGetOrderList()

View File

@ -55,12 +55,15 @@
物流信息 物流信息
</h3> </h3>
<el-form ref="formRef" :model="formData" inline :rules="formRules" label-width="100px"> <el-form ref="formRef" :model="formData" inline :rules="formRules" label-width="100px">
<el-form-item label="物流单号" prop="trackingNumber"> <el-form-item label="物流单号" prop="trackNumber">
<el-input v-model="formData.trackingNumber" placeholder="请输入物流单号" clearable /> <el-input v-model="formData.trackNumber" placeholder="请输入物流单号" clearable />
</el-form-item> </el-form-item>
<el-form-item label="物流公司" prop="logisticsCompany"> <el-form-item label="物流公司" prop="logisticsCompany">
<el-input v-model="formData.logisticsCompany" placeholder="请输入物流公司" clearable /> <el-input v-model="formData.logisticsCompany" placeholder="请输入物流公司" clearable />
</el-form-item> </el-form-item>
<el-form-item label="卖家" prop="sellerId">
<el-cascader v-model="formData.sellerId" :options="sellerList" />
</el-form-item>
<el-form-item label="运费" prop="shippingAmount"> <el-form-item label="运费" prop="shippingAmount">
<el-input <el-input
v-model="formData.shippingAmount" v-model="formData.shippingAmount"
@ -128,10 +131,11 @@ interface TableItem {
} }
interface FormData { interface FormData {
trackingNumber: string trackNumber: string
logisticsCompany: string logisticsCompany: string
shippingAmount: string shippingAmount: string
packageImageUrls: string[] packageImageUrls: string[]
sellerId: number[]
} }
interface OrderItem { interface OrderItem {
@ -166,14 +170,15 @@ const selectedRows = ref<OrderItem[]>([])
// //
const formRef = ref<FormInstance>() const formRef = ref<FormInstance>()
const formData = ref<FormData>({ const formData = ref<FormData>({
trackingNumber: '', trackNumber: '',
logisticsCompany: '', logisticsCompany: '',
shippingAmount: '', shippingAmount: '',
packageImageUrls: [] packageImageUrls: [],
sellerId: []
}) })
const formRules: FormRules = { const formRules: FormRules = {
trackingNumber: [{ required: true, message: '请输入物流单号', trigger: 'blur' }], trackNumber: [{ required: true, message: '请输入物流单号', trigger: 'blur' }],
logisticsCompany: [{ required: true, message: '请输入物流公司', trigger: 'blur' }], logisticsCompany: [{ required: true, message: '请输入物流公司', trigger: 'blur' }],
shippingAmount: [{ required: true, message: '请输入运费', trigger: 'blur' }], shippingAmount: [{ required: true, message: '请输入运费', trigger: 'blur' }],
packageImageUrls: [{ required: true, message: '请上传照片', trigger: 'blur' }] packageImageUrls: [{ required: true, message: '请上传照片', trigger: 'blur' }]
@ -217,7 +222,9 @@ const handleSubmit = async () => {
try { try {
const submitData = { const submitData = {
tradeOrderLineIds: selectedRows.value.map((row) => row.orderLineId), tradeOrderLineIds: selectedRows.value.map((row) => row.orderLineId),
...formData.value ...formData.value,
sellerId: formData.value.sellerId[0],
sellerWarehouseId: formData.value.sellerId[1]
} }
await api.order.packOrder.post!(submitData) await api.order.packOrder.post!(submitData)
ElMessage.success('打包成功') ElMessage.success('打包成功')
@ -247,7 +254,7 @@ const handleClose = (isClose = true) => {
// //
selectedRows.value = [] selectedRows.value = []
formData.value = { formData.value = {
trackingNumber: '', trackNumber: '',
logisticsCompany: '', logisticsCompany: '',
shippingAmount: '', shippingAmount: '',
packageImageUrls: [] packageImageUrls: []
@ -257,11 +264,24 @@ const handleClose = (isClose = true) => {
isClose && emit('close') isClose && emit('close')
} }
const sellerList = ref<any[]>([])
// id // id
watch( watch(
() => dialogVisible.value, () => dialogVisible.value,
(visible) => { (visible) => {
if (visible) { if (visible) {
api.order.getSellerList.post!<{ rows: any[] }>().then((res) => {
sellerList.value = res.data.rows.map((item) => ({
label: item.sellerName,
value: item.id,
children:
item.sellerWarehouseList?.map((child) => ({
label: `${child.province}${child.city}${child.district}${child.warehouseDetailAddress}${child.warehousePhone}`,
value: child.id
})) || []
}))
})
tableData.value = [] tableData.value = []
;(props.orderLineData || []).forEach((item: OrderItem) => { ;(props.orderLineData || []).forEach((item: OrderItem) => {
tableData.value.push( tableData.value.push(

View File

@ -0,0 +1,45 @@
export const orderStatusOptions = [
{ label: '待支付', value: 'wait_pay' },
{ label: '待发货', value: 'wait_shipping' },
{ label: '已经发货', value: 'shipping' },
{ label: '已接收', value: 'delivered' },
{ label: '全部退款', value: 'all_refund' },
{ label: '部分退款', value: 'part_refund' }
]
const configData = ref()
export const initConfig = () => {
configData.value = pageConfig({
search: {
id: { label: '卖家ID' },
sellerName: { label: '卖家名称' }
},
table: {
index: { label: '序号' },
id: { label: '卖家ID' },
sellerName: { label: '卖家名称' },
sellerCount: { label: '卖出订单数量' },
refundCount: { label: '退款数量' },
sellerOriginGmv: { label: '卖家原始销售金额', width: 120 },
sellerSaleGmv: { label: '已经销售金额' },
refundOriginGmv: { label: '退款原始金额' },
refundSaleGmv: { label: '退款销售金额' },
createTimestamp: { label: '创建时间' },
btn: {
types: ['primary', 'success', 'danger'],
names: ['编辑', '编辑仓库', '删除'],
width: 170
}
},
dialog: [
{
sellerName: { label: '卖家名称' }
},
{
sellerName: { label: '卖家名称', disabled: true },
addresses: { label: '仓库地址', slot: 'addresses', class: '!w-full' }
}
]
})
return configData
}

View File

@ -0,0 +1,214 @@
<template>
<div>
<div id="seller-list">
<search-module :search="search" :table="table">
<template #btn>
<el-button type="primary" @click="onAddOrEditSeller('add', null)">新增卖家</el-button>
</template>
</search-module>
<table-module :table="table">
<template #status="{ row }">
<el-switch
v-model="row.status"
:active-value="'online'"
:inactive-value="'down'"
size="small"
/>
</template>
</table-module>
<dialog-module :dialog="$dialog" width="1000px">
<template #addresses>
<el-form
ref="formRef"
:model="$dialog.data.sellerWarehouseList[index]"
:rules="rules"
inline
v-for="(item, index) in $dialog.data.sellerWarehouseList"
:key="item.random"
>
<el-form-item label="仓库电话" prop="warehousePhone">
<el-input
v-model="$dialog.data.sellerWarehouseList[index].warehousePhone"
placeholder="请输入仓库电话"
clearable
/>
</el-form-item>
<el-form-item label="省市区" prop="region">
<el-cascader
@change="addressChange"
v-model="$dialog.data.sellerWarehouseList[index].region"
:options="regionData"
:props="{ value: 'label' }"
clearable
></el-cascader>
</el-form-item>
<el-form-item
label="详细地址"
prop="warehouseDetailAddress"
style="width: calc(100% - 150px)"
>
<el-input
v-model="$dialog.data.sellerWarehouseList[index].warehouseDetailAddress"
placeholder="请输入详细地址"
clearable
type="textarea"
:rows="1"
/>
</el-form-item>
<div class="w-20 inline-block">
<el-icon size="30" @click="onAddAddress(index)">
<CirclePlusFilled color="#409EFF"
/></el-icon>
<el-icon size="30" class="ml-2" @click="onDeleteAddress(item, index)"
><RemoveFilled color="#F56C6C"
/></el-icon>
</div>
</el-form>
</template>
</dialog-module>
</div>
</div>
</template>
<script lang="ts" setup>
import { ElMessage } from 'element-plus'
import { initConfig } from './config'
import { regionData } from 'element-china-area-data'
import { CirclePlusFilled, RemoveFilled } from '@element-plus/icons-vue'
const onAddOrEditSeller = (row: any) => {
const dialog = $dialog.value
dialog.config = dialog1.value
dialog.show = true
dialog.title = row ? '编辑卖家' : '新增卖家'
dialog.data = {
id: row?.id,
sellerName: row?.sellerName || ''
}
dialog.submit = async (value: any) => {
await api.order.updateSeller.post!(value)
ElMessage.success('操作成功')
table.value.$onGetData(table.value, 1)
dialog.show = false
}
}
//
const formRef = ref<Array<any>>([])
const onEditAddress = (row: any) => {
const dialog = $dialog.value
dialog.config = dialog2.value
dialog.show = true
dialog.title = '编辑仓库'
//
formRef.value = []
row.sellerWarehouseList = (row.sellerWarehouseList || []).map((item: any) => ({
...item,
region: [item.province, item.city, item.district],
random: Math.random()
}))
const address = row.sellerWarehouseList?.[0]
dialog.data = {
sellerId: row.id,
id: address?.id,
sellerName: row?.sellerName || '',
sellerWarehouseList: row.sellerWarehouseList
}
dialog.submit = async (value: any) => {
// DOM
await nextTick()
const validatePromises = formRef.value
.filter((form: any) => form)
.map((form: any) => form.validate())
Promise.all(validatePromises)
.then(async () => {
value.sellerWarehouseList.forEach(async (item) => {
item.province = item.region[0]
item.city = item.region[1]
item.district = item.region[2]
await api.order.updateAddress.post!(item)
})
deleteIds.forEach(async (id) => {
await api.order.updateAddress.post!({ id, isDelete: 1 })
})
ElMessage.success('操作成功')
table.value.$onGetData(table.value, 1)
dialog.show = false
deleteIds.length = 0
})
.catch(() => {
ElMessage.error('请填写完整')
})
}
}
//
const onDeleteUser = (row: any) => {
handleMessageBox({
msg: `是否确认删除?`,
success: api.order.updateSeller.post!,
data: { id: row.id, isDelete: 1 }
}).then(() => {
ElMessage.success('操作成功')
table.value.$onGetData(table.value, 1)
})
}
const addressChange = (arr: any) => {
$dialog.value.data.province = arr[0]
$dialog.value.data.city = arr[1]
$dialog.value.data.district = arr[2]
}
const validateRegion = (rule: any, value: any, callback: any) => {
if (value.length !== 3) {
callback(new Error('请选择省市区'))
} else {
callback()
}
}
const rules = {
warehousePhone: [{ required: true, message: '请输入仓库电话', trigger: 'blur' }],
region: [{ required: true, validator: validateRegion, message: '请选择省市区', trigger: 'blur' }],
warehouseDetailAddress: [{ required: true, message: '请输入详细地址', trigger: 'blur' }]
}
const onAddAddress = (index: number) => {
$dialog.value.data.sellerWarehouseList.splice(index + 1, 0, {
random: Math.random(),
warehousePhone: '',
region: [],
warehouseDetailAddress: '',
sellerId: $dialog.value.data.sellerId
})
}
const deleteIds = []
const onDeleteAddress = (item: any, index: number) => {
if (item.id) {
deleteIds.push(item.id)
}
$dialog.value.data.sellerWarehouseList.splice(index, 1)
}
/** 初始化页面 */
const ConfigData = initConfig()
const { search, table, dialog1, $dialog, dialog2 } = handleInit(
ConfigData,
api.order.getSellerList.post,
[onAddOrEditSeller, onEditAddress, onDeleteUser]
)
table.value.$onGetData(table.value, 1)
/** 页面缓存 */
defineOptions({ name: 'OrderList' })
</script>
<style scoped lang="scss">
#seller-list {
.el-form-item .el-form-item {
margin-bottom: 20px;
}
}
</style>

View File

@ -1560,6 +1560,11 @@ chalk@^4.0.0:
ansi-styles "^4.1.0" ansi-styles "^4.1.0"
supports-color "^7.1.0" supports-color "^7.1.0"
china-division@^2.7.0:
version "2.7.0"
resolved "https://registry.npmmirror.com/china-division/-/china-division-2.7.0.tgz#4060a4d243be66c7833dea64a48a4038f3e53e74"
integrity sha512-4uUPAT+1WfqDh5jytq7omdCmHNk3j+k76zEG/2IqaGcYB90c2SwcixttcypdsZ3T/9tN1TTpBDoeZn+Yw/qBEA==
chokidar@^3.6.0: chokidar@^3.6.0:
version "3.6.0" version "3.6.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
@ -1964,6 +1969,13 @@ electron-to-chromium@^1.5.204:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.208.tgz#609c29502fd7257b4d721e3446f3ae391a0ca1b3" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.208.tgz#609c29502fd7257b4d721e3446f3ae391a0ca1b3"
integrity sha512-ozZyibehoe7tOhNaf16lKmljVf+3npZcJIEbJRVftVsmAg5TeA1mGS9dVCZzOwr2xT7xK15V0p7+GZqSPgkuPg== integrity sha512-ozZyibehoe7tOhNaf16lKmljVf+3npZcJIEbJRVftVsmAg5TeA1mGS9dVCZzOwr2xT7xK15V0p7+GZqSPgkuPg==
element-china-area-data@^6.1.0:
version "6.1.0"
resolved "https://registry.npmmirror.com/element-china-area-data/-/element-china-area-data-6.1.0.tgz#f14b90c0762b21432e097ed5be8423514a0b57e3"
integrity sha512-IkpcjwQv2A/2AxFiSoaISZ+oMw1rZCPUSOg5sOCwT5jKc96TaawmKZeY81xfxXsO0QbKxU5LLc6AirhG52hUmg==
dependencies:
china-division "^2.7.0"
element-plus@^2.11.5: element-plus@^2.11.5:
version "2.11.5" version "2.11.5"
resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.11.5.tgz#752c2c4f70e86d615e577686c2f08054860a0902" resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.11.5.tgz#752c2c4f70e86d615e577686c2f08054860a0902"