增加产品导入功能

This commit is contained in:
wuweihua
2026-09-04 18:08:08 +08:00
parent 5e15ab9c44
commit 7204497a65
16 changed files with 1644 additions and 22 deletions
@@ -0,0 +1,299 @@
<template>
<div class="app-container">
<el-alert
type="info"
:closable="false"
show-icon
class="mb16"
title="Excel 导入:上传威狮B2C小包价表.xlsx。仅入库产品目录 + 各产品 sheet 的国家尺寸/尾程/时效;不导入运费、挂号费、退件重派、申报说明、赔偿标准。"
/>
<basic-table
ref="tableRef"
:columns="columns"
:search-columns="searchColumns"
:query="queryList"
row-key="productId"
>
<template #toolbar="{ selection, ids }">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['price:product:add']">新建产品</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="selection.length !== 1" @click="handleUpdate(selection[0])" v-hasPermi="['price:product:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="!selection.length" @click="handleDelete(ids)" v-hasPermi="['price:product:remove']">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['price:product:import']">导入 Excel</el-button>
</el-col>
</template>
<template #col-taxMode="{ row }">
<el-tag :type="row.taxMode === 'TAX_INCLUDED' ? 'success' : 'warning'" size="small">
{{ row.taxMode === 'TAX_INCLUDED' ? '包税' : '不包税' }}
</el-tag>
</template>
<template #col-cargoAttr="{ row }">
<el-tag type="primary" size="small" effect="plain">{{ row.cargoAttr || '-' }}</el-tag>
</template>
<template #col-status="{ row }">
<el-tag :type="row.status === '0' ? 'success' : 'info'" size="small">
{{ row.status === '0' ? '正常' : '停用' }}
</el-tag>
</template>
<template #col-action="{ row }">
<el-button link type="primary" icon="View" @click="openCountries(row)" v-hasPermi="['price:product:query']">国家明细</el-button>
<el-button link type="primary" icon="Edit" @click="handleUpdate(row)" v-hasPermi="['price:product:edit']">编辑</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(row.productId)" v-hasPermi="['price:product:remove']">删除</el-button>
</template>
</basic-table>
<el-dialog :title="title" v-model="open" width="640px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-row>
<el-col :span="12">
<el-form-item label="产品代码" prop="productCode">
<el-input v-model="form.productCode" placeholder="如 WSP037" maxlength="20" :disabled="!!form.productId" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="产品名称" prop="productName">
<el-input v-model="form.productName" placeholder="产品名称" maxlength="100" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="时效等级" prop="serviceLevel">
<el-input v-model="form.serviceLevel" placeholder="如 标准" maxlength="20" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="走货属性" prop="cargoAttr">
<el-input v-model="form.cargoAttr" placeholder="普货/带电等" maxlength="100" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="目的区域" prop="destRegion">
<el-input v-model="form.destRegion" placeholder="如 欧盟" maxlength="50" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="税模式" prop="taxMode">
<el-select v-model="form.taxMode" placeholder="请选择" style="width: 100%">
<el-option label="包税" value="TAX_INCLUDED" />
<el-option label="不包税" value="TAX_EXCLUDED" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="生效日期" prop="effectiveDate">
<el-date-picker v-model="form.effectiveDate" type="date" value-format="YYYY-MM-DD" placeholder="选择日期" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<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-col>
</el-row>
<el-form-item label="更新内容" prop="updateNote">
<el-input v-model="form.updateNote" type="textarea" :rows="2" maxlength="500" />
</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>
<el-drawer v-model="countryOpen" size="720px" :title="countryTitle" append-to-body>
<el-table v-loading="countryLoading" :data="countryList" border empty-text="暂无国家明细">
<el-table-column label="国家" prop="countryName" width="90" show-overflow-tooltip />
<el-table-column label="ISO2" prop="countryCode" width="60" align="center" />
<el-table-column label="重量段(KG)" width="120" align="center">
<template #default="{ row }">
<span v-if="row.weightMin != null || row.weightMax != null">{{ row.weightMin }} ~ {{ row.weightMax }}</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="尺寸限制" prop="sizeLimit" min-width="140">
<template #default="{ row }">
<span class="pre-wrap">{{ row.sizeLimit }}</span>
</template>
</el-table-column>
<el-table-column label="尾程" prop="lastMile" min-width="110">
<template #default="{ row }">
<span class="pre-wrap">{{ row.lastMile }}</span>
</template>
</el-table-column>
<el-table-column label="时效" prop="leadTime" width="100" show-overflow-tooltip />
</el-table>
</el-drawer>
<excel-import-dialog
ref="importRef"
title="产品 Excel 导入"
action="/price/product/importData"
update-support-label="是否更新已经存在的产品(含国家明细覆盖)"
@success="() => tableRef.refresh()"
/>
</div>
</template>
<script setup name="PriceProduct">
import ExcelImportDialog from '@/components/ExcelImportDialog'
import { listProduct, getProduct, addProduct, updateProduct, delProduct, listProductCountries } from '@/api/price/product'
const { proxy } = getCurrentInstance()
const tableRef = ref()
const importRef = ref()
const formRef = ref()
const open = ref(false)
const title = ref('')
const countryOpen = ref(false)
const countryTitle = ref('')
const countryLoading = ref(false)
const countryList = ref([])
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: 'taxMode', label: '税模式', width: 90, align: 'center', slot: 'taxMode' },
{ prop: 'effectiveDate', label: '生效日期', width: 120, align: 'center' },
{ prop: 'countryCount', label: '国家数', width: 80, align: 'center' },
{ prop: 'status', label: '状态', width: 80, 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: '产品代码' },
{ prop: 'productName', label: '产品名称' },
{
prop: 'taxMode',
label: '税模式',
type: 'select',
options: [
{ label: '包税', value: 'TAX_INCLUDED' },
{ label: '不包税', value: 'TAX_EXCLUDED' }
]
},
{
prop: 'status',
label: '状态',
type: 'select',
options: [
{ label: '正常', value: '0' },
{ label: '停用', value: '1' }
]
}
]
const data = reactive({
form: {},
rules: {
productCode: [{ required: true, message: '产品代码不能为空', trigger: 'blur' }],
productName: [{ required: true, message: '产品名称不能为空', trigger: 'blur' }],
taxMode: [{ required: true, message: '请选择税模式', trigger: 'change' }]
}
})
const { form, rules } = toRefs(data)
function queryList(params) {
return listProduct(params)
}
function reset() {
form.value = {
productId: undefined,
productCode: undefined,
productName: undefined,
serviceLevel: undefined,
cargoAttr: undefined,
destRegion: undefined,
updateNote: undefined,
taxMode: 'TAX_INCLUDED',
effectiveDate: undefined,
status: '0',
remark: undefined
}
proxy.resetForm('formRef')
}
function handleAdd() {
reset()
open.value = true
title.value = '新建产品'
}
function handleUpdate(row) {
reset()
getProduct(row.productId).then(res => {
form.value = res.data
open.value = true
title.value = '修改产品'
})
}
function submitForm() {
proxy.$refs['formRef'].validate(valid => {
if (!valid) return
const req = form.value.productId ? updateProduct(form.value) : addProduct(form.value)
req.then(() => {
proxy.$modal.msgSuccess('操作成功')
open.value = false
tableRef.value.refresh()
})
})
}
function handleDelete(ids) {
const idText = Array.isArray(ids) ? ids.join(',') : ids
proxy.$modal.confirm('是否确认删除所选产品?国家明细将一并删除。').then(() => {
return delProduct(idText)
}).then(() => {
proxy.$modal.msgSuccess('删除成功')
tableRef.value.refresh()
}).catch(() => {})
}
function handleImport() {
importRef.value.open()
}
function openCountries(row) {
countryTitle.value = `${row.productCode} · ${row.productName} · 国家服务明细`
countryOpen.value = true
countryLoading.value = true
countryList.value = []
listProductCountries(row.productCode).then(res => {
countryList.value = res.data || []
}).finally(() => {
countryLoading.value = false
})
}
</script>
<style scoped>
.mb16 { margin-bottom: 16px; }
.pre-wrap { white-space: pre-wrap; font-size: 12px; line-height: 1.4; }
</style>