报价模板下线分类字段,匹配只靠业务类型与关键词;并补后台站点标题配置。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
linmianxun
2026-09-15 14:31:52 +08:00
co-authored by Cursor
parent 0a3c74e3a1
commit 235ecd8c83
10 changed files with 45 additions and 117 deletions
+6
View File
@@ -1,6 +1,12 @@
# 是否启用mock(生产必须 false,否则会走本地 Mock 像旧骨架)
VITE_USE_MOCK = false
# 网站标题(浏览器标签后缀;缺省会显示「页面名 undefined」)
VITE_GLOB_APP_TITLE = 友通达询价系统
# 简称,用于生产 _app.config.js 变量名;勿空格/数字开头
VITE_GLOB_APP_SHORT_NAME = YtdInquiryAdmin
# 发布路径
VITE_PUBLIC_PATH = /
+3 -1
View File
@@ -28,7 +28,9 @@ export function useTitle() {
}
const tTitle = t(route?.meta?.title as string);
pageTitle.value = tTitle ? ` ${tTitle} - ${title} ` : `${title}`;
// 生产 _app.config.js 若漏打 VITE_GLOB_APP_TITLE,title 会是 undefined → 标签出现「报价模板 undefined」
const appTitle = title || '友通达询价系统';
pageTitle.value = tTitle ? `${tTitle} - ${appTitle}` : `${appTitle}`;
},
{ immediate: true }
);
@@ -3,11 +3,14 @@
* placeholderCount 语义改为 mappingPointCount(映射点数)。
*/
/**
* 列表行类型。分类字段已从运营页移除(筛选项/列/表单均不展示);
* 主账库仍可存 category,由服务端按业务默认,前端不再读写。
*/
export type TemplateRow = {
id: string;
name: string;
bizType: "空运" | "海运" | "陆运";
category: string;
fileLabel: string;
fileName: string;
format: "excel" | "pdf";
@@ -28,34 +31,6 @@ export type TemplateRow = {
objectKey?: string;
};
/** 业务 → 分类(与 contracts/quote-template-mapping-v1.json 一致) */
export const CATEGORY_BY_BIZ: Record<TemplateRow["bizType"], string[]> = {
海运: ["海运报价单模板"],
空运: ["空运到港报价单模板", "空运到门报价单模板"],
陆运: [
"双清或中越",
"国内整车",
"国内拼车",
"跨境整车",
"跨境集拼",
"中港",
"凭祥保税仓&普仓仓储",
],
};
export const ALL_CATEGORIES = [
"空运到港报价单模板",
"空运到门报价单模板",
"海运报价单模板",
"双清或中越",
"国内整车",
"国内拼车",
"跨境整车",
"跨境集拼",
"中港",
"凭祥保税仓&普仓仓储",
];
/** 将主账 list 记录规范为页面行 */
export function mapApiRow(raw: any): TemplateRow {
const created =
@@ -68,7 +43,6 @@ export function mapApiRow(raw: any): TemplateRow {
id: String(raw.id || ""),
name: raw.name || "",
bizType: (raw.bizType || "海运") as TemplateRow["bizType"],
category: raw.category || "",
fileLabel: raw.fileLabel || raw.name || "",
fileName: raw.fileName || "",
format: raw.format === "pdf" ? "pdf" : "excel",
@@ -7,12 +7,7 @@
*/
import { computed, onMounted, reactive, ref } from "vue";
import { message } from "ant-design-vue";
import {
ALL_CATEGORIES,
CATEGORY_BY_BIZ,
mapApiRow,
type TemplateRow,
} from "/@/views/inquiry/mock/templates";
import { mapApiRow, type TemplateRow } from "/@/views/inquiry/mock/templates";
import ProtoIcon from "/@/views/inquiry/components/ProtoIcon.vue";
import {
disableTemplate,
@@ -47,7 +42,6 @@ const rows = ref<TemplateRow[]>([]);
const loading = ref(false);
const keyword = ref("");
const bizFilter = ref("");
const categoryFilter = ref("");
const statusFilter = ref("");
const formOpen = ref(false);
@@ -59,10 +53,10 @@ const previewVm = ref<PreviewViewModel>(emptyPreviewViewModel());
const fileInputRef = ref<HTMLInputElement | null>(null);
const pendingFile = ref<File | null>(null);
/** 运营表单不含「分类」:主账上传时按业务自行补默认分类 */
const form = reactive({
name: "",
bizType: "海运" as TemplateRow["bizType"],
category: "",
mode: "auto" as "auto" | "manual",
keywordItems: [""] as string[],
isDefault: false,
@@ -98,10 +92,6 @@ const previewTotalText = computed(() => {
return `${vm.currency || "USD"} ${amt}`;
});
function defaultCategoryForBiz(biz: TemplateRow["bizType"]) {
return (CATEGORY_BY_BIZ[biz] || [])[0] || "";
}
const filtered = computed(() => rows.value);
function bizTag(biz: string) {
@@ -110,13 +100,18 @@ function bizTag(biz: string) {
return "tag-blue";
}
/** 列表展示用:关键词用顿号拼接;空则显示占位 */
function formatKeywords(keywords: string[] | undefined) {
if (!keywords?.length) return "";
return keywords.map((k) => String(k || "").trim()).filter(Boolean).join("、");
}
async function loadList() {
loading.value = true;
try {
const res: any = await getTemplateList({
keyword: keyword.value.trim() || undefined,
bizType: bizFilter.value || undefined,
category: categoryFilter.value || undefined,
status: statusFilter.value || undefined,
pageNo: 1,
pageSize: 200,
@@ -139,7 +134,6 @@ function resetForm() {
Object.assign(form, {
name: "",
bizType: "海运" as TemplateRow["bizType"],
category: defaultCategoryForBiz("海运"),
mode: "auto" as const,
keywordItems: [""],
isDefault: false,
@@ -165,7 +159,6 @@ function openEdit(row: TemplateRow) {
Object.assign(form, {
name: row.name,
bizType: row.bizType,
category: row.category || defaultCategoryForBiz(row.bizType),
mode: row.mode || "auto",
keywordItems: row.keywords.length ? [...row.keywords] : [""],
isDefault: Boolean(row.isDefault),
@@ -318,8 +311,7 @@ async function submitForm() {
message.warning("请至少填写一个关键词");
return;
}
// 分类/模式不对运营展示:分类按业务默认,模式新建默认 auto,编辑保留原值
const category = defaultCategoryForBiz(form.bizType);
// 模式不对运营展示:新建默认 auto,编辑保留原值;分类交主账按业务补默认
const mode = editing.value?.mode || "auto";
try {
@@ -328,7 +320,6 @@ async function submitForm() {
if (editing.value?.id) fd.append("templateId", editing.value.id);
fd.append("name", form.name.trim());
fd.append("bizType", form.bizType);
fd.append("category", category);
fd.append("keywordsJson", JSON.stringify(keywords));
fd.append("mode", mode);
fd.append("isDefault", String(form.isDefault));
@@ -340,7 +331,6 @@ async function submitForm() {
id: editing.value.id,
name: form.name.trim(),
bizType: form.bizType,
category,
keywordsJson: JSON.stringify(keywords),
mode,
isDefault: form.isDefault ? 1 : 0,
@@ -398,10 +388,6 @@ async function downloadPreviewOrOriginal() {
<option value="海运">海运</option>
<option value="陆运">陆运</option>
</select>
<select v-model="categoryFilter" @change="loadList">
<option value="">全部分类</option>
<option v-for="c in ALL_CATEGORIES" :key="c" :value="c">{{ c }}</option>
</select>
<select v-model="statusFilter" @change="loadList">
<option value="">全部状态</option>
<option value="enabled">启用</option>
@@ -415,8 +401,7 @@ async function downloadPreviewOrOriginal() {
<tr>
<th>模板名称</th>
<th>业务</th>
<th>分类</th>
<th>模板文件</th>
<th>关键词</th>
<th>解析状态</th>
<th>状态</th>
<th>总使用次数</th>
@@ -434,8 +419,9 @@ async function downloadPreviewOrOriginal() {
<td>
<span class="tag" :class="bizTag(row.bizType)">{{ row.bizType }}</span>
</td>
<td class="qt-col-muted">{{ row.category }}</td>
<td class="qt-col-file">{{ row.fileLabel }}</td>
<td class="qt-col-keywords" :title="formatKeywords(row.keywords)">
{{ formatKeywords(row.keywords) || "—" }}
</td>
<td class="qt-col-muted">{{ row.parseStatus }}</td>
<td class="qt-col-muted">{{ row.status === "enabled" ? "启用" : "禁用" }}</td>
<td class="qt-col-count">{{ row.useCount }} 次</td>
@@ -732,10 +718,14 @@ async function downloadPreviewOrOriginal() {
font-size: 11px;
color: #334155;
}
.qt-col-file {
.qt-col-keywords {
font-size: 11px;
font-weight: 700;
font-weight: 600;
color: #334155;
max-width: 220px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.qt-col-count {
font-size: 11px;