0010
This commit is contained in:
+576
-51
@@ -1,4 +1,5 @@
|
||||
import request from '@/utils/request'
|
||||
import { pickGoodsNameFromListedRow } from '@/utils/goodsCategorys'
|
||||
|
||||
function unwrapListed(res) {
|
||||
const data = res?.data
|
||||
@@ -22,8 +23,8 @@ function unwrapListed(res) {
|
||||
export function pickCustomerPriceCode(row) {
|
||||
if (!row || typeof row !== 'object') return ''
|
||||
const candidates = [
|
||||
row.price_code,
|
||||
row.customer_price_code,
|
||||
row.price_code,
|
||||
row.price_book_code,
|
||||
row.priceCode,
|
||||
row.code,
|
||||
@@ -45,6 +46,362 @@ function flattenPriceListedRow(raw) {
|
||||
return raw
|
||||
}
|
||||
|
||||
function toPriceNum(v, fallback = 0) {
|
||||
if (v == null || v === '') return fallback
|
||||
const n = Number(v)
|
||||
return Number.isFinite(n) ? n : fallback
|
||||
}
|
||||
|
||||
function parseJsonObject(raw) {
|
||||
if (raw == null || raw === '') return null
|
||||
if (typeof raw === 'object' && !Array.isArray(raw)) return raw
|
||||
if (typeof raw === 'string') {
|
||||
const text = raw.trim()
|
||||
if (!text) return null
|
||||
try {
|
||||
const parsed = JSON.parse(text)
|
||||
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** @returns {{ rate: number, amount: number }} */
|
||||
export function createEmptyExcessPerformance() {
|
||||
return { rate: 0, amount: 0 }
|
||||
}
|
||||
|
||||
/** @returns {{ rate: number, fee: number }} */
|
||||
export function createEmptyBenefit() {
|
||||
return { rate: 0, fee: 0 }
|
||||
}
|
||||
|
||||
/** 超额绩效 JSON:{ rate, amount } 或 { [bill_code]: { rate, amount } } */
|
||||
function isNestedMetricEntry(value) {
|
||||
return value && typeof value === 'object' && !Array.isArray(value)
|
||||
&& ('rate' in value || 'amount' in value || 'fee' in value)
|
||||
}
|
||||
|
||||
/** @returns {Array<{ bill_code: string, rate: number, amount: number }>} */
|
||||
export function parseExcessPerformanceGroups(src) {
|
||||
const row = src && typeof src === 'object' ? src : {}
|
||||
const json = parseJsonObject(row.excess_performance)
|
||||
if (json) {
|
||||
if (isNestedMetricEntry(json) && !Object.keys(json).some((k) => isNestedMetricEntry(json[k]))) {
|
||||
return [{
|
||||
bill_code: '',
|
||||
rate: toPriceNum(json.rate),
|
||||
amount: toPriceNum(json.amount),
|
||||
}]
|
||||
}
|
||||
const groups = Object.entries(json)
|
||||
.filter(([, val]) => isNestedMetricEntry(val))
|
||||
.map(([bill_code, val]) => ({
|
||||
bill_code: String(bill_code || '').trim(),
|
||||
rate: toPriceNum(val.rate),
|
||||
amount: toPriceNum(val.amount),
|
||||
}))
|
||||
if (groups.length) return groups
|
||||
}
|
||||
if (row.excess_performance_rate != null || row.excess_performance_amount != null) {
|
||||
return [{
|
||||
bill_code: '',
|
||||
rate: toPriceNum(row.excess_performance_rate),
|
||||
amount: toPriceNum(row.excess_performance_amount),
|
||||
}]
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/** @returns {Array<{ bill_code: string, rate: number, fee: number }>} */
|
||||
export function parseBenefitGroups(src) {
|
||||
const row = src && typeof src === 'object' ? src : {}
|
||||
const json = parseJsonObject(row.benefit)
|
||||
if (json) {
|
||||
if (isNestedMetricEntry(json) && !Object.keys(json).some((k) => isNestedMetricEntry(json[k]))) {
|
||||
return [{
|
||||
bill_code: '',
|
||||
rate: toPriceNum(json.rate),
|
||||
fee: toPriceNum(json.fee ?? json.amount),
|
||||
}]
|
||||
}
|
||||
const groups = Object.entries(json)
|
||||
.filter(([, val]) => isNestedMetricEntry(val))
|
||||
.map(([bill_code, val]) => ({
|
||||
bill_code: String(bill_code || '').trim(),
|
||||
rate: toPriceNum(val.rate),
|
||||
fee: toPriceNum(val.fee ?? val.amount),
|
||||
}))
|
||||
if (groups.length) return groups
|
||||
}
|
||||
if (row.benefit_rate != null || row.benefit_fee != null) {
|
||||
return [{
|
||||
bill_code: '',
|
||||
rate: toPriceNum(row.benefit_rate),
|
||||
fee: toPriceNum(row.benefit_fee),
|
||||
}]
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/** 列表展示:0.05 → 5% */
|
||||
export function formatCustomerPriceRate(rate) {
|
||||
if (rate == null || rate === '') return '—'
|
||||
const n = Number(rate)
|
||||
if (!Number.isFinite(n)) return '—'
|
||||
const pct = n !== 0 && Math.abs(n) <= 1 ? n * 100 : n
|
||||
const text = Number.isInteger(pct)
|
||||
? String(pct)
|
||||
: String(Number(pct.toFixed(4))).replace(/\.?0+$/, '')
|
||||
return `${text}%`
|
||||
}
|
||||
|
||||
export function formatCustomerPriceMetricAmount(v) {
|
||||
if (v == null || v === '') return '—'
|
||||
const n = Number(v)
|
||||
return Number.isFinite(n) ? String(n) : '—'
|
||||
}
|
||||
|
||||
/** 展示用:金额 + 元 */
|
||||
export function formatCustomerPriceAmount(v) {
|
||||
const text = formatCustomerPriceMetricAmount(v)
|
||||
return text === '—' ? '—' : `${text}元`
|
||||
}
|
||||
|
||||
/** 展示用:月数 + 月 */
|
||||
export function formatCustomerPriceMonths(v) {
|
||||
const text = formatCustomerPriceMetricAmount(v)
|
||||
return text === '—' ? '—' : `${text}月`
|
||||
}
|
||||
|
||||
/** @param {{ rate?: number, amount?: number, fee?: number }} group */
|
||||
export function formatCustomerPriceMetricPair(group, amountKey = 'amount') {
|
||||
if (!group) return '—'
|
||||
const amt = group[amountKey]
|
||||
const rateText = formatCustomerPriceRate(group.rate)
|
||||
const amtText = formatCustomerPriceAmount(amt)
|
||||
if (rateText === '—' && amtText === '—') return '—'
|
||||
return `${rateText} / ${amtText}`
|
||||
}
|
||||
|
||||
function metricGroupSignature(group, amountKey) {
|
||||
return `${group?.rate ?? ''}|${group?.[amountKey] ?? ''}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表单元格:摘要 + 多种悬停
|
||||
* @param {Array<{ bill_code?: string, rate?: number }>} groups
|
||||
* @param {{ amountKey?: string, billNameMap?: Record<string, string> }} [opts]
|
||||
*/
|
||||
export function summarizeCustomerPriceMetricGroups(groups, opts = {}) {
|
||||
const amountKey = opts.amountKey || 'amount'
|
||||
const billNameMap = opts.billNameMap || {}
|
||||
const list = Array.isArray(groups) ? groups : []
|
||||
if (!list.length) {
|
||||
return { display: '—', multi: false, count: 0, tooltip: '' }
|
||||
}
|
||||
|
||||
const fmt = (g) => formatCustomerPriceMetricPair(g, amountKey)
|
||||
const pickBillLabel = (code) => {
|
||||
const c = String(code || '').trim()
|
||||
if (!c) return '默认'
|
||||
return billNameMap[c] || c
|
||||
}
|
||||
|
||||
if (list.length === 1) {
|
||||
return { display: fmt(list[0]), multi: false, count: 1, tooltip: '' }
|
||||
}
|
||||
|
||||
const sig0 = metricGroupSignature(list[0], amountKey)
|
||||
const allSame = list.every((g) => metricGroupSignature(g, amountKey) === sig0)
|
||||
if (allSame) {
|
||||
return { display: fmt(list[0]), multi: false, count: list.length, tooltip: '' }
|
||||
}
|
||||
|
||||
const tooltip = list
|
||||
.map((g) => `${pickBillLabel(g.bill_code)}:${fmt(g)}`)
|
||||
.join('\n')
|
||||
|
||||
return {
|
||||
display: fmt(list[0]),
|
||||
multi: true,
|
||||
count: list.length,
|
||||
tooltip,
|
||||
}
|
||||
}
|
||||
|
||||
/** 超额绩效 JSON:{ rate, amount } */
|
||||
export function normalizeExcessPerformance(raw) {
|
||||
const json = parseJsonObject(raw)
|
||||
if (json) {
|
||||
const groups = parseExcessPerformanceGroups({ excess_performance: json })
|
||||
if (groups.length) return { rate: groups[0].rate, amount: groups[0].amount }
|
||||
}
|
||||
return createEmptyExcessPerformance()
|
||||
}
|
||||
|
||||
/** 让利 JSON:{ rate, fee } */
|
||||
export function normalizeBenefit(raw) {
|
||||
const json = parseJsonObject(raw)
|
||||
if (json) {
|
||||
const groups = parseBenefitGroups({ benefit: json })
|
||||
if (groups.length) return { rate: groups[0].rate, fee: groups[0].fee }
|
||||
}
|
||||
return createEmptyBenefit()
|
||||
}
|
||||
|
||||
function normalizeExcessPerformanceFromRow(row) {
|
||||
const groups = parseExcessPerformanceGroups(row)
|
||||
if (!groups.length) return createEmptyExcessPerformance()
|
||||
return { rate: groups[0].rate, amount: groups[0].amount }
|
||||
}
|
||||
|
||||
function normalizeBenefitFromRow(row) {
|
||||
const groups = parseBenefitGroups(row)
|
||||
if (!groups.length) return createEmptyBenefit()
|
||||
return { rate: groups[0].rate, fee: groups[0].fee }
|
||||
}
|
||||
|
||||
/** @returns {{ rate: number, amount: number }} */
|
||||
export function createEmptyExcessPerformanceBucket() {
|
||||
return { rate: 0, amount: 0 }
|
||||
}
|
||||
|
||||
/** @returns {{ rate: number, fee: number }} */
|
||||
export function createEmptyBenefitBucket() {
|
||||
return { rate: 0, fee: 0 }
|
||||
}
|
||||
|
||||
function groupsToPerformanceBuckets(groups) {
|
||||
/** @type {Record<string, { rate: number, amount: number }>} */
|
||||
const buckets = {}
|
||||
for (const g of groups || []) {
|
||||
const code = String(g.bill_code ?? '').trim()
|
||||
buckets[code] = {
|
||||
rate: toPriceNum(g.rate),
|
||||
amount: toPriceNum(g.amount),
|
||||
}
|
||||
}
|
||||
if (!Object.keys(buckets).length) {
|
||||
buckets[''] = createEmptyExcessPerformanceBucket()
|
||||
}
|
||||
return buckets
|
||||
}
|
||||
|
||||
function groupsToBenefitBuckets(groups) {
|
||||
/** @type {Record<string, { rate: number, fee: number }>} */
|
||||
const buckets = {}
|
||||
for (const g of groups || []) {
|
||||
const code = String(g.bill_code ?? '').trim()
|
||||
buckets[code] = {
|
||||
rate: toPriceNum(g.rate),
|
||||
fee: toPriceNum(g.fee),
|
||||
}
|
||||
}
|
||||
if (!Object.keys(buckets).length) {
|
||||
buckets[''] = createEmptyBenefitBucket()
|
||||
}
|
||||
return buckets
|
||||
}
|
||||
|
||||
function mergeCustomerPriceBucketKeys(excessBuckets, benefitBuckets) {
|
||||
const keys = new Set([
|
||||
...Object.keys(excessBuckets || {}),
|
||||
...Object.keys(benefitBuckets || {}),
|
||||
])
|
||||
for (const key of keys) {
|
||||
if (!excessBuckets[key]) excessBuckets[key] = createEmptyExcessPerformanceBucket()
|
||||
if (!benefitBuckets[key]) benefitBuckets[key] = createEmptyBenefitBucket()
|
||||
}
|
||||
}
|
||||
|
||||
/** 编辑 Tab:仅 JSON 里实际存在的分桶 key(空 key 表示单组,不出 Tab) */
|
||||
export function listCustomerPriceJsonBucketKeys(excessBuckets, benefitBuckets) {
|
||||
const ordered = []
|
||||
const seen = new Set()
|
||||
const add = (raw) => {
|
||||
const key = String(raw ?? '').trim()
|
||||
if (seen.has(key)) return
|
||||
seen.add(key)
|
||||
ordered.push(key)
|
||||
}
|
||||
for (const k of Object.keys(excessBuckets || {})) add(k)
|
||||
for (const k of Object.keys(benefitBuckets || {})) add(k)
|
||||
const named = ordered.filter((k) => k !== '')
|
||||
return named.length ? named : []
|
||||
}
|
||||
|
||||
/** 编辑保存:buckets → 接口 JSON(有命名 key 则分桶,否则 { rate, amount }) */
|
||||
export function serializeExcessPerformanceBuckets(buckets) {
|
||||
const src = buckets && typeof buckets === 'object' ? buckets : {}
|
||||
const namedKeys = Object.keys(src).filter((k) => String(k).trim() !== '')
|
||||
if (!namedKeys.length) {
|
||||
const b = src[''] || createEmptyExcessPerformanceBucket()
|
||||
return { rate: toPriceNum(b.rate), amount: toPriceNum(b.amount) }
|
||||
}
|
||||
/** @type {Record<string, { rate: number, amount: number }>} */
|
||||
const out = {}
|
||||
for (const code of namedKeys) {
|
||||
const b = src[code] || createEmptyExcessPerformanceBucket()
|
||||
out[code] = { rate: toPriceNum(b.rate), amount: toPriceNum(b.amount) }
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/** 编辑保存:buckets → 接口 JSON(有命名 key 则分桶,否则 { rate, fee }) */
|
||||
export function serializeBenefitBuckets(buckets) {
|
||||
const src = buckets && typeof buckets === 'object' ? buckets : {}
|
||||
const namedKeys = Object.keys(src).filter((k) => String(k).trim() !== '')
|
||||
if (!namedKeys.length) {
|
||||
const b = src[''] || createEmptyBenefitBucket()
|
||||
return { rate: toPriceNum(b.rate), fee: toPriceNum(b.fee) }
|
||||
}
|
||||
/** @type {Record<string, { rate: number, fee: number }>} */
|
||||
const out = {}
|
||||
for (const code of namedKeys) {
|
||||
const b = src[code] || createEmptyBenefitBucket()
|
||||
out[code] = { rate: toPriceNum(b.rate), fee: toPriceNum(b.fee) }
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/** 表单 → 新增/修改请求体 */
|
||||
export function buildCustomerPriceWriteBody(form, extras = {}) {
|
||||
const excess_performance = serializeExcessPerformanceBuckets(form?.excess_performance_buckets)
|
||||
const benefit = serializeBenefitBuckets(form?.benefit_buckets)
|
||||
const body = {
|
||||
is_support_acceptance: !!form?.is_support_acceptance,
|
||||
rebate_delay_base_months: toPriceNum(form?.rebate_delay_base_months),
|
||||
excess_performance,
|
||||
excess_profit_rate: toPriceNum(form?.excess_profit_rate),
|
||||
excess_profit_amount: toPriceNum(form?.excess_profit_amount),
|
||||
habitual_bargain_ratio: toPriceNum(form?.habitual_bargain_ratio),
|
||||
benefit,
|
||||
is_special_price: !!form?.is_special_price,
|
||||
is_valid: form?.is_valid == null ? true : !!form?.is_valid,
|
||||
}
|
||||
if (extras.sales_policy_type != null) {
|
||||
body.sales_policy_type = toPriceNum(extras.sales_policy_type)
|
||||
} else if (form?.sales_policy_type != null) {
|
||||
body.sales_policy_type = toPriceNum(form.sales_policy_type)
|
||||
}
|
||||
if (extras.pos != null) body.pos = toPriceNum(extras.pos)
|
||||
else if (form?.pos != null) body.pos = toPriceNum(form.pos)
|
||||
|
||||
const pc = String(
|
||||
extras.customer_price_code ?? extras.price_code
|
||||
?? form?.customer_price_code ?? form?.price_code ?? '',
|
||||
).trim()
|
||||
const cc = String(extras.company_code ?? form?.company_code ?? '').trim()
|
||||
const gc = String(extras.goods_code ?? form?.goods_code ?? '').trim()
|
||||
if (pc) body.customer_price_code = pc
|
||||
if (cc) body.company_code = cc
|
||||
if (gc) body.goods_code = gc
|
||||
return body
|
||||
}
|
||||
|
||||
/** POST /api/customer/price/listed */
|
||||
export async function customerPriceListed(body = {}) {
|
||||
const payload = { ...(body || {}) }
|
||||
@@ -71,23 +428,19 @@ export function customerPriceBatchAdd(body) {
|
||||
export function buildCustomerPriceAddItem(company_code, goods_code, overrides = {}) {
|
||||
const cc = String(company_code ?? '').trim()
|
||||
const gc = String(goods_code ?? '').trim()
|
||||
return {
|
||||
const o = overrides && typeof overrides === 'object' ? overrides : {}
|
||||
return buildCustomerPriceWriteBody(
|
||||
{
|
||||
excess_performance_buckets: groupsToPerformanceBuckets([]),
|
||||
benefit_buckets: groupsToBenefitBuckets([]),
|
||||
...o,
|
||||
},
|
||||
{
|
||||
company_code: cc,
|
||||
goods_code: gc,
|
||||
sales_policy_type: 0,
|
||||
is_support_acceptance: false,
|
||||
rebate_delay_base_months: 0,
|
||||
excess_performance_rate: 0,
|
||||
excess_performance_amount: 0,
|
||||
excess_profit_rate: 0,
|
||||
excess_profit_amount: 0,
|
||||
habitual_bargain_ratio: 0,
|
||||
benefit_rate: 0,
|
||||
benefit_fee: 0,
|
||||
is_special_price: false,
|
||||
is_valid: true,
|
||||
...(overrides && typeof overrides === 'object' ? overrides : {}),
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,49 +499,32 @@ export function customerPriceBatchModify(body) {
|
||||
return request.post('/api/customer/price/batch_modify', body)
|
||||
}
|
||||
|
||||
export function buildCustomerPriceModifyPayload(form, { price_code, company_code, goods_code } = {}) {
|
||||
const pc = String(price_code ?? '').trim()
|
||||
const body = {
|
||||
is_support_acceptance: !!form?.is_support_acceptance,
|
||||
rebate_delay_base_months: Number(form?.rebate_delay_base_months) || 0,
|
||||
excess_performance_rate: Number(form?.excess_performance_rate) || 0,
|
||||
excess_performance_amount: Number(form?.excess_performance_amount) || 0,
|
||||
excess_profit_rate: Number(form?.excess_profit_rate) || 0,
|
||||
excess_profit_amount: Number(form?.excess_profit_amount) || 0,
|
||||
habitual_bargain_ratio: Number(form?.habitual_bargain_ratio) || 0,
|
||||
benefit_rate: Number(form?.benefit_rate) || 0,
|
||||
benefit_fee: Number(form?.benefit_fee) || 0,
|
||||
is_special_price: !!form?.is_special_price,
|
||||
is_valid: !!form?.is_valid,
|
||||
}
|
||||
if (pc) body.price_code = pc
|
||||
const cc = String(company_code ?? form?.company_code ?? '').trim()
|
||||
const gc = String(goods_code ?? form?.goods_code ?? '').trim()
|
||||
const cat = String(form?.category_code ?? '').trim()
|
||||
if (cc) body.company_code = cc
|
||||
if (gc) body.goods_code = gc
|
||||
if (cat) body.category_code = cat
|
||||
return body
|
||||
export function buildCustomerPriceModifyPayload(form, { customer_price_code, company_code, goods_code, price_code } = {}) {
|
||||
return buildCustomerPriceWriteBody(form, {
|
||||
customer_price_code: customer_price_code ?? price_code,
|
||||
company_code,
|
||||
goods_code,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改一条或批量修改价格本
|
||||
* @param {object[]} rows 含 price_code 的行
|
||||
* @param {object[]} rows 含 customer_price_code 的行
|
||||
* @param {object} form 表单字段
|
||||
*/
|
||||
export async function persistCustomerPriceModify(rows, form) {
|
||||
const list = (Array.isArray(rows) ? rows : [])
|
||||
.map((row) => {
|
||||
const price_code = pickCustomerPriceCode(row)
|
||||
if (!price_code) return null
|
||||
const customer_price_code = pickCustomerPriceCode(row)
|
||||
if (!customer_price_code) return null
|
||||
return buildCustomerPriceModifyPayload(form, {
|
||||
price_code,
|
||||
customer_price_code,
|
||||
company_code: row.company_code,
|
||||
goods_code: row.goods_code,
|
||||
})
|
||||
})
|
||||
.filter(Boolean)
|
||||
if (!list.length) throw new Error('缺少 price_code,无法修改')
|
||||
if (!list.length) throw new Error('缺少 customer_price_code,无法修改')
|
||||
if (list.length === 1) {
|
||||
await customerPriceModify(list[0])
|
||||
} else {
|
||||
@@ -199,8 +535,117 @@ export async function persistCustomerPriceModify(rows, form) {
|
||||
|
||||
/** POST /api/customer/price/remove */
|
||||
export function customerPriceRemove(body) {
|
||||
const price_code = pickCustomerPriceCode(body)
|
||||
return request.post('/api/customer/price/remove', { price_code })
|
||||
const customer_price_code = pickCustomerPriceCode(body)
|
||||
return request.post('/api/customer/price/remove', { customer_price_code })
|
||||
}
|
||||
|
||||
/** POST /api/customer/price/modify_custom */
|
||||
export function customerPriceModifyCustom(body) {
|
||||
return request.post('/api/customer/price/modify_custom', body)
|
||||
}
|
||||
|
||||
/** POST /api/customer/price/remove_custom */
|
||||
export function customerPriceRemoveCustom(body) {
|
||||
const customer_price_code = pickCustomerPriceCode(body)
|
||||
return request.post('/api/customer/price/remove_custom', { customer_price_code })
|
||||
}
|
||||
|
||||
function normalizeCustomerPriceCustom(raw) {
|
||||
if (raw == null || typeof raw !== 'object') return null
|
||||
const src = raw
|
||||
const excess_performance_groups = parseExcessPerformanceGroups(src)
|
||||
const benefit_groups = parseBenefitGroups(src)
|
||||
return {
|
||||
...src,
|
||||
sales_policy_type: src.sales_policy_type == null ? 0 : toPriceNum(src.sales_policy_type),
|
||||
is_support_acceptance: src.is_support_acceptance === true
|
||||
|| src.is_support_acceptance === 1
|
||||
|| src.is_support_acceptance === '1',
|
||||
rebate_delay_base_months: toPriceNum(src.rebate_delay_base_months),
|
||||
excess_performance_groups,
|
||||
benefit_groups,
|
||||
excess_performance: normalizeExcessPerformanceFromRow(src),
|
||||
benefit: normalizeBenefitFromRow(src),
|
||||
excess_profit_rate: toPriceNum(src.excess_profit_rate),
|
||||
excess_profit_amount: toPriceNum(src.excess_profit_amount),
|
||||
habitual_bargain_ratio: toPriceNum(src.habitual_bargain_ratio),
|
||||
}
|
||||
}
|
||||
|
||||
/** 特殊定价表单默认值(无 custom 时全部为 0) */
|
||||
export function createEmptyCustomerPriceCustomForm() {
|
||||
return {
|
||||
sales_policy_type: 0,
|
||||
is_support_acceptance: false,
|
||||
rebate_delay_base_months: 0,
|
||||
excess_performance_buckets: groupsToPerformanceBuckets([]),
|
||||
excess_profit_rate: 0,
|
||||
excess_profit_amount: 0,
|
||||
habitual_bargain_ratio: 0,
|
||||
benefit_buckets: groupsToBenefitBuckets([]),
|
||||
}
|
||||
}
|
||||
|
||||
/** 列表行 custom → 特殊定价表单;custom 为空则全 0 */
|
||||
export function customerPriceCustomFormFromRow(row) {
|
||||
const src = row && typeof row === 'object' ? row : {}
|
||||
const custom = src.custom != null && typeof src.custom === 'object' ? src.custom : null
|
||||
if (!custom) return createEmptyCustomerPriceCustomForm()
|
||||
|
||||
const excess_performance_buckets = groupsToPerformanceBuckets(
|
||||
Array.isArray(custom.excess_performance_groups) && custom.excess_performance_groups.length
|
||||
? custom.excess_performance_groups
|
||||
: parseExcessPerformanceGroups(custom),
|
||||
)
|
||||
const benefit_buckets = groupsToBenefitBuckets(
|
||||
Array.isArray(custom.benefit_groups) && custom.benefit_groups.length
|
||||
? custom.benefit_groups
|
||||
: parseBenefitGroups(custom),
|
||||
)
|
||||
mergeCustomerPriceBucketKeys(excess_performance_buckets, benefit_buckets)
|
||||
return {
|
||||
...createEmptyCustomerPriceCustomForm(),
|
||||
sales_policy_type: custom.sales_policy_type == null ? 0 : toPriceNum(custom.sales_policy_type),
|
||||
is_support_acceptance: custom.is_support_acceptance === true
|
||||
|| custom.is_support_acceptance === 1
|
||||
|| custom.is_support_acceptance === '1',
|
||||
rebate_delay_base_months: toPriceNum(custom.rebate_delay_base_months),
|
||||
excess_performance_buckets,
|
||||
excess_profit_rate: toPriceNum(custom.excess_profit_rate),
|
||||
excess_profit_amount: toPriceNum(custom.excess_profit_amount),
|
||||
habitual_bargain_ratio: toPriceNum(custom.habitual_bargain_ratio),
|
||||
benefit_buckets,
|
||||
}
|
||||
}
|
||||
|
||||
/** 特殊定价 → modify_custom 请求体 */
|
||||
export function buildCustomerPriceCustomWriteBody(form, { customer_price_code } = {}) {
|
||||
const pc = String(customer_price_code ?? '').trim()
|
||||
return {
|
||||
customer_price_code: pc,
|
||||
sales_policy_type: toPriceNum(form?.sales_policy_type),
|
||||
is_support_acceptance: !!form?.is_support_acceptance,
|
||||
rebate_delay_base_months: toPriceNum(form?.rebate_delay_base_months),
|
||||
excess_performance: serializeExcessPerformanceBuckets(form?.excess_performance_buckets),
|
||||
excess_profit_rate: toPriceNum(form?.excess_profit_rate),
|
||||
excess_profit_amount: toPriceNum(form?.excess_profit_amount),
|
||||
habitual_bargain_ratio: toPriceNum(form?.habitual_bargain_ratio),
|
||||
benefit: serializeBenefitBuckets(form?.benefit_buckets),
|
||||
}
|
||||
}
|
||||
|
||||
/** 保存特殊定价(modify_custom) */
|
||||
export async function persistCustomerPriceModifyCustom(row, form) {
|
||||
const customer_price_code = pickCustomerPriceCode(row)
|
||||
if (!customer_price_code) throw new Error('缺少 customer_price_code,无法保存自定义')
|
||||
await customerPriceModifyCustom(buildCustomerPriceCustomWriteBody(form, { customer_price_code }))
|
||||
}
|
||||
|
||||
/** 删除自定义(remove_custom) */
|
||||
export async function persistCustomerPriceRemoveCustom(row) {
|
||||
const customer_price_code = pickCustomerPriceCode(row)
|
||||
if (!customer_price_code) throw new Error('缺少 customer_price_code,无法删除自定义')
|
||||
await customerPriceRemoveCustom({ customer_price_code })
|
||||
}
|
||||
|
||||
function normalizeGoodsCategorys(raw) {
|
||||
@@ -239,25 +684,105 @@ export function formatCustomerPriceGoodsDisplay(row) {
|
||||
export function normalizeCustomerPriceRow(raw) {
|
||||
const src = flattenPriceListedRow(raw)
|
||||
if (!src || typeof src !== 'object') return null
|
||||
const price_code = pickCustomerPriceCode(src)
|
||||
const customer_price_code = pickCustomerPriceCode(src)
|
||||
const company_code = String(src.company_code ?? '').trim()
|
||||
const goods_code = String(src.goods_code ?? '').trim()
|
||||
if (!price_code && !company_code && !goods_code) return null
|
||||
if (!customer_price_code && !company_code && !goods_code) return null
|
||||
const goods_categorys_list = normalizeGoodsCategorys(src)
|
||||
const goods_categorys_text = formatCustomerPriceGoodsDisplay({ goods_categorys_list })
|
||||
const excess_performance_groups = parseExcessPerformanceGroups(src)
|
||||
const benefit_groups = parseBenefitGroups(src)
|
||||
const excess_performance = normalizeExcessPerformanceFromRow(src)
|
||||
const benefit = normalizeBenefitFromRow(src)
|
||||
const has_custom = src.custom != null && typeof src.custom === 'object'
|
||||
const custom = has_custom ? normalizeCustomerPriceCustom(src.custom) : null
|
||||
return {
|
||||
...src,
|
||||
price_code,
|
||||
customer_price_code,
|
||||
price_code: customer_price_code,
|
||||
company_code,
|
||||
goods_code,
|
||||
category_code: String(src.category_code ?? '').trim(),
|
||||
sales_policy_type: src.sales_policy_type == null ? 0 : toPriceNum(src.sales_policy_type),
|
||||
pos: src.pos == null || src.pos === '' ? null : toPriceNum(src.pos),
|
||||
excess_performance_groups,
|
||||
benefit_groups,
|
||||
excess_performance,
|
||||
benefit,
|
||||
has_custom,
|
||||
custom,
|
||||
custom_excess_performance_groups: custom?.excess_performance_groups ?? [],
|
||||
custom_benefit_groups: custom?.benefit_groups ?? [],
|
||||
excess_profit_rate: toPriceNum(src.excess_profit_rate),
|
||||
excess_profit_amount: toPriceNum(src.excess_profit_amount),
|
||||
habitual_bargain_ratio: toPriceNum(src.habitual_bargain_ratio),
|
||||
goods_categorys_list,
|
||||
goods_categorys_text,
|
||||
goods_name: String(
|
||||
src.goods_name ?? src.goods?.name ?? src.name ?? '',
|
||||
).trim(),
|
||||
goods_name: pickGoodsNameFromListedRow(src)
|
||||
|| String(src.goods_name ?? src.goods?.name ?? src.name ?? '').trim(),
|
||||
company_name: String(
|
||||
src.company_name ?? src.customer_name ?? src.full_name ?? '',
|
||||
).trim(),
|
||||
}
|
||||
}
|
||||
|
||||
function pickExcessPerformanceGroupsFromRow(row) {
|
||||
const src = row && typeof row === 'object' ? row : {}
|
||||
const groups = src.excess_performance_groups
|
||||
if (Array.isArray(groups) && groups.length) return groups
|
||||
return parseExcessPerformanceGroups(src)
|
||||
}
|
||||
|
||||
function pickBenefitGroupsFromRow(row) {
|
||||
const src = row && typeof row === 'object' ? row : {}
|
||||
const groups = src.benefit_groups
|
||||
if (Array.isArray(groups) && groups.length) return groups
|
||||
return parseBenefitGroups(src)
|
||||
}
|
||||
|
||||
/** 编辑表单默认值 */
|
||||
export function createEmptyCustomerPriceForm() {
|
||||
return {
|
||||
company_code: '',
|
||||
goods_code: '',
|
||||
category_code: '',
|
||||
goods_categorys_text: '',
|
||||
sales_policy_type: 0,
|
||||
is_support_acceptance: false,
|
||||
rebate_delay_base_months: 0,
|
||||
excess_performance_buckets: groupsToPerformanceBuckets([]),
|
||||
excess_profit_rate: 0,
|
||||
excess_profit_amount: 0,
|
||||
habitual_bargain_ratio: 0,
|
||||
benefit_buckets: groupsToBenefitBuckets([]),
|
||||
is_special_price: false,
|
||||
is_valid: true,
|
||||
pos: null,
|
||||
}
|
||||
}
|
||||
|
||||
/** 列表行 → 编辑表单(JSON 有几组就加载几组) */
|
||||
export function customerPriceFormFromRow(row) {
|
||||
const src = row && typeof row === 'object' ? row : {}
|
||||
const excess_performance_buckets = groupsToPerformanceBuckets(pickExcessPerformanceGroupsFromRow(src))
|
||||
const benefit_buckets = groupsToBenefitBuckets(pickBenefitGroupsFromRow(src))
|
||||
mergeCustomerPriceBucketKeys(excess_performance_buckets, benefit_buckets)
|
||||
return {
|
||||
...createEmptyCustomerPriceForm(),
|
||||
company_code: src.company_code || '',
|
||||
goods_code: src.goods_code || '',
|
||||
category_code: src.category_code || '',
|
||||
goods_categorys_text: src.goods_categorys_text || formatCustomerPriceGoodsDisplay(src) || '',
|
||||
sales_policy_type: src.sales_policy_type == null ? 0 : toPriceNum(src.sales_policy_type),
|
||||
is_support_acceptance: src.is_support_acceptance === true || src.is_support_acceptance === 1 || src.is_support_acceptance === '1',
|
||||
rebate_delay_base_months: toPriceNum(src.rebate_delay_base_months),
|
||||
excess_performance_buckets,
|
||||
excess_profit_rate: toPriceNum(src.excess_profit_rate),
|
||||
excess_profit_amount: toPriceNum(src.excess_profit_amount),
|
||||
habitual_bargain_ratio: toPriceNum(src.habitual_bargain_ratio),
|
||||
benefit_buckets,
|
||||
is_special_price: src.is_special_price === true || src.is_special_price === 1 || src.is_special_price === '1',
|
||||
is_valid: src.is_valid == null ? true : (src.is_valid === true || src.is_valid === 1 || src.is_valid === '1'),
|
||||
pos: src.pos == null || src.pos === '' ? null : toPriceNum(src.pos),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"buildId": "20260706004",
|
||||
"generatedAt": "2026-07-06T11:25:37.842Z"
|
||||
"buildId": "20260707001",
|
||||
"generatedAt": "2026-07-06T16:11:57.042Z"
|
||||
}
|
||||
@@ -50,6 +50,16 @@ export const APP_CHANGELOG = [
|
||||
{
|
||||
date: '2026-07-06',
|
||||
items: [
|
||||
{
|
||||
icon: 'mdi:warehouse',
|
||||
kind: 'change',
|
||||
text: '「仓库管理」RDC 物料右侧面板 Tab「可调配仓」更名为「源采购 RDC」。',
|
||||
},
|
||||
{
|
||||
icon: 'mdi:notebook-outline',
|
||||
kind: 'add',
|
||||
text: '「客户管理 → 客户价格本 → 特殊定价」:批量值只读展示主记录,「自定义」单独对接 `modify_custom` / `remove_custom`(确定保存、已有 custom 时可删);交易利润区保留费用票据切换;超额绩效/好处费 JSON 按接口实际 key 动态 Tab,无 custom 默认全 0。',
|
||||
},
|
||||
{
|
||||
icon: 'mdi:fence',
|
||||
kind: 'change',
|
||||
|
||||
@@ -206,6 +206,59 @@ function isRdcEntryEmpty(rdcEntry) {
|
||||
return bills.every((b) => isRdcBucketEmpty(b))
|
||||
}
|
||||
|
||||
/** @param {unknown} rdcEntry warehouse[rdcCode] */
|
||||
export function pickWarehouseMetricsForChannel(rdcEntry, sectionKey, channelKey) {
|
||||
if (!rdcEntry || typeof rdcEntry !== 'object') {
|
||||
return normalizeChannelMetrics(null)
|
||||
}
|
||||
if (isLegacyRdcMetricsBucket(rdcEntry)) {
|
||||
return normalizeChannelMetrics(rdcEntry[sectionKey]?.[channelKey])
|
||||
}
|
||||
let fallback = null
|
||||
for (const billBucket of Object.values(rdcEntry)) {
|
||||
if (!isLegacyRdcMetricsBucket(billBucket)) continue
|
||||
const m = normalizeChannelMetrics(billBucket[sectionKey]?.[channelKey])
|
||||
if (m.rate != null || m.amount != null) return m
|
||||
if (!fallback) fallback = m
|
||||
}
|
||||
return fallback ?? normalizeChannelMetrics(null)
|
||||
}
|
||||
|
||||
/** @param {unknown} rdcEntry warehouse[rdcCode] */
|
||||
export function listWarehouseRdcBillKeys(rdcEntry) {
|
||||
if (!rdcEntry || typeof rdcEntry !== 'object' || Array.isArray(rdcEntry)) return []
|
||||
if (isLegacyRdcMetricsBucket(rdcEntry)) return []
|
||||
return Object.keys(rdcEntry)
|
||||
.map((k) => String(k || '').trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
/** @param {unknown} rdcEntry warehouse[rdcCode] @param {unknown} billCode */
|
||||
export function pickWarehouseMetricsForBillChannel(rdcEntry, billCode, sectionKey, channelKey) {
|
||||
if (!rdcEntry || typeof rdcEntry !== 'object') {
|
||||
return normalizeChannelMetrics(null)
|
||||
}
|
||||
if (isLegacyRdcMetricsBucket(rdcEntry)) {
|
||||
return normalizeChannelMetrics(rdcEntry[sectionKey]?.[channelKey])
|
||||
}
|
||||
const bill = String(billCode ?? '').trim()
|
||||
if (!bill) {
|
||||
return pickWarehouseMetricsForChannel(rdcEntry, sectionKey, channelKey)
|
||||
}
|
||||
const billBucket = rdcEntry[bill]
|
||||
if (isLegacyRdcMetricsBucket(billBucket)) {
|
||||
return normalizeChannelMetrics(billBucket[sectionKey]?.[channelKey])
|
||||
}
|
||||
return normalizeChannelMetrics(null)
|
||||
}
|
||||
|
||||
/** 客户 sale_type → 仓库渠道 key(忽略 internal) */
|
||||
export function warehouseChannelKeyFromSaleType(saleType) {
|
||||
const n = Number(saleType)
|
||||
if (n === 2) return 'retail'
|
||||
return 'channel'
|
||||
}
|
||||
|
||||
/** 保存前剔除全空的 RDC / 票据条目 */
|
||||
export function pruneEmptyWarehouseRdcEntries(bucket) {
|
||||
const normalized = normalizeWarehouseGoodsBucket(bucket)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<header class="wh-srf-rdc-right-hd">
|
||||
<el-radio-group v-model="activeTab" size="small" class="wh-srf-rdc-right-tabs">
|
||||
<el-radio-button value="region">可售卖区域</el-radio-button>
|
||||
<el-radio-button value="deploy">可调配仓</el-radio-button>
|
||||
<el-radio-button value="deploy">源采购 RDC</el-radio-button>
|
||||
</el-radio-group>
|
||||
</header>
|
||||
<div class="wh-srf-rdc-right-body">
|
||||
|
||||
@@ -84,10 +84,10 @@
|
||||
<div class="dpb-col dpb-col--goods dpb-head-col dpb-head-col--goods">物料</div>
|
||||
<div class="dpb-col dpb-col--cats dpb-head-col dpb-head-col--cats">分类</div>
|
||||
<div class="dpb-col dpb-col--flag dpb-head-col dpb-head-col--accept">承兑</div>
|
||||
<div class="dpb-col dpb-col--num dpb-head-col dpb-head-col--rebate">返利月</div>
|
||||
<div class="dpb-col dpb-col--num dpb-col--rebate dpb-head-col dpb-head-col--rebate">返利月</div>
|
||||
<div class="dpb-col dpb-col--pair dpb-head-col dpb-head-col--perf">超额绩效<span class="dpb-head-sub">(率/额)</span></div>
|
||||
<div class="dpb-col dpb-col--pair dpb-head-col dpb-head-col--profit">超额利润<span class="dpb-head-sub">(率/额)</span></div>
|
||||
<div class="dpb-col dpb-col--num dpb-head-col dpb-head-col--bargain">议价率</div>
|
||||
<div class="dpb-col dpb-col--num dpb-col--bargain dpb-head-col dpb-head-col--bargain">客户砍价率</div>
|
||||
<div class="dpb-col dpb-col--pair dpb-head-col dpb-head-col--benefit">好处费<span class="dpb-head-sub">(率/额)</span></div>
|
||||
<div class="dpb-col dpb-col--flag dpb-head-col dpb-head-col--special">特价</div>
|
||||
<div class="dpb-col dpb-col--flag dpb-head-col dpb-head-col--valid">状态</div>
|
||||
@@ -115,7 +115,10 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="dpb-col dpb-col--goods">
|
||||
<span v-if="row.goods_code" class="dpb-code dpb-code--inline">{{ row.goods_code }}</span>
|
||||
<template v-if="row.goods_code">
|
||||
<span class="dpb-goods-name">{{ pickGoodsDisplayName(row) }}</span>
|
||||
<span v-if="pickGoodsDisplayCode(row)" class="dpb-code dpb-code--inline">{{ pickGoodsDisplayCode(row) }}</span>
|
||||
</template>
|
||||
<span v-else class="dpb-muted">—</span>
|
||||
</div>
|
||||
<div class="dpb-col dpb-col--cats dpb-cats-cell">
|
||||
@@ -133,14 +136,47 @@
|
||||
<div class="dpb-col dpb-col--flag">
|
||||
<span class="dpb-flag-text">{{ fmtBool(row.is_support_acceptance) }}</span>
|
||||
</div>
|
||||
<div class="dpb-col dpb-col--num">{{ fmtNum(row.rebate_delay_base_months) }}</div>
|
||||
<div class="dpb-col dpb-col--pair">{{ fmtNum(row.excess_performance_rate) }}<span class="dpb-pair-sep"> / </span>{{ fmtNum(row.excess_performance_amount) }}</div>
|
||||
<div class="dpb-col dpb-col--pair">{{ fmtNum(row.excess_profit_rate) }}<span class="dpb-pair-sep"> / </span>{{ fmtNum(row.excess_profit_amount) }}</div>
|
||||
<div class="dpb-col dpb-col--num">{{ fmtNum(row.habitual_bargain_ratio) }}</div>
|
||||
<div class="dpb-col dpb-col--pair">{{ fmtNum(row.benefit_rate) }}<span class="dpb-pair-sep"> / </span>{{ fmtNum(row.benefit_fee) }}</div>
|
||||
<div class="dpb-col dpb-col--num dpb-col--rebate">{{ formatCustomerPriceMonths(row.rebate_delay_base_months) }}</div>
|
||||
<div class="dpb-col dpb-col--pair dpb-metric-cell">
|
||||
<el-tooltip
|
||||
v-if="metricSummary(row, 'excess_performance').tooltip"
|
||||
:content="metricSummary(row, 'excess_performance').tooltip"
|
||||
placement="top"
|
||||
:show-after="280"
|
||||
popper-class="dpb-metric-tooltip"
|
||||
:popper-options="{ strategy: 'fixed' }"
|
||||
>
|
||||
<span class="dpb-metric-line">
|
||||
<span class="dpb-metric-text">{{ metricSummary(row, 'excess_performance').display }}</span>
|
||||
<span class="dpb-metric-multi">{{ metricSummary(row, 'excess_performance').count }}种</span>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else class="dpb-metric-line">
|
||||
<span class="dpb-metric-text">{{ metricSummary(row, 'excess_performance').display }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="dpb-col dpb-col--pair">{{ formatCustomerPriceRate(row.excess_profit_rate) }}<span class="dpb-pair-sep"> / </span>{{ formatCustomerPriceAmount(row.excess_profit_amount) }}</div>
|
||||
<div class="dpb-col dpb-col--num dpb-col--bargain">{{ formatCustomerPriceRate(row.habitual_bargain_ratio) }}</div>
|
||||
<div class="dpb-col dpb-col--pair dpb-metric-cell">
|
||||
<el-tooltip
|
||||
v-if="metricSummary(row, 'benefit').tooltip"
|
||||
:content="metricSummary(row, 'benefit').tooltip"
|
||||
placement="top"
|
||||
:show-after="280"
|
||||
popper-class="dpb-metric-tooltip"
|
||||
:popper-options="{ strategy: 'fixed' }"
|
||||
>
|
||||
<span class="dpb-metric-line">
|
||||
<span class="dpb-metric-text">{{ metricSummary(row, 'benefit').display }}</span>
|
||||
<span class="dpb-metric-multi">{{ metricSummary(row, 'benefit').count }}种</span>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else class="dpb-metric-line">
|
||||
<span class="dpb-metric-text">{{ metricSummary(row, 'benefit').display }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="dpb-col dpb-col--flag">
|
||||
<el-tag v-if="isTruthy(row.is_special_price)" size="small" type="warning" effect="plain">特价</el-tag>
|
||||
<span v-else class="dpb-muted">—</span>
|
||||
<span class="dpb-flag-text">{{ fmtBool(row.is_special_price) }}</span>
|
||||
</div>
|
||||
<div class="dpb-col dpb-col--flag">
|
||||
<el-tag
|
||||
@@ -152,6 +188,7 @@
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="dpb-col dpb-col--op">
|
||||
<span class="dpb-op-link dpb-op-link--special" @click="openSpecialPrice(row)">特殊定价</span>
|
||||
<span class="dpb-op-link" @click="openEdit(row)">编辑</span>
|
||||
<span class="dpb-op-link dpb-op-link--muted" @click="removeRow(row)">删除</span>
|
||||
</div>
|
||||
@@ -188,7 +225,7 @@
|
||||
<p v-if="formMode === 'batch'" class="dpb-form-batch-tip">
|
||||
将以下定价参数应用到已选 <strong>{{ batchEditRows.length }}</strong> 条价格本(客户:{{ activeCompanyLabel }})。
|
||||
</p>
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="88px" size="small" class="dpb-form">
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="120px" size="small" class="dpb-form">
|
||||
<template v-if="formMode !== 'batch'">
|
||||
<el-row :gutter="16" class="dpb-form-meta-row">
|
||||
<el-col :span="16">
|
||||
@@ -234,31 +271,101 @@
|
||||
|
||||
<div class="dpb-form-block">
|
||||
<div class="dpb-form-block-hd">定价参数</div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="返利延迟月数">
|
||||
<div class="dpb-form-param-shell">
|
||||
<div class="dpb-form-param-top">
|
||||
<div class="dpb-form-param-pair">
|
||||
<label class="dpb-form-param-label">返利延迟月数</label>
|
||||
<div class="dpb-form-rate-field">
|
||||
<el-input-number
|
||||
v-model="form.rebate_delay_base_months"
|
||||
:min="0"
|
||||
:controls="false"
|
||||
class="dpb-form-num"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="习惯性议价比率">
|
||||
<span class="dpb-form-unit">月</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dpb-form-param-pair">
|
||||
<label class="dpb-form-param-label">客户砍价率</label>
|
||||
<div class="dpb-form-rate-field">
|
||||
<el-input-number
|
||||
v-model="form.habitual_bargain_ratio"
|
||||
:controls="false"
|
||||
class="dpb-form-num"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<span class="dpb-form-unit">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dpb-form-rate-grid">
|
||||
<div v-for="block in formRateBlocks" :key="block.key" class="dpb-form-rate-row">
|
||||
<span class="dpb-form-rate-name">{{ block.label }}</span>
|
||||
<div v-if="editJsonBucketKeys.length" class="dpb-form-bill-tabs">
|
||||
<button
|
||||
v-for="code in editJsonBucketKeys"
|
||||
:key="code"
|
||||
type="button"
|
||||
class="dpb-form-bill-tab"
|
||||
:class="{ 'is-active': activeEditBillCode === code }"
|
||||
@click="activeEditBillCode = code"
|
||||
>
|
||||
<span class="dpb-form-bill-tab-name">{{ editBillTabLabel(code) }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<template v-if="form.excess_performance_buckets[activeEditBucketKey]">
|
||||
<div class="dpb-form-param-rate">
|
||||
<label class="dpb-form-param-label">超额绩效</label>
|
||||
<div class="dpb-form-rate-fields">
|
||||
<div class="dpb-form-rate-field">
|
||||
<span class="dpb-form-rate-field-label">率</span>
|
||||
<el-input-number
|
||||
v-model="form.excess_performance_buckets[activeEditBucketKey].rate"
|
||||
:controls="false"
|
||||
class="dpb-form-num"
|
||||
/>
|
||||
<span class="dpb-form-unit">%</span>
|
||||
</div>
|
||||
<div class="dpb-form-rate-field">
|
||||
<span class="dpb-form-rate-field-label">额</span>
|
||||
<el-input-number
|
||||
v-model="form.excess_performance_buckets[activeEditBucketKey].amount"
|
||||
:controls="false"
|
||||
class="dpb-form-num"
|
||||
/>
|
||||
<span class="dpb-form-unit">元</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dpb-form-param-rate">
|
||||
<label class="dpb-form-param-label">好处费</label>
|
||||
<div class="dpb-form-rate-fields">
|
||||
<div class="dpb-form-rate-field">
|
||||
<span class="dpb-form-rate-field-label">率</span>
|
||||
<el-input-number
|
||||
v-model="form.benefit_buckets[activeEditBucketKey].rate"
|
||||
:controls="false"
|
||||
class="dpb-form-num"
|
||||
/>
|
||||
<span class="dpb-form-unit">%</span>
|
||||
</div>
|
||||
<div class="dpb-form-rate-field">
|
||||
<span class="dpb-form-rate-field-label">额</span>
|
||||
<el-input-number
|
||||
v-model="form.benefit_buckets[activeEditBucketKey].fee"
|
||||
:controls="false"
|
||||
class="dpb-form-num"
|
||||
/>
|
||||
<span class="dpb-form-unit">元</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div
|
||||
v-for="block in formRateBlocks"
|
||||
:key="block.key"
|
||||
class="dpb-form-param-rate"
|
||||
>
|
||||
<label class="dpb-form-param-label">{{ block.label }}</label>
|
||||
<div class="dpb-form-rate-fields">
|
||||
<div class="dpb-form-rate-field">
|
||||
<span class="dpb-form-rate-field-label">率</span>
|
||||
@@ -267,6 +374,7 @@
|
||||
:controls="false"
|
||||
class="dpb-form-num"
|
||||
/>
|
||||
<span class="dpb-form-unit">%</span>
|
||||
</div>
|
||||
<div class="dpb-form-rate-field">
|
||||
<span class="dpb-form-rate-field-label">额</span>
|
||||
@@ -275,6 +383,7 @@
|
||||
:controls="false"
|
||||
class="dpb-form-num"
|
||||
/>
|
||||
<span class="dpb-form-unit">元</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -287,6 +396,14 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<DealerPriceSpecialPriceDlg
|
||||
v-model="specialPriceDlgVisible"
|
||||
:company-label="activeCompanyLabel"
|
||||
:customer="activeCustomer"
|
||||
:price-row="specialPriceRow"
|
||||
@saved="onSpecialPriceSaved"
|
||||
/>
|
||||
|
||||
<DealerPriceSceneGoodsDlg
|
||||
v-model="sceneGoodsDlgVisible"
|
||||
:customer="activeCustomer"
|
||||
@@ -310,23 +427,48 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import request from '@/utils/request'
|
||||
import { customerListed, pickCustomerDisplayName, pickOrgBlocCode, extractIgnoreSceneCodes } from '@/api/customer'
|
||||
import { normalizeOrgSaleType } from '@/api/company'
|
||||
import DealerPriceSceneGoodsDlg from './DealerPriceSceneGoodsDlg.vue'
|
||||
import DealerPriceCategoryGoodsDlg from './DealerPriceCategoryGoodsDlg.vue'
|
||||
import DealerPriceSpecialPriceDlg from './DealerPriceSpecialPriceDlg.vue'
|
||||
import {
|
||||
customerPriceAdd,
|
||||
customerPriceListed,
|
||||
customerPriceRemove,
|
||||
buildCustomerPriceWriteBody,
|
||||
createEmptyCustomerPriceForm,
|
||||
customerPriceFormFromRow,
|
||||
fetchCustomerPriceGoodsCodes,
|
||||
formatCustomerPriceAmount,
|
||||
formatCustomerPriceGoodsDisplay,
|
||||
formatCustomerPriceMonths,
|
||||
formatCustomerPriceRate,
|
||||
listCustomerPriceJsonBucketKeys,
|
||||
normalizeCustomerPriceRow,
|
||||
persistCustomerPriceGoods,
|
||||
persistCustomerPriceModify,
|
||||
pickCustomerPriceCode,
|
||||
summarizeCustomerPriceMetricGroups,
|
||||
} from '@/api/customerPrice'
|
||||
import {
|
||||
FINANCE_BILL_TYPE_EXPENSE,
|
||||
expenseBillTabParts,
|
||||
normalizeFinanceBillType,
|
||||
} from '@/utils/financeBillTypes'
|
||||
import { pickDisplayCode, pickDisplayName } from '@/utils/warehouseOpsDisplay'
|
||||
|
||||
defineOptions({ name: 'DealerPriceBook' })
|
||||
|
||||
function pickGoodsDisplayName(row) {
|
||||
return pickDisplayName(row?.goods_name, row?.goods_code)
|
||||
}
|
||||
|
||||
function pickGoodsDisplayCode(row) {
|
||||
return pickDisplayCode(row?.goods_name, row?.goods_code)
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const loadError = ref('')
|
||||
@@ -350,12 +492,18 @@ const activeCompanyLabel = computed(() => {
|
||||
})
|
||||
|
||||
const existingPriceGoodsCodes = ref([])
|
||||
/** @type {import('vue').Ref<Record<string, string>>} */
|
||||
const expenseBillNameMap = ref({})
|
||||
const activeEditBillCode = ref('')
|
||||
|
||||
const sceneGoodsDlgVisible = ref(false)
|
||||
const categoryGoodsDlgVisible = ref(false)
|
||||
const addingPriceBook = ref(false)
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const specialPriceDlgVisible = ref(false)
|
||||
/** @type {import('vue').Ref<object|null>} */
|
||||
const specialPriceRow = ref(null)
|
||||
const formMode = ref('add') // add | edit | batch
|
||||
const editPriceCode = ref('')
|
||||
const batchEditRows = ref([])
|
||||
@@ -376,9 +524,7 @@ const formCategoryLabel = computed(() => {
|
||||
})
|
||||
|
||||
const formRateBlocks = [
|
||||
{ key: 'excess_performance', label: '超额绩效', rateKey: 'excess_performance_rate', amountKey: 'excess_performance_amount' },
|
||||
{ key: 'excess_profit', label: '超额利润', rateKey: 'excess_profit_rate', amountKey: 'excess_profit_amount' },
|
||||
{ key: 'benefit', label: '好处费', rateKey: 'benefit_rate', amountKey: 'benefit_fee' },
|
||||
]
|
||||
|
||||
const selectableRows = computed(() =>
|
||||
@@ -426,30 +572,47 @@ function clearSelection() {
|
||||
selected.value = new Set()
|
||||
}
|
||||
|
||||
const form = reactive(emptyForm())
|
||||
const form = reactive(createEmptyCustomerPriceForm())
|
||||
|
||||
const editJsonBucketKeys = computed(() =>
|
||||
listCustomerPriceJsonBucketKeys(form.excess_performance_buckets, form.benefit_buckets),
|
||||
)
|
||||
|
||||
const activeEditBucketKey = computed(() => {
|
||||
const keys = editJsonBucketKeys.value
|
||||
if (keys.length) {
|
||||
const cur = String(activeEditBillCode.value || '').trim()
|
||||
if (cur && keys.includes(cur)) return cur
|
||||
return keys[0]
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
const formRules = {
|
||||
goods_code: [{ required: true, message: '请填写物料编码', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
function emptyForm() {
|
||||
return {
|
||||
company_code: '',
|
||||
goods_code: '',
|
||||
category_code: '',
|
||||
goods_categorys_text: '',
|
||||
is_support_acceptance: false,
|
||||
rebate_delay_base_months: 0,
|
||||
excess_performance_rate: 0,
|
||||
excess_performance_amount: 0,
|
||||
excess_profit_rate: 0,
|
||||
excess_profit_amount: 0,
|
||||
habitual_bargain_ratio: 0,
|
||||
benefit_rate: 0,
|
||||
benefit_fee: 0,
|
||||
is_special_price: false,
|
||||
is_valid: true,
|
||||
function assignFormFromRow(row) {
|
||||
Object.assign(form, customerPriceFormFromRow(row), {
|
||||
company_code: row.company_code || activeCompanyCode.value,
|
||||
})
|
||||
pickInitialEditBill()
|
||||
}
|
||||
|
||||
function editBillTabLabel(code) {
|
||||
const c = String(code || '').trim()
|
||||
return expenseBillNameMap.value[c] || c || '—'
|
||||
}
|
||||
|
||||
function pickInitialEditBill() {
|
||||
const keys = editJsonBucketKeys.value
|
||||
if (!keys.length) {
|
||||
activeEditBillCode.value = ''
|
||||
return
|
||||
}
|
||||
const cur = String(activeEditBillCode.value || '').trim()
|
||||
if (cur && keys.includes(cur)) return
|
||||
activeEditBillCode.value = keys[0]
|
||||
}
|
||||
|
||||
function flattenDealers(list, parentGroup = null, out = []) {
|
||||
@@ -463,6 +626,7 @@ function flattenDealers(list, parentGroup = null, out = []) {
|
||||
name: pickCustomerDisplayName(item),
|
||||
parent_code: String(item?.parent_code ?? '').trim(),
|
||||
bloc_code: pickOrgBlocCode(item, groupRow),
|
||||
sale_type: normalizeOrgSaleType(item?.sale_type ?? item?.sales_type),
|
||||
ignore_scenes: extractIgnoreSceneCodes(item),
|
||||
raw: item,
|
||||
groupRaw: groupRow,
|
||||
@@ -498,41 +662,47 @@ function fmtNum(v) {
|
||||
return Number.isFinite(n) ? String(n) : '—'
|
||||
}
|
||||
|
||||
function buildFormPayload() {
|
||||
return {
|
||||
company_code: String(activeCompanyCode.value || '').trim(),
|
||||
goods_code: String(form.goods_code || '').trim(),
|
||||
is_support_acceptance: !!form.is_support_acceptance,
|
||||
rebate_delay_base_months: Number(form.rebate_delay_base_months) || 0,
|
||||
excess_performance_rate: Number(form.excess_performance_rate) || 0,
|
||||
excess_performance_amount: Number(form.excess_performance_amount) || 0,
|
||||
excess_profit_rate: Number(form.excess_profit_rate) || 0,
|
||||
excess_profit_amount: Number(form.excess_profit_amount) || 0,
|
||||
habitual_bargain_ratio: Number(form.habitual_bargain_ratio) || 0,
|
||||
benefit_rate: Number(form.benefit_rate) || 0,
|
||||
benefit_fee: Number(form.benefit_fee) || 0,
|
||||
is_special_price: !!form.is_special_price,
|
||||
is_valid: !!form.is_valid,
|
||||
function metricSummary(row, kind) {
|
||||
const billNameMap = expenseBillNameMap.value
|
||||
if (kind === 'benefit') {
|
||||
return summarizeCustomerPriceMetricGroups(row?.benefit_groups, {
|
||||
amountKey: 'fee',
|
||||
billNameMap,
|
||||
})
|
||||
}
|
||||
return summarizeCustomerPriceMetricGroups(row?.excess_performance_groups, {
|
||||
amountKey: 'amount',
|
||||
billNameMap,
|
||||
})
|
||||
}
|
||||
|
||||
async function loadExpenseBillNames() {
|
||||
try {
|
||||
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 map = {}
|
||||
for (const bill of list) {
|
||||
if (normalizeFinanceBillType(bill?.type) !== FINANCE_BILL_TYPE_EXPENSE) continue
|
||||
const code = String(bill?.bill_code ?? '').trim()
|
||||
if (!code) continue
|
||||
map[code] = expenseBillTabParts(bill).name
|
||||
}
|
||||
expenseBillNameMap.value = map
|
||||
} catch {
|
||||
expenseBillNameMap.value = {}
|
||||
}
|
||||
}
|
||||
|
||||
function assignFormFromRow(row) {
|
||||
Object.assign(form, emptyForm(), {
|
||||
company_code: row.company_code || activeCompanyCode.value,
|
||||
goods_code: row.goods_code || '',
|
||||
category_code: row.category_code || '',
|
||||
goods_categorys_text: row.goods_categorys_text || formatCustomerPriceGoodsDisplay(row) || '',
|
||||
is_support_acceptance: isTruthy(row.is_support_acceptance),
|
||||
rebate_delay_base_months: Number(row.rebate_delay_base_months) || 0,
|
||||
excess_performance_rate: Number(row.excess_performance_rate) || 0,
|
||||
excess_performance_amount: Number(row.excess_performance_amount) || 0,
|
||||
excess_profit_rate: Number(row.excess_profit_rate) || 0,
|
||||
excess_profit_amount: Number(row.excess_profit_amount) || 0,
|
||||
habitual_bargain_ratio: Number(row.habitual_bargain_ratio) || 0,
|
||||
benefit_rate: Number(row.benefit_rate) || 0,
|
||||
benefit_fee: Number(row.benefit_fee) || 0,
|
||||
is_special_price: isTruthy(row.is_special_price),
|
||||
is_valid: row.is_valid == null ? true : isTruthy(row.is_valid),
|
||||
function buildFormPayload() {
|
||||
return buildCustomerPriceWriteBody(form, {
|
||||
company_code: String(activeCompanyCode.value || '').trim(),
|
||||
sales_policy_type: 0,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -689,14 +859,17 @@ function openAdd() {
|
||||
formMode.value = 'add'
|
||||
editPriceCode.value = ''
|
||||
batchEditRows.value = []
|
||||
Object.assign(form, emptyForm(), { company_code: activeCompanyCode.value })
|
||||
Object.assign(form, createEmptyCustomerPriceForm(), {
|
||||
company_code: activeCompanyCode.value,
|
||||
})
|
||||
pickInitialEditBill()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
function openEdit(row) {
|
||||
const price_code = pickCustomerPriceCode(row)
|
||||
if (!price_code) {
|
||||
ElMessage.warning('缺少 price_code,无法编辑(请确认列表接口返回 price_code)')
|
||||
ElMessage.warning('缺少 customer_price_code,无法编辑(请确认列表接口返回 customer_price_code)')
|
||||
return
|
||||
}
|
||||
formMode.value = 'edit'
|
||||
@@ -706,6 +879,15 @@ function openEdit(row) {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
function openSpecialPrice(row) {
|
||||
specialPriceRow.value = row
|
||||
specialPriceDlgVisible.value = true
|
||||
}
|
||||
|
||||
async function onSpecialPriceSaved() {
|
||||
await loadData()
|
||||
}
|
||||
|
||||
function openBatchEdit() {
|
||||
const list = selectedEditableRows.value
|
||||
if (!list.length) {
|
||||
@@ -714,7 +896,7 @@ function openBatchEdit() {
|
||||
}
|
||||
const missing = list.filter((row) => !pickCustomerPriceCode(row))
|
||||
if (missing.length) {
|
||||
ElMessage.warning('部分记录缺少 price_code,无法批量修改')
|
||||
ElMessage.warning('部分记录缺少 customer_price_code,无法批量修改')
|
||||
return
|
||||
}
|
||||
formMode.value = 'batch'
|
||||
@@ -755,7 +937,7 @@ async function saveForm() {
|
||||
async function removeRow(row) {
|
||||
const price_code = pickCustomerPriceCode(row)
|
||||
if (!price_code) {
|
||||
ElMessage.warning('缺少 price_code,无法删除')
|
||||
ElMessage.warning('缺少 customer_price_code,无法删除')
|
||||
return
|
||||
}
|
||||
const label = row.goods_categorys_text
|
||||
@@ -771,7 +953,7 @@ async function removeRow(row) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await customerPriceRemove({ price_code })
|
||||
await customerPriceRemove(row)
|
||||
ElMessage.success('已删除')
|
||||
await loadData()
|
||||
} catch (e) {
|
||||
@@ -781,7 +963,10 @@ async function removeRow(row) {
|
||||
|
||||
defineExpose({ openAdd })
|
||||
|
||||
onMounted(() => loadCustomerList())
|
||||
onMounted(() => {
|
||||
void loadExpenseBillNames()
|
||||
loadCustomerList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -940,7 +1125,7 @@ onMounted(() => loadCustomerList())
|
||||
}
|
||||
|
||||
.dpb-flex-table {
|
||||
--dpb-cols: 36px 7fr 10fr 4fr 5fr 7fr 7fr 5fr 7fr 4fr 4fr 88px;
|
||||
--dpb-cols: 36px 12fr 7fr minmax(2.5em, 48px) minmax(3.5em, 68px) 7fr 7fr minmax(5em, 84px) 7fr minmax(2.5em, 48px) 4fr 196px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
@@ -995,6 +1180,22 @@ onMounted(() => loadCustomerList())
|
||||
.dpb-head-col--valid { border-bottom-color: #67c23a; }
|
||||
.dpb-head-col--ops { border-bottom-color: #909399; }
|
||||
.dpb-flex-head .dpb-col--num { text-align: right; padding-right: 20px; }
|
||||
.dpb-flex-head .dpb-col--rebate,
|
||||
.dpb-flex-data .dpb-col--rebate {
|
||||
padding-right: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.dpb-flex-head .dpb-col--bargain,
|
||||
.dpb-flex-data .dpb-col--bargain {
|
||||
padding-right: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.dpb-flex-head .dpb-col--rebate {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.dpb-flex-head .dpb-col--bargain {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.dpb-flex-head .dpb-col--pair { text-align: right; padding-right: 20px; }
|
||||
.dpb-flex-head .dpb-col--flag,
|
||||
.dpb-flex-head .dpb-col--op,
|
||||
@@ -1084,6 +1285,16 @@ onMounted(() => loadCustomerList())
|
||||
overflow: hidden;
|
||||
padding-left: 12px;
|
||||
}
|
||||
.dpb-goods-name {
|
||||
font-family: 'DingTalk_JinBuTi_Regular', sans-serif;
|
||||
font-size: calc(13px + var(--wh-fs-delta, 0px));
|
||||
font-weight: 700;
|
||||
color: #1d3461;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 0;
|
||||
}
|
||||
.dpb-col--cats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1135,6 +1346,29 @@ onMounted(() => loadCustomerList())
|
||||
color: #606266;
|
||||
}
|
||||
.dpb-pair-sep { color: #c0c4cc; }
|
||||
.dpb-metric-cell {
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
}
|
||||
.dpb-metric-line {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
.dpb-metric-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
}
|
||||
.dpb-metric-multi {
|
||||
flex-shrink: 0;
|
||||
font-size: calc(11px + var(--wh-fs-delta, 0px));
|
||||
color: #e6a23c;
|
||||
font-weight: 600;
|
||||
}
|
||||
.dpb-col--flag {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1149,7 +1383,9 @@ onMounted(() => loadCustomerList())
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 18px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.dpb-name {
|
||||
@@ -1191,6 +1427,11 @@ onMounted(() => loadCustomerList())
|
||||
white-space: nowrap;
|
||||
}
|
||||
.dpb-op-link:hover { color: #66b1ff; }
|
||||
.dpb-op-link--special {
|
||||
color: #e6a23c;
|
||||
font-weight: 600;
|
||||
}
|
||||
.dpb-op-link--special:hover { color: #f0b84d; }
|
||||
.dpb-op-link--muted { color: #909399; }
|
||||
.dpb-op-link--muted:hover { color: #606266; }
|
||||
|
||||
@@ -1203,11 +1444,15 @@ onMounted(() => loadCustomerList())
|
||||
}
|
||||
|
||||
.dpb-form-num {
|
||||
width: 100%;
|
||||
width: 108px;
|
||||
max-width: 100%;
|
||||
}
|
||||
.dpb-form :deep(.el-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.dpb-form :deep(.el-form-item__label) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.dpb-form-meta-row :deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
@@ -1239,37 +1484,84 @@ onMounted(() => loadCustomerList())
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
}
|
||||
.dpb-form-rate-grid {
|
||||
.dpb-form-bill-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.dpb-form-bill-tab {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 10px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
cursor: pointer;
|
||||
}
|
||||
.dpb-form-bill-tab.is-active {
|
||||
border-color: #409eff;
|
||||
background: #ecf5ff;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.dpb-form-bill-tab-tax {
|
||||
font-size: 11px;
|
||||
color: #e6a23c;
|
||||
}
|
||||
.dpb-form-bill-tab.is-active .dpb-form-bill-tab-tax {
|
||||
color: #cf9236;
|
||||
}
|
||||
.dpb-form-param-shell {
|
||||
--dpb-param-label-w: 7.5em;
|
||||
--dpb-param-input-w: 108px;
|
||||
--dpb-param-gap: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.dpb-form-rate-row {
|
||||
display: grid;
|
||||
grid-template-columns: 88px 1fr;
|
||||
.dpb-form-param-top {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 6px;
|
||||
gap: 8px 32px;
|
||||
}
|
||||
.dpb-form-rate-name {
|
||||
.dpb-form-param-pair {
|
||||
display: grid;
|
||||
grid-template-columns: var(--dpb-param-label-w) var(--dpb-param-input-w);
|
||||
align-items: center;
|
||||
column-gap: var(--dpb-param-gap);
|
||||
}
|
||||
.dpb-form-param-label {
|
||||
justify-self: start;
|
||||
text-align: left;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
line-height: 1.4;
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
cursor: default;
|
||||
}
|
||||
.dpb-form-param-rate {
|
||||
display: grid;
|
||||
grid-template-columns: var(--dpb-param-label-w) 1fr;
|
||||
align-items: center;
|
||||
column-gap: var(--dpb-param-gap);
|
||||
}
|
||||
.dpb-form-rate-fields {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 10px 20px;
|
||||
}
|
||||
.dpb-form-rate-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
gap: 6px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.dpb-form-rate-field-label {
|
||||
flex: 0 0 18px;
|
||||
@@ -1277,6 +1569,11 @@ onMounted(() => loadCustomerList())
|
||||
color: #909399;
|
||||
text-align: center;
|
||||
}
|
||||
.dpb-form-unit {
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
.dpb-form-num :deep(.el-input__inner) {
|
||||
text-align: left;
|
||||
}
|
||||
@@ -1290,3 +1587,11 @@ onMounted(() => loadCustomerList())
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.dpb-metric-tooltip {
|
||||
white-space: pre-line;
|
||||
max-width: 320px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user