落地报价模板主账与后台:合同映射、灌种 SQL/脚本、智能体填表,并补测服运维探针与合同文档对齐。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,22 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 询价工单列表:筛选、详情、关单(Mock);表头左右列 sticky,中间列横滑。
|
||||
* 六态只展示;正式 transition 必须调主账,禁止本地改真相。
|
||||
* 询价工单列表:对接主账 /inquiry/workOrder。
|
||||
* 六态关单/重试必须调主账;禁止本地改 status 冒充成功。
|
||||
*/
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { onMounted, reactive, ref, watch } from "vue";
|
||||
import { message, Modal } from "ant-design-vue";
|
||||
import { mockTickets, type TicketRow } from "/@/views/inquiry/mock/tickets";
|
||||
import {
|
||||
getWorkOrderDetail,
|
||||
getWorkOrderList,
|
||||
retryAbnormal,
|
||||
transitionWorkOrder,
|
||||
} from "/@/views/inquiry/workOrder/workOrder.api";
|
||||
import ProtoIcon from "/@/views/inquiry/components/ProtoIcon.vue";
|
||||
|
||||
const rows = ref<TicketRow[]>([...mockTickets]);
|
||||
type TicketRow = {
|
||||
id: string;
|
||||
initiator: string;
|
||||
demandTitle: string;
|
||||
demandDetail: string;
|
||||
quotes: { source: string; price: string; highlight?: boolean }[];
|
||||
attachments: string[];
|
||||
status: string;
|
||||
costPrice: string;
|
||||
salePrice: string;
|
||||
profit: string;
|
||||
eventException: string;
|
||||
systemException: string;
|
||||
systemExceptionReason: string;
|
||||
createdAt: string;
|
||||
quoteDate: string;
|
||||
bizType: string;
|
||||
};
|
||||
|
||||
const rows = ref<TicketRow[]>([]);
|
||||
const loading = ref(false);
|
||||
const total = ref(0);
|
||||
const filters = reactive({
|
||||
keyword: "",
|
||||
status: "",
|
||||
eventException: "",
|
||||
systemException: "",
|
||||
bizType: "",
|
||||
dateFrom: "2026-06-01",
|
||||
dateTo: "2026-06-30",
|
||||
dateFrom: "",
|
||||
dateTo: "",
|
||||
});
|
||||
|
||||
const detailOpen = ref(false);
|
||||
@@ -24,28 +50,86 @@ const closeOpen = ref(false);
|
||||
const current = ref<TicketRow | null>(null);
|
||||
const closeReason = ref("");
|
||||
|
||||
const filtered = computed(() =>
|
||||
rows.value.filter((t) => {
|
||||
const kw = filters.keyword.trim().toLowerCase();
|
||||
if (kw) {
|
||||
const hay = `${t.id}${t.initiator}${t.demandTitle}`.toLowerCase();
|
||||
if (!hay.includes(kw)) return false;
|
||||
}
|
||||
if (filters.status && t.status !== filters.status) return false;
|
||||
if (filters.eventException && t.eventException !== filters.eventException) return false;
|
||||
if (filters.systemException && t.systemException !== filters.systemException) return false;
|
||||
if (filters.bizType && t.bizType !== filters.bizType) return false;
|
||||
// 创建时间:选完日期即生效,无需再点查询按钮
|
||||
const day = (t.createdAt || "").slice(0, 10);
|
||||
if (filters.dateFrom && day && day < filters.dateFrom) return false;
|
||||
if (filters.dateTo && day && day > filters.dateTo) return false;
|
||||
return true;
|
||||
}),
|
||||
function dash(v: any) {
|
||||
const s = (v ?? "").toString().trim();
|
||||
return s ? s : "-";
|
||||
}
|
||||
|
||||
function mapTicket(t: any, quotes: any[] = [], attachments: any[] = []): TicketRow {
|
||||
return {
|
||||
id: t.workOrderNo || t.id || "",
|
||||
initiator: t.salesName || t.salesWecomId || "—",
|
||||
demandTitle: t.demandTitle || `${t.origin || ""} → ${t.destination || ""}` || "—",
|
||||
demandDetail: t.demandDetail || "",
|
||||
quotes: (quotes || []).map((q) => ({
|
||||
source: q.sourceLabel || q.source || "报价",
|
||||
price: q.priceText || "-",
|
||||
highlight: q.highlighted === 1 || q.highlighted === true,
|
||||
})),
|
||||
attachments: (attachments || []).map((a) => a.fileName || a.objectKey || "附件").filter(Boolean),
|
||||
status: t.status || "询价中",
|
||||
costPrice: dash(t.costPrice),
|
||||
salePrice: dash(t.salePrice),
|
||||
profit: dash(t.profitRate),
|
||||
eventException: dash(t.eventException),
|
||||
systemException: dash(t.systemException),
|
||||
systemExceptionReason: dash(t.systemExceptionReason),
|
||||
createdAt: (t.createTime || "").toString().slice(0, 19),
|
||||
quoteDate: dash((t.quoteDate || "").toString().slice(0, 10)),
|
||||
bizType: t.bizTypeLabel || t.businessLine || "",
|
||||
};
|
||||
}
|
||||
|
||||
async function loadList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res: any = await getWorkOrderList({
|
||||
keyword: filters.keyword.trim() || undefined,
|
||||
status: filters.status || undefined,
|
||||
eventException: filters.eventException || undefined,
|
||||
systemException: filters.systemException || undefined,
|
||||
bizType: filters.bizType || undefined,
|
||||
dateFrom: filters.dateFrom || undefined,
|
||||
dateTo: filters.dateTo || undefined,
|
||||
pageNo: 1,
|
||||
pageSize: 200,
|
||||
});
|
||||
const page = res?.records ? res : res?.result || {};
|
||||
const records = page.records || [];
|
||||
total.value = Number(page.total || records.length || 0);
|
||||
rows.value = (records as any[]).map((t) => mapTicket(t));
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || "加载工单失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
watch(
|
||||
() => ({ ...filters }),
|
||||
() => {
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(() => loadList(), 250);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
function openDetail(row: TicketRow) {
|
||||
onMounted(() => loadList());
|
||||
|
||||
async function openDetail(row: TicketRow) {
|
||||
current.value = row;
|
||||
detailOpen.value = true;
|
||||
try {
|
||||
const res: any = await getWorkOrderDetail(row.id);
|
||||
const detail = res?.result || res || {};
|
||||
const ticket = detail.ticket || detail;
|
||||
current.value = mapTicket(ticket, detail.quotes || [], detail.attachments || []);
|
||||
const idx = rows.value.findIndex((r) => r.id === row.id);
|
||||
if (idx >= 0) rows.value[idx] = current.value;
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || "加载详情失败");
|
||||
}
|
||||
}
|
||||
|
||||
function openClose(row: TicketRow) {
|
||||
@@ -54,32 +138,47 @@ function openClose(row: TicketRow) {
|
||||
closeOpen.value = true;
|
||||
}
|
||||
|
||||
function confirmClose() {
|
||||
async function confirmClose() {
|
||||
if (!current.value) return;
|
||||
current.value.status = "已关闭";
|
||||
closeOpen.value = false;
|
||||
message.success(`工单 ${current.value.id} 已关闭`);
|
||||
try {
|
||||
await transitionWorkOrder({
|
||||
workOrderNo: current.value.id,
|
||||
toStatus: "已关闭",
|
||||
remark: closeReason.value.trim() || undefined,
|
||||
});
|
||||
closeOpen.value = false;
|
||||
message.success(`工单 ${current.value.id} 已关闭`);
|
||||
await loadList();
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || "关单失败");
|
||||
}
|
||||
}
|
||||
|
||||
function retryTicket(row: TicketRow) {
|
||||
Modal.confirm({
|
||||
title: "重试系统任务?",
|
||||
content: `确认对工单 ${row.id} 发起重试?`,
|
||||
onOk() {
|
||||
message.success(`已提交重试:${row.id}`);
|
||||
async onOk() {
|
||||
try {
|
||||
await retryAbnormal({ workOrderNo: row.id });
|
||||
message.success(`已提交重试:${row.id}`);
|
||||
await loadList();
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || "重试失败");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
message.success("已刷新");
|
||||
loadList().then(() => message.success("已刷新"));
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport defer to="#page-topbar-actions">
|
||||
<div style="display: flex; align-items: center; gap: 12px">
|
||||
<button type="button" class="btn btn-secondary">
|
||||
<button type="button" class="btn btn-secondary" disabled title="导出待后续开放">
|
||||
<ProtoIcon name="download" :size="16" />导出工单
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" @click="refresh">
|
||||
@@ -173,7 +272,13 @@ function refresh() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in filtered" :key="row.id">
|
||||
<tr v-if="loading">
|
||||
<td colspan="14" style="text-align: center; color: #94a3b8">加载中…</td>
|
||||
</tr>
|
||||
<tr v-else-if="!rows.length">
|
||||
<td colspan="14" style="text-align: center; color: #94a3b8">暂无工单</td>
|
||||
</tr>
|
||||
<tr v-for="row in rows" :key="row.id">
|
||||
<td class="align-top ticket-cell col-sticky-left">
|
||||
<button class="ticket-no" type="button" @click="openDetail(row)">
|
||||
{{ row.id }}
|
||||
@@ -205,10 +310,7 @@ function refresh() {
|
||||
style="margin-bottom: 8px"
|
||||
>
|
||||
<div class="quote-source">{{ q.source }}</div>
|
||||
<div
|
||||
class="quote-price"
|
||||
:style="q.highlight ? { color: '#1d4ed8' } : undefined"
|
||||
>
|
||||
<div class="quote-price" :style="q.highlight ? { color: '#1d4ed8' } : undefined">
|
||||
{{ q.price }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -232,15 +334,11 @@ function refresh() {
|
||||
<span class="ticket-margin-cell">{{ row.profit }}</span>
|
||||
</td>
|
||||
<td class="col-center">
|
||||
<span v-if="row.eventException !== '-'" class="tag tag-rose">{{
|
||||
row.eventException
|
||||
}}</span>
|
||||
<span v-if="row.eventException !== '-'" class="tag tag-rose">{{ row.eventException }}</span>
|
||||
<span v-else style="color: #94a3b8">-</span>
|
||||
</td>
|
||||
<td class="align-top">
|
||||
<span v-if="row.systemException !== '-'" class="tag tag-rose">{{
|
||||
row.systemException
|
||||
}}</span>
|
||||
<span v-if="row.systemException !== '-'" class="tag tag-rose">{{ row.systemException }}</span>
|
||||
<span v-else style="color: #94a3b8">-</span>
|
||||
</td>
|
||||
<td class="align-top">
|
||||
@@ -269,15 +367,16 @@ function refresh() {
|
||||
>
|
||||
关闭工单
|
||||
</button>
|
||||
<button class="link-action" type="button" @click="openDetail(row)">
|
||||
查看详情
|
||||
</button>
|
||||
<button class="link-action" type="button" @click="openDetail(row)">查看详情</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="pagination">
|
||||
<span>共 <b style="color: #0f172a">{{ total }}</b> 条工单</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -288,6 +387,7 @@ function refresh() {
|
||||
<p><b>需求:</b>{{ current.demandTitle }}</p>
|
||||
<p style="white-space: pre-line">{{ current.demandDetail }}</p>
|
||||
<p><b>状态:</b>{{ current.status }}</p>
|
||||
<p><b>报价日期:</b>{{ current.quoteDate }}</p>
|
||||
<p><b>创建时间:</b>{{ current.createdAt }}</p>
|
||||
</template>
|
||||
</a-modal>
|
||||
|
||||
Reference in New Issue
Block a user