空运人工价锁舱现拉补全舱位并拦清缺参;成交/报价填表与工单陆运费率展示同步收紧。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -325,6 +325,7 @@ function removeAccount(row: AccountRow) {
|
||||
<th>账号</th>
|
||||
<th>姓名</th>
|
||||
<th>绑定角色</th>
|
||||
<th>数据范围</th>
|
||||
<th>手机号</th>
|
||||
<th>最后登录</th>
|
||||
<th>状态</th>
|
||||
@@ -333,10 +334,10 @@ function removeAccount(row: AccountRow) {
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="loading">
|
||||
<td colspan="7" style="text-align: center; color: #94a3b8">加载中…</td>
|
||||
<td colspan="8" style="text-align: center; color: #94a3b8">加载中…</td>
|
||||
</tr>
|
||||
<tr v-else-if="!rows.length">
|
||||
<td colspan="7" style="text-align: center; color: #94a3b8">暂无账号</td>
|
||||
<td colspan="8" style="text-align: center; color: #94a3b8">暂无账号</td>
|
||||
</tr>
|
||||
<tr v-for="row in rows" :key="row.id">
|
||||
<td class="account-username">{{ row.username }}</td>
|
||||
@@ -344,6 +345,20 @@ function removeAccount(row: AccountRow) {
|
||||
<td>
|
||||
<span class="tag" :class="row.roleTag">{{ row.roleLabel }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<!-- 与编辑弹窗「数据范围」同源:dashboardSelfOnly -->
|
||||
<span
|
||||
class="tag"
|
||||
:class="row.dashboardSelfOnly ? 'tag-amber' : 'tag-teal'"
|
||||
:title="
|
||||
row.dashboardSelfOnly
|
||||
? '分析看板仅统计本账号企微归属的工单'
|
||||
: '分析看板可看权限内全部数据'
|
||||
"
|
||||
>
|
||||
{{ row.dashboardSelfOnly ? "只看自己数据" : "全部数据" }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ row.phoneMasked }}</td>
|
||||
<td>{{ row.lastLogin }}</td>
|
||||
<td>
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
/**
|
||||
* 询价后台登录页:视觉 1:1 对齐 admin/LoginView(居中白卡片 + 品牌区,无验证码)。
|
||||
* 鉴权走 Jeecg loginApi;主账 inquiry 配置关闭图形验证码(jeecg.loginCaptchaEnabled=false)。
|
||||
*
|
||||
* 禁止写死 admin/123456:退出后再进登录页会误显示测试账号。
|
||||
* 仅记住上次成功登录的账号(不存密码),方便换人登录时改账号。
|
||||
*/
|
||||
import { reactive, ref, toRaw } from 'vue';
|
||||
import { ConfigProvider, message } from 'ant-design-vue';
|
||||
@@ -10,11 +13,29 @@
|
||||
import '/@/views/inquiry/styles/admin-prototype.css';
|
||||
import '/@/views/inquiry/styles/admin-fidelity.css';
|
||||
|
||||
const LAST_USERNAME_KEY = 'inquiry_admin_last_username';
|
||||
|
||||
function readLastUsername(): string {
|
||||
try {
|
||||
return String(localStorage.getItem(LAST_USERNAME_KEY) || '').trim();
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function rememberUsername(username: string) {
|
||||
try {
|
||||
localStorage.setItem(LAST_USERNAME_KEY, username);
|
||||
} catch {
|
||||
/* 隐私模式等写失败时忽略 */
|
||||
}
|
||||
}
|
||||
|
||||
const userStore = useUserStore();
|
||||
const loading = ref(false);
|
||||
const form = reactive({
|
||||
username: 'admin',
|
||||
password: '123456',
|
||||
username: readLastUsername(),
|
||||
password: '',
|
||||
});
|
||||
|
||||
const themeToken = {
|
||||
@@ -36,9 +57,10 @@
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const username = form.username.trim();
|
||||
const { userInfo } = await userStore.login(
|
||||
toRaw({
|
||||
username: form.username.trim(),
|
||||
username,
|
||||
password: form.password,
|
||||
// 主账已关验证码;仍传空字段以兼容 LoginParams 类型
|
||||
captcha: '',
|
||||
@@ -48,6 +70,9 @@
|
||||
})
|
||||
);
|
||||
if (userInfo) {
|
||||
rememberUsername(username);
|
||||
// 成功后清空密码,避免浏览器后退/再进登录页时残留明文
|
||||
form.password = '';
|
||||
message.success(`登录成功,欢迎 ${userInfo.realname || userInfo.username}`);
|
||||
}
|
||||
} catch (e: any) {
|
||||
|
||||
@@ -342,7 +342,7 @@ export const mockReplyCategories: ReplyCategory[] = [
|
||||
id: "brv10000000000000000000000000041",
|
||||
replyCode: "lost.reason_ack",
|
||||
trigger: "销售回复未成交原因",
|
||||
text: "已记下未成交原因:{{lost_reason}}。",
|
||||
text: "已将未成交原因“{{lost_reason}}”记录在后台,工单 {{ticket_no}}已关闭,智能体不再跟进该工单,请悉知。",
|
||||
enabled: true,
|
||||
}
|
||||
],
|
||||
|
||||
@@ -23,13 +23,16 @@ body {
|
||||
grid-template-columns: 220px minmax(0, 1fr) !important;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow-x: hidden;
|
||||
/* 锁视口:避免 html overflow:hidden 裁切后整页既无文档滚、内容区又不内滚 */
|
||||
height: 100vh !important;
|
||||
max-height: 100vh !important;
|
||||
min-height: 0 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.main-stage {
|
||||
background: #f7f8fb !important;
|
||||
min-width: 0 !important;
|
||||
overflow-x: hidden;
|
||||
/* 主区锁视口高度:列表页表格区内滚;看板等非列表页在 .admin-content 内滚 */
|
||||
height: 100vh !important;
|
||||
max-height: 100vh !important;
|
||||
@@ -43,10 +46,16 @@ body {
|
||||
padding: 16px 16px 40px !important;
|
||||
gap: 12px !important;
|
||||
min-width: 0 !important;
|
||||
overflow-x: hidden;
|
||||
flex: 1 1 auto !important;
|
||||
/*
|
||||
* flex-basis:0:先按剩余视口占高,再在区内滚动。
|
||||
* 若用 1 1 auto,内容区会先被看板撑满,再被 .main-stage 裁掉 → 右侧无滚动条、滚轮无效。
|
||||
*/
|
||||
flex: 1 1 0 !important;
|
||||
min-height: 0 !important;
|
||||
overflow: auto !important;
|
||||
height: 0 !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto !important;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1070,6 +1079,9 @@ h6 {
|
||||
.admin-content:has(.ref-surface--metrics) {
|
||||
padding: 14px 14px 36px !important;
|
||||
gap: 10px !important;
|
||||
/* 分析看板:明确在内容区竖滚,避免被列表页 overflow:hidden 规则误伤 */
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
/* ---- 话术 ---- */
|
||||
|
||||
@@ -253,9 +253,88 @@ function parseQuoteFees(q: any): TicketQuoteFee[] {
|
||||
pushRows(asFeeArray(payload[key] || payload[bucket]));
|
||||
}
|
||||
}
|
||||
// 陆运 TMS:燃油附加费率 / 税率挂在线路段上,不是金额费目行。
|
||||
for (const extra of parseLandRateFees(payload)) {
|
||||
if (!out.some((x) => x.name === extra.name && x.amount === extra.amount)) {
|
||||
out.push(extra);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 陆运线路:fuelSurchargeRate=燃油附加费率;rate=税率;rateFlag=是否含税(1是0否)。
|
||||
* 只读 payload,不写库。海运/空运没有这些键则不出行。
|
||||
*/
|
||||
function parseLandRateFees(payload: Record<string, any>): TicketQuoteFee[] {
|
||||
const rows = asFeeArray(payload.route_rows || payload.landSegments || payload.land_segments);
|
||||
const out: TicketQuoteFee[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const row of rows) {
|
||||
if (!row || typeof row !== "object") continue;
|
||||
const fuel = formatLandPercent(row.fuel_surcharge_rate ?? row.fuelSurchargeRate);
|
||||
const tax = formatLandTaxRate(row.tax_rate ?? row.taxRate ?? row.rate);
|
||||
const included = formatLandTaxIncluded(
|
||||
row.tax_included ?? row.rateFlag ?? row.rate_flag
|
||||
);
|
||||
if (fuel) {
|
||||
const key = `fuel:${fuel}`;
|
||||
if (!seen.has(key)) {
|
||||
seen.add(key);
|
||||
out.push({ name: "燃油附加费率", amount: fuel });
|
||||
}
|
||||
}
|
||||
if (tax) {
|
||||
const key = `tax:${tax}`;
|
||||
if (!seen.has(key)) {
|
||||
seen.add(key);
|
||||
out.push({ name: "税率", amount: tax });
|
||||
}
|
||||
}
|
||||
if (included) {
|
||||
const key = `inc:${included}`;
|
||||
if (!seen.has(key)) {
|
||||
seen.add(key);
|
||||
out.push({ name: "是否含税", amount: included });
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 燃油附加费率:数字带 %。 */
|
||||
function formatLandPercent(raw: unknown): string {
|
||||
const s = String(raw ?? "").trim();
|
||||
if (!s || s === "-" || s.toLowerCase() === "null") return "";
|
||||
if (s.includes("%")) return s;
|
||||
if (/^-?\d+(\.\d+)?$/.test(s)) {
|
||||
return `${s.replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "")}%`;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/** 税率 rate:0.09→9%;大于 1 按百分点。 */
|
||||
function formatLandTaxRate(raw: unknown): string {
|
||||
const s = String(raw ?? "").trim();
|
||||
if (!s || s === "-" || s.toLowerCase() === "null") return "";
|
||||
if (s.includes("%")) return s;
|
||||
if (!/^-?\d+(\.\d+)?$/.test(s)) return s;
|
||||
const n = Number(s);
|
||||
if (!Number.isFinite(n)) return s;
|
||||
const abs = Math.abs(n);
|
||||
const pct = abs > 0 && abs <= 1 ? n * 100 : n;
|
||||
return `${String(pct).replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "")}%`;
|
||||
}
|
||||
|
||||
/** rateFlag:1=含税,0=不含税。 */
|
||||
function formatLandTaxIncluded(raw: unknown): string {
|
||||
const s = String(raw ?? "").trim();
|
||||
if (!s || s === "-" || s.toLowerCase() === "null") return "";
|
||||
if (s === "1" || s.toLowerCase() === "true" || s === "是" || s === "含税") return "含税";
|
||||
if (s === "0" || s.toLowerCase() === "false" || s === "否" || s === "不含税") return "不含税";
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* 主账 inq_ticket_status_log.action 是内部码,禁止当备注展示。
|
||||
* transition 与「已报价」等 toStatus 重复,无用户备注时不再跟一段英文。
|
||||
|
||||
Reference in New Issue
Block a user