Files
smartDriveEEFront/src/views/lb-business/shopping-manage/index.vue
2026-06-06 22:55:38 +08:00

784 lines
28 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container">
<el-card class="filter-container" shadow="never">
<el-form :model="queryParams" :inline="true" label-width="100px">
<el-form-item label="卖家ID">
<el-input v-model="queryParams.sellerId" clearable placeholder="卖家ID" style="width: 140px" />
</el-form-item>
<el-form-item label="商品ID">
<el-input v-model="queryParams.merchandiseId" clearable placeholder="商品ID" style="width: 140px" />
</el-form-item>
<el-form-item label="买家ID">
<el-input v-model="queryParams.buyerId" clearable placeholder="买家ID" style="width: 140px" />
</el-form-item>
<el-form-item label="数据类型">
<el-select v-model="queryParams.dataType" clearable placeholder="请选择数据类型" style="width: 140px">
<el-option label="明细" value="detail_data" />
<el-option label="按天统计" value="day_stat" />
<el-option label="总和统计" value="sum_data" />
</el-select>
</el-form-item>
<el-form-item label="卖家昵称">
<el-input v-model="queryParams.sellerNickname" clearable placeholder="卖家昵称" style="width: 140px" />
</el-form-item>
<el-form-item label="卖家手机">
<el-input v-model="queryParams.sellerMobile" clearable placeholder="卖家手机号" style="width: 140px" />
</el-form-item>
<el-form-item label="买家昵称">
<el-input v-model="queryParams.buyerNickname" clearable placeholder="买家昵称" style="width: 140px" />
</el-form-item>
<el-form-item label="买家手机">
<el-input v-model="queryParams.buyerMobile" clearable placeholder="买家手机号" style="width: 140px" />
</el-form-item>
<el-form-item label="订单金额">
<el-input-number
v-model="queryParams.totalMoneyMin"
:min="0"
:precision="2"
:controls="false"
placeholder="最小"
style="width: 110px"
/>
<span class="range-separator">-</span>
<el-input-number
v-model="queryParams.totalMoneyMax"
:min="0"
:precision="2"
:controls="false"
placeholder="最大"
style="width: 110px"
/>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker
v-model="createdAtRange"
type="datetimerange"
range-separator=""
start-placeholder="开始"
end-placeholder="结束"
value-format="yyyy-MM-dd HH:mm:ss"
:default-time="['00:00:00', '23:59:59']"
style="width: 360px"
/>
</el-form-item>
<el-form-item label="购买时间">
<el-date-picker
v-model="buyTimeRange"
type="datetimerange"
range-separator=""
start-placeholder="开始"
end-placeholder="结束"
value-format="yyyy-MM-dd HH:mm:ss"
:default-time="['00:00:00', '23:59:59']"
style="width: 360px"
/>
</el-form-item>
<el-form-item label="确认时间">
<el-date-picker
v-model="confirmTimeRange"
type="datetimerange"
range-separator=""
start-placeholder="开始"
end-placeholder="结束"
value-format="yyyy-MM-dd HH:mm:ss"
:default-time="['00:00:00', '23:59:59']"
style="width: 360px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-delete" @click="resetQuery">清空</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="action-container" shadow="never">
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增</el-button>
<el-button
type="success"
icon="el-icon-download"
:loading="pullLoading"
style="margin-left: 12px"
@click="openPullOrderDialog"
>
拉取订单
</el-button>
<el-button
type="warning"
icon="el-icon-data-analysis"
:loading="statLoading"
style="margin-left: 12px"
@click="openStatDialog"
>
数据统计
</el-button>
</el-card>
<el-card class="table-container" shadow="never">
<el-table v-loading="loading" :data="list" style="width: 100%">
<el-table-column label="买家ID" prop="buyerId" min-width="100" show-overflow-tooltip />
<el-table-column label="买家昵称" prop="buyerNickname" min-width="110" show-overflow-tooltip />
<el-table-column label="买家手机" prop="buyerMobile" min-width="120" show-overflow-tooltip />
<el-table-column label="数据类型" prop="dataType" min-width="100" show-overflow-tooltip>
<template slot-scope="scope">
{{ formatDataType(scope.row.dataType) }}
</template>
</el-table-column>
<el-table-column label="商品ID" prop="merchandiseId" min-width="100" show-overflow-tooltip />
<el-table-column label="卖家ID" prop="sellerId" min-width="100" show-overflow-tooltip />
<el-table-column label="卖家昵称" prop="sellerNickname" min-width="110" show-overflow-tooltip />
<el-table-column label="卖家手机" prop="sellerMobile" min-width="120" show-overflow-tooltip />
<el-table-column label="更新后金额" prop="newTotal" min-width="110" align="right">
<template slot-scope="scope">
{{ formatAmount(scope.row.newTotal) }}
</template>
</el-table-column>
<el-table-column label="总金额" prop="statTotalMoney" min-width="110" align="right">
<template slot-scope="scope">
{{ formatAmount(scope.row.statTotalMoney) }}
</template>
</el-table-column>
<el-table-column label="总单数" prop="totalOrderCount" min-width="90" align="right">
<template slot-scope="scope">
{{ formatCount(scope.row.totalOrderCount) }}
</template>
</el-table-column>
<el-table-column label="平均金额" prop="avgAmount" min-width="110" align="right">
<template slot-scope="scope">
{{ formatAmount(scope.row.avgAmount) }}
</template>
</el-table-column>
<el-table-column label="创建时间" prop="createdAt" min-width="160">
<template slot-scope="scope">
{{ formatDateTime(scope.row.createdAt) }}
</template>
</el-table-column>
<el-table-column label="购买时间" prop="buyTime" min-width="160">
<template slot-scope="scope">
{{ formatDateTime(scope.row.buyTime) }}
</template>
</el-table-column>
<el-table-column label="确认时间" prop="confirmTime" min-width="160">
<template slot-scope="scope">
{{ formatDateTime(scope.row.confirmTime) }}
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="140">
<template slot-scope="scope">
<el-button type="text" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="text" style="color:#F56C6C;" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
:current-page="queryParams.current"
:page-size="queryParams.size"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
<el-dialog title="数据统计" :visible.sync="statDialogVisible" width="520px" @close="resetStatForm">
<el-form ref="statFormRef" :model="statForm" :rules="statFormRules" label-width="100px">
<el-form-item label="租户ID">
<el-input :value="tenantIdDisplay" disabled placeholder="未获取到租户ID" />
</el-form-item>
<el-form-item label="买家ID">
<el-input v-model="statForm.buyerId" clearable placeholder="选填,不填则统计全部买家" />
</el-form-item>
<el-form-item label="统计类型" prop="statType">
<el-radio-group v-model="statForm.statType">
<el-radio label="day_stat">按天统计</el-radio>
<el-radio label="sum_data">总和统计</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="statDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="statLoading" @click="handleGenerateStat">确定</el-button>
</div>
</el-dialog>
<el-dialog title="拉取订单" :visible.sync="pullDialogVisible" width="560px" @close="resetPullForm">
<el-form ref="pullFormRef" :model="pullForm" :rules="pullFormRules" label-width="100px">
<el-form-item label="租户ID">
<el-input :value="tenantIdDisplay" disabled placeholder="未获取到租户ID" />
</el-form-item>
<el-form-item label="手机号" prop="mobileListText">
<el-input
v-model="pullForm.mobileListText"
type="textarea"
:rows="6"
placeholder="每行一个手机号,或用逗号、空格分隔"
/>
</el-form-item>
<el-form-item label="密码">
<el-input
v-model="pullForm.password"
clearable
placeholder="选填,未填默认 123456"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="pullDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="pullLoading" @click="handlePullOrder">确定</el-button>
</div>
</el-dialog>
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="760px" @close="resetForm">
<el-form ref="dataForm" :model="form" :rules="formRules" label-width="110px">
<el-form-item label="订单ID" prop="id">
<el-input v-model="form.id" :disabled="isEdit" placeholder="外部订单ID新增时需指定" />
</el-form-item>
<el-form-item label="订单号" prop="orderSn">
<el-input v-model="form.orderSn" placeholder="请输入订单号" />
</el-form-item>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="卖家ID">
<el-input v-model="form.sellerId" placeholder="卖家ID" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="商品ID">
<el-input v-model="form.merchandiseId" placeholder="商品ID" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="买家ID">
<el-input v-model="form.buyerId" placeholder="买家ID" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="订单金额">
<el-input-number
v-model="form.totalMoney"
:min="0"
:precision="2"
:step="0.01"
controls-position="right"
style="width: 100%"
placeholder="订单总金额"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="更新后金额">
<el-input-number
v-model="form.newTotal"
:min="0"
:precision="2"
:step="0.01"
controls-position="right"
style="width: 100%"
placeholder="更新后总金额"
/>
</el-form-item>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="卖家昵称">
<el-input v-model="form.sellerNickname" placeholder="卖家昵称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="卖家手机">
<el-input v-model="form.sellerMobile" placeholder="卖家手机号" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="买家昵称">
<el-input v-model="form.buyerNickname" placeholder="买家昵称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="买家手机">
<el-input v-model="form.buyerMobile" placeholder="买家手机号" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="创建时间">
<el-date-picker
v-model="form.createdAt"
type="datetime"
placeholder="创建时间"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="购买时间">
<el-date-picker
v-model="form.buyTime"
type="datetime"
placeholder="下单/购买时间"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="确认时间">
<el-date-picker
v-model="form.confirmTime"
type="datetime"
placeholder="确认时间"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
addLbBuyerShopping,
deleteLbBuyerShopping,
generateLbBuyerShoppingStat,
getLbBuyerShoppingList,
pullLbBuyerShoppingFromThird,
updateLbBuyerShopping
} from '@/api/lb-buyer-shopping'
import { getBusinessHeaders } from '@/utils/business-headers'
function parseOptionalLong(value) {
if (value === undefined || value === null || value === '') return undefined
const n = Number(String(value).trim())
return Number.isNaN(n) ? undefined : n
}
function emptyForm() {
return {
id: '',
sellerId: '',
merchandiseId: '',
buyerId: '',
orderSn: '',
totalMoney: undefined,
newTotal: undefined,
createdAt: '',
buyTime: '',
confirmTime: '',
sellerNickname: '',
sellerMobile: '',
buyerNickname: '',
buyerMobile: ''
}
}
export default {
name: 'LbShoppingManage',
data() {
return {
loading: false,
submitLoading: false,
pullLoading: false,
statLoading: false,
pullDialogVisible: false,
statDialogVisible: false,
pullForm: {
mobileListText: '',
password: ''
},
statForm: {
buyerId: '',
statType: 'day_stat'
},
pullFormRules: {
mobileListText: [{ required: true, message: '请输入手机号', trigger: 'blur' }]
},
statFormRules: {
statType: [{ required: true, message: '请选择统计类型', trigger: 'change' }]
},
list: [],
total: 0,
createdAtRange: [],
buyTimeRange: [],
confirmTimeRange: [],
dialogVisible: false,
dialogTitle: '',
isEdit: false,
queryParams: {
current: 1,
size: 10,
sellerId: '',
merchandiseId: '',
buyerId: '',
dataType: '',
sellerNickname: '',
sellerMobile: '',
buyerNickname: '',
buyerMobile: '',
totalMoneyMin: undefined,
totalMoneyMax: undefined
},
form: emptyForm(),
formRules: {
id: [{ required: true, message: '请输入订单ID', trigger: 'blur' }]
}
}
},
computed: {
tenantIdDisplay() {
return this.getTenantIdFromBusinessHeaders()
}
},
created() {
this.fetchList()
},
methods: {
getTenantIdFromBusinessHeaders() {
const headers = getBusinessHeaders()
const value = headers && headers['X-Tenant-Id']
return value ? String(value).trim() : ''
},
openPullOrderDialog() {
this.pullDialogVisible = true
this.$nextTick(() => {
this.$refs.pullFormRef && this.$refs.pullFormRef.clearValidate()
})
},
openStatDialog() {
this.statForm.buyerId = this.queryParams.buyerId || ''
this.statDialogVisible = true
this.$nextTick(() => {
this.$refs.statFormRef && this.$refs.statFormRef.clearValidate()
})
},
resetStatForm() {
this.statForm = {
buyerId: '',
statType: 'day_stat'
}
this.$refs.statFormRef && this.$refs.statFormRef.resetFields()
},
handleGenerateStat() {
this.$refs.statFormRef.validate((valid) => {
if (!valid) return
const tenantId = this.getTenantIdFromBusinessHeaders()
if (!tenantId) {
this.$message.error('未获取到租户ID请先登录或配置业务请求头中的租户')
return
}
const buyerIdText = (this.statForm.buyerId || '').trim()
const buyerId = parseOptionalLong(this.statForm.buyerId)
if (buyerIdText && buyerId == null) {
this.$message.warning('请输入有效的买家ID')
return
}
const payload = { statType: this.statForm.statType }
if (buyerId != null) payload.buyerId = buyerId
this.statLoading = true
generateLbBuyerShoppingStat(payload)
.then((res) => {
const isSuccess = res && (res.success === true || res.code === 200 || res.code === 20000)
if (!isSuccess) {
this.$message.error((res && (res.message || res.msg)) || '数据统计失败')
return
}
this.$message.success((res && (res.message || res.msg)) || '数据统计完成')
this.statDialogVisible = false
})
.finally(() => {
this.statLoading = false
})
})
},
resetPullForm() {
this.pullForm = {
mobileListText: '',
password: ''
}
this.$refs.pullFormRef && this.$refs.pullFormRef.resetFields()
},
parseMobileList(text) {
const raw = (text || '').trim()
if (!raw) return []
return Array.from(new Set(
raw.split(/[\s,;]+/)
.map((item) => item.trim())
.filter(Boolean)
))
},
handlePullOrder() {
this.$refs.pullFormRef.validate((valid) => {
if (!valid) return
const tenantId = this.getTenantIdFromBusinessHeaders()
if (!tenantId) {
this.$message.error('未获取到租户ID请先登录或配置业务请求头中的租户')
return
}
const mobiles = this.parseMobileList(this.pullForm.mobileListText)
if (!mobiles.length) {
this.$message.warning('请输入至少一个有效手机号')
return
}
const payload = { tenantId, mobiles }
const password = (this.pullForm.password || '').trim()
if (password) payload.password = password
this.pullLoading = true
pullLbBuyerShoppingFromThird(payload)
.then((res) => {
const isSuccess = res && (res.success === true || res.code === 200 || res.code === 20000)
if (!isSuccess) {
this.$message.error((res && (res.message || res.msg)) || '拉取订单失败')
return
}
this.$message.success((res && res.message) || '拉取订单完成')
this.pullDialogVisible = false
this.fetchList()
})
.finally(() => {
this.pullLoading = false
})
})
},
fetchList() {
this.loading = true
getLbBuyerShoppingList(this.buildListParams())
.then((res) => {
if (res && (res.code === 20000 || res.code === 200 || res.success === true)) {
this.list = Array.isArray(res.data) ? res.data : []
this.total = typeof res.total === 'number'
? res.total
: (res.page && typeof res.page.total === 'number' ? res.page.total : this.list.length)
} else {
this.list = []
this.total = 0
}
})
.catch(() => {
this.list = []
this.total = 0
})
.finally(() => {
this.loading = false
})
},
buildListParams() {
const params = {
current: this.queryParams.current,
size: this.queryParams.size,
sellerId: parseOptionalLong(this.queryParams.sellerId),
merchandiseId: parseOptionalLong(this.queryParams.merchandiseId),
buyerId: parseOptionalLong(this.queryParams.buyerId),
dataType: this.queryParams.dataType || undefined,
sellerNickname: this.queryParams.sellerNickname || undefined,
sellerMobile: this.queryParams.sellerMobile || undefined,
buyerNickname: this.queryParams.buyerNickname || undefined,
buyerMobile: this.queryParams.buyerMobile || undefined,
totalMoneyMin: this.queryParams.totalMoneyMin,
totalMoneyMax: this.queryParams.totalMoneyMax
}
const createdRange = this.createdAtRange || []
if (createdRange[0]) params.createdAtStart = createdRange[0]
if (createdRange[1]) params.createdAtEnd = createdRange[1]
const buyRange = this.buyTimeRange || []
if (buyRange[0]) params.buyTimeStart = buyRange[0]
if (buyRange[1]) params.buyTimeEnd = buyRange[1]
const confirmRange = this.confirmTimeRange || []
if (confirmRange[0]) params.confirmTimeStart = confirmRange[0]
if (confirmRange[1]) params.confirmTimeEnd = confirmRange[1]
Object.keys(params).forEach((key) => {
if (params[key] === undefined || params[key] === '') delete params[key]
})
return params
},
handleQuery() {
this.queryParams.current = 1
this.fetchList()
},
resetQuery() {
this.queryParams = {
current: 1,
size: 10,
sellerId: '',
merchandiseId: '',
buyerId: '',
dataType: '',
sellerNickname: '',
sellerMobile: '',
buyerNickname: '',
buyerMobile: '',
totalMoneyMin: undefined,
totalMoneyMax: undefined
}
this.createdAtRange = []
this.buyTimeRange = []
this.confirmTimeRange = []
this.fetchList()
},
handleSizeChange(size) {
this.queryParams.size = size
this.fetchList()
},
handleCurrentChange(current) {
this.queryParams.current = current
this.fetchList()
},
rowToForm(row) {
const pick = (key) => (row[key] !== undefined && row[key] !== null ? String(row[key]) : '')
return {
id: pick('id'),
sellerId: pick('sellerId'),
merchandiseId: pick('merchandiseId'),
buyerId: pick('buyerId'),
orderSn: row.orderSn || '',
totalMoney: row.totalMoney == null ? undefined : Number(row.totalMoney),
newTotal: row.newTotal == null ? undefined : Number(row.newTotal),
createdAt: row.createdAt || '',
buyTime: row.buyTime || '',
confirmTime: row.confirmTime || '',
sellerNickname: row.sellerNickname || '',
sellerMobile: row.sellerMobile || '',
buyerNickname: row.buyerNickname || '',
buyerMobile: row.buyerMobile || ''
}
},
handleAdd() {
this.dialogTitle = '新增购物记录'
this.isEdit = false
this.form = emptyForm()
this.dialogVisible = true
this.$nextTick(() => {
this.$refs.dataForm && this.$refs.dataForm.clearValidate()
})
},
handleEdit(row) {
this.dialogTitle = '编辑购物记录'
this.isEdit = true
this.form = this.rowToForm(row)
this.dialogVisible = true
this.$nextTick(() => {
this.$refs.dataForm && this.$refs.dataForm.clearValidate()
})
},
handleDelete(row) {
if (row.id == null || row.id === '') {
this.$message.error('缺少订单ID无法删除')
return
}
this.$confirm('确定删除这条购物记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => deleteLbBuyerShopping(row.id))
.then((res) => {
this.$message.success((res && res.message) || '删除成功')
this.fetchList()
})
.catch(() => {})
},
handleSubmit() {
this.$refs.dataForm.validate((valid) => {
if (!valid) return
const payload = this.buildPayload()
this.submitLoading = true
const req = this.isEdit ? updateLbBuyerShopping(payload) : addLbBuyerShopping(payload)
req.then((res) => {
this.$message.success((res && res.message) || (this.isEdit ? '更新成功' : '新增成功'))
this.dialogVisible = false
this.fetchList()
}).finally(() => {
this.submitLoading = false
})
})
},
buildPayload() {
const payload = {
id: parseOptionalLong(this.form.id),
sellerId: parseOptionalLong(this.form.sellerId),
merchandiseId: parseOptionalLong(this.form.merchandiseId),
buyerId: parseOptionalLong(this.form.buyerId),
orderSn: this.form.orderSn || undefined,
totalMoney: this.form.totalMoney,
newTotal: this.form.newTotal,
createdAt: this.form.createdAt || undefined,
buyTime: this.form.buyTime || undefined,
confirmTime: this.form.confirmTime || undefined,
sellerNickname: this.form.sellerNickname || undefined,
sellerMobile: this.form.sellerMobile || undefined,
buyerNickname: this.form.buyerNickname || undefined,
buyerMobile: this.form.buyerMobile || undefined
}
Object.keys(payload).forEach((key) => {
if (payload[key] === undefined || payload[key] === '') delete payload[key]
})
return payload
},
resetForm() {
this.$refs.dataForm && this.$refs.dataForm.resetFields()
this.form = emptyForm()
},
formatAmount(val) {
if (val === null || val === undefined || val === '') return '-'
const n = Number(val)
return Number.isNaN(n) ? String(val) : n.toFixed(2)
},
formatDataType(val) {
const map = {
detail_data: '明细',
day_stat: '按天统计',
sum_data: '总和统计'
}
if (val === null || val === undefined || val === '') return '-'
return map[val] || String(val)
},
formatCount(val) {
if (val === null || val === undefined || val === '') return '-'
const n = Number(val)
return Number.isNaN(n) ? String(val) : String(n)
},
formatDateTime(dateTime) {
if (!dateTime) return '-'
const date = new Date(String(dateTime).replace('T', ' '))
if (Number.isNaN(date.getTime())) return String(dateTime)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
}
}
</script>
<style lang="scss" scoped>
.app-container {
.filter-container {
margin-bottom: 20px;
}
.action-container {
margin-bottom: 20px;
}
.table-container {
.el-pagination {
margin-top: 20px;
text-align: right;
}
}
.range-separator {
display: inline-block;
width: 24px;
text-align: center;
color: #909399;
}
}
</style>