dev #1
@ -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"
|
||||
|
||||
@ -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%;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user