277 lines
5.6 KiB
Vue
277 lines
5.6 KiB
Vue
<script setup lang="ts">
|
||
import {
|
||
BookOpenCheck,
|
||
Bot,
|
||
Box,
|
||
Building2,
|
||
Camera,
|
||
Mic,
|
||
Scale,
|
||
Search,
|
||
} from "@lucide/vue";
|
||
import { computed } from "vue";
|
||
|
||
import { featureFlags } from "@/config/features";
|
||
|
||
const emit = defineEmits<{
|
||
select: [prompt: string];
|
||
tool: [value: "assessment" | "ocr"];
|
||
}>();
|
||
const props = defineProps<{ canUpload: boolean }>();
|
||
|
||
const actions = [
|
||
{
|
||
title: "评估新品风险",
|
||
description: "含品类预警 + 标准判定",
|
||
prompt: "帮我评估一款500ml PET瓶洗发水,泵头用祥印的,加工厂博元",
|
||
icon: Search,
|
||
tone: "blue",
|
||
tool: "assessment" as const,
|
||
},
|
||
{
|
||
title: "查供应商",
|
||
description: "含合作频次 + 分层",
|
||
prompt: "佳亿的风险怎么样?",
|
||
icon: Building2,
|
||
tone: "green",
|
||
},
|
||
{
|
||
title: "查物料风险",
|
||
description: "品类常见问题",
|
||
prompt: "软管类物料有什么常见问题?",
|
||
icon: Box,
|
||
tone: "amber",
|
||
},
|
||
{
|
||
title: "AI方案生成",
|
||
description: "无历史时按标准兜底",
|
||
prompt: "我要做一款次氯酸消毒喷雾,50ml,铝瓶加喷头",
|
||
icon: Bot,
|
||
tone: "purple",
|
||
isNew: true,
|
||
},
|
||
{
|
||
title: "标准判定",
|
||
description: "检测数值对标标准库",
|
||
prompt: "洗发水 pH 值检测报告 4.8,请判定",
|
||
icon: BookOpenCheck,
|
||
tone: "sky",
|
||
isNew: true,
|
||
},
|
||
{
|
||
title: "法规合规",
|
||
description: "法规标准快速核验",
|
||
prompt: "新产品宣称能祛痘,帮我查下合规风险",
|
||
icon: Scale,
|
||
tone: "rose",
|
||
isNew: true,
|
||
},
|
||
{
|
||
title: "OCR识别录入",
|
||
description: "拍照自动填表 + 数值判定",
|
||
prompt: "请帮我识别检测报告并进行风险判定",
|
||
icon: Camera,
|
||
tone: "sky",
|
||
requiresUpload: true,
|
||
tool: "ocr" as const,
|
||
},
|
||
...(featureFlags.voiceEntry
|
||
? [
|
||
{
|
||
title: "语音录入异常",
|
||
description: "口述信息自动结构化",
|
||
prompt: "记录一条新的质量异常",
|
||
icon: Mic,
|
||
tone: "purple",
|
||
},
|
||
]
|
||
: []),
|
||
];
|
||
const visibleActions = computed(() =>
|
||
actions.filter(
|
||
(action) =>
|
||
!("requiresUpload" in action) ||
|
||
!action.requiresUpload ||
|
||
props.canUpload,
|
||
),
|
||
);
|
||
|
||
function handleAction(action: (typeof actions)[number]) {
|
||
if ("tool" in action && action.tool) {
|
||
emit("tool", action.tool);
|
||
return;
|
||
}
|
||
emit("select", action.prompt);
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<section class="welcome">
|
||
<div class="welcome__mark">蝶</div>
|
||
<h1>你好,我是风险预警助手</h1>
|
||
<p>
|
||
双引擎风险预警:历史异常经验引擎 +
|
||
判定标准规则引擎,帮助你在立项、开发和量产阶段提前识别风险。
|
||
</p>
|
||
|
||
<div class="welcome-grid">
|
||
<button
|
||
v-for="action in visibleActions"
|
||
:key="action.title"
|
||
class="welcome-action"
|
||
type="button"
|
||
@click="handleAction(action)"
|
||
>
|
||
<span class="welcome-action__icon" :class="`tone-${action.tone}`">
|
||
<component :is="action.icon" :size="16" />
|
||
</span>
|
||
<span class="welcome-action__copy">
|
||
<strong>{{ action.title }}</strong>
|
||
<small>{{ action.description }}</small>
|
||
</span>
|
||
<span v-if="action.isNew" class="welcome-action__new">新</span>
|
||
</button>
|
||
</div>
|
||
</section>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.welcome {
|
||
padding: 42px 20px 26px;
|
||
text-align: center;
|
||
}
|
||
|
||
.welcome__mark {
|
||
display: inline-grid;
|
||
width: 48px;
|
||
height: 48px;
|
||
place-items: center;
|
||
border-radius: 14px;
|
||
margin-bottom: 18px;
|
||
background: var(--color-text);
|
||
color: white;
|
||
font-size: 20px;
|
||
font-weight: 650;
|
||
}
|
||
|
||
h1 {
|
||
margin: 0 0 6px;
|
||
font-size: 20px;
|
||
font-weight: 650;
|
||
}
|
||
|
||
p {
|
||
max-width: 440px;
|
||
margin: 0 auto 26px;
|
||
color: var(--color-text-muted);
|
||
font-size: 12px;
|
||
line-height: 1.75;
|
||
}
|
||
|
||
.welcome-grid {
|
||
display: grid;
|
||
max-width: 520px;
|
||
margin: 0 auto;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 8px;
|
||
}
|
||
|
||
.welcome-action {
|
||
display: flex;
|
||
min-width: 0;
|
||
min-height: 62px;
|
||
align-items: center;
|
||
gap: 10px;
|
||
border: 1px solid var(--color-border);
|
||
border-radius: 10px;
|
||
padding: 10px 13px;
|
||
background: var(--color-surface);
|
||
color: var(--color-text);
|
||
text-align: left;
|
||
transition:
|
||
border-color 0.15s,
|
||
box-shadow 0.15s,
|
||
transform 0.15s;
|
||
}
|
||
|
||
.welcome-action:hover {
|
||
border-color: var(--color-border-strong);
|
||
box-shadow: var(--shadow-md);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.welcome-action__icon {
|
||
display: grid;
|
||
width: 32px;
|
||
height: 32px;
|
||
flex: 0 0 32px;
|
||
place-items: center;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.tone-blue,
|
||
.tone-sky {
|
||
background: var(--color-accent-subtle);
|
||
color: var(--color-accent);
|
||
}
|
||
|
||
.tone-green {
|
||
background: var(--color-success-soft);
|
||
color: var(--color-success);
|
||
}
|
||
|
||
.tone-amber {
|
||
background: var(--color-warning-soft);
|
||
color: var(--color-warning);
|
||
}
|
||
|
||
.tone-purple {
|
||
background: var(--color-purple-soft);
|
||
color: var(--color-purple);
|
||
}
|
||
|
||
.tone-rose {
|
||
background: #fce7f3;
|
||
color: #be185d;
|
||
}
|
||
|
||
.welcome-action__copy {
|
||
display: grid;
|
||
min-width: 0;
|
||
gap: 1px;
|
||
}
|
||
|
||
.welcome-action strong {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.welcome-action small {
|
||
overflow: hidden;
|
||
color: var(--color-text-faint);
|
||
font-size: 10px;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.welcome-action__new {
|
||
margin-left: auto;
|
||
border-radius: 3px;
|
||
padding: 0 4px;
|
||
background: var(--color-accent-subtle);
|
||
color: var(--color-accent);
|
||
font-size: 8px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.welcome {
|
||
padding-inline: 10px;
|
||
}
|
||
|
||
.welcome-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
</style>
|