增加代码检测,和映射
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listProductCodeMap(query) {
|
||||
return request({
|
||||
url: '/data/productCodeMap/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function getProductCodeMap(mapId) {
|
||||
return request({
|
||||
url: '/data/productCodeMap/' + mapId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function addProductCodeMap(data) {
|
||||
return request({
|
||||
url: '/data/productCodeMap',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateProductCodeMap(data) {
|
||||
return request({
|
||||
url: '/data/productCodeMap',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function delProductCodeMap(mapIds) {
|
||||
return request({
|
||||
url: '/data/productCodeMap/' + mapIds,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@@ -48,3 +48,12 @@ export function getOrderSourceSyncLog(docId) {
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function checkOrderSourceProducts(data) {
|
||||
return request({
|
||||
url: '/monitor/orderSource/checkProducts',
|
||||
method: 'post',
|
||||
data,
|
||||
timeout: 180000
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<basic-table
|
||||
ref="tableRef"
|
||||
:columns="columns"
|
||||
:search-columns="searchColumns"
|
||||
:query="queryList"
|
||||
row-key="mapId"
|
||||
selection
|
||||
>
|
||||
<template #toolbar="{ ids }">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['data:productCodeMap:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="!ids.length" @click="handleDelete(ids)" v-hasPermi="['data:productCodeMap:remove']">删除</el-button>
|
||||
</el-col>
|
||||
</template>
|
||||
<template #col-status="{ row }">
|
||||
<el-tag size="small" :type="row.status === '0' ? 'success' : 'info'">{{ row.status === '0' ? '正常' : '停用' }}</el-tag>
|
||||
</template>
|
||||
<template #col-action="{ row }">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(row)" v-hasPermi="['data:productCodeMap:edit']">修改</el-button>
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete([row.mapId])" v-hasPermi="['data:productCodeMap:remove']">删除</el-button>
|
||||
</template>
|
||||
</basic-table>
|
||||
|
||||
<el-dialog :title="title" v-model="open" width="520px" append-to-body destroy-on-close>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-form-item label="上游产品代码" prop="upstreamCode">
|
||||
<el-input v-model="form.upstreamCode" placeholder="上游/渠道产品代码" maxlength="64" />
|
||||
</el-form-item>
|
||||
<el-form-item label="BMS产品代码" prop="bmsProductCode">
|
||||
<el-select
|
||||
v-model="form.bmsProductCode"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
reserve-keyword
|
||||
placeholder="请输入产品代码或名称"
|
||||
:remote-method="searchProduct"
|
||||
:loading="productLoading"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="p in productOptions"
|
||||
:key="p.productCode"
|
||||
:label="(p.productName || '') + ' (' + p.productCode + ')'"
|
||||
:value="p.productCode"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio value="0">正常</el-radio>
|
||||
<el-radio value="1">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" maxlength="500" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
<el-button @click="open = false">取消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="ProductCodeMap">
|
||||
import { listProductCodeMap, getProductCodeMap, addProductCodeMap, updateProductCodeMap, delProductCodeMap } from '@/api/data/productCodeMap'
|
||||
import { listProductOptions } from '@/api/price/product'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const tableRef = ref()
|
||||
const formRef = ref()
|
||||
const open = ref(false)
|
||||
const title = ref('')
|
||||
const productLoading = ref(false)
|
||||
const productOptions = ref([])
|
||||
|
||||
const statusOptions = [
|
||||
{ label: '正常', value: '0' },
|
||||
{ label: '停用', value: '1' }
|
||||
]
|
||||
|
||||
const searchColumns = [
|
||||
{ label: '上游代码', prop: 'upstreamCode', component: 'input', placeholder: '上游产品代码', width: '180px' },
|
||||
{ label: 'BMS代码', prop: 'bmsProductCode', component: 'input', placeholder: 'BMS产品代码', width: '180px' },
|
||||
{ label: '状态', prop: 'status', component: 'select', options: statusOptions, placeholder: '请选择', width: '140px', props: { clearable: true } }
|
||||
]
|
||||
|
||||
const columns = [
|
||||
{ label: '上游产品代码', prop: 'upstreamCode', minWidth: 160, showOverflowTooltip: true },
|
||||
{ label: 'BMS产品代码', prop: 'bmsProductCode', minWidth: 160, showOverflowTooltip: true },
|
||||
{ label: '状态', prop: 'status', align: 'center', width: 90, slot: 'status' },
|
||||
{ label: '备注', prop: 'remark', minWidth: 160, showOverflowTooltip: true },
|
||||
{ label: '创建时间', prop: 'createTime', align: 'center', width: 170 },
|
||||
{ label: '操作', prop: 'action', align: 'center', width: 150, fixed: 'right', slot: 'action', showInToolbar: false }
|
||||
]
|
||||
|
||||
const data = reactive({
|
||||
form: {},
|
||||
rules: {
|
||||
upstreamCode: [{ required: true, message: '请输入上游产品代码', trigger: 'blur' }],
|
||||
bmsProductCode: [{ required: true, message: '请选择BMS产品代码', trigger: 'change' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
}
|
||||
})
|
||||
const { form, rules } = toRefs(data)
|
||||
|
||||
function queryList(params) {
|
||||
return listProductCodeMap(params)
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
form.value = {
|
||||
mapId: undefined,
|
||||
upstreamCode: '',
|
||||
bmsProductCode: '',
|
||||
status: '0',
|
||||
remark: ''
|
||||
}
|
||||
productOptions.value = []
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
resetForm()
|
||||
title.value = '新增代码映射'
|
||||
open.value = true
|
||||
}
|
||||
|
||||
function handleUpdate(row) {
|
||||
resetForm()
|
||||
getProductCodeMap(row.mapId).then(res => {
|
||||
form.value = Object.assign({}, res.data || {})
|
||||
if (form.value.bmsProductCode) {
|
||||
productOptions.value = [{ productCode: form.value.bmsProductCode, productName: form.value.bmsProductCode }]
|
||||
searchProduct(form.value.bmsProductCode)
|
||||
}
|
||||
title.value = '修改代码映射'
|
||||
open.value = true
|
||||
})
|
||||
}
|
||||
|
||||
function submitForm() {
|
||||
formRef.value.validate(valid => {
|
||||
if (!valid) return
|
||||
const req = form.value.mapId ? updateProductCodeMap(form.value) : addProductCodeMap(form.value)
|
||||
req.then(() => {
|
||||
proxy.$modal.msgSuccess('保存成功')
|
||||
open.value = false
|
||||
tableRef.value?.refresh()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function handleDelete(ids) {
|
||||
if (!ids || !ids.length) return
|
||||
proxy.$modal.confirm('确认删除选中的 ' + ids.length + ' 条映射?').then(() => {
|
||||
return delProductCodeMap(ids.join(','))
|
||||
}).then(() => {
|
||||
proxy.$modal.msgSuccess('删除成功')
|
||||
tableRef.value?.refresh()
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
function searchProduct(keyword) {
|
||||
productLoading.value = true
|
||||
listProductOptions({ productCode: keyword || undefined, status: '1' }).then(res => {
|
||||
productOptions.value = res.data || res.rows || []
|
||||
}).finally(() => {
|
||||
productLoading.value = false
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" size="920px" title="产品套餐比对" append-to-body destroy-on-close>
|
||||
<div v-loading="loading">
|
||||
<el-empty v-if="!loading && !result" description="暂无结果" />
|
||||
<template v-else-if="result">
|
||||
<el-alert
|
||||
v-if="result.truncated"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb12"
|
||||
title="订单超过上限,仅对已扫描部分进行比对"
|
||||
/>
|
||||
<el-descriptions :column="3" border size="small" class="mb12">
|
||||
<el-descriptions-item label="筛选订单数">{{ result.orderTotal ?? 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="已扫描">{{ result.scannedOrders ?? 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="套餐截止时间">{{ result.asOfTime || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="套餐产品数">{{ result.packageProductCount ?? 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="订单产品数">{{ result.distinctOrderProducts ?? 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="命中/缺失">
|
||||
<el-tag type="success" size="small">{{ result.matchedCount ?? 0 }}</el-tag>
|
||||
<span class="sep">/</span>
|
||||
<el-tag type="danger" size="small">{{ result.missingCount ?? 0 }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane :label="missingTabLabel" name="missing">
|
||||
<el-table :data="result.missingProducts || []" border size="small" max-height="520">
|
||||
<el-table-column prop="customerCode" label="客户代码" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="upstreamCode" label="上游产品代码" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="productName" label="产品名称" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="mappedCode" label="映射后代码" min-width="120" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.mappedCode || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="orderCount" label="订单数" width="90" align="center" />
|
||||
<el-table-column label="示例订单" min-width="180" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ (row.sampleOrderIds || []).join(', ') || '-' }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!(result.missingProducts || []).length" description="未发现缺失产品" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="全部比对" name="all">
|
||||
<el-table :data="result.orderProducts || []" border size="small" max-height="520">
|
||||
<el-table-column prop="customerCode" label="客户代码" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="upstreamCode" label="上游产品代码" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="productName" label="产品名称" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="mappedCode" label="映射后代码" min-width="120" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.mappedCode || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否在套餐" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.covered" size="small" type="success">在套餐</el-tag>
|
||||
<el-tag v-else size="small" type="danger">不在套餐</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="命中方式" min-width="140" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ hitText(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="orderCount" label="订单数" width="90" align="center" />
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="packageTabLabel" name="package">
|
||||
<el-table :data="result.packageProducts || []" border size="small" max-height="520">
|
||||
<el-table-column prop="customerCode" label="客户代码" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="productCode" label="套餐产品代码" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="productName" label="产品名称" min-width="160" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<el-empty v-if="!(result.packageProducts || []).length" description="客户未绑定套餐产品" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { checkOrderSourceProducts } from '@/api/monitor/orderSource'
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const result = ref(null)
|
||||
const activeTab = ref('missing')
|
||||
|
||||
const missingTabLabel = computed(() => {
|
||||
const n = result.value?.missingCount ?? 0
|
||||
return '缺失产品 (' + n + ')'
|
||||
})
|
||||
const packageTabLabel = computed(() => {
|
||||
const n = result.value?.packageProductCount ?? 0
|
||||
return '套餐产品 (' + n + ')'
|
||||
})
|
||||
|
||||
function hitText(row) {
|
||||
if (!row) return '-'
|
||||
if (row.inPackage) return '直接命中'
|
||||
if (row.mappedInPackage) return '映射后命中'
|
||||
if (row.mappedCode) return '有映射但仍不在套餐'
|
||||
return '无映射'
|
||||
}
|
||||
|
||||
function open(query) {
|
||||
if (!query || !query.customerCodes || !query.beginTime || !query.endTime) {
|
||||
ElMessage.warning('请先选择客户和提货时间')
|
||||
return
|
||||
}
|
||||
visible.value = true
|
||||
activeTab.value = 'missing'
|
||||
result.value = null
|
||||
loading.value = true
|
||||
checkOrderSourceProducts(query).then(res => {
|
||||
result.value = res.data || null
|
||||
if ((result.value?.missingCount || 0) > 0) {
|
||||
activeTab.value = 'missing'
|
||||
} else {
|
||||
activeTab.value = 'all'
|
||||
}
|
||||
}).catch(() => {
|
||||
result.value = null
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb12 { margin-bottom: 12px; }
|
||||
.sep { margin: 0 6px; color: var(--el-text-color-secondary); }
|
||||
</style>
|
||||
@@ -46,6 +46,16 @@
|
||||
@click="openFilterSync()"
|
||||
>同步计费</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="Search"
|
||||
:loading="checkLoading"
|
||||
v-hasPermi="['monitor:orderSource:list']"
|
||||
@click="handleCheckProducts()"
|
||||
>检测产品</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="info"
|
||||
@@ -93,6 +103,7 @@
|
||||
@success="onSyncSuccess"
|
||||
/>
|
||||
<order-source-sync-log-drawer ref="logDrawerRef" />
|
||||
<order-source-product-check-drawer ref="productCheckDrawerRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -101,11 +112,14 @@ import { ElMessage } from 'element-plus'
|
||||
import { listOrderSource, syncOrderSourceSelected } from '@/api/monitor/orderSource'
|
||||
import OrderSourceSyncDialog from './components/OrderSourceSyncDialog.vue'
|
||||
import OrderSourceSyncLogDrawer from './components/OrderSourceSyncLogDrawer.vue'
|
||||
import OrderSourceProductCheckDrawer from './components/OrderSourceProductCheckDrawer.vue'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const tableRef = ref()
|
||||
const syncDialogRef = ref()
|
||||
const logDrawerRef = ref()
|
||||
const productCheckDrawerRef = ref()
|
||||
const checkLoading = ref(false)
|
||||
const syncingKey = ref(null)
|
||||
const batchLoading = ref(false)
|
||||
const syncFloat = ref(null)
|
||||
@@ -281,6 +295,24 @@ function handleSyncOne(row) {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function handleCheckProducts() {
|
||||
const q = buildFilterQuery()
|
||||
if (!q.customerCodes || !q.beginTime || !q.endTime) {
|
||||
ElMessage.warning('请先选择客户和提货时间')
|
||||
return
|
||||
}
|
||||
const codes = Array.isArray(q.customerCodes) ? q.customerCodes.filter(v => v != null && String(v).trim() !== '') : []
|
||||
if (!codes.length) {
|
||||
ElMessage.warning('请先选择客户和提货时间')
|
||||
return
|
||||
}
|
||||
q.customerCodes = codes
|
||||
checkLoading.value = true
|
||||
productCheckDrawerRef.value?.open(q)
|
||||
window.setTimeout(() => { checkLoading.value = false }, 300)
|
||||
}
|
||||
|
||||
function handleBatchSync(selection) {
|
||||
if (!selection || !selection.length) return
|
||||
proxy.$modal.confirm('确认同步选中的 ' + selection.length + ' 条订单?', '二次确认').then(() => {
|
||||
|
||||
Reference in New Issue
Block a user