智能体按 reply_code 读后台回复语;陆运整车报价单顶栏色与填表更稳,工单页展示同步收紧。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -126,6 +126,17 @@ function asPriceLines(raw: any): PriceLine[] {
|
||||
.filter((row) => row.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 未调价利润点必须是 0%。主账应已逐行给 0%;旧接口没给时,按成本/销售费用名补 0%。
|
||||
* 禁止把金额拿来填利润点列。
|
||||
*/
|
||||
function displayProfitLines(row: TicketRow): PriceLine[] {
|
||||
if (row.profitLines.length) return row.profitLines;
|
||||
const src = row.saleLines.length ? row.saleLines : row.costLines;
|
||||
if (!src.length) return [];
|
||||
return src.map((line) => ({ name: line.name, text: "0%" }));
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台报价来源:TMS标准报价 / 手工报价 / 销售调价。
|
||||
* 禁止把产品报价或销售调价再显示成 TMS。
|
||||
@@ -413,6 +424,44 @@ function quoteHasFees(row: TicketRow) {
|
||||
return (row.quotes || []).some((q) => (q.fees || []).length > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 报价信息 / 成本价 / 销售价 / 利润点共用一把「展开」:
|
||||
* 收起时只留摘要(报价来源+总额、费用合计行),展开才看逐费明细。
|
||||
* 避免只收起报价列、成本/利润仍撑很高。
|
||||
*/
|
||||
const priceExpandedMap = reactive<Record<string, boolean>>({});
|
||||
const detailPriceExpanded = ref(false);
|
||||
|
||||
function detailPriceNeedsToggle(row: TicketRow | null) {
|
||||
if (!row) return false;
|
||||
return priceBlockNeedsToggle(row);
|
||||
}
|
||||
|
||||
function isPriceExpanded(id: string) {
|
||||
return !!priceExpandedMap[id];
|
||||
}
|
||||
|
||||
function togglePriceExpanded(id: string) {
|
||||
priceExpandedMap[id] = !priceExpandedMap[id];
|
||||
}
|
||||
|
||||
function priceBlockNeedsToggle(row: TicketRow) {
|
||||
return (
|
||||
quoteHasFees(row) ||
|
||||
row.costLines.length > 1 ||
|
||||
row.saleLines.length > 1 ||
|
||||
displayProfitLines(row).length > 1
|
||||
);
|
||||
}
|
||||
|
||||
/** 收起:优先只留「费用合计」;没有合计则留首行,避免整列空白。 */
|
||||
function visiblePriceLines(lines: PriceLine[], expanded: boolean): PriceLine[] {
|
||||
if (!lines.length) return [];
|
||||
if (expanded || lines.length <= 1) return lines;
|
||||
const totals = lines.filter((l) => l.name === "费用合计");
|
||||
return totals.length ? totals : lines.slice(0, 1);
|
||||
}
|
||||
|
||||
/** 列表询价需求第二行起:与旧后台 summary-cell 多行展示一致。 */
|
||||
function demandDetailLines(row: TicketRow) {
|
||||
return String(row?.demandDetail || "")
|
||||
@@ -472,6 +521,7 @@ onMounted(() => loadList());
|
||||
|
||||
async function openDetail(row: TicketRow) {
|
||||
current.value = row;
|
||||
detailPriceExpanded.value = false;
|
||||
detailOpen.value = true;
|
||||
try {
|
||||
const res: any = await getWorkOrderDetail(row.id);
|
||||
@@ -540,7 +590,7 @@ function refresh() {
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
<div class="ticket-page">
|
||||
<div class="ticket-page is-list-page">
|
||||
<div class="section-card">
|
||||
<div class="section-card-header">
|
||||
<h2 class="section-card-title">询价工单列表</h2>
|
||||
@@ -664,35 +714,41 @@ function refresh() {
|
||||
</ExpandableClamp>
|
||||
</td>
|
||||
<td class="align-top col-quote">
|
||||
<ExpandableClamp
|
||||
v-if="row.quotes.length"
|
||||
v-slot="{ expanded }"
|
||||
:content-key="row.id + '-quote-' + row.quotes.length"
|
||||
:force-toggle="quoteHasFees(row)"
|
||||
:clip-collapsed="false"
|
||||
>
|
||||
<div
|
||||
v-for="q in row.quotes"
|
||||
:key="q.source + q.price"
|
||||
class="quote-row"
|
||||
style="margin-bottom: 8px"
|
||||
>
|
||||
<div class="quote-source">{{ q.source }}</div>
|
||||
<div class="quote-price" :style="q.highlight ? { color: '#1d4ed8' } : undefined">
|
||||
{{ q.price }}
|
||||
</div>
|
||||
<template v-if="expanded && q.fees.length">
|
||||
<div
|
||||
v-for="(fee, fi) in q.fees"
|
||||
:key="q.source + '-fee-' + fi"
|
||||
class="quote-fee-line"
|
||||
>
|
||||
{{ fee.name }}:{{ fee.amount }}
|
||||
<div class="ticket-price-expand">
|
||||
<template v-if="row.quotes.length">
|
||||
<div
|
||||
v-for="q in row.quotes"
|
||||
:key="q.source + q.price"
|
||||
class="quote-row"
|
||||
style="margin-bottom: 8px"
|
||||
>
|
||||
<div class="quote-source">{{ q.source }}</div>
|
||||
<div class="quote-price" :style="q.highlight ? { color: '#1d4ed8' } : undefined">
|
||||
{{ q.price }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</ExpandableClamp>
|
||||
<span v-else style="color: #94a3b8">-</span>
|
||||
<template v-if="isPriceExpanded(row.id) && q.fees.length">
|
||||
<div
|
||||
v-for="(fee, fi) in q.fees"
|
||||
:key="q.source + '-fee-' + fi"
|
||||
class="quote-fee-line"
|
||||
>
|
||||
{{ fee.name }}:{{ fee.amount }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else style="color: #94a3b8">-</span>
|
||||
<button
|
||||
v-if="priceBlockNeedsToggle(row)"
|
||||
type="button"
|
||||
class="ticket-price-toggle"
|
||||
:class="{ 'is-open': isPriceExpanded(row.id) }"
|
||||
:title="isPriceExpanded(row.id) ? '收起费用明细' : '展开费用明细'"
|
||||
@click.stop="togglePriceExpanded(row.id)"
|
||||
>
|
||||
<ProtoIcon name="chevron" :size="14" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-top">
|
||||
<div v-if="row.attachments.length" class="attach-tags">
|
||||
@@ -702,9 +758,12 @@ function refresh() {
|
||||
</td>
|
||||
<td class="align-top"><span class="ticket-status">{{ row.status }}</span></td>
|
||||
<td class="align-top">
|
||||
<div v-if="row.costLines.length" class="ticket-price-lines">
|
||||
<div
|
||||
v-if="visiblePriceLines(row.costLines, isPriceExpanded(row.id)).length"
|
||||
class="ticket-price-lines"
|
||||
>
|
||||
<div
|
||||
v-for="(line, i) in row.costLines"
|
||||
v-for="(line, i) in visiblePriceLines(row.costLines, isPriceExpanded(row.id))"
|
||||
:key="row.id + '-cost-' + i"
|
||||
class="ticket-price-line"
|
||||
:class="{ 'is-total': line.name === '费用合计' }"
|
||||
@@ -715,9 +774,12 @@ function refresh() {
|
||||
<span v-else class="ticket-price-cell">{{ row.costPrice }}</span>
|
||||
</td>
|
||||
<td class="align-top">
|
||||
<div v-if="row.saleLines.length" class="ticket-price-lines">
|
||||
<div
|
||||
v-if="visiblePriceLines(row.saleLines, isPriceExpanded(row.id)).length"
|
||||
class="ticket-price-lines"
|
||||
>
|
||||
<div
|
||||
v-for="(line, i) in row.saleLines"
|
||||
v-for="(line, i) in visiblePriceLines(row.saleLines, isPriceExpanded(row.id))"
|
||||
:key="row.id + '-sale-' + i"
|
||||
class="ticket-price-line"
|
||||
:class="{ 'is-total': line.name === '费用合计' }"
|
||||
@@ -728,9 +790,12 @@ function refresh() {
|
||||
<span v-else class="ticket-price-cell" style="font-weight: 800">{{ row.salePrice }}</span>
|
||||
</td>
|
||||
<td class="align-top">
|
||||
<div v-if="row.profitLines.length" class="ticket-price-lines">
|
||||
<div
|
||||
v-if="visiblePriceLines(displayProfitLines(row), isPriceExpanded(row.id)).length"
|
||||
class="ticket-price-lines"
|
||||
>
|
||||
<div
|
||||
v-for="(line, i) in row.profitLines"
|
||||
v-for="(line, i) in visiblePriceLines(displayProfitLines(row), isPriceExpanded(row.id))"
|
||||
:key="row.id + '-profit-' + i"
|
||||
class="ticket-price-line"
|
||||
:class="{ 'is-total': line.name === '费用合计' }"
|
||||
@@ -738,7 +803,7 @@ function refresh() {
|
||||
{{ line.name }}:{{ line.text || "-" }}
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="ticket-margin-cell">{{ row.profit }}</span>
|
||||
<span v-else class="ticket-margin-cell">{{ row.profit === "-" ? "0%" : row.profit }}</span>
|
||||
</td>
|
||||
<td class="col-center">
|
||||
<span v-if="row.eventException !== '-'" class="tag tag-rose">{{ row.eventException }}</span>
|
||||
@@ -843,56 +908,60 @@ function refresh() {
|
||||
<div class="ticket-detail-title">
|
||||
<ProtoIcon name="gauge" :size="14" />价格与利润
|
||||
</div>
|
||||
<div class="ticket-detail-grid">
|
||||
<div class="ticket-detail-item">
|
||||
<label>成本价</label>
|
||||
<div v-if="current.costLines.length" class="ticket-price-lines">
|
||||
<div class="ticket-price-expand">
|
||||
<div class="ticket-detail-grid">
|
||||
<div class="ticket-detail-item">
|
||||
<label>成本价</label>
|
||||
<div
|
||||
v-for="(line, i) in current.costLines"
|
||||
:key="'detail-cost-' + i"
|
||||
class="ticket-price-line"
|
||||
:class="{ 'is-total': line.name === '费用合计' }"
|
||||
v-if="visiblePriceLines(current.costLines, detailPriceExpanded).length"
|
||||
class="ticket-price-lines"
|
||||
>
|
||||
{{ line.name }}:{{ line.text || "-" }}
|
||||
<div
|
||||
v-for="(line, i) in visiblePriceLines(current.costLines, detailPriceExpanded)"
|
||||
:key="'detail-cost-' + i"
|
||||
class="ticket-price-line"
|
||||
:class="{ 'is-total': line.name === '费用合计' }"
|
||||
>
|
||||
{{ line.name }}:{{ line.text || "-" }}
|
||||
</div>
|
||||
</div>
|
||||
<span v-else>{{ current.costPrice }}</span>
|
||||
</div>
|
||||
<span v-else>{{ current.costPrice }}</span>
|
||||
</div>
|
||||
<div class="ticket-detail-item">
|
||||
<label>销售价</label>
|
||||
<div v-if="current.saleLines.length" class="ticket-price-lines">
|
||||
<div class="ticket-detail-item">
|
||||
<label>销售价</label>
|
||||
<div
|
||||
v-for="(line, i) in current.saleLines"
|
||||
:key="'detail-sale-' + i"
|
||||
class="ticket-price-line"
|
||||
:class="{ 'is-total': line.name === '费用合计' }"
|
||||
v-if="visiblePriceLines(current.saleLines, detailPriceExpanded).length"
|
||||
class="ticket-price-lines"
|
||||
>
|
||||
{{ line.name }}:{{ line.text || "-" }}
|
||||
<div
|
||||
v-for="(line, i) in visiblePriceLines(current.saleLines, detailPriceExpanded)"
|
||||
:key="'detail-sale-' + i"
|
||||
class="ticket-price-line"
|
||||
:class="{ 'is-total': line.name === '费用合计' }"
|
||||
>
|
||||
{{ line.name }}:{{ line.text || "-" }}
|
||||
</div>
|
||||
</div>
|
||||
<span v-else>{{ current.salePrice }}</span>
|
||||
</div>
|
||||
<span v-else>{{ current.salePrice }}</span>
|
||||
</div>
|
||||
<div class="ticket-detail-item">
|
||||
<label>利润点</label>
|
||||
<div v-if="current.profitLines.length" class="ticket-price-lines">
|
||||
<div class="ticket-detail-item">
|
||||
<label>利润点</label>
|
||||
<div
|
||||
v-for="(line, i) in current.profitLines"
|
||||
:key="'detail-profit-' + i"
|
||||
class="ticket-price-line"
|
||||
:class="{ 'is-total': line.name === '费用合计' }"
|
||||
v-if="visiblePriceLines(displayProfitLines(current), detailPriceExpanded).length"
|
||||
class="ticket-price-lines"
|
||||
>
|
||||
{{ line.name }}:{{ line.text || "-" }}
|
||||
<div
|
||||
v-for="(line, i) in visiblePriceLines(displayProfitLines(current), detailPriceExpanded)"
|
||||
:key="'detail-profit-' + i"
|
||||
class="ticket-price-line"
|
||||
:class="{ 'is-total': line.name === '费用合计' }"
|
||||
>
|
||||
{{ line.name }}:{{ line.text || "-" }}
|
||||
</div>
|
||||
</div>
|
||||
<span v-else>{{ current.profit === "-" ? "0%" : current.profit }}</span>
|
||||
</div>
|
||||
<span v-else>{{ current.profit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ExpandableClamp
|
||||
v-if="current.quotes.length"
|
||||
class="ticket-detail-demand"
|
||||
:content-key="current.id + '-detail-quote-' + current.quotes.length"
|
||||
:max-height="220"
|
||||
>
|
||||
<div
|
||||
v-for="q in current.quotes"
|
||||
:key="q.source + q.price + (q.time || '')"
|
||||
@@ -901,7 +970,7 @@ function refresh() {
|
||||
<div class="quote-source">
|
||||
{{ q.source }}<span v-if="q.time && q.time !== '-'"> · {{ q.time }}</span>
|
||||
</div>
|
||||
<div v-if="q.fees.length" class="quote-fee-list">
|
||||
<div v-if="detailPriceExpanded && q.fees.length" class="quote-fee-list">
|
||||
<div
|
||||
v-for="(fee, fi) in q.fees"
|
||||
:key="q.source + '-detail-fee-' + fi"
|
||||
@@ -918,7 +987,17 @@ function refresh() {
|
||||
合计 {{ q.price }}
|
||||
</div>
|
||||
</div>
|
||||
</ExpandableClamp>
|
||||
<button
|
||||
v-if="detailPriceNeedsToggle(current)"
|
||||
type="button"
|
||||
class="ticket-price-toggle"
|
||||
:class="{ 'is-open': detailPriceExpanded }"
|
||||
:title="detailPriceExpanded ? '收起费用明细' : '展开费用明细'"
|
||||
@click="detailPriceExpanded = !detailPriceExpanded"
|
||||
>
|
||||
<ProtoIcon name="chevron" :size="14" />
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="ticket-detail-section">
|
||||
@@ -1032,4 +1111,32 @@ function refresh() {
|
||||
color: #708090;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 报价+成本+利润共用展开:箭头管整块费用明细 */
|
||||
.ticket-price-expand {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
}
|
||||
.ticket-price-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
padding: 2px 0 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #64748b;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
}
|
||||
.ticket-price-toggle:hover {
|
||||
color: #1d4ed8;
|
||||
}
|
||||
.ticket-price-toggle :deep(svg) {
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
.ticket-price-toggle.is-open :deep(svg) {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user