后台成本价、销售价、利润点按费用逐行展示,费用合计用报价官方总价。选完线路只关页面。报价单文件名带上模板名,空运起运地和转人工跟对应工单走。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-05 12:33:34 +08:00
co-authored by Cursor
parent 7c77e45e03
commit e95e8869a6
30 changed files with 1926 additions and 75 deletions
@@ -570,8 +570,8 @@ body {
.ticket-list-table .col-w-quote { width: 140px; }
.ticket-list-table .col-w-attach { width: 160px; }
.ticket-list-table .col-w-status { width: 84px; }
.ticket-list-table .col-w-price { width: 100px; }
.ticket-list-table .col-w-margin { width: 72px; }
.ticket-list-table .col-w-price { width: 168px; }
.ticket-list-table .col-w-margin { width: 132px; }
.ticket-list-table .col-w-ex { width: 110px; }
.ticket-list-table .col-w-reason { width: 140px; }
.ticket-list-table .col-w-time { width: 132px; }
@@ -627,6 +627,20 @@ body {
white-space: nowrap;
}
.ticket-price-lines {
display: flex;
flex-direction: column;
gap: 2px;
white-space: normal;
font-size: 11px;
line-height: 1.45;
color: #0f172a;
}
.ticket-price-line.is-total {
font-weight: 800;
}
.ticket-status {
display: inline-flex !important;
align-items: center !important;
@@ -19,6 +19,8 @@ import AdminPagination from "/@/views/inquiry/components/AdminPagination.vue";
/** 单条费用明细:名称 + 带币种金额,对齐企微价格明细。 */
type TicketQuoteFee = { name: string; amount: string };
/** 成本价 / 销售价 / 利润点的一行。最后一行名称是「费用合计」。 */
type PriceLine = { name: string; text: string };
type TicketQuote = {
source: string;
price: string;
@@ -43,6 +45,9 @@ type TicketRow = {
costPrice: string;
salePrice: string;
profit: string;
costLines: PriceLine[];
saleLines: PriceLine[];
profitLines: PriceLine[];
eventException: string;
systemException: string;
systemExceptionReason: string;
@@ -102,6 +107,17 @@ function dash(v: any) {
return s ? s : "-";
}
/** 主账逐行。没有行时页面退回原来的总价字符串。 */
function asPriceLines(raw: any): PriceLine[] {
if (!Array.isArray(raw)) return [];
return raw
.map((row) => ({
name: String(row?.name ?? "").trim(),
text: String(row?.text ?? "").trim(),
}))
.filter((row) => row.name);
}
/**
* 后台报价来源:TMS标准报价 / 手工报价 / 销售调价。
* 禁止把产品报价或销售调价再显示成 TMS。
@@ -386,6 +402,9 @@ function mapTicket(t: any, quotes: any[] = [], attachments: any[] = [], timeline
costPrice: dash(t.costPrice),
salePrice: dash(t.salePrice),
profit: dash(t.profitRate),
costLines: asPriceLines(t.costLines),
saleLines: asPriceLines(t.saleLines),
profitLines: asPriceLines(t.profitLines),
eventException: dash(t.eventException),
systemException: dash(t.systemException),
systemExceptionReason: dash(t.systemExceptionReason),
@@ -692,13 +711,43 @@ function refresh() {
</td>
<td class="align-top"><span class="ticket-status">{{ row.status }}</span></td>
<td class="align-top">
<span class="ticket-price-cell">{{ row.costPrice }}</span>
<div v-if="row.costLines.length" class="ticket-price-lines">
<div
v-for="(line, i) in row.costLines"
:key="row.id + '-cost-' + i"
class="ticket-price-line"
:class="{ 'is-total': line.name === '费用合计' }"
>
{{ line.name }}:{{ line.text || "-" }}
</div>
</div>
<span v-else class="ticket-price-cell">{{ row.costPrice }}</span>
</td>
<td class="align-top">
<span class="ticket-price-cell" style="font-weight: 800">{{ row.salePrice }}</span>
<div v-if="row.saleLines.length" class="ticket-price-lines">
<div
v-for="(line, i) in row.saleLines"
:key="row.id + '-sale-' + i"
class="ticket-price-line"
:class="{ 'is-total': line.name === '费用合计' }"
>
{{ line.name }}:{{ line.text || "-" }}
</div>
</div>
<span v-else class="ticket-price-cell" style="font-weight: 800">{{ row.salePrice }}</span>
</td>
<td class="align-top">
<span class="ticket-margin-cell">{{ row.profit }}</span>
<div v-if="row.profitLines.length" class="ticket-price-lines">
<div
v-for="(line, i) in row.profitLines"
:key="row.id + '-profit-' + i"
class="ticket-price-line"
:class="{ 'is-total': line.name === '费用合计' }"
>
{{ line.name }}:{{ line.text || "-" }}
</div>
</div>
<span v-else class="ticket-margin-cell">{{ row.profit }}</span>
</td>
<td class="col-center">
<span v-if="row.eventException !== '-'" class="tag tag-rose">{{ row.eventException }}</span>
@@ -804,9 +853,48 @@ function refresh() {
<ProtoIcon name="gauge" :size="14" />价格与利润
</div>
<div class="ticket-detail-grid">
<div class="ticket-detail-item"><label>成本价</label><span>{{ current.costPrice }}</span></div>
<div class="ticket-detail-item"><label>销售价</label><span>{{ current.salePrice }}</span></div>
<div class="ticket-detail-item"><label>利润点</label><span>{{ current.profit }}</span></div>
<div class="ticket-detail-item">
<label>成本价</label>
<div v-if="current.costLines.length" class="ticket-price-lines">
<div
v-for="(line, i) in current.costLines"
: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>
<div class="ticket-detail-item">
<label>销售价</label>
<div v-if="current.saleLines.length" class="ticket-price-lines">
<div
v-for="(line, i) in current.saleLines"
: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>
<div class="ticket-detail-item">
<label>利润点</label>
<div v-if="current.profitLines.length" class="ticket-price-lines">
<div
v-for="(line, i) in current.profitLines"
:key="'detail-profit-' + i"
class="ticket-price-line"
:class="{ 'is-total': line.name === '费用合计' }"
>
{{ line.name }}:{{ line.text || "-" }}
</div>
</div>
<span v-else>{{ current.profit }}</span>
</div>
</div>
<div v-if="current.quotes.length" class="ticket-detail-demand">
<div