dev #1

Merged
kx_guo merged 31 commits from dev into main 2026-09-21 10:50:45 +08:00
2 changed files with 153 additions and 19 deletions
Showing only changes of commit d05be62e02 - Show all commits

View File

@ -403,14 +403,6 @@ function generateReport() {
>
查看标准详情
</button>
<button
v-if="card.type === 'solution'"
type="button"
disabled
title="功能开发中"
>
📌 收藏方案
</button>
<button
v-if="card.type === 'solution'"
type="button"

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from "vue";
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { fetchSupplierOptions } from "@/api/suppliers";
@ -10,6 +10,11 @@ const router = useRouter();
const route = useRoute();
const supplierNames = ref<string[]>([]);
const selected = ref("");
const supplierQuery = ref("");
const supplierResults = ref<string[]>([]);
const supplierDropdownOpen = ref(false);
const supplierSearching = ref(false);
let supplierSearchTimer: ReturnType<typeof setTimeout> | undefined;
const profile = ref<SupplierProfile | null>(null);
const loading = ref(false);
const error = ref("");
@ -89,11 +94,64 @@ async function loadOptions() {
typeof route.query.supplier === "string"
? route.query.supplier
: (response.data.data?.defaultSupplier ?? supplierNames.value[0] ?? "");
supplierQuery.value = selected.value;
supplierResults.value = supplierNames.value;
if (selected.value) await loadProfile();
} catch {
error.value = "供应商列表加载失败,请重试。";
}
}
function openSupplierDropdown() {
supplierDropdownOpen.value = true;
if (!supplierQuery.value || supplierQuery.value === selected.value) {
supplierResults.value = supplierNames.value;
}
}
function searchSuppliers(value: string) {
if (supplierSearchTimer) clearTimeout(supplierSearchTimer);
const keyword = value.trim();
if (!keyword) {
supplierResults.value = supplierNames.value;
supplierSearching.value = false;
return;
}
const local = supplierNames.value.filter((name) => name.includes(keyword));
supplierResults.value = local;
supplierSearching.value = true;
supplierSearchTimer = setTimeout(async () => {
try {
const response = await fetchSupplierOptions(keyword);
supplierResults.value =
response.data.data?.items.map((item) => item.name) ?? [];
} catch {
supplierResults.value = local;
} finally {
supplierSearching.value = false;
}
}, 180);
}
function onSupplierInput(event: Event) {
supplierQuery.value = (event.target as HTMLInputElement).value;
supplierDropdownOpen.value = true;
searchSuppliers(supplierQuery.value);
}
function selectSupplier(name: string) {
selected.value = name;
supplierQuery.value = name;
supplierDropdownOpen.value = false;
chooseSupplier();
}
function closeSupplierDropdown() {
window.setTimeout(() => {
supplierDropdownOpen.value = false;
if (selected.value) supplierQuery.value = selected.value;
}, 120);
}
async function loadProfile() {
if (!selected.value) return;
loading.value = true;
@ -116,11 +174,15 @@ function chooseSupplier() {
void loadProfile();
}
onMounted(() => void loadOptions());
onBeforeUnmount(() => {
if (supplierSearchTimer) clearTimeout(supplierSearchTimer);
});
watch(
() => route.query.supplier,
(value) => {
if (typeof value === "string" && value !== selected.value) {
selected.value = value;
supplierQuery.value = value;
void loadProfile();
}
},
@ -132,16 +194,46 @@ watch(
<div class="supplier-inner">
<header class="supplier-head">
<h1>供应商深度画像</h1>
<select
v-model="selected"
class="supplier-select"
aria-label="选择供应商"
@change="chooseSupplier"
>
<option v-for="name in supplierNames" :key="name" :value="name">
{{ name }}
</option>
</select>
<div class="supplier-combobox" @focusout="closeSupplierDropdown">
<input
:value="supplierQuery"
class="supplier-select"
type="text"
role="combobox"
aria-label="搜索并选择供应商"
aria-autocomplete="list"
:aria-expanded="supplierDropdownOpen"
placeholder="输入供应商名称"
@focus="openSupplierDropdown"
@input="onSupplierInput"
/>
<div
v-if="supplierDropdownOpen"
class="supplier-options"
role="listbox"
>
<button
v-for="name in supplierResults"
:key="name"
type="button"
class="supplier-option"
:class="{ active: name === selected }"
role="option"
:aria-selected="name === selected"
@mousedown.prevent="selectSupplier(name)"
>
{{ name }}
</button>
<span v-if="supplierSearching" class="supplier-option-state"
>搜索中…</span
>
<span
v-else-if="!supplierResults.length"
class="supplier-option-state"
>未找到匹配供应商</span
>
</div>
</div>
<span v-if="profile" class="tier-badge" :class="tierClass">{{
tierLabel
}}</span>
@ -418,6 +510,8 @@ watch(
font-weight: 650;
}
.supplier-select {
width: 180px;
min-height: 34px;
padding: 7px 14px;
border: 1px solid #e4e4e2;
border-radius: 5px;
@ -428,6 +522,47 @@ watch(
font-size: 13px;
cursor: pointer;
}
.supplier-combobox {
position: relative;
z-index: 5;
}
.supplier-combobox .supplier-select {
cursor: text;
}
.supplier-options {
position: absolute;
top: calc(100% + 4px);
right: 0;
left: 0;
max-height: 260px;
overflow-y: auto;
border: 1px solid var(--color-border-strong);
border-radius: 5px;
padding: 4px 0;
background: #fff;
box-shadow: var(--shadow-lg);
}
.supplier-option {
display: block;
width: 100%;
border: 0;
padding: 8px 12px;
background: #fff;
color: var(--color-text-secondary);
font-size: 12px;
text-align: left;
}
.supplier-option:hover,
.supplier-option.active {
background: var(--color-accent-subtle);
color: var(--color-accent);
}
.supplier-option-state {
display: block;
padding: 9px 12px;
color: var(--color-text-faint);
font-size: 11px;
}
.version-note {
color: #999;
font-size: 11px;
@ -700,6 +835,13 @@ watch(
order: 3;
width: 100%;
}
.supplier-combobox {
order: 3;
width: 100%;
}
.supplier-combobox .supplier-select {
width: 100%;
}
.version-note {
width: 100%;
}