角色业务范围收紧工单与模板;报价日期不当费用;未选舱不迁已报价;另开一票可清违禁 hold
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
* - 版式由上传 Excel 决定;列表展示「映射点数」而非假占位符
|
||||
* - 预览:原表截图(LibreOffice 转图)
|
||||
* - 解析状态列可点:弹窗看已解析 / 未解析清单
|
||||
* - 业务下拉和列表按主账返回的 allowedBizTypes 收窄:陆运角色不出现海运、空运
|
||||
*/
|
||||
import { computed, onMounted, onUnmounted, reactive, ref } from "vue";
|
||||
import { Modal, message } from "ant-design-vue";
|
||||
@@ -55,6 +56,29 @@ const keyword = ref("");
|
||||
const bizFilter = ref("");
|
||||
const statusFilter = ref("");
|
||||
|
||||
/** 报价模板只有这三条业务。主账按角色收窄后,下拉只留账号能看的。 */
|
||||
const ALL_BIZ = ["空运", "海运", "陆运"] as const;
|
||||
type BizName = (typeof ALL_BIZ)[number];
|
||||
const bizOptions = ref<BizName[]>([...ALL_BIZ]);
|
||||
|
||||
function defaultBiz(): TemplateRow["bizType"] {
|
||||
return bizOptions.value[0] || "海运";
|
||||
}
|
||||
|
||||
/** 主账没带回范围时保持三项,避免旧接口把下拉清空。 */
|
||||
function applyAllowedBiz(raw: unknown) {
|
||||
const incoming = Array.isArray(raw) ? raw.map((item) => String(item)) : [];
|
||||
const next = ALL_BIZ.filter((biz) => incoming.includes(biz));
|
||||
if (!next.length) return;
|
||||
bizOptions.value = [...next];
|
||||
if (bizFilter.value && !bizOptions.value.includes(bizFilter.value as BizName)) {
|
||||
bizFilter.value = "";
|
||||
}
|
||||
if (!bizOptions.value.includes(form.bizType as BizName)) {
|
||||
form.bizType = defaultBiz();
|
||||
}
|
||||
}
|
||||
|
||||
const formOpen = ref(false);
|
||||
const editing = ref<TemplateRow | null>(null);
|
||||
const previewOpen = ref(false);
|
||||
@@ -364,6 +388,7 @@ async function loadList() {
|
||||
pageSize: 200,
|
||||
});
|
||||
const records = res?.records || res?.result?.records || [];
|
||||
applyAllowedBiz(res?.allowedBizTypes ?? res?.result?.allowedBizTypes);
|
||||
rows.value = (records as any[]).map(mapApiRow);
|
||||
scheduleParsePoll();
|
||||
} catch (e: any) {
|
||||
@@ -397,6 +422,7 @@ function scheduleParsePoll() {
|
||||
pageSize: 200,
|
||||
});
|
||||
const records = res?.records || res?.result?.records || [];
|
||||
applyAllowedBiz(res?.allowedBizTypes ?? res?.result?.allowedBizTypes);
|
||||
rows.value = (records as any[]).map(mapApiRow);
|
||||
} catch {
|
||||
/* 轮询失败不打断页面 */
|
||||
@@ -420,7 +446,7 @@ onUnmounted(() => {
|
||||
function resetForm() {
|
||||
Object.assign(form, {
|
||||
name: "",
|
||||
bizType: "海运" as TemplateRow["bizType"],
|
||||
bizType: defaultBiz(),
|
||||
mode: "auto" as const,
|
||||
keywordItems: [""],
|
||||
isDefault: false,
|
||||
@@ -708,9 +734,7 @@ async function downloadPreviewOrOriginal() {
|
||||
</div>
|
||||
<select v-model="bizFilter" @change="loadList">
|
||||
<option value="">全部业务</option>
|
||||
<option value="空运">空运</option>
|
||||
<option value="海运">海运</option>
|
||||
<option value="陆运">陆运</option>
|
||||
<option v-for="biz in bizOptions" :key="biz" :value="biz">{{ biz }}</option>
|
||||
</select>
|
||||
<select v-model="statusFilter" @change="loadList">
|
||||
<option value="">全部状态</option>
|
||||
@@ -874,9 +898,7 @@ async function downloadPreviewOrOriginal() {
|
||||
<div class="qt-create-row">
|
||||
<label>业务</label>
|
||||
<select v-model="form.bizType" class="field">
|
||||
<option value="海运">海运</option>
|
||||
<option value="空运">空运</option>
|
||||
<option value="陆运">陆运</option>
|
||||
<option v-for="biz in bizOptions" :key="biz" :value="biz">{{ biz }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="qt-create-row qt-create-row--keywords">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/**
|
||||
* 询价工单列表:对接主账 /inquiry/workOrder。
|
||||
* 六态关单/重试必须调主账;禁止本地改 status 冒充成功。
|
||||
* 业务下拉按主账 allowedBizTypes 收窄:陆运角色不出现海运、空运。
|
||||
* 详情/关单弹窗必须用 AdminModal:本工程 ant-design-vue 3 的 a-modal 只认 visible,
|
||||
* v-model:open 不会弹出(账号/角色页已统一走 AdminModal)。
|
||||
*/
|
||||
@@ -112,6 +113,20 @@ const filters = reactive({
|
||||
dateTo: "",
|
||||
});
|
||||
|
||||
/** 工单业务下拉。主账按角色收窄后,陆运账号不再出现海运、空运、多段。 */
|
||||
const ALL_TICKET_BIZ = ["空运", "海运", "陆运", "多段联运"] as const;
|
||||
const ticketBizOptions = ref<string[]>([...ALL_TICKET_BIZ]);
|
||||
|
||||
function applyTicketBizOptions(raw: unknown) {
|
||||
const incoming = Array.isArray(raw) ? raw.map((item) => String(item)) : [];
|
||||
const next = ALL_TICKET_BIZ.filter((biz) => incoming.includes(biz));
|
||||
if (!next.length) return;
|
||||
ticketBizOptions.value = [...next];
|
||||
if (filters.bizType && !ticketBizOptions.value.includes(filters.bizType)) {
|
||||
filters.bizType = "";
|
||||
}
|
||||
}
|
||||
|
||||
const detailOpen = ref(false);
|
||||
const closeOpen = ref(false);
|
||||
const current = ref<TicketRow | null>(null);
|
||||
@@ -460,6 +475,20 @@ function requirementSummaryLines(t: any): string[] {
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* 空运尚未选定舱位/线路:payload 有 airOptions 且无 selected,或 pending_cabin_pick。
|
||||
* 此时后台不应展示 TMS 费用,避免「未选舱却已报价」。
|
||||
*/
|
||||
function isPendingAirCabinQuote(q: any): boolean {
|
||||
const payload = asObject(q?.payloadJson) || asObject(q?.payload) || {};
|
||||
if (String(payload.pending_cabin_pick || "") === "1") return true;
|
||||
const selected =
|
||||
asObject(payload.selected_air_option) || asObject(payload.selectedAirOption);
|
||||
if (selected) return false;
|
||||
const opts = asFeeArray(payload.airOptions || payload.air_options);
|
||||
return opts.length > 0;
|
||||
}
|
||||
|
||||
function mapTicket(t: any, quotes: any[] = [], attachments: any[] = [], timeline: any[] = []): TicketRow {
|
||||
const attachRows: TicketAttach[] = (attachments || [])
|
||||
.map((a) => ({
|
||||
@@ -479,7 +508,9 @@ function mapTicket(t: any, quotes: any[] = [], attachments: any[] = [], timeline
|
||||
initiator,
|
||||
demandTitle: reqLines[0] || "—",
|
||||
demandDetail: reqLines.slice(1).join("\n"),
|
||||
quotes: (quotes || []).map((q) => ({
|
||||
quotes: (quotes || [])
|
||||
.filter((q) => !isPendingAirCabinQuote(q))
|
||||
.map((q) => ({
|
||||
source: quoteSourceLabel(q),
|
||||
price: q.priceText || "-",
|
||||
time: fmtTime(q.createTime),
|
||||
@@ -587,6 +618,7 @@ async function loadList() {
|
||||
});
|
||||
const page = res?.records ? res : res?.result || {};
|
||||
const records = page.records || [];
|
||||
applyTicketBizOptions(page.allowedBizTypes);
|
||||
total.value = Number(page.total || records.length || 0);
|
||||
rows.value = (records as any[]).map((t) => mapTicket(t, t.quotes || []));
|
||||
// 删减后当前页可能越界,回退到最后一页
|
||||
@@ -766,10 +798,7 @@ async function exportTickets() {
|
||||
</select>
|
||||
<select v-model="filters.bizType">
|
||||
<option value="">业务类型 · 全部</option>
|
||||
<option value="空运">空运</option>
|
||||
<option value="海运">海运</option>
|
||||
<option value="陆运">陆运</option>
|
||||
<option value="多段联运">多段联运</option>
|
||||
<option v-for="biz in ticketBizOptions" :key="biz" :value="biz">{{ biz }}</option>
|
||||
</select>
|
||||
<div class="toolbar-date-range" title="按创建时间筛选">
|
||||
<a-range-picker
|
||||
|
||||
Reference in New Issue
Block a user