Files
BMS/bms-front/src/views/price/basic/redline/index.vue
T

376 lines
14 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-alert
type="info"
:closable="false"
show-icon
class="mb16"
title="导入前须先选择生效时间;红线价与国家明细均按此时间落库,上一版产品/明细失效时间设为该时间。多国家价表用「全部导入」,单国专线成本价用「按国家导入」。相同产品代码+生效日期不覆盖主数据。"
/>
<basic-table
ref="tableRef"
:extra-params="extraParams"
:columns="columns"
:search-columns="searchColumns"
:query="queryList"
row-key="productId"
>
<template #toolbar="{ selection }">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['price:redline:add']">新建红线价</el-button>
</el-col>
<el-col :span="1.5">
<el-dropdown @command="handleImportCommand">
<el-button type="info" plain icon="Upload" v-hasPermi="['price:redline:import']">
导入 Excel<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="all">全部导入(多国家价表)</el-dropdown-item>
<el-dropdown-item command="country">按国家导入(单国专线成本价)</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-col>
<el-col :span="1.5">
<el-button plain icon="Clock" @click="expiredRef.open()" v-hasPermi="['price:redline:list']">失效价格</el-button>
</el-col>
</template>
<template #col-cargoAttr="{ row }">
<el-tag type="primary" size="small" effect="plain">{{ row.cargoAttr || '-' }}</el-tag>
</template>
<template #col-status="{ row }">
<dict-tag :options="bms_product_effect_status" :value="row.status" />
</template>
<template #col-action="{ row }">
<el-button link type="primary" title="价格" icon="Document" @click="excelFileRef.open(row)" v-hasPermi="['price:redline:query']"></el-button>
<el-button link type="primary" title="红线价明细" icon="View" @click="countryRef.open(row)" v-hasPermi="['price:redline:query']"></el-button>
<el-button link type="primary" title="附加费" icon="Money" @click="surchargeRef.open(row)" v-hasPermi="['price:surcharge:query']"></el-button>
<el-button title="编辑" link type="primary" icon="Edit" @click="handleUpdate(row)" v-hasPermi="['price:redline:edit']"></el-button>
</template>
</basic-table>
<redline-form-dialog ref="formRef" @success="() => tableRef.refresh()" />
<redline-excel-file-drawer ref="excelFileRef" />
<redline-country-drawer ref="countryRef" />
<redline-surcharge-drawer ref="surchargeRef" @success="() => tableRef.refresh()" />
<redline-expired-drawer ref="expiredRef" @view-country="(row) => countryRef.open(row)" />
<excel-import-dialog
ref="importRef"
title="红线价 Excel 导入"
width="560px"
:show-update-support="false"
template-url="/templates/product/product-import-all-template.xlsx"
template-file-name="产品多国家价表导入模板"
:import-desc="allImportDesc"
:before-submit="validateImportEffectiveDate"
:submit-handler="runImportAllAsync"
@success="() => tableRef.refresh()"
>
<template #extra>
<el-form label-width="90px">
<el-form-item label="生效时间" required>
<el-date-picker
v-model="importEffectiveDate"
type="datetime"
format="YYYY-MM-DD HH:00"
value-format="YYYY-MM-DD HH:00:00"
popper-class="redline-import-hour-picker"
placeholder="请选择生效时间"
style="width: 100%"
@change="onImportEffectiveTimeChange"
/>
</el-form-item>
</el-form>
</template>
</excel-import-dialog>
<excel-import-dialog
ref="countryImportRef"
title="按国家导入"
width="560px"
:show-update-support="false"
:before-submit="validateCountryImport"
:import-desc="countryImportDesc"
template-url="/templates/product/product-import-country-template.xlsx"
template-file-name="产品按国家导入模板"
:submit-handler="runImportCountryAsync"
@success="() => tableRef.refresh()"
>
<template #extra>
<el-form label-width="90px">
<el-form-item label="生效时间" required>
<el-date-picker
v-model="importEffectiveDate"
type="datetime"
format="YYYY-MM-DD HH:00"
value-format="YYYY-MM-DD HH:00:00"
popper-class="redline-import-hour-picker"
placeholder="请选择生效时间"
style="width: 100%"
@change="onImportEffectiveTimeChange"
/>
</el-form-item>
<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>
<el-dialog
v-model="importProgressVisible"
title="正在导入产品"
width="420px"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
append-to-body
>
<div class="import-progress">
<el-progress :percentage="importProgressPercent" :status="importProgressStatus" />
<div class="import-progress-msg">{{ importProgressMessage }}</div>
</div>
</el-dialog>
</div>
</template>
<script setup name="PriceProduct">
import ExcelImportDialog from '@/components/ExcelImportDialog'
import RedlineFormDialog from './components/RedlineFormDialog.vue'
import RedlineExcelFileDrawer from './components/RedlineExcelFileDrawer.vue'
import RedlineCountryDrawer from './components/RedlineCountryDrawer.vue'
import RedlineSurchargeDrawer from './components/RedlineSurchargeDrawer.vue'
import RedlineExpiredDrawer from './components/RedlineExpiredDrawer.vue'
import { listRedline, importRedlineExcelAsync, importRedlineCountryExcelAsync } from '@/api/price/redline'
import { listRegionOptions } from '@/api/data/region'
const { bms_product_effect_status } = useDict('bms_product_effect_status')
const extraParams = { statusGroup: 'active' }
const tableRef = ref()
const formRef = ref()
const excelFileRef = ref()
const countryRef = ref()
const surchargeRef = ref()
const expiredRef = ref()
const importRef = ref()
const countryImportRef = ref()
const importCountryCode = ref('')
const importEffectiveDate = ref('')
function pad2(n) {
return String(n).padStart(2, '0')
}
function formatImportHour(d) {
if (!d || Number.isNaN(d.getTime())) return undefined
return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate())
+ ' ' + pad2(d.getHours()) + ':00:00'
}
function normalizeImportToHour(val) {
if (val == null || val === '') return val
if (val instanceof Date) return formatImportHour(val)
const s = String(val).trim().replace('T', ' ')
const m = s.match(/^(\d{4}-\d{2}-\d{2})[ ](\d{2})/)
if (m) return m[1] + ' ' + m[2] + ':00:00'
if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return s + ' 00:00:00'
return formatImportHour(new Date(s.replace(/-/g, '/'))) || val
}
function onImportEffectiveTimeChange(val) {
importEffectiveDate.value = normalizeImportToHour(val) || ''
}
const regionOptions = ref([])
const importProgressVisible = ref(false)
const importProgressPercent = ref(0)
const importProgressMessage = ref('')
const importProgressStatus = ref(undefined)
const allImportDesc = [
'适用多国家价表(B2C 小包价表等),按目录 sheet 匹配国家导入。',
'必选生效时间:产品与 product_country 明细均按此时间计费;上一版产品/明细失效时间设为该时间。',
'请在弹窗底部下载模板,按模板格式整理后上传。相同产品代码+生效日期不覆盖主数据。'
]
const countryImportDesc = [
'选择目的国家后上传专线成本价表,目录中的产品都会记到该国家。',
'必选生效时间:产品与 product_country 明细均按此时间计费;上一版产品/明细失效时间设为该时间。',
'邮编、赔偿标准等 sheet 不导入。可在弹窗底部下载模板。'
]
const columns = [
{ prop: 'productCode', label: '产品代码', minWidth: 110 },
{ prop: 'productName', label: '产品名称', minWidth: 140, showOverflowTooltip: true },
{ prop: 'serviceLevel', label: '时效等级', width: 90, align: 'center' },
{ prop: 'cargoAttr', label: '走货属性', minWidth: 120, slot: 'cargoAttr' },
{ prop: 'destRegion', label: '目的区域', width: 90, align: 'center' },
{ prop: 'effectiveDate', label: '生效时间', width: 160, align: 'center' },
{ prop: 'expireDate', label: '失效时间', width: 160, align: 'center' },
{ prop: 'countryCount', label: '国家数', width: 80, align: 'center' },
{ prop: 'surchargeCount', label: '附加费数', width: 90, align: 'center' },
{ prop: 'status', label: '状态', width: 90, align: 'center', slot: 'status' },
{ prop: 'updateNote', label: '更新内容', minWidth: 160, showOverflowTooltip: true },
{ prop: 'action', label: '操作', width: 240, fixed: 'right', slot: 'action' }
]
const searchColumns = [
{ prop: 'productCode', label: '产品代码', component: 'input', placeholder: '产品代码' },
{ prop: 'productName', label: '产品名称', component: 'input', placeholder: '产品名称' }
]
function queryList(params) {
return listRedline(params)
}
function handleAdd() {
formRef.value.open()
}
function handleUpdate(row) {
formRef.value.open(row)
}
function handleImportCommand(command) {
if (command === 'country') {
handleCountryImport()
return
}
handleImport()
}
function handleImport() {
importEffectiveDate.value = ''
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 = ''
importEffectiveDate.value = ''
loadRegionOptions()
countryImportRef.value.open()
}
function validateImportEffectiveDate() {
if (!importEffectiveDate.value) {
return '请先选择生效时间'
}
return ''
}
function validateCountryImport() {
const effErr = validateImportEffectiveDate()
if (effErr) return effErr
if (!importCountryCode.value) {
return '请先选择目的国家'
}
return ''
}
function applyImportProgress(t) {
importProgressPercent.value = Math.min(100, Number(t.percent) || 0)
if (t.status === 'PENDING') importProgressMessage.value = '排队中…'
else if (t.status === 'RUNNING') {
const m = t.message || ''
if (m === 'parsing excel') importProgressMessage.value = '正在解析价表…'
else if (m === 'finishing') importProgressMessage.value = '收尾中…'
else if (m === 'queued') importProgressMessage.value = '排队中…'
else if (m.indexOf('importing ') === 0) {
const rest = m.slice('importing '.length)
importProgressMessage.value = '正在导入产品 ' + rest
} else importProgressMessage.value = m || '正在导入…'
} else if (t.status === 'SUCCESS') {
importProgressMessage.value = t.message || '导入完成'
importProgressStatus.value = 'success'
} else if (t.status === 'FAIL') {
importProgressMessage.value = t.error || t.message || '导入失败'
importProgressStatus.value = 'exception'
}
}
async function runImportWithProgress(runner) {
importProgressVisible.value = true
importProgressPercent.value = 0
importProgressMessage.value = '上传并解析中…'
importProgressStatus.value = undefined
try {
const { task } = await runner(applyImportProgress)
importProgressPercent.value = 100
importProgressStatus.value = 'success'
return (task && task.message) ? task.message : '导入完成'
} catch (e) {
importProgressStatus.value = 'exception'
const tip = (e && e.task && e.task.error) || (e && e.message) || '导入失败'
importProgressMessage.value = tip
await new Promise(resolve => setTimeout(resolve, 600))
throw e
} finally {
importProgressVisible.value = false
}
}
async function runImportAllAsync(uploadFile) {
const raw = uploadFile && (uploadFile.raw || uploadFile)
if (!raw) {
throw new Error('请先选择文件')
}
if (!importEffectiveDate.value) {
throw new Error('请先选择生效时间')
}
const eff = importEffectiveDate.value
return runImportWithProgress((onProgress) => importRedlineExcelAsync(raw, eff, onProgress))
}
async function runImportCountryAsync(uploadFile) {
const raw = uploadFile && (uploadFile.raw || uploadFile)
if (!raw) {
throw new Error('请先选择文件')
}
if (!importEffectiveDate.value) {
throw new Error('请先选择生效时间')
}
if (!importCountryCode.value) {
throw new Error('请先选择目的国家')
}
const code = importCountryCode.value
const eff = importEffectiveDate.value
return runImportWithProgress((onProgress) => importRedlineCountryExcelAsync(raw, code, eff, onProgress))
}
</script>
<style scoped>
.mb16 { margin-bottom: 16px; }
.import-progress { padding: 8px 4px 4px; }
.import-progress-msg {
margin-top: 12px;
color: var(--el-text-color-secondary);
font-size: 13px;
line-height: 1.5;
word-break: break-word;
max-height: 160px;
overflow: auto;
}
</style>