20260706002
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
|||||||
<div class="gt-wh-panel" v-loading="panelLoading" element-loading-text="加载中…">
|
<div class="gt-wh-panel" v-loading="panelLoading" element-loading-text="加载中…">
|
||||||
<div v-if="showSchemaHint" class="gt-wh-hint">
|
<div v-if="showSchemaHint" class="gt-wh-hint">
|
||||||
<el-icon><InfoFilled /></el-icon>
|
<el-icon><InfoFilled /></el-icon>
|
||||||
<span>仓库管理为系统固定结构:左侧选择 RDC,上方横向切换费用票据,填写「提成 / 利润」下「内部、零售、渠道」的率与额。</span>
|
<span>仓库管理:左侧选 RDC,右侧按费用票据分组填写「提成 / 利润」下各渠道的率与额。</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="!rdcOptions.length">
|
<template v-if="!rdcOptions.length">
|
||||||
@@ -22,71 +22,70 @@
|
|||||||
<span v-if="rdcDisplaySub(rdc)" class="gt-wh-rdc-nav-sub">{{ rdcDisplaySub(rdc) }}</span>
|
<span v-if="rdcDisplaySub(rdc)" class="gt-wh-rdc-nav-sub">{{ rdcDisplaySub(rdc) }}</span>
|
||||||
</button>
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="gt-wh-main">
|
|
||||||
<div v-if="showLegacyRdcHint" class="gt-wh-legacy-hint">
|
|
||||||
当前 RDC 存在历史格式数据(未按票据分桶);请选择票据后重新填写并保存以迁移到新结构。
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="gt-wh-workspace">
|
||||||
<template v-if="!expenseBills.length">
|
<template v-if="!expenseBills.length">
|
||||||
<el-empty :image-size="64" description="暂无费用票据" class="gt-wh-empty" />
|
<el-empty :image-size="64" description="暂无费用票据" class="gt-wh-empty" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="!activeBillCode">
|
|
||||||
<el-empty :image-size="64" description="请选择费用票据" class="gt-wh-empty" />
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<el-tabs v-model="activeBillCode" class="gt-wh-bill-tabs">
|
<div v-if="showLegacyRdcHint" class="gt-wh-legacy-hint gt-wh-legacy-hint--workspace">
|
||||||
<el-tab-pane
|
当前 RDC 存在历史格式数据(未按票据分桶);请重新填写并保存以迁移到新结构。
|
||||||
v-for="bill in expenseBills"
|
</div>
|
||||||
:key="bill.bill_code"
|
<div class="gt-wh-bills-scroll">
|
||||||
:name="normBillCode(bill.bill_code)"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
<span class="gt-wh-bill-tab" :title="billTabTitle(bill)">
|
|
||||||
<span class="gt-wh-bill-tab-name">{{ billTabParts(bill).name }}</span>
|
|
||||||
<span v-if="billTabParts(bill).tax" class="gt-wh-bill-tab-tax">{{ billTabParts(bill).tax }}%</span>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
<div class="gt-wh-form-body">
|
|
||||||
<div
|
<div
|
||||||
v-for="sec in WAREHOUSE_SECTIONS"
|
v-for="bill in expenseBills"
|
||||||
:key="sec.key"
|
:key="normBillCode(bill.bill_code)"
|
||||||
class="gt-wh-section-block"
|
class="gt-wh-bill-group"
|
||||||
>
|
>
|
||||||
<div class="gt-wh-section-title">{{ sec.label }}</div>
|
<div class="gt-wh-bill-group-title">
|
||||||
<div class="gt-wh-channel-grid">
|
<span class="gt-wh-bill-group-name">{{ billTabParts(bill).name }}</span>
|
||||||
<div
|
<span v-if="billTabParts(bill).tax" class="gt-wh-bill-group-tax">{{ billTabParts(bill).tax }}%</span>
|
||||||
v-for="ch in WAREHOUSE_CHANNELS"
|
</div>
|
||||||
:key="ch.key"
|
<div class="gt-wh-bill-group-body">
|
||||||
class="gt-wh-channel-card"
|
<div class="gt-wh-sections-row">
|
||||||
>
|
<div
|
||||||
<div class="gt-wh-channel-title">{{ ch.label }}</div>
|
v-for="sec in WAREHOUSE_SECTIONS"
|
||||||
<div class="gt-wh-metrics">
|
:key="`${normBillCode(bill.bill_code)}-${sec.key}`"
|
||||||
<div class="gt-wh-metric">
|
class="gt-wh-section-block"
|
||||||
<span class="gt-wh-metric-label">率</span>
|
:class="sectionBlockClass(sec.key)"
|
||||||
<el-input-number
|
>
|
||||||
:model-value="metricValue(sec.key, ch.key, 'rate')"
|
<div class="gt-wh-section-title">{{ sec.label }}</div>
|
||||||
:disabled="readonly"
|
<div class="gt-wh-channel-grid">
|
||||||
:controls="false"
|
<div
|
||||||
placeholder="—"
|
v-for="ch in WAREHOUSE_CHANNELS"
|
||||||
size="small"
|
:key="`${normBillCode(bill.bill_code)}-${sec.key}-${ch.key}`"
|
||||||
class="gt-wh-metric-input"
|
class="gt-wh-channel-card"
|
||||||
@update:model-value="(v) => setMetric(sec.key, ch.key, 'rate', v)"
|
>
|
||||||
/>
|
<div class="gt-wh-channel-title">{{ ch.label }}</div>
|
||||||
<span class="gt-wh-metric-suffix">%</span>
|
<div class="gt-wh-metrics">
|
||||||
</div>
|
<div class="gt-wh-metric">
|
||||||
<div class="gt-wh-metric">
|
<span class="gt-wh-metric-label">率</span>
|
||||||
<span class="gt-wh-metric-label">额</span>
|
<el-input-number
|
||||||
<el-input-number
|
:model-value="metricValue(sec.key, ch.key, 'rate', bill.bill_code)"
|
||||||
:model-value="metricValue(sec.key, ch.key, 'amount')"
|
:disabled="readonly"
|
||||||
:disabled="readonly"
|
:controls="false"
|
||||||
:controls="false"
|
placeholder="—"
|
||||||
placeholder="—"
|
size="small"
|
||||||
size="small"
|
class="gt-wh-metric-input"
|
||||||
class="gt-wh-metric-input"
|
@update:model-value="(v) => setMetric(sec.key, ch.key, 'rate', v, bill.bill_code)"
|
||||||
@update:model-value="(v) => setMetric(sec.key, ch.key, 'amount', v)"
|
/>
|
||||||
/>
|
<span class="gt-wh-metric-suffix">%</span>
|
||||||
|
</div>
|
||||||
|
<div class="gt-wh-metric">
|
||||||
|
<span class="gt-wh-metric-label">额</span>
|
||||||
|
<el-input-number
|
||||||
|
:model-value="metricValue(sec.key, ch.key, 'amount', bill.bill_code)"
|
||||||
|
:disabled="readonly"
|
||||||
|
:controls="false"
|
||||||
|
placeholder="—"
|
||||||
|
size="small"
|
||||||
|
class="gt-wh-metric-input"
|
||||||
|
@update:model-value="(v) => setMetric(sec.key, ch.key, 'amount', v, bill.bill_code)"
|
||||||
|
/>
|
||||||
|
<span class="gt-wh-metric-suffix">元</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -120,9 +119,7 @@ import {
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: { type: Object, default: () => ({}) },
|
modelValue: { type: Object, default: () => ({}) },
|
||||||
readonly: { type: Boolean, default: false },
|
readonly: { type: Boolean, default: false },
|
||||||
/** 全局页签管理:展示结构说明 */
|
|
||||||
showSchemaHint: { type: Boolean, default: false },
|
showSchemaHint: { type: Boolean, default: false },
|
||||||
/** 物料 /goods/view 返回的 rdcs;公共页签、物料分类留空即可 */
|
|
||||||
rdcList: { type: Array, default: () => [] },
|
rdcList: { type: Array, default: () => [] },
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -136,7 +133,6 @@ const billsLoaded = ref(false)
|
|||||||
/** @type {import('vue').Ref<object[]>} */
|
/** @type {import('vue').Ref<object[]>} */
|
||||||
const allBills = ref([])
|
const allBills = ref([])
|
||||||
const activeRdcCode = ref('')
|
const activeRdcCode = ref('')
|
||||||
const activeBillCode = ref('')
|
|
||||||
|
|
||||||
const panelLoading = computed(() => billLoading.value)
|
const panelLoading = computed(() => billLoading.value)
|
||||||
|
|
||||||
@@ -147,7 +143,6 @@ const bucket = computed({
|
|||||||
set: (v) => emit('update:modelValue', v),
|
set: (v) => emit('update:modelValue', v),
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 仓库管理仅使用费用票据 type=4 */
|
|
||||||
const expenseBills = computed(() => {
|
const expenseBills = computed(() => {
|
||||||
return allBills.value.filter((b) => {
|
return allBills.value.filter((b) => {
|
||||||
if (b?.is_valid === false || b?.is_valid === 0 || b?.is_valid === '0') return false
|
if (b?.is_valid === false || b?.is_valid === 0 || b?.is_valid === '0') return false
|
||||||
@@ -161,6 +156,12 @@ const showLegacyRdcHint = computed(() => {
|
|||||||
return isLegacyRdcMetricsBucket(rdc)
|
return isLegacyRdcMetricsBucket(rdc)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function sectionBlockClass(sectionKey) {
|
||||||
|
if (sectionKey === 'commission') return 'is-commission'
|
||||||
|
if (sectionKey === 'profit') return 'is-profit'
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
function rdcDisplayName(rdc) {
|
function rdcDisplayName(rdc) {
|
||||||
const name = String(rdc?.name || '').trim()
|
const name = String(rdc?.name || '').trim()
|
||||||
const code = String(rdc?.code || '').trim()
|
const code = String(rdc?.code || '').trim()
|
||||||
@@ -179,11 +180,6 @@ function billTabParts(bill) {
|
|||||||
return expenseBillTabParts(bill)
|
return expenseBillTabParts(bill)
|
||||||
}
|
}
|
||||||
|
|
||||||
function billTabTitle(bill) {
|
|
||||||
const { name, tax } = billTabParts(bill)
|
|
||||||
return tax ? `${name} · ${tax}%` : name
|
|
||||||
}
|
|
||||||
|
|
||||||
function normBillCode(code) {
|
function normBillCode(code) {
|
||||||
return String(code ?? '').trim()
|
return String(code ?? '').trim()
|
||||||
}
|
}
|
||||||
@@ -192,26 +188,18 @@ function normRdcCode(code) {
|
|||||||
return String(code ?? '').trim()
|
return String(code ?? '').trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
function pickInitialBillCode() {
|
function ensureAllBillBucketsForRdc(rdcCode) {
|
||||||
const list = expenseBills.value
|
const rdc = normRdcCode(rdcCode)
|
||||||
if (!list.length) {
|
if (!rdc || props.readonly) return
|
||||||
activeBillCode.value = ''
|
for (const bill of expenseBills.value) {
|
||||||
return
|
ensureBillBucket(rdc, bill.bill_code)
|
||||||
}
|
}
|
||||||
const cur = normBillCode(activeBillCode.value)
|
|
||||||
if (cur && list.some((b) => normBillCode(b.bill_code) === cur)) {
|
|
||||||
activeBillCode.value = cur
|
|
||||||
return
|
|
||||||
}
|
|
||||||
activeBillCode.value = normBillCode(list[0].bill_code)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncActiveWarehouseSelection() {
|
function syncActiveRdcBuckets() {
|
||||||
pickInitialBillCode()
|
|
||||||
const rdc = normRdcCode(activeRdcCode.value)
|
const rdc = normRdcCode(activeRdcCode.value)
|
||||||
const bill = normBillCode(activeBillCode.value)
|
if (!rdc || !expenseBills.value.length) return
|
||||||
if (!rdc || !bill) return
|
if (!props.readonly) ensureAllBillBucketsForRdc(rdc)
|
||||||
if (!props.readonly) ensureBillBucket(rdc, bill)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectRdc(code) {
|
function selectRdc(code) {
|
||||||
@@ -219,12 +207,11 @@ function selectRdc(code) {
|
|||||||
activeRdcCode.value = c
|
activeRdcCode.value = c
|
||||||
if (!c || props.readonly) return
|
if (!c || props.readonly) return
|
||||||
const next = { ...bucket.value }
|
const next = { ...bucket.value }
|
||||||
const rdc = next[c]
|
if (!next[c]) {
|
||||||
if (!rdc) {
|
|
||||||
next[c] = {}
|
next[c] = {}
|
||||||
bucket.value = next
|
bucket.value = next
|
||||||
}
|
}
|
||||||
syncActiveWarehouseSelection()
|
syncActiveRdcBuckets()
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureBillBucket(rdcCode, billCode) {
|
function ensureBillBucket(rdcCode, billCode) {
|
||||||
@@ -246,25 +233,25 @@ function ensureBillBucket(rdcCode, billCode) {
|
|||||||
return next[rdc][bill]
|
return next[rdc][bill]
|
||||||
}
|
}
|
||||||
|
|
||||||
function currentBillBucket() {
|
function billBucketFor(rdcCode, billCode) {
|
||||||
const rdc = bucket.value[normRdcCode(activeRdcCode.value)]
|
const rdc = bucket.value[normRdcCode(rdcCode)]
|
||||||
const bill = normBillCode(activeBillCode.value)
|
const bill = normBillCode(billCode)
|
||||||
if (!rdc || !bill) return null
|
if (!rdc || !bill) return null
|
||||||
if (isLegacyRdcMetricsBucket(rdc)) return rdc
|
if (isLegacyRdcMetricsBucket(rdc)) return rdc
|
||||||
return rdc[bill] || null
|
return rdc[bill] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
function metricValue(sectionKey, channelKey, metricKey) {
|
function metricValue(sectionKey, channelKey, metricKey, billCode) {
|
||||||
const b = currentBillBucket()
|
const b = billBucketFor(activeRdcCode.value, billCode)
|
||||||
const v = b?.[sectionKey]?.[channelKey]?.[metricKey]
|
const v = b?.[sectionKey]?.[channelKey]?.[metricKey]
|
||||||
return v === null || v === undefined ? null : Number(v)
|
return v === null || v === undefined ? null : Number(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setMetric(sectionKey, channelKey, metricKey, raw) {
|
function setMetric(sectionKey, channelKey, metricKey, raw, billCode) {
|
||||||
const rdcCode = normRdcCode(activeRdcCode.value)
|
const rdcCode = normRdcCode(activeRdcCode.value)
|
||||||
const billCode = normBillCode(activeBillCode.value)
|
const bill = normBillCode(billCode)
|
||||||
if (props.readonly || !rdcCode || !billCode) return
|
if (props.readonly || !rdcCode || !bill) return
|
||||||
const billBucket = ensureBillBucket(rdcCode, billCode)
|
const billBucket = ensureBillBucket(rdcCode, bill)
|
||||||
if (!billBucket) return
|
if (!billBucket) return
|
||||||
if (!billBucket[sectionKey]) billBucket[sectionKey] = createEmptyRdcBucket()[sectionKey]
|
if (!billBucket[sectionKey]) billBucket[sectionKey] = createEmptyRdcBucket()[sectionKey]
|
||||||
if (!billBucket[sectionKey][channelKey]) {
|
if (!billBucket[sectionKey][channelKey]) {
|
||||||
@@ -281,17 +268,16 @@ async function loadBillList() {
|
|||||||
const res = await request.post('/api/bill/listed', { page: 1, limit: 500, type: FINANCE_BILL_TYPE_EXPENSE })
|
const res = await request.post('/api/bill/listed', { page: 1, limit: 500, type: FINANCE_BILL_TYPE_EXPENSE })
|
||||||
const list = Array.isArray(res?.data) ? res.data : (Array.isArray(res?.data?.list) ? res.data.list : [])
|
const list = Array.isArray(res?.data) ? res.data : (Array.isArray(res?.data?.list) ? res.data.list : [])
|
||||||
allBills.value = list.filter((b) => normalizeFinanceBillType(b?.type) === FINANCE_BILL_TYPE_EXPENSE)
|
allBills.value = list.filter((b) => normalizeFinanceBillType(b?.type) === FINANCE_BILL_TYPE_EXPENSE)
|
||||||
syncActiveWarehouseSelection()
|
syncActiveRdcBuckets()
|
||||||
} catch {
|
} catch {
|
||||||
allBills.value = []
|
allBills.value = []
|
||||||
activeBillCode.value = ''
|
|
||||||
} finally {
|
} finally {
|
||||||
billLoading.value = false
|
billLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(expenseBills, () => {
|
watch(expenseBills, () => {
|
||||||
syncActiveWarehouseSelection()
|
syncActiveRdcBuckets()
|
||||||
})
|
})
|
||||||
|
|
||||||
function pickInitialRdcCode(opts) {
|
function pickInitialRdcCode(opts) {
|
||||||
@@ -314,7 +300,7 @@ watch(
|
|||||||
if (nextRdc !== normRdcCode(activeRdcCode.value)) {
|
if (nextRdc !== normRdcCode(activeRdcCode.value)) {
|
||||||
selectRdc(nextRdc)
|
selectRdc(nextRdc)
|
||||||
} else {
|
} else {
|
||||||
syncActiveWarehouseSelection()
|
syncActiveRdcBuckets()
|
||||||
}
|
}
|
||||||
if (!billsLoaded.value) {
|
if (!billsLoaded.value) {
|
||||||
billsLoaded.value = true
|
billsLoaded.value = true
|
||||||
@@ -324,10 +310,6 @@ watch(
|
|||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(activeBillCode, () => {
|
|
||||||
syncActiveWarehouseSelection()
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
() => {
|
() => {
|
||||||
@@ -364,12 +346,6 @@ defineExpose({
|
|||||||
border: 1px solid #d9ecff;
|
border: 1px solid #d9ecff;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
.gt-wh-hint code {
|
|
||||||
font-size: 11px;
|
|
||||||
padding: 0 4px;
|
|
||||||
background: #ecf5ff;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
.gt-wh-legacy-hint {
|
.gt-wh-legacy-hint {
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -379,6 +355,10 @@ defineExpose({
|
|||||||
border: 1px solid #faecd8;
|
border: 1px solid #faecd8;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
.gt-wh-legacy-hint--workspace {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 10px 12px 0;
|
||||||
|
}
|
||||||
.gt-wh-split {
|
.gt-wh-split {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
@@ -389,7 +369,7 @@ defineExpose({
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
.gt-wh-rdc-nav {
|
.gt-wh-rdc-nav {
|
||||||
flex: 0 0 132px;
|
flex: 0 0 168px;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
border-right: 1px solid #ebeef5;
|
border-right: 1px solid #ebeef5;
|
||||||
@@ -436,44 +416,53 @@ defineExpose({
|
|||||||
color: #909399;
|
color: #909399;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
.gt-wh-main {
|
.gt-wh-workspace {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
|
||||||
padding: 12px 14px 14px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
.gt-wh-bill-tabs :deep(.el-tabs__header) {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.gt-wh-bill-tabs :deep(.el-tabs__nav-wrap::after) {
|
|
||||||
height: 1px;
|
|
||||||
}
|
|
||||||
.gt-wh-bill-tabs :deep(.el-tabs__item) {
|
|
||||||
height: 38px;
|
|
||||||
padding: 0 14px;
|
|
||||||
}
|
|
||||||
.gt-wh-bill-tab {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
max-width: 168px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
.gt-wh-bill-tab-name {
|
|
||||||
flex: 0 1 auto;
|
|
||||||
min-width: 0;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
}
|
||||||
.gt-wh-bill-tab-tax {
|
.gt-wh-bills-scroll {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 10px 10px 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.gt-wh-bill-group {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #fafbfc;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.gt-wh-bill-group-title {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 14px 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #8b9ab0;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
background: #fafbfc;
|
||||||
|
}
|
||||||
|
.gt-wh-bill-group-name {
|
||||||
|
color: #606266;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.gt-wh-bill-group-tax {
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
@@ -482,36 +471,32 @@ defineExpose({
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
background: #fef0f0;
|
background: #fef0f0;
|
||||||
color: #f56c6c;
|
color: #f56c6c;
|
||||||
transition: background 0.15s, color 0.15s;
|
|
||||||
}
|
}
|
||||||
.gt-wh-bill-tabs :deep(.el-tabs__item.is-active) .gt-wh-bill-tab-name {
|
.gt-wh-bill-group-body {
|
||||||
font-weight: 600;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
.gt-wh-bill-tabs :deep(.el-tabs__item.is-active) .gt-wh-bill-tab-tax {
|
.gt-wh-sections-row {
|
||||||
background: #f56c6c;
|
display: grid;
|
||||||
color: #fff;
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
gap: 12px;
|
||||||
.gt-wh-bill-tabs :deep(.el-tabs__item:hover) .gt-wh-bill-tab-tax {
|
align-items: start;
|
||||||
background: #fde2e2;
|
|
||||||
}
|
|
||||||
.gt-wh-bill-tabs :deep(.el-tabs__item.is-active:hover) .gt-wh-bill-tab-tax {
|
|
||||||
background: #f56c6c;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.gt-wh-form-body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 14px;
|
|
||||||
}
|
}
|
||||||
.gt-wh-section-block {
|
.gt-wh-section-block {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 10px 10px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid transparent;
|
||||||
}
|
}
|
||||||
.gt-wh-section-block + .gt-wh-section-block {
|
.gt-wh-section-block.is-commission {
|
||||||
margin-top: 4px;
|
background: #f7fafd;
|
||||||
padding-top: 14px;
|
border-color: #e6eef8;
|
||||||
border-top: 1px solid #ebeef5;
|
}
|
||||||
|
.gt-wh-section-block.is-profit {
|
||||||
|
background: #f8fbf6;
|
||||||
|
border-color: #e5efe5;
|
||||||
}
|
}
|
||||||
.gt-wh-section-title {
|
.gt-wh-section-title {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -519,56 +504,50 @@ defineExpose({
|
|||||||
color: #303133;
|
color: #303133;
|
||||||
padding-left: 2px;
|
padding-left: 2px;
|
||||||
}
|
}
|
||||||
|
.gt-wh-section-block.is-commission .gt-wh-section-title {
|
||||||
|
color: #4a6fa5;
|
||||||
|
}
|
||||||
|
.gt-wh-section-block.is-profit .gt-wh-section-title {
|
||||||
|
color: #5a8f5a;
|
||||||
|
}
|
||||||
.gt-wh-channel-grid {
|
.gt-wh-channel-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
gap: 12px;
|
gap: 10px;
|
||||||
}
|
|
||||||
@media (max-width: 900px) {
|
|
||||||
.gt-wh-split {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.gt-wh-rdc-nav {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
max-height: 140px;
|
|
||||||
border-right: none;
|
|
||||||
border-bottom: 1px solid #ebeef5;
|
|
||||||
}
|
|
||||||
.gt-wh-channel-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.gt-wh-channel-card {
|
.gt-wh-channel-card {
|
||||||
padding: 12px;
|
padding: 10px 8px;
|
||||||
border: 1px solid #ebeef5;
|
border: 1px solid #ebeef5;
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
.gt-wh-channel-title {
|
.gt-wh-channel-title {
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 8px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 6px;
|
||||||
border-bottom: 1px dashed #ebeef5;
|
border-bottom: 1px dashed #ebeef5;
|
||||||
}
|
}
|
||||||
.gt-wh-metrics {
|
.gt-wh-metrics {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
.gt-wh-metric {
|
.gt-wh-metric {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
.gt-wh-metric-label {
|
.gt-wh-metric-label {
|
||||||
flex: 0 0 20px;
|
flex: 0 0 18px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
}
|
}
|
||||||
.gt-wh-metric-input {
|
.gt-wh-metric-input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.gt-wh-metric-input :deep(.el-input__inner) {
|
.gt-wh-metric-input :deep(.el-input__inner) {
|
||||||
@@ -582,4 +561,21 @@ defineExpose({
|
|||||||
.gt-wh-empty {
|
.gt-wh-empty {
|
||||||
padding: 24px 0;
|
padding: 24px 0;
|
||||||
}
|
}
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.gt-wh-split {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.gt-wh-rdc-nav {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
max-height: 120px;
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
}
|
||||||
|
.gt-wh-sections-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
.gt-wh-channel-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -89,6 +89,10 @@
|
|||||||
.wops-flex-table.is-cols-pr-cost {
|
.wops-flex-table.is-cols-pr-cost {
|
||||||
--wops-cols: 7fr 10.5fr 10.5fr 7fr 8fr 8fr 6fr 9fr 6fr 8fr;
|
--wops-cols: 7fr 10.5fr 10.5fr 7fr 8fr 8fr 6fr 9fr 6fr 8fr;
|
||||||
}
|
}
|
||||||
|
/* 物料成本围栏(11 列):内部交易相关表头加宽,配合 88vw 弹窗单行显示 */
|
||||||
|
.wops-flex-table.is-cols-cost-fence {
|
||||||
|
--wops-cols: 8fr 9fr 10fr 7fr 6fr 9fr 10fr 8fr 11fr 7fr 5fr;
|
||||||
|
}
|
||||||
|
|
||||||
.wops-flex-scroll {
|
.wops-flex-scroll {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -197,6 +201,7 @@
|
|||||||
.wops-flex-table.is-cols-share-deploy .wops-flex-row,
|
.wops-flex-table.is-cols-share-deploy .wops-flex-row,
|
||||||
.wops-flex-table.is-cols-share-waste .wops-flex-row,
|
.wops-flex-table.is-cols-share-waste .wops-flex-row,
|
||||||
.wops-flex-table.is-cols-pr-cost .wops-flex-row,
|
.wops-flex-table.is-cols-pr-cost .wops-flex-row,
|
||||||
|
.wops-flex-table.is-cols-cost-fence .wops-flex-row,
|
||||||
.wops-flex-table.is-cols-collect-whp .wops-flex-row,
|
.wops-flex-table.is-cols-collect-whp .wops-flex-row,
|
||||||
.wops-flex-table.is-cols-logs-whp .wops-flex-row,
|
.wops-flex-table.is-cols-logs-whp .wops-flex-row,
|
||||||
.wops-flex-table.is-cols-orders-whp .wops-flex-row,
|
.wops-flex-table.is-cols-orders-whp .wops-flex-row,
|
||||||
@@ -220,6 +225,8 @@
|
|||||||
.wops-flex-table.is-cols-share-waste .wops-flex-head .wops-col--num,
|
.wops-flex-table.is-cols-share-waste .wops-flex-head .wops-col--num,
|
||||||
.wops-flex-table.is-cols-pr-cost .wops-col--num,
|
.wops-flex-table.is-cols-pr-cost .wops-col--num,
|
||||||
.wops-flex-table.is-cols-pr-cost .wops-flex-head .wops-col--num,
|
.wops-flex-table.is-cols-pr-cost .wops-flex-head .wops-col--num,
|
||||||
|
.wops-flex-table.is-cols-cost-fence .wops-col--num,
|
||||||
|
.wops-flex-table.is-cols-cost-fence .wops-flex-head .wops-col--num,
|
||||||
.wops-flex-table.is-cols-collect-whp .wops-col--num,
|
.wops-flex-table.is-cols-collect-whp .wops-col--num,
|
||||||
.wops-flex-table.is-cols-collect-whp .wops-flex-head .wops-col--num,
|
.wops-flex-table.is-cols-collect-whp .wops-flex-head .wops-col--num,
|
||||||
.wops-flex-table.is-cols-logs-whp .wops-col--num,
|
.wops-flex-table.is-cols-logs-whp .wops-col--num,
|
||||||
|
|||||||
@@ -414,7 +414,7 @@
|
|||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="materialDialogVisible"
|
v-model="materialDialogVisible"
|
||||||
:title="batchSelMode ? `批量设置选配(${selectedRows.length} 条物料)` : (materialDialogMode === 'add' ? '新增物料' : String(materialEditingId || '修改物料'))"
|
:title="batchSelMode ? `批量设置选配(${selectedRows.length} 条物料)` : (materialDialogMode === 'add' ? '新增物料' : String(materialEditingId || '修改物料'))"
|
||||||
width="72vw"
|
width="88vw"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
append-to-body
|
append-to-body
|
||||||
@@ -504,7 +504,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<template v-for="(cat, catIdx) in materialForm.categories" :key="`${cat.category_code}-${catIdx}`">
|
<template v-for="(cat, catIdx) in materialForm.categories" :key="`${cat.category_code}-${catIdx}`">
|
||||||
<div
|
<div
|
||||||
v-show="activeCatCode === cat.category_code && mlActiveNavKey !== ML_NAV_WAREHOUSE"
|
v-show="activeCatCode === cat.category_code && mlActiveNavKey !== ML_NAV_WAREHOUSE && mlActiveNavKey !== ML_NAV_COST"
|
||||||
class="mf-cat-pane"
|
class="mf-cat-pane"
|
||||||
:class="{ 'mf-cat-pane--fill': mlActiveNavKey === ML_NAV_BASIC }"
|
:class="{ 'mf-cat-pane--fill': mlActiveNavKey === ML_NAV_BASIC }"
|
||||||
>
|
>
|
||||||
@@ -2579,10 +2579,25 @@ const windowW = ref(window.innerWidth)
|
|||||||
const isMobile = computed(() => windowW.value <= 600)
|
const isMobile = computed(() => windowW.value <= 600)
|
||||||
window.addEventListener('resize', () => { windowW.value = window.innerWidth })
|
window.addEventListener('resize', () => { windowW.value = window.innerWidth })
|
||||||
|
|
||||||
// 字段网格列数:72vw 弹窗,减去 padding/边框约 80px,每列最小 240px(1920屏→5列,2560→7列)
|
/** 物料弹窗宽度(须与 template width 一致) */
|
||||||
|
const MF_DIALOG_VW = 0.88
|
||||||
|
/** 弹窗 inset + 左侧导航 + 页签区内边距 + 字段网格内边距 */
|
||||||
|
const MF_GRID_INSET = 244
|
||||||
|
/** 每列最小宽度(1920×88vw 右侧字段区 → 6 列) */
|
||||||
|
const MF_COL_MIN_PX = 215
|
||||||
|
const MF_GRID_GAP = 16
|
||||||
|
|
||||||
|
function mfGridAvailW() {
|
||||||
|
return Math.max(320, windowW.value * MF_DIALOG_VW - MF_GRID_INSET)
|
||||||
|
}
|
||||||
|
|
||||||
|
function mfGridColPx(cols = mfColCount.value) {
|
||||||
|
return Math.max(160, Math.floor((mfGridAvailW() - (cols - 1) * MF_GRID_GAP) / cols))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字段网格列数:88vw 弹窗,按右侧实际可用宽度计(1920→6列,2560→8列)
|
||||||
const mfColCount = computed(() => {
|
const mfColCount = computed(() => {
|
||||||
const bodyW = windowW.value * 0.72 - 80
|
return Math.max(2, Math.min(8, Math.floor(mfGridAvailW() / MF_COL_MIN_PX)))
|
||||||
return Math.max(2, Math.min(8, Math.floor(bodyW / 240)))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 字段占列数(与 MaterialCategory.fieldGridSpan 规则统一)
|
// 字段占列数(与 MaterialCategory.fieldGridSpan 规则统一)
|
||||||
@@ -2591,7 +2606,7 @@ function mfFieldSpan(field) {
|
|||||||
if (field.type === 'table' || field.type === 'textarea') return 'mf-field-full'
|
if (field.type === 'table' || field.type === 'textarea') return 'mf-field-full'
|
||||||
if (field.type === 'attachment') return 'mf-field-span2'
|
if (field.type === 'attachment') return 'mf-field-span2'
|
||||||
const cols = mfColCount.value
|
const cols = mfColCount.value
|
||||||
const colPx = Math.max(160, Math.floor((windowW.value * 0.72 - 80 - (cols - 1) * 16) / cols))
|
const colPx = mfGridColPx(cols)
|
||||||
if (field.type === 'radio') {
|
if (field.type === 'radio') {
|
||||||
const opts = field.options || []
|
const opts = field.options || []
|
||||||
if (opts.length <= 1) return ''
|
if (opts.length <= 1) return ''
|
||||||
@@ -2616,7 +2631,7 @@ function mfFieldSpan(field) {
|
|||||||
/** 适配项占列数:名称较长时占两列 */
|
/** 适配项占列数:名称较长时占两列 */
|
||||||
function adpItemSpan(name) {
|
function adpItemSpan(name) {
|
||||||
const cols = mfColCount.value
|
const cols = mfColCount.value
|
||||||
const colPx = Math.max(160, Math.floor((windowW.value * 0.72 - 80 - (cols - 1) * 16) / cols))
|
const colPx = mfGridColPx(cols)
|
||||||
const len = String(name || '').length
|
const len = String(name || '').length
|
||||||
// 中文约 14px/字,英文约 8px/字,粗估;超过一列宽度则占两列
|
// 中文约 14px/字,英文约 8px/字,粗估;超过一列宽度则占两列
|
||||||
const estPx = len * 12 + 36 // 36 = 圆点 + gap
|
const estPx = len * 12 + 36 // 36 = 圆点 + gap
|
||||||
@@ -2650,7 +2665,7 @@ function _collectCategoryWarehouseFlatFields(categoryCode, warehouseCode) {
|
|||||||
/** 选配查看态:每个控件在 mf-fields-grid 中的占列规则,与 mfFieldSpan 一致 */
|
/** 选配查看态:每个控件在 mf-fields-grid 中的占列规则,与 mfFieldSpan 一致 */
|
||||||
function mfSelCompGridClass(comp) {
|
function mfSelCompGridClass(comp) {
|
||||||
const cols = mfColCount.value
|
const cols = mfColCount.value
|
||||||
const colPx = Math.max(160, Math.floor((windowW.value * 0.72 - 80 - (cols - 1) * 16) / cols))
|
const colPx = mfGridColPx(cols)
|
||||||
const type = comp?.type
|
const type = comp?.type
|
||||||
const opts = comp?.options || []
|
const opts = comp?.options || []
|
||||||
if (type === 'radio') {
|
if (type === 'radio') {
|
||||||
@@ -10891,6 +10906,20 @@ provide('mlTabPaneCtx', {
|
|||||||
padding: 8px 12px 12px;
|
padding: 8px 12px 12px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
.mf-nav-pane--cost-api {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px 12px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.mf-right-scroll:has(.mf-nav-pane--cost-api) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
.mf-nav-pane--basic {
|
.mf-nav-pane--basic {
|
||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
}
|
}
|
||||||
@@ -11059,7 +11088,7 @@ provide('mlTabPaneCtx', {
|
|||||||
|
|
||||||
.mf-fields-grid {
|
.mf-fields-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr); /* 默认 4 列,JS 会覆盖 */
|
grid-template-columns: repeat(6, 1fr); /* 默认 6 列,JS 会覆盖 */
|
||||||
gap: 10px 16px;
|
gap: 10px 16px;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
|
|||||||
Reference in New Issue
Block a user