应付账单初始化
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="导入应付价卡" width="1080px" append-to-body destroy-on-close @closed="reset">
|
||||
<el-form label-width="88px" class="import-form">
|
||||
<el-form-item label="支付类型" required>
|
||||
<el-select v-model="productType" filterable placeholder="请选择支付类型" style="width: 420px">
|
||||
<el-option v-for="item in bms_pay_type" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="产品">
|
||||
<el-select v-model="dictValue" clearable filterable placeholder="不选则按工作表名称匹配全部字典产品" style="width: 420px">
|
||||
<el-option v-for="item in payment_product_type" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-upload
|
||||
:key="uploadKey"
|
||||
drag
|
||||
accept=".xlsx"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:disabled="recognizing"
|
||||
@change="onFile"
|
||||
>
|
||||
<el-icon class="upload-icon"><UploadFilled /></el-icon>
|
||||
<div class="el-upload__text">将 xlsx 价卡拖到此处,或<em>点击选择</em></div>
|
||||
<template #tip>
|
||||
<div class="upload-tip">选一个产品就只读对应工作表。不选时,一个文件里有多张表,只读取名称能对上字典的工作表。</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
|
||||
<div v-if="preview" v-loading="recognizing" class="preview-box">
|
||||
<el-alert
|
||||
:title="`${preview.sourceFile || ''} 归属 ${preview.supplierName || '-'} 生效 ${preview.effectiveDate || '-'} 共 ${products.length} 个产品`"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
<div class="preview-bar">
|
||||
<el-switch v-model="overwrite" active-text="已存在时另存为新版本" />
|
||||
<span class="preview-hint">未开启时,已存在的产品不能勾选,保存时会跳过。开启后产品编号不变,旧价格留在历史里,新价格从今天起生效。</span>
|
||||
</div>
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="products"
|
||||
border
|
||||
size="small"
|
||||
max-height="280"
|
||||
row-key="rowKey"
|
||||
@selection-change="onSelect"
|
||||
>
|
||||
<el-table-column type="selection" width="48" :selectable="canSelect" />
|
||||
<el-table-column prop="productName" label="产品" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="sheetName" label="报价工作表" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="countryScope" label="国家/口岸" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="relatedSheet" label="关联分区表" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="生效日期" width="110">
|
||||
<template #default="{ row }">{{ row.effectiveDate || preview.effectiveDate || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="weightCount" label="重量段" width="80" align="right" />
|
||||
<el-table-column prop="surchargeCount" label="附加费" width="80" align="right" />
|
||||
<el-table-column label="识别" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusType(row.parseStatus)" size="small">{{ statusText(row.parseStatus) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库中" width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.duplicated" type="warning" size="small">已存在</el-tag>
|
||||
<span v-else>新记录</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div v-if="skippedText" class="upload-tip">未匹配字典、已跳过:{{ skippedText }}</div>
|
||||
<div class="sheet-scroll">
|
||||
<button
|
||||
v-for="item in products"
|
||||
:key="item.rowKey"
|
||||
type="button"
|
||||
class="sheet-tab"
|
||||
:class="{ active: activeKey === item.rowKey }"
|
||||
@click="activeKey = item.rowKey"
|
||||
>{{ item.sheetName || item.productName }}</button>
|
||||
</div>
|
||||
<div class="preview-detail">
|
||||
<pay-config-detail v-if="activeProduct" :product="activeProduct" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" :disabled="!selected.length" :loading="saving" @click="save">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { UploadFilled } from '@element-plus/icons-vue'
|
||||
import { previewPay, savePay } from '@/api/price/pay'
|
||||
import { useDict } from '@/utils/dict'
|
||||
import PayConfigDetail from './PayConfigDetail.vue'
|
||||
|
||||
const emit = defineEmits(['saved'])
|
||||
const { proxy } = getCurrentInstance()
|
||||
const { payment_product_type, bms_pay_type } = useDict('payment_product_type', 'bms_pay_type')
|
||||
|
||||
const visible = ref(false)
|
||||
const recognizing = ref(false)
|
||||
const saving = ref(false)
|
||||
const overwrite = ref(false)
|
||||
const dictValue = ref('')
|
||||
const productType = ref('AP')
|
||||
const preview = ref(null)
|
||||
const selected = ref([])
|
||||
const tableRef = ref()
|
||||
const activeKey = ref('')
|
||||
const uploadKey = ref(0)
|
||||
|
||||
const products = computed(() => preview.value?.products || [])
|
||||
const activeProduct = computed(() => products.value.find(item => item.rowKey === activeKey.value) || products.value[0])
|
||||
const skippedText = computed(() => (preview.value?.skippedSheets || []).filter(Boolean).join('、'))
|
||||
|
||||
function open() {
|
||||
reset()
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
function reset() {
|
||||
preview.value = null
|
||||
selected.value = []
|
||||
overwrite.value = false
|
||||
dictValue.value = ''
|
||||
productType.value = 'AP'
|
||||
activeKey.value = ''
|
||||
recognizing.value = false
|
||||
saving.value = false
|
||||
uploadKey.value += 1
|
||||
}
|
||||
|
||||
async function onFile(uploadFile) {
|
||||
const raw = uploadFile?.raw
|
||||
if (!raw) return
|
||||
if (!raw.name.toLowerCase().endsWith('.xlsx')) {
|
||||
proxy.$modal.msgError('请上传xlsx价卡')
|
||||
return
|
||||
}
|
||||
recognizing.value = true
|
||||
try {
|
||||
const res = await previewPay(raw, dictValue.value)
|
||||
const data = res.data || {}
|
||||
data.products = (data.products || []).map((item, index) => ({
|
||||
...item,
|
||||
rowKey: `${item.sheetName || ''}-${item.productName || ''}-${index}`
|
||||
}))
|
||||
preview.value = data
|
||||
activeKey.value = data.products[0]?.rowKey || ''
|
||||
const picked = data.products.filter(row => canSelect(row))
|
||||
selected.value = picked
|
||||
await nextTick()
|
||||
tableRef.value?.clearSelection()
|
||||
picked.forEach(row => tableRef.value?.toggleRowSelection(row, true))
|
||||
} finally {
|
||||
recognizing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function canSelect(row) {
|
||||
if (row.parseStatus === 'MISSING') return false
|
||||
return overwrite.value || !row.duplicated
|
||||
}
|
||||
|
||||
watch(overwrite, async () => {
|
||||
if (!visible.value || !preview.value) return
|
||||
const picked = products.value.filter(row => canSelect(row))
|
||||
selected.value = picked
|
||||
await nextTick()
|
||||
tableRef.value?.clearSelection()
|
||||
picked.forEach(row => tableRef.value?.toggleRowSelection(row, true))
|
||||
})
|
||||
|
||||
function onSelect(rows) {
|
||||
if (!visible.value) return
|
||||
selected.value = rows || []
|
||||
}
|
||||
|
||||
async function save() {
|
||||
if (!productType.value) {
|
||||
proxy.$modal.msgError('请选择支付类型')
|
||||
return
|
||||
}
|
||||
if (!selected.value.length) {
|
||||
proxy.$modal.msgError('请勾选要保存的产品')
|
||||
return
|
||||
}
|
||||
const duplicated = selected.value.filter(row => row.duplicated).length
|
||||
const tip = overwrite.value && duplicated
|
||||
? `将保存 ${selected.value.length} 个产品,其中 ${duplicated} 个会保留旧版本并生成新版本。`
|
||||
: `将保存 ${selected.value.length} 个产品。`
|
||||
try {
|
||||
await proxy.$modal.confirm(tip)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
const products = selected.value.map(row => ({ ...row, productType: productType.value }))
|
||||
const res = await savePay({ overwrite: overwrite.value, products })
|
||||
const message = res.msg || '保存成功'
|
||||
reset()
|
||||
visible.value = false
|
||||
ElMessageBox.close()
|
||||
proxy.$modal.msgSuccess(message)
|
||||
emit('saved')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function statusText(status) {
|
||||
return { OK: '已识别', EMPTY: '未识别到配置', MISSING: '缺少工作表' }[status] || status || '-'
|
||||
}
|
||||
|
||||
function statusType(status) {
|
||||
if (status === 'OK') return 'success'
|
||||
if (status === 'EMPTY') return 'warning'
|
||||
if (status === 'MISSING') return 'danger'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.upload-icon {
|
||||
font-size: 42px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
.upload-tip {
|
||||
margin-top: 6px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
.preview-box {
|
||||
margin-top: 14px;
|
||||
}
|
||||
.preview-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.preview-hint {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
.preview-detail {
|
||||
max-height: 460px;
|
||||
margin-top: 14px;
|
||||
overflow: auto;
|
||||
}
|
||||
.import-form {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.sheet-scroll {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
.sheet-tab {
|
||||
flex: 0 0 auto;
|
||||
border: 1px solid var(--el-border-color);
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.sheet-tab.active {
|
||||
color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
background: var(--el-color-primary-light-9);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user