导入新产品,20.套餐编辑的时候,不能低于基准价,低于就提示不能保存
This commit is contained in:
@@ -9,6 +9,10 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="$slots.extra" class="import-extra">
|
||||
<slot name="extra" />
|
||||
</div>
|
||||
|
||||
<!-- 上传拖拽区 -->
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
@@ -138,6 +142,14 @@ const props = defineProps({
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
extraQuery: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
beforeSubmit: {
|
||||
type: Function,
|
||||
default: null
|
||||
},
|
||||
// 确定按钮文字
|
||||
submitText: {
|
||||
type: String,
|
||||
@@ -157,7 +169,16 @@ const validateError = ref('')
|
||||
const headers = { Authorization: 'Bearer ' + getToken() }
|
||||
|
||||
const uploadUrl = computed(() => {
|
||||
return import.meta.env.VITE_APP_BASE_API + props.action + '?updateSupport=' + (updateSupport.value ? 1 : 0)
|
||||
const query = new URLSearchParams()
|
||||
query.set('updateSupport', updateSupport.value ? '1' : '0')
|
||||
const extra = props.extraQuery || {}
|
||||
Object.keys(extra).forEach(key => {
|
||||
const value = extra[key]
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
query.set(key, value)
|
||||
}
|
||||
})
|
||||
return import.meta.env.VITE_APP_BASE_API + props.action + '?' + query.toString()
|
||||
})
|
||||
|
||||
const templateUrl = computed(() => !!props.templateAction)
|
||||
@@ -256,6 +277,13 @@ function handleSuccess(response) {
|
||||
|
||||
// 提交上传
|
||||
function handleSubmit() {
|
||||
if (typeof props.beforeSubmit === 'function') {
|
||||
const msg = props.beforeSubmit()
|
||||
if (msg) {
|
||||
proxy.$modal.msgError(msg)
|
||||
return
|
||||
}
|
||||
}
|
||||
const file = selectedFile.value
|
||||
const err = checkFile(file)
|
||||
if (err) {
|
||||
@@ -440,6 +468,10 @@ defineExpose({ open })
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.import-extra {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.file-progress {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ const columns = [
|
||||
{ prop: 'effectiveDate', label: '生效日期', width: 110, align: 'center' },
|
||||
{ prop: 'expireDate', label: '失效日期', width: 110, align: 'center', formatter: (row) => row.expireDate || '长期' },
|
||||
{ prop: 'status', label: '状态', width: 90, align: 'center', dict: 'bms_product_effect_status' },
|
||||
{ prop: 'action', label: '操作', width: 70, fixed: 'right', slot: 'action' }
|
||||
// { prop: 'action', label: '操作', width: 70, fixed: 'right', slot: 'action' }
|
||||
]
|
||||
|
||||
const visible = ref(false)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb16"
|
||||
title="Excel 导入:相同产品代码+生效日期不覆盖;不同生效日期新增一条产品。同时入库国家×重量段(运费/挂号费/尺寸/尾程/时效)。"
|
||||
title="多国家价表用「导入 Excel」。单国专线成本价用「按国家导入」:先选目的国家,目录里的产品都记到该国。相同产品代码+生效日期不覆盖主数据。"
|
||||
/>
|
||||
|
||||
<basic-table
|
||||
@@ -25,6 +25,9 @@
|
||||
<el-col :span="1.5">
|
||||
<el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['price:product:import']">导入 Excel</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="info" plain icon="Upload" @click="handleCountryImport" v-hasPermi="['price:product:import']">按国家导入</el-button>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<template #col-cargoAttr="{ row }">
|
||||
@@ -53,6 +56,32 @@
|
||||
:show-update-support="false"
|
||||
@success="() => tableRef.refresh()"
|
||||
/>
|
||||
<excel-import-dialog
|
||||
ref="countryImportRef"
|
||||
title="按国家导入"
|
||||
action="/price/product/importCountryData"
|
||||
width="560px"
|
||||
:show-update-support="false"
|
||||
:extra-query="countryImportQuery"
|
||||
:before-submit="validateCountryImport"
|
||||
:import-desc="countryImportDesc"
|
||||
@success="() => tableRef.refresh()"
|
||||
>
|
||||
<template #extra>
|
||||
<el-form label-width="90px">
|
||||
<el-form-item label="目的国家" required>
|
||||
<el-select v-model="importCountryCode" filterable clearable placeholder="请选择国家" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in regionOptions"
|
||||
:key="item.countryCode2"
|
||||
:label="regionLabel(item)"
|
||||
:value="item.countryCode2"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</excel-import-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -62,6 +91,7 @@ import ProductFormDialog from './components/ProductFormDialog.vue'
|
||||
import ProductCountryDrawer from './components/ProductCountryDrawer.vue'
|
||||
import ProductSurchargeDrawer from './components/ProductSurchargeDrawer.vue'
|
||||
import { listProduct } from '@/api/price/product'
|
||||
import { listRegionOptions } from '@/api/data/region'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const { bms_product_effect_status } = useDict('bms_product_effect_status')
|
||||
@@ -71,6 +101,14 @@ const formRef = ref()
|
||||
const countryRef = ref()
|
||||
const surchargeRef = ref()
|
||||
const importRef = ref()
|
||||
const countryImportRef = ref()
|
||||
const importCountryCode = ref('')
|
||||
const regionOptions = ref([])
|
||||
const countryImportQuery = computed(() => ({ countryCode: importCountryCode.value }))
|
||||
const countryImportDesc = [
|
||||
'选择目的国家后上传专线成本价表,目录中的产品都会记到该国家。',
|
||||
'邮编、赔偿标准等 sheet 不导入。相同产品代码+生效日期不覆盖主数据,重量段按生效日写入。'
|
||||
]
|
||||
|
||||
const columns = [
|
||||
{ prop: 'productCode', label: '产品代码', minWidth: 110 },
|
||||
@@ -120,6 +158,32 @@ function handleUpdate(row) {
|
||||
function handleImport() {
|
||||
importRef.value.open()
|
||||
}
|
||||
|
||||
function regionLabel(item) {
|
||||
const name = item.countryNameCn || item.countryNameEn || ''
|
||||
const code = item.countryCode2 || ''
|
||||
return name && code ? name + ' (' + code + ')' : (name || code)
|
||||
}
|
||||
|
||||
function loadRegionOptions() {
|
||||
if (regionOptions.value.length) return
|
||||
listRegionOptions().then(res => {
|
||||
regionOptions.value = res.data || []
|
||||
})
|
||||
}
|
||||
|
||||
function handleCountryImport() {
|
||||
importCountryCode.value = ''
|
||||
loadRegionOptions()
|
||||
countryImportRef.value.open()
|
||||
}
|
||||
|
||||
function validateCountryImport() {
|
||||
if (!importCountryCode.value) {
|
||||
return '请先选择目的国家'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -98,9 +98,6 @@ const columns = [
|
||||
{ label: '订单号', prop: 'orderNo', minWidth: 140, showOverflowTooltip: true },
|
||||
{ label: '运单号', prop: 'trackingNo', minWidth: 140, showOverflowTooltip: true },
|
||||
{ label: '转单号', prop: 'transferNo', minWidth: 140, showOverflowTooltip: true },
|
||||
{ label: '销售员', prop: 'belongSaler', minWidth: 100, showOverflowTooltip: true },
|
||||
{ label: '结算员', prop: 'settlementMan', minWidth: 100, showOverflowTooltip: true },
|
||||
{ label: '客服员', prop: 'serviceMan', minWidth: 100, showOverflowTooltip: true },
|
||||
{ label: '客户', prop: 'customerName', minWidth: 140, showOverflowTooltip: true },
|
||||
{ label: '客户代码', prop: 'customerCode', width: 120 },
|
||||
{ label: '产品', prop: 'product', minWidth: 160, slot: 'product' },
|
||||
@@ -113,6 +110,9 @@ const columns = [
|
||||
{ label: '客户生效', prop: 'customerActiveStatus', align: 'center', width: 100, slot: 'customerActiveStatus' },
|
||||
{ label: '额度欠费', prop: 'creditArrearsStatus', align: 'center', width: 100, slot: 'creditArrearsStatus' },
|
||||
{ label: '下单时间', prop: 'orderTime', align: 'center', width: 170, parseTime: true },
|
||||
{ label: '销售员', prop: 'belongSaler', minWidth: 100, showOverflowTooltip: true },
|
||||
{ label: '结算员', prop: 'settlementMan', minWidth: 100, showOverflowTooltip: true },
|
||||
// { label: '客服员', prop: 'serviceMan', minWidth: 100, showOverflowTooltip: true },
|
||||
{ label: '操作', prop: 'action', align: 'center', width: 90, fixed: 'right', slot: 'action', showInToolbar: false }
|
||||
]
|
||||
|
||||
|
||||
@@ -99,6 +99,8 @@ const props = defineProps({
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue', 'pick-surcharge', 'row-change'])
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const keyword = ref('')
|
||||
const editingKey = ref('')
|
||||
const committing = ref(false)
|
||||
@@ -109,7 +111,7 @@ const title = computed(() => {
|
||||
const name = props.productName || ''
|
||||
return code + ' · ' + name + ' · 国家服务价格'
|
||||
})
|
||||
const hint = '黄色行=已改价;点编辑图标后修改,失焦即刻写入数据库。单位:运费 RMB/KG,挂号费 RMB/票。'
|
||||
const hint = '黄色行=已改价;点编辑图标后修改,失焦即刻写入数据库。单位:运费 RMB/KG,挂号费 RMB/票。不能低于导入基准价。'
|
||||
const phSearch = '搜索国家/分区'
|
||||
const colCountry = '国家'
|
||||
const colZone = '分区'
|
||||
@@ -136,6 +138,8 @@ function setEditRef(el, row, field) {
|
||||
|
||||
function startEdit(row, field) {
|
||||
if (!row) return
|
||||
row._editPrevFv = row.fvValue
|
||||
row._editPrevReg = row.fvRegistrationFee
|
||||
editingKey.value = cellKey(row, field)
|
||||
nextTick(() => {
|
||||
const el = editInputRefs[editingKey.value]
|
||||
@@ -143,13 +147,36 @@ function startEdit(row, field) {
|
||||
})
|
||||
}
|
||||
|
||||
function isBelowFloor(value, floor) {
|
||||
if (value == null || value === '' || floor == null || floor === '') return false
|
||||
const n = Number(value)
|
||||
const f = Number(floor)
|
||||
if (Number.isNaN(n) || Number.isNaN(f)) return false
|
||||
return n + 1e-8 < f
|
||||
}
|
||||
|
||||
function belowFloorMessage(field, floor) {
|
||||
const name = field === 'fv' ? '\u5957\u9910\u8fd0\u8d39' : '\u5957\u9910\u6302\u53f7\u8d39'
|
||||
const prec = field === 'fv' ? 4 : 2
|
||||
return name + '\u4e0d\u80fd\u4f4e\u4e8e\u5bfc\u5165\u57fa\u51c6\u4ef7 ' + formatNum(floor, prec)
|
||||
}
|
||||
|
||||
async function commitEdit(row, field) {
|
||||
if (committing.value) return
|
||||
if (editingKey.value !== cellKey(row, field)) return
|
||||
editingKey.value = ''
|
||||
if (!row || !row.psdId) return
|
||||
committing.value = true
|
||||
editingKey.value = ''
|
||||
try {
|
||||
if (!row) return
|
||||
const floor = field === 'fv' ? row._floorFreight : row._floorReg
|
||||
const value = field === 'fv' ? row.fvValue : row.fvRegistrationFee
|
||||
if (isBelowFloor(value, floor)) {
|
||||
if (field === 'fv') row.fvValue = row._editPrevFv
|
||||
else row.fvRegistrationFee = row._editPrevReg
|
||||
proxy.$modal.msgError(belowFloorMessage(field, floor))
|
||||
return
|
||||
}
|
||||
if (!row.psdId) return
|
||||
emit('row-change', row)
|
||||
} finally {
|
||||
committing.value = false
|
||||
|
||||
@@ -268,6 +268,8 @@ function countryToDetail(item, productCode, productName) {
|
||||
const baseReg = item.registrationFee != null ? Number(item.registrationFee) : undefined
|
||||
row._baseFreight = baseFv
|
||||
row._baseReg = baseReg
|
||||
row._floorFreight = baseFv
|
||||
row._floorReg = baseReg
|
||||
row.fvValue = baseFv
|
||||
row.fvRegistrationFee = baseReg
|
||||
row.effectiveDate = item.effectiveDate
|
||||
@@ -293,7 +295,9 @@ function openPrice(row) {
|
||||
return r
|
||||
})
|
||||
editGroup.value = row
|
||||
priceVisible.value = true
|
||||
Promise.resolve(stampPriceFloors(editRows.value, row.productCode)).finally(() => {
|
||||
priceVisible.value = true
|
||||
})
|
||||
}
|
||||
|
||||
/** 抽屉里点附加费:带行 = 行级(只改该行,国家+重量段),不带 = 产品级(覆盖全部明细行) */
|
||||
@@ -319,10 +323,45 @@ async function persistDetail(row, withSurcharge) {
|
||||
/** drawer field change -> immediate DB update */
|
||||
async function onRowPriceChange(row) {
|
||||
if (!row || !row.psdId) return
|
||||
const prevFv = row._editPrevFv
|
||||
const prevReg = row._editPrevReg
|
||||
try {
|
||||
await persistDetail(row, false)
|
||||
proxy.$modal.msgSuccess('价格已更新')
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
if (prevFv !== undefined) row.fvValue = prevFv
|
||||
if (prevReg !== undefined) row.fvRegistrationFee = prevReg
|
||||
}
|
||||
}
|
||||
|
||||
function applyImportedFloor(row, hit) {
|
||||
if (!row || !hit) return
|
||||
if (hit.freightPrice != null) row._floorFreight = Number(hit.freightPrice)
|
||||
if (hit.registrationFee != null) row._floorReg = Number(hit.registrationFee)
|
||||
}
|
||||
|
||||
function findImportedCountry(rows, row) {
|
||||
if (!rows || !rows.length || !row) return null
|
||||
if (row.productCountryId != null) {
|
||||
const byId = rows.find(item => Number(item.detailId) === Number(row.productCountryId))
|
||||
if (byId) return byId
|
||||
}
|
||||
return rows.find(item => {
|
||||
const sameCountry = (row.countryCode && item.countryCode
|
||||
&& String(row.countryCode).toUpperCase() === String(item.countryCode).toUpperCase())
|
||||
|| (!row.countryCode && row.countryName && item.countryName && row.countryName === item.countryName)
|
||||
if (!sameCountry) return false
|
||||
if (row.weightMin != null && item.weightMin != null && Number(row.weightMin) !== Number(item.weightMin)) return false
|
||||
if (row.weightMax != null && item.weightMax != null && Number(row.weightMax) !== Number(item.weightMax)) return false
|
||||
return row.weightMin != null || row.weightMax != null
|
||||
}) || null
|
||||
}
|
||||
|
||||
function stampPriceFloors(rows, productCode) {
|
||||
if (!props.refData || !productCode || !rows || !rows.length) return Promise.resolve()
|
||||
return props.refData.ensureCountries(productCode, props.effectiveTime).then(list => {
|
||||
rows.forEach(row => applyImportedFloor(row, findImportedCountry(list, row)))
|
||||
})
|
||||
}
|
||||
|
||||
async function saveDiscount() {
|
||||
|
||||
Reference in New Issue
Block a user