员工岗位可重新启用;调价认空运航线;会话存档跳号补拉;陆运报价按车型列回填

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
linmianxun
2026-10-28 19:48:47 +08:00
co-authored by Cursor
parent 2b528758b4
commit 56ba8e93b5
62 changed files with 2960 additions and 793 deletions
@@ -129,31 +129,6 @@ export function createPermissionGuard(router: Router) {
}
if (permissionStore.getIsDynamicAddedRoute) {
// #region agent log
fetch('http://127.0.0.1:7800/ingest/a69bf0d0-32e3-486e-80eb-ae1824ba17a1', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': 'a0b47d' },
body: JSON.stringify({
sessionId: 'a0b47d',
runId: 'pre-fix',
hypothesisId: 'H4-H5',
location: 'permissionGuard.ts:alreadyAdded',
message: '动态路由已添加,直接放行',
data: {
toPath: to.path,
toName: String(to.name || ''),
matched: (to.matched || []).map((m) => ({ path: m.path, name: String(m.name || '') })),
hasDashRoute: router.getRoutes().some((r) => String(r.path).includes('inquiry/dashboard') || String(r.name) === 'inquiry-dashboard'),
routeNamesSample: router
.getRoutes()
.map((r) => String(r.name || ''))
.filter((n) => n.includes('inquiry'))
.slice(0, 20),
},
timestamp: Date.now(),
}),
}).catch(() => {});
// #endregion
next();
return;
}
@@ -168,32 +143,6 @@ export function createPermissionGuard(router: Router) {
permissionStore.setDynamicAddedRoute(true);
// #region agent log
fetch('http://127.0.0.1:7800/ingest/a69bf0d0-32e3-486e-80eb-ae1824ba17a1', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': 'a0b47d' },
body: JSON.stringify({
sessionId: 'a0b47d',
runId: 'pre-fix',
hypothesisId: 'H3-H5',
location: 'permissionGuard.ts:afterAddRoute',
message: '刚动态加路由后的匹配情况',
data: {
toPath: to.path,
toName: String(to.name || ''),
toIs404: to.name === PAGE_NOT_FOUND_NAME,
hasDashRoute: router.getRoutes().some((r) => String(r.path).includes('inquiry/dashboard') || String(r.name) === 'inquiry-dashboard'),
inquiryRoutes: router
.getRoutes()
.filter((r) => String(r.path).includes('inquiry') || String(r.name || '').includes('inquiry'))
.map((r) => ({ path: r.path, name: String(r.name || '') }))
.slice(0, 30),
},
timestamp: Date.now(),
}),
}).catch(() => {});
// #endregion
if (to.name === PAGE_NOT_FOUND_NAME) {
// 动态添加路由后,此处应当重定向到fullPath,否则会加载404页面内容
next({ path: to.fullPath, replace: true, query: to.query });
@@ -461,79 +461,8 @@ export const usePermissionStore = defineStore({
}
// 只纠正/补挂「已有菜单或已有查看权限」的手写页,禁止灌入未授权路由
const permCodes = this.getPermCodeList || [];
// #region agent log
const _dbgCollectPaths = (list: AppRouteRecordRaw[] = [], out: string[] = []): string[] => {
for (const item of list || []) {
out.push(String(item.path || ''));
if (item.children?.length) _dbgCollectPaths(item.children as AppRouteRecordRaw[], out);
}
return out;
};
const _dbgPathsBefore = _dbgCollectPaths(routeList || []);
const _dbgHasDashPerm = (permCodes as any[]).map(String).includes('inquiry:dashboard:view');
fetch('http://127.0.0.1:7800/ingest/a69bf0d0-32e3-486e-80eb-ae1824ba17a1', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': 'a0b47d' },
body: JSON.stringify({
sessionId: 'a0b47d',
runId: 'pre-fix',
hypothesisId: 'H1-H2',
location: 'permission.ts:buildRoutesAction:beforeEnsure',
message: '菜单拉取后、ensure/prune 前',
data: {
menuCount: (routeList || []).length,
pathCount: _dbgPathsBefore.length,
hasDashboardPath: _dbgPathsBefore.some((p) => p.includes('inquiry/dashboard')),
hasDashPerm: _dbgHasDashPerm,
permCount: (permCodes as any[]).length,
samplePerms: (permCodes as any[]).map(String).filter((c) => c.includes('inquiry')).slice(0, 20),
samplePaths: _dbgPathsBefore.filter((p) => p.includes('inquiry')).slice(0, 20),
},
timestamp: Date.now(),
}),
}).catch(() => {});
// #endregion
routeList = ensureInquiryHandwrittenRoutes(routeList || [], permCodes);
// #region agent log
const _dbgAfterEnsure = _dbgCollectPaths(routeList || []);
fetch('http://127.0.0.1:7800/ingest/a69bf0d0-32e3-486e-80eb-ae1824ba17a1', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': 'a0b47d' },
body: JSON.stringify({
sessionId: 'a0b47d',
runId: 'pre-fix',
hypothesisId: 'H2',
location: 'permission.ts:buildRoutesAction:afterEnsure',
message: 'ensureInquiryHandwrittenRoutes 后',
data: {
hasDashboardPath: _dbgAfterEnsure.some((p) => p.includes('inquiry/dashboard')),
inquiryPaths: _dbgAfterEnsure.filter((p) => p.includes('inquiry')).slice(0, 30),
},
timestamp: Date.now(),
}),
}).catch(() => {});
// #endregion
routeList = pruneInquiryLeavesWithoutViewPerm(routeList, permCodes);
// #region agent log
const _dbgAfterPrune = _dbgCollectPaths(routeList || []);
fetch('http://127.0.0.1:7800/ingest/a69bf0d0-32e3-486e-80eb-ae1824ba17a1', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': 'a0b47d' },
body: JSON.stringify({
sessionId: 'a0b47d',
runId: 'pre-fix',
hypothesisId: 'H1',
location: 'permission.ts:buildRoutesAction:afterPrune',
message: 'pruneInquiryLeavesWithoutViewPerm 后',
data: {
hasDashboardPath: _dbgAfterPrune.some((p) => p.includes('inquiry/dashboard')),
inquiryPaths: _dbgAfterPrune.filter((p) => p.includes('inquiry')).slice(0, 30),
prunedAway: _dbgAfterEnsure.includes('/inquiry/dashboard') && !_dbgAfterPrune.some((p) => p.includes('inquiry/dashboard')),
},
timestamp: Date.now(),
}),
}).catch(() => {});
// #endregion
// 补挂后再标一次(ensure 新建的父级也要带 Vue3 标记)
routeList = normalizeVue3MenuMarkers(routeList);
// 组件地址前加斜杠处理 author: lsq date:2021-09-08
@@ -550,33 +479,6 @@ export const usePermissionStore = defineStore({
routeList = routeList.filter(routeRemoveIgnoreFilter);
routeList = flatMultiLevelRoutes(routeList);
// #region agent log
const _dbgFinal = _dbgCollectPaths(routeList || []);
const _dbgTopComp = (routeList || []).slice(0, 5).map((r) => ({
path: r.path,
name: String(r.name || ''),
compType: typeof r.component,
childN: r.children?.length || 0,
childPaths: (r.children || []).slice(0, 8).map((c) => c.path),
}));
fetch('http://127.0.0.1:7800/ingest/a69bf0d0-32e3-486e-80eb-ae1824ba17a1', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': 'a0b47d' },
body: JSON.stringify({
sessionId: 'a0b47d',
runId: 'pre-fix',
hypothesisId: 'H3',
location: 'permission.ts:buildRoutesAction:afterFlat',
message: 'transform+flat 后最终路由树',
data: {
hasDashboardPath: _dbgFinal.some((p) => p.includes('inquiry/dashboard')),
inquiryPaths: _dbgFinal.filter((p) => p.includes('inquiry')).slice(0, 40),
top: _dbgTopComp,
},
timestamp: Date.now(),
}),
}).catch(() => {});
// #endregion
routes = [PAGE_NOT_FOUND_ROUTE, ...routeList];
break;
}
@@ -3,7 +3,7 @@
* 分析看板:对接主账 /inquiry/dashboard/metrics。
* 指标 / 询价趋势 / 人员排名 / 成本趋势均来自主账聚合;禁止用 Mock 假线冒充生产。
*/
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
import { computed, onMounted, onUnmounted, ref } from "vue";
import { message } from "ant-design-vue";
import ProtoIcon from "/@/views/inquiry/components/ProtoIcon.vue";
import { getDashboardMetrics } from "/@/views/inquiry/dashboard/dashboard.api";
@@ -100,28 +100,6 @@ async function loadMetrics() {
const res: any = await getDashboardMetrics(req);
const data = res?.result || res || {};
todayMetrics.value = (data.metrics || []) as MetricCard[];
// #region agent log
fetch("http://127.0.0.1:7800/ingest/a69bf0d0-32e3-486e-80eb-ae1824ba17a1", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "7b2ee5" },
body: JSON.stringify({
sessionId: "7b2ee5",
runId: "post-fix",
hypothesisId: "THOUSAND_SEP",
location: "dashboard/index.vue:loadMetrics",
message: "指标千分位展示抽样",
data: {
samples: todayMetrics.value.slice(0, 9).map((m) => ({
key: m.key,
raw: m.value,
display: formatMetricDisplay(m),
isTime: !!m.isTime,
})),
},
timestamp: Date.now(),
}),
}).catch(() => {});
// #endregion
inquiryTrend.value = ((data.trend || []) as any[]).map((p) => ({
date: String(p.date || ""),
value: Number(p.value || 0),
@@ -383,92 +361,6 @@ function onDocClick(e: MouseEvent) {
if (!t.closest(".ref-route-filter")) routeFilterOpen.value = false;
}
/**
* 调试:打开线路筛选时采集弹层/chips/祖先裁切与可滚尺寸。
*/
function probeRouteFilterMenu(runId: string) {
// #region agent log
const menu = document.querySelector(".ref-route-filter__menu") as HTMLElement | null;
const chips = document.querySelector(".ref-route-filter__chips") as HTMLElement | null;
const filter = document.querySelector(".ref-route-filter") as HTMLElement | null;
if (!menu || !chips) {
fetch("http://127.0.0.1:7800/ingest/a69bf0d0-32e3-486e-80eb-ae1824ba17a1", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "7b2ee5" },
body: JSON.stringify({
sessionId: "7b2ee5",
runId,
hypothesisId: "H2",
location: "dashboard/index.vue:probeRouteFilterMenu",
message: "弹层或 chips 未找到",
data: { hasMenu: !!menu, hasChips: !!chips },
timestamp: Date.now(),
}),
}).catch(() => {});
return;
}
const menuCs = getComputedStyle(menu);
const chipsCs = getComputedStyle(chips);
const menuRect = menu.getBoundingClientRect();
const chipsRect = chips.getBoundingClientRect();
const clipAncestors: Array<{ tag: string; cls: string; overflow: string; overflowY: string; rectBottom: number }> = [];
let node: HTMLElement | null = filter;
while (node && node !== document.body) {
const cs = getComputedStyle(node);
if (cs.overflow !== "visible" || cs.overflowY !== "visible" || cs.overflowX !== "visible") {
clipAncestors.push({
tag: node.tagName.toLowerCase(),
cls: (node.className || "").toString().slice(0, 80),
overflow: cs.overflow,
overflowY: cs.overflowY,
rectBottom: Math.round(node.getBoundingClientRect().bottom),
});
}
node = node.parentElement;
}
const spaceBelow = Math.round(window.innerHeight - menuRect.top);
fetch("http://127.0.0.1:7800/ingest/a69bf0d0-32e3-486e-80eb-ae1824ba17a1", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "7b2ee5" },
body: JSON.stringify({
sessionId: "7b2ee5",
runId,
hypothesisId: "H1-H5",
location: "dashboard/index.vue:probeRouteFilterMenu",
message: "线路筛选弹层尺寸与溢出",
data: {
routeCount: costRoutes.value.length,
menu: {
h: Math.round(menuRect.height),
bottom: Math.round(menuRect.bottom),
overflow: menuCs.overflow,
overflowY: menuCs.overflowY,
maxHeight: menuCs.maxHeight,
},
chips: {
h: Math.round(chipsRect.height),
clientH: chips.clientHeight,
scrollH: chips.scrollHeight,
canScroll: chips.scrollHeight > chips.clientHeight + 1,
overflow: chipsCs.overflow,
overflowY: chipsCs.overflowY,
maxHeight: chipsCs.maxHeight,
},
viewport: { innerH: window.innerHeight, spaceBelow, menuClippedByViewport: menuRect.bottom > window.innerHeight + 1 },
clipAncestors,
},
timestamp: Date.now(),
}),
}).catch(() => {});
// #endregion
}
watch(routeFilterOpen, async (open) => {
if (!open) return;
await nextTick();
requestAnimationFrame(() => probeRouteFilterMenu("post-fix"));
});
const inquiryWrap = ref<HTMLElement | null>(null);
const costWrap = ref<HTMLElement | null>(null);
@@ -119,29 +119,6 @@ export async function downloadAuthGet(
const cd = res.headers.get('content-disposition') || '';
const name = parseDownloadFilename(cd, fallbackName);
// #region agent log
fetch('http://127.0.0.1:7800/ingest/a69bf0d0-32e3-486e-80eb-ae1824ba17a1', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': '489366' },
body: JSON.stringify({
sessionId: '489366',
runId: 'post-fix',
hypothesisId: 'FN1',
location: 'inquiryAuthDownload.ts:downloadAuthGet',
message: '导出文件名解析',
data: {
path,
cd,
fallbackName,
resolvedName: name,
hasStar: /filename\*=/i.test(cd),
hasCjk: /[\u4e00-\u9fff]/.test(name),
},
timestamp: Date.now(),
}),
}).catch(() => {});
// #endregion
const href = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = href;
@@ -9,6 +9,7 @@ import { computed, onMounted, reactive, ref, watch } from "vue";
import { message, Modal } from "ant-design-vue";
import {
disableStaff,
enableStaff,
getStaffCounts,
getStaffDepartments,
getStaffList,
@@ -465,6 +466,25 @@ function removeStaff(row: StaffRow) {
});
}
/**
* 列表「启用」:停用行恢复 active,权限与停用相同。
*/
function activateStaff(row: StaffRow) {
Modal.confirm({
title: `启用员工 ${row.name}?`,
content: "确认后状态设为启用,可重新参与询价匹配。",
async onOk() {
try {
await enableStaff(row.id);
message.success("已启用");
await loadList();
} catch (e: any) {
message.error(e?.message || "启用失败");
}
},
});
}
async function syncWecom() {
if (syncing.value) return;
syncing.value = true;
@@ -615,6 +635,14 @@ function filterRouteOption(input: string, option: any) {
>
停用
</button>
<button
v-if="canDisableStaff() && row.status === 'disabled'"
class="link-action"
type="button"
@click="activateStaff(row)"
>
启用
</button>
</div>
</td>
</tr>
@@ -11,6 +11,7 @@ enum Api {
add = '/inquiry/staffRole/add',
edit = '/inquiry/staffRole/edit',
disable = '/inquiry/staffRole/disable',
enable = '/inquiry/staffRole/enable',
queryById = '/inquiry/staffRole/queryById',
syncWecom = '/inquiry/staffRole/syncWecom',
/** 旧项目同名接口:部门短名全量 */
@@ -22,7 +23,17 @@ export const getStaffList = (params) => defHttp.get({ url: Api.list, params });
export const getStaffCounts = () => defHttp.get({ url: Api.counts });
export const saveStaff = (data, isUpdate: boolean) =>
isUpdate ? defHttp.put({ url: Api.edit, data }) : defHttp.post({ url: Api.add, data });
export const disableStaff = (id: string) => defHttp.post({ url: Api.disable, params: { id } });
/**
* 停用员工。主账 @RequestParam id。
* 非 GET 时 axios 会把 params 挪进 JSON 体,必须 joinParamsToUrl 拼到查询串。
*/
export const disableStaff = (id: string) =>
defHttp.post({ url: Api.disable, params: { id } }, { joinParamsToUrl: true });
/**
* 重新启用员工。与停用同权限、同参数拼法。
*/
export const enableStaff = (id: string) =>
defHttp.post({ url: Api.enable, params: { id } }, { joinParamsToUrl: true });
export const getStaffById = (id: string) => defHttp.get({ url: Api.queryById, params: { id } });
/** 主账拉企微通讯录 upsert;返回 { fetched, created, updated, skipped, message } */
export const syncWecomStaff = () => defHttp.post({ url: Api.syncWecom });
@@ -203,29 +203,6 @@ async function openPreview(row: TemplateRow) {
previewState.isImage = contentType.includes("image/") || blob.type.includes("image/");
previewState.objectUrl = URL.createObjectURL(blob);
previewState.loading = false;
// #region agent log
const payload = {
sessionId: "b4fa63",
runId: "post-fix",
hypothesisId: "H10",
location: "index.vue:openPreview",
message: "snapshot preview loaded",
data: {
versionId: row.versionId,
name: row.name,
isImage: previewState.isImage,
blobSize: blob.size,
contentType,
mappedCount: previewState.mappedCount,
},
timestamp: Date.now(),
};
fetch("http://127.0.0.1:7800/ingest/a69bf0d0-32e3-486e-80eb-ae1824ba17a1", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "b4fa63" },
body: JSON.stringify(payload),
}).catch(() => {});
// #endregion
} catch (e: any) {
previewState.loading = false;
previewState.error = e?.message || "原表截图尚未就绪,请稍后重试或下载原模板";
@@ -48,28 +48,6 @@
const statusMapRef = ref(new Map<string | number, MapValue>());
const { query } = useRoute();
const route = useRoute();
// #region agent log
fetch('http://127.0.0.1:7800/ingest/a69bf0d0-32e3-486e-80eb-ae1824ba17a1', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': 'a0b47d' },
body: JSON.stringify({
sessionId: 'a0b47d',
runId: 'pre-fix',
hypothesisId: 'H5',
location: 'Exception.vue:setup',
message: '进入 ErrorPage(前端路由未匹配)',
data: {
fullPath: route.fullPath,
path: route.path,
name: String(route.name || ''),
statusProp: props.status,
queryStatus: query?.status,
},
timestamp: Date.now(),
}),
}).catch(() => {});
// #endregion
const go = useGo();
const redo = useRedo();
const { t } = useI18n();