优化供应商评分说明并隐藏快捷入口

This commit is contained in:
kx_guo 2026-09-18 15:21:49 +08:00
parent 31e5325cc6
commit e172d82eb5
2 changed files with 66 additions and 60 deletions

View File

@ -1,13 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { import {
BarChart3,
BookOpenCheck,
Building2,
ClipboardCheck,
FileText,
PenLine, PenLine,
Search, Search,
Smartphone,
} from "@lucide/vue"; } from "@lucide/vue";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
@ -25,34 +19,6 @@ const { items: conversations, loading: conversationsLoading } =
const router = useRouter(); const router = useRouter();
const search = ref(""); const search = ref("");
const shortcuts = [
{ label: "数据看板", icon: BarChart3, route: "overview" },
{
label: "录入风险项",
icon: ClipboardCheck,
route: "risk-item-entry",
permission: "risk_item.submit",
},
{
label: "供应商画像",
icon: Building2,
route: "supplier",
permission: "supplier.read",
},
{
label: "判定标准库",
icon: BookOpenCheck,
route: "standards",
isNew: true,
},
{ label: "最新报告", icon: FileText, route: "assessment" },
{ label: "钉钉移动端", icon: Smartphone, route: "assessment" },
];
function canReview() {
return auth.hasPermission("risk_item.review");
}
onMounted(() => { onMounted(() => {
// Keep the first non-chat navigation fast by warming the small route chunks // Keep the first non-chat navigation fast by warming the small route chunks
// after the browser has finished the initial render. This does not block the // after the browser has finished the initial render. This does not block the
@ -173,30 +139,6 @@ function searchConversations() {
</div> </div>
</section> </section>
<section>
<h2>快捷入口</h2>
<RouterLink
v-for="item in shortcuts.filter(
(item) => !item.permission || auth.hasPermission(item.permission),
)"
:key="item.label"
class="sidebar-item"
:to="{ name: item.route }"
>
<component :is="item.icon" :size="14" />
<span class="sidebar-item__label">{{ item.label }}</span>
<span v-if="item.isNew" class="new-label">新</span>
</RouterLink>
<RouterLink
v-if="canReview()"
class="sidebar-item"
:to="{ name: 'review' }"
>
<ClipboardCheck :size="14" />
<span class="sidebar-item__label">审核队列</span>
</RouterLink>
</section>
<footer> <footer>
<div class="knowledge-progress"><i></i></div> <div class="knowledge-progress"><i></i></div>
<p>{{ roleLabel(user?.role) }} · 知识库 2,448 / 3,000 条</p> <p>{{ roleLabel(user?.role) }} · 知识库 2,448 / 3,000 条</p>

View File

@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ChevronDown, CircleHelp } from "@lucide/vue";
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue"; import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
@ -18,6 +19,7 @@ let supplierSearchTimer: ReturnType<typeof setTimeout> | undefined;
const profile = ref<SupplierProfile | null>(null); const profile = ref<SupplierProfile | null>(null);
const loading = ref(false); const loading = ref(false);
const error = ref(""); const error = ref("");
const modelExpanded = ref(false);
const maxCategoryCount = computed(() => const maxCategoryCount = computed(() =>
Math.max( Math.max(
@ -164,6 +166,7 @@ function selectSupplier(name: string) {
selected.value = name; selected.value = name;
supplierQuery.value = name; supplierQuery.value = name;
supplierDropdownOpen.value = false; supplierDropdownOpen.value = false;
modelExpanded.value = false;
chooseSupplier(); chooseSupplier();
} }
@ -204,6 +207,7 @@ watch(
if (typeof value === "string" && value !== selected.value) { if (typeof value === "string" && value !== selected.value) {
selected.value = value; selected.value = value;
supplierQuery.value = value; supplierQuery.value = value;
modelExpanded.value = false;
void loadProfile(); void loadProfile();
} }
}, },
@ -299,7 +303,27 @@ watch(
</div> </div>
</section> </section>
<section class="supplier-panel"> <button
type="button"
class="model-disclosure"
:aria-expanded="modelExpanded"
aria-controls="supplier-risk-model"
@click="modelExpanded = !modelExpanded"
>
<span>本评分通过风险量化模型进行判定</span>
<CircleHelp :size="13" />
<ChevronDown
:size="15"
class="model-disclosure__arrow"
:class="{ expanded: modelExpanded }"
/>
</button>
<section
v-if="modelExpanded"
id="supplier-risk-model"
class="supplier-panel model-panel"
>
<h3> <h3>
📐 合作频次 × 风险量化模型 📐 合作频次 × 风险量化模型
<span class="new-tag">含基础分档位表</span> <span class="new-tag">含基础分档位表</span>
@ -313,6 +337,14 @@ watch(
profile.metric.cooperationTotal ?? "暂无" profile.metric.cooperationTotal ?? "暂无"
}}</b }}</b
>。 >。
<br v-if="supplierBaseScore != null && supplierScore != null" />
<template v-if="supplierBaseScore != null && supplierScore != null">
例:{{ profile.supplierName }} N={{
profile.metric.cooperationTotal ?? profile.metric.anomalyTotal
}} → 基础分 {{ supplierBaseScore }},α =
{{ frequencyAlpha.toFixed(2) }} → S供 = {{ supplierBaseScore }} ×
{{ frequencyAlpha.toFixed(2) }} ≈ <b>{{ supplierScore }}</b>。
</template>
</div> </div>
<table class="prototype-table"> <table class="prototype-table">
<thead> <thead>
@ -624,7 +656,7 @@ watch(
display: grid; display: grid;
grid-template-columns: repeat(6, 1fr); grid-template-columns: repeat(6, 1fr);
gap: 10px; gap: 10px;
margin-bottom: 20px; margin-bottom: 0;
} }
.stat-card { .stat-card {
min-width: 0; min-width: 0;
@ -667,6 +699,38 @@ watch(
color: #666; color: #666;
font-size: 11px; font-size: 11px;
} }
.model-disclosure {
display: flex;
width: fit-content;
align-items: center;
gap: 6px;
border: 0;
margin: 0 0 14px;
padding: 10px 6px;
background: transparent;
color: #777;
font: inherit;
font-size: 12px;
cursor: pointer;
}
.model-disclosure:hover {
color: #333;
}
.model-disclosure:focus-visible {
border-radius: 4px;
outline: 2px solid #93c5fd;
outline-offset: 1px;
}
.model-disclosure__arrow {
color: #999;
transition: transform 0.18s ease;
}
.model-disclosure__arrow.expanded {
transform: rotate(180deg);
}
.model-panel {
margin-top: 0;
}
.supplier-panel { .supplier-panel {
margin-bottom: 14px; margin-bottom: 14px;
padding: 20px; padding: 20px;