优化风险卡片与历史会话加载
- 展示数据库七维风险维度、默认值和数据说明\n- 增加标准兜底校验卡片及无历史案例提示\n- 支持 AI 风险建议和案例推理卡片的新数据结构\n- 进入会话默认定位最新内容\n- 上划到顶部自动加载更早的 20 条消息并保持阅读位置\n- 移除手动加载更早消息按钮\n- 更新风险评估表单和前端类型定义
This commit is contained in:
parent
b672f025dd
commit
32739944c7
@ -8,6 +8,7 @@ import {
|
|||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
ClipboardCheck,
|
ClipboardCheck,
|
||||||
PackageSearch,
|
PackageSearch,
|
||||||
|
Ruler,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
} from "@lucide/vue";
|
} from "@lucide/vue";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
@ -196,8 +197,17 @@ function formatCount(value: unknown) {
|
|||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="solution-base"><b>分析基础:</b>{{ data.analysisBase }}</div>
|
<div class="solution-base"><b>分析基础:</b>{{ data.analysisBase }}</div>
|
||||||
<section v-if="data.standards?.length" class="solution-section">
|
<section
|
||||||
<h4><BookOpenCheck :size="14" /> 标准兜底校验</h4>
|
v-if="data.standardFallbackEnabled || data.standards?.length"
|
||||||
|
class="solution-section"
|
||||||
|
>
|
||||||
|
<h4>
|
||||||
|
<Ruler :size="14" />
|
||||||
|
<span v-if="data.standardFallbackEnabled"
|
||||||
|
>标准兜底校验(无历史案例时启用)</span
|
||||||
|
>
|
||||||
|
<span v-else>关联标准校验</span>
|
||||||
|
</h4>
|
||||||
<div
|
<div
|
||||||
v-for="standard in data.standards"
|
v-for="standard in data.standards"
|
||||||
:key="`${standard.category}-${standard.riskPoint}`"
|
:key="`${standard.category}-${standard.riskPoint}`"
|
||||||
@ -210,6 +220,9 @@ function formatCount(value: unknown) {
|
|||||||
</div>
|
</div>
|
||||||
<strong>{{ standard.status }}</strong>
|
<strong>{{ standard.status }}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
<p v-if="!data.standards?.length" class="empty">
|
||||||
|
当前标准库暂无可匹配规则,请补充产品品类、包材或检测指标。
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<section v-if="data.risks?.length" class="solution-section risk-list">
|
<section v-if="data.risks?.length" class="solution-section risk-list">
|
||||||
<div v-for="risk in data.risks" :key="risk.title" class="solution-risk">
|
<div v-for="risk in data.risks" :key="risk.title" class="solution-risk">
|
||||||
|
|||||||
@ -22,6 +22,15 @@ const props = defineProps<{
|
|||||||
const expanded = ref(true);
|
const expanded = ref(true);
|
||||||
const feedback = ref("");
|
const feedback = ref("");
|
||||||
|
|
||||||
|
interface DimensionView {
|
||||||
|
name: string;
|
||||||
|
score: number;
|
||||||
|
weight: string;
|
||||||
|
tone: string;
|
||||||
|
status?: string;
|
||||||
|
reason?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
function notify(label: string) {
|
function notify(label: string) {
|
||||||
feedback.value = `${label}功能将在对应模块接入后执行`;
|
feedback.value = `${label}功能将在对应模块接入后执行`;
|
||||||
window.setTimeout(() => (feedback.value = ""), 2600);
|
window.setTimeout(() => (feedback.value = ""), 2600);
|
||||||
@ -34,19 +43,29 @@ function copyScore() {
|
|||||||
notify("评分已复制");
|
notify("评分已复制");
|
||||||
}
|
}
|
||||||
|
|
||||||
const dimensions = computed(() =>
|
const dimensions = computed<DimensionView[]>(() =>
|
||||||
(props.result.dimensionScores?.length
|
(props.result.dimensionScores?.length
|
||||||
? props.result.dimensionScores
|
? props.result.dimensionScores.map((item) => ({
|
||||||
|
dimension: item.dimension,
|
||||||
|
score: item.score,
|
||||||
|
weight: item.weight,
|
||||||
|
status: item.status,
|
||||||
|
reason: item.reason,
|
||||||
|
}))
|
||||||
: props.result.factors.map((factor) => ({
|
: props.result.factors.map((factor) => ({
|
||||||
dimension: factor.name,
|
dimension: factor.name,
|
||||||
score: Math.max(0, Math.round(100 - factor.value)),
|
score: Math.max(0, Math.round(100 - factor.value)),
|
||||||
weight: factor.weight,
|
weight: factor.weight,
|
||||||
|
status: undefined,
|
||||||
|
reason: undefined,
|
||||||
}))
|
}))
|
||||||
).map((item) => ({
|
).map((item) => ({
|
||||||
name: item.dimension,
|
name: item.dimension,
|
||||||
score: Math.round(item.score),
|
score: Math.round(item.score),
|
||||||
weight: `${item.weight}%`,
|
weight: `${item.weight}%`,
|
||||||
tone: item.score < 40 ? "danger" : item.score < 60 ? "warning" : "success",
|
tone: item.score < 40 ? "danger" : item.score < 60 ? "warning" : "success",
|
||||||
|
status: item.status,
|
||||||
|
reason: item.reason,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
const suggestions = computed(() =>
|
const suggestions = computed(() =>
|
||||||
@ -195,7 +214,8 @@ const stars = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
<small
|
<small
|
||||||
><b>{{ item.score }}</b
|
><b>{{ item.score }}</b
|
||||||
>分 · {{ item.weight }}</small
|
>分 · {{ item.weight }}
|
||||||
|
<em v-if="item.status === 'default'">默认</em></small
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Plus, ShieldCheck, Trash2 } from "@lucide/vue";
|
import { ShieldCheck } from "@lucide/vue";
|
||||||
import { reactive } from "vue";
|
import { reactive } from "vue";
|
||||||
|
|
||||||
import type { RiskAssessmentRequest, RiskIndicator } from "@/types/risk";
|
import type { RiskAssessmentRequest } from "@/types/risk";
|
||||||
|
|
||||||
defineProps<{ loading: boolean }>();
|
defineProps<{ loading: boolean }>();
|
||||||
|
|
||||||
@ -10,111 +10,99 @@ const emit = defineEmits<{
|
|||||||
submit: [payload: RiskAssessmentRequest];
|
submit: [payload: RiskAssessmentRequest];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const form = reactive<RiskAssessmentRequest>({
|
const form = reactive({
|
||||||
subjectId: "customer-001",
|
subjectId: "脱毛膏 100g 新品",
|
||||||
indicators: [
|
productCategory: "脱毛膏",
|
||||||
{ name: "historicalDefault", value: 28, weight: 4 },
|
materialType: "PE软管",
|
||||||
{ name: "debtBurden", value: 52, weight: 3 },
|
supplier: "瑞沣",
|
||||||
{ name: "cashflowVolatility", value: 36, weight: 3 },
|
factory: "",
|
||||||
],
|
stageNew: "第三阶段(产品开发)",
|
||||||
|
defectLevel: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
function addIndicator() {
|
|
||||||
form.indicators.push({
|
|
||||||
name: `indicator${form.indicators.length + 1}`,
|
|
||||||
value: 0,
|
|
||||||
weight: 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeIndicator(index: number) {
|
|
||||||
if (form.indicators.length > 1) form.indicators.splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function submit() {
|
function submit() {
|
||||||
const indicators: RiskIndicator[] = form.indicators.map((item) => ({
|
emit("submit", {
|
||||||
...item,
|
subjectId: form.subjectId,
|
||||||
}));
|
indicators: [],
|
||||||
emit("submit", { subjectId: form.subjectId, indicators });
|
productCategory: form.productCategory || null,
|
||||||
|
materialType: form.materialType || null,
|
||||||
|
supplier: form.supplier || null,
|
||||||
|
factory: form.factory || null,
|
||||||
|
stageNew: form.stageNew || null,
|
||||||
|
defectLevel: form.defectLevel || null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form class="assessment-form" @submit.prevent="submit">
|
<form class="assessment-form" @submit.prevent="submit">
|
||||||
<div class="field-group">
|
<div class="form-intro">
|
||||||
<label for="subject-id">评估对象</label>
|
<strong>新品风险参数</strong>
|
||||||
<input
|
<span>系统将结合近 3 年异常案例计算七维安全评分</span>
|
||||||
id="subject-id"
|
|
||||||
v-model.trim="form.subjectId"
|
|
||||||
required
|
|
||||||
maxlength="128"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-grid">
|
||||||
<div class="indicator-heading">
|
<label class="field-wide">
|
||||||
<div>
|
评估对象 *
|
||||||
<strong>风险指标</strong>
|
<input v-model.trim="form.subjectId" required maxlength="128" />
|
||||||
<span>指标值范围 0-100</span>
|
</label>
|
||||||
</div>
|
<label>
|
||||||
<button
|
产品品类 *
|
||||||
class="button button--secondary"
|
<input
|
||||||
type="button"
|
v-model.trim="form.productCategory"
|
||||||
@click="addIndicator"
|
required
|
||||||
>
|
maxlength="100"
|
||||||
<Plus :size="16" />
|
placeholder="如:洗发水、面霜、脱毛膏"
|
||||||
添加指标
|
/>
|
||||||
</button>
|
</label>
|
||||||
|
<label>
|
||||||
|
包装材质 *
|
||||||
|
<input
|
||||||
|
v-model.trim="form.materialType"
|
||||||
|
required
|
||||||
|
maxlength="200"
|
||||||
|
placeholder="如:PET瓶、PE软管、泵头"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
供应商
|
||||||
|
<input
|
||||||
|
v-model.trim="form.supplier"
|
||||||
|
maxlength="300"
|
||||||
|
placeholder="可填写已知供应商或新供应商"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
加工厂
|
||||||
|
<input
|
||||||
|
v-model.trim="form.factory"
|
||||||
|
maxlength="300"
|
||||||
|
placeholder="如:博元、瑞沣"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
开发阶段
|
||||||
|
<select v-model="form.stageNew">
|
||||||
|
<option>第一阶段(需求发现)</option>
|
||||||
|
<option>第二阶段(产品立项)</option>
|
||||||
|
<option>第三阶段(产品开发)</option>
|
||||||
|
<option>第四阶段(量产上市)</option>
|
||||||
|
<option>第五阶段(产品生命管理)</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
已知缺陷等级
|
||||||
|
<select v-model="form.defectLevel">
|
||||||
|
<option value="">暂无</option>
|
||||||
|
<option>A</option>
|
||||||
|
<option>B</option>
|
||||||
|
<option>C</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="data-note">
|
||||||
<div class="indicator-table">
|
<span class="data-note__dot"></span>
|
||||||
<div class="indicator-table__header" aria-hidden="true">
|
缺少巡检或检测判定数据的维度,将按 PRD 中性值 50 分计入并标注。
|
||||||
<span>指标名称</span>
|
|
||||||
<span>指标值</span>
|
|
||||||
<span>权重</span>
|
|
||||||
<span></span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-for="(indicator, index) in form.indicators"
|
|
||||||
:key="index"
|
|
||||||
class="indicator-row"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
v-model.trim="indicator.name"
|
|
||||||
required
|
|
||||||
maxlength="64"
|
|
||||||
aria-label="指标名称"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
v-model.number="indicator.value"
|
|
||||||
required
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
max="100"
|
|
||||||
step="0.1"
|
|
||||||
aria-label="指标值"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
v-model.number="indicator.weight"
|
|
||||||
required
|
|
||||||
type="number"
|
|
||||||
min="0.1"
|
|
||||||
max="100"
|
|
||||||
step="0.1"
|
|
||||||
aria-label="权重"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
class="icon-button"
|
|
||||||
type="button"
|
|
||||||
title="删除指标"
|
|
||||||
aria-label="删除指标"
|
|
||||||
:disabled="form.indicators.length === 1"
|
|
||||||
@click="removeIndicator(index)"
|
|
||||||
>
|
|
||||||
<Trash2 :size="16" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="button button--primary submit-button"
|
class="button button--primary submit-button"
|
||||||
type="submit"
|
type="submit"
|
||||||
@ -129,99 +117,82 @@ function submit() {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.assessment-form {
|
.assessment-form {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 22px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
.form-intro {
|
||||||
.field-group,
|
|
||||||
.indicator-heading div {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 7px;
|
gap: 3px;
|
||||||
|
border-bottom: 1px solid var(--color-border-light);
|
||||||
|
padding-bottom: 14px;
|
||||||
}
|
}
|
||||||
|
.form-intro strong {
|
||||||
label,
|
|
||||||
.indicator-heading strong {
|
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
font-size: 13px;
|
font-size: 15px;
|
||||||
font-weight: 700;
|
|
||||||
}
|
}
|
||||||
|
.form-intro span {
|
||||||
.field-group input {
|
|
||||||
width: min(420px, 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator-heading {
|
|
||||||
display: flex;
|
|
||||||
align-items: end;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator-heading span {
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator-table {
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator-table__header,
|
|
||||||
.indicator-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns:
|
|
||||||
minmax(180px, 1.5fr) minmax(100px, 0.7fr) minmax(90px, 0.6fr)
|
|
||||||
42px;
|
|
||||||
gap: 10px;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator-table__header {
|
|
||||||
background: var(--color-surface-muted);
|
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.form-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
color: var(--color-text);
|
||||||
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
}
|
||||||
|
.field-wide {
|
||||||
.indicator-row + .indicator-row {
|
grid-column: 1 / -1;
|
||||||
border-top: 1px solid var(--color-border);
|
|
||||||
}
|
}
|
||||||
|
input,
|
||||||
.indicator-row input {
|
select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-height: 38px;
|
||||||
|
border: 1px solid var(--color-border-strong);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0 10px;
|
||||||
|
background: white;
|
||||||
|
color: var(--color-text);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
input:focus,
|
||||||
|
select:focus {
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
outline: 2px solid var(--color-accent-subtle);
|
||||||
|
outline-offset: 0;
|
||||||
|
}
|
||||||
|
.data-note {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
border: 1px solid var(--color-warning-border);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 9px 10px;
|
||||||
|
background: var(--color-warning-soft);
|
||||||
|
color: #92400e;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.data-note__dot {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
flex: 0 0 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--color-warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
.submit-button {
|
.submit-button {
|
||||||
justify-self: start;
|
justify-self: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 620px) {
|
@media (max-width: 620px) {
|
||||||
.indicator-heading {
|
.form-grid {
|
||||||
align-items: stretch;
|
grid-template-columns: 1fr;
|
||||||
flex-direction: column;
|
|
||||||
}
|
}
|
||||||
|
.field-wide {
|
||||||
.indicator-table__header {
|
grid-column: auto;
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator-row {
|
|
||||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 34px;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 10px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator-row input:first-child {
|
|
||||||
grid-column: 1 / 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator-row .icon-button {
|
|
||||||
grid-column: 3;
|
|
||||||
grid-row: 1 / 3;
|
|
||||||
align-self: center;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,10 +1,23 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { BarChart3 } from "@lucide/vue";
|
import { BarChart3 } from "@lucide/vue";
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
import RiskBadge from "@/components/common/RiskBadge.vue";
|
import RiskBadge from "@/components/common/RiskBadge.vue";
|
||||||
import type { RiskAssessmentResult } from "@/types/risk";
|
import type { RiskAssessmentResult, RiskDimensionScore } from "@/types/risk";
|
||||||
|
|
||||||
defineProps<{ result: RiskAssessmentResult | null; error: string | null }>();
|
const props = defineProps<{
|
||||||
|
result: RiskAssessmentResult | null;
|
||||||
|
error: string | null;
|
||||||
|
}>();
|
||||||
|
const dimensions = computed<RiskDimensionScore[]>(() =>
|
||||||
|
props.result?.dimensionScores?.length
|
||||||
|
? props.result.dimensionScores
|
||||||
|
: (props.result?.factors ?? []).map((factor) => ({
|
||||||
|
dimension: factor.name,
|
||||||
|
score: Math.max(0, Math.round(100 - factor.value)),
|
||||||
|
weight: factor.weight,
|
||||||
|
})),
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -32,18 +45,24 @@ defineProps<{ result: RiskAssessmentResult | null; error: string | null }>();
|
|||||||
<dt>模型</dt>
|
<dt>模型</dt>
|
||||||
<dd>{{ result.modelVersion }}</dd>
|
<dd>{{ result.modelVersion }}</dd>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt>数据口径</dt>
|
||||||
|
<dd>异常案例库 + 缺失值兜底</dd>
|
||||||
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
<div class="factor-list">
|
<div class="factor-list">
|
||||||
<div
|
<div
|
||||||
v-for="factor in result.factors"
|
v-for="factor in dimensions"
|
||||||
:key="factor.name"
|
:key="factor.dimension"
|
||||||
class="factor-row"
|
class="factor-row"
|
||||||
|
:title="factor.reason || undefined"
|
||||||
>
|
>
|
||||||
<span>{{ factor.name }}</span>
|
<span>{{ factor.dimension }}</span>
|
||||||
<div class="factor-bar">
|
<div class="factor-bar">
|
||||||
<i :style="{ width: `${factor.value}%` }"></i>
|
<i :style="{ width: `${factor.score}%` }"></i>
|
||||||
</div>
|
</div>
|
||||||
<strong>{{ factor.contribution.toFixed(1) }}</strong>
|
<strong>{{ Math.round(factor.score) }}<small>分</small></strong>
|
||||||
|
<em v-if="factor.status === 'default'">默认</em>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -147,6 +166,22 @@ dd {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.factor-row > strong small {
|
||||||
|
margin-left: 2px;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.factor-row > em {
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
background: var(--color-warning-soft);
|
||||||
|
color: var(--color-warning);
|
||||||
|
font-size: 9px;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
.factor-bar {
|
.factor-bar {
|
||||||
height: 6px;
|
height: 6px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@ -180,13 +180,17 @@ function formatMessageTime(value: string) {
|
|||||||
function loadOlder() {
|
function loadOlder() {
|
||||||
const node = scrollArea.value;
|
const node = scrollArea.value;
|
||||||
const before = node?.scrollHeight ?? 0;
|
const before = node?.scrollHeight ?? 0;
|
||||||
void conversationStore.loadOlder().then((loaded) => {
|
const beforeTop = node?.scrollTop ?? 0;
|
||||||
if (loaded && node) node.scrollTop = node.scrollHeight - before;
|
void conversationStore.loadOlder().then(async (loaded) => {
|
||||||
|
if (loaded && node) {
|
||||||
|
await nextTick();
|
||||||
|
node.scrollTop = node.scrollHeight - before + beforeTop;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleScroll() {
|
function handleScroll() {
|
||||||
if (scrollArea.value && scrollArea.value.scrollTop < 60) loadOlder();
|
if (scrollArea.value && scrollArea.value.scrollTop <= 80) loadOlder();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openFromRoute() {
|
async function openFromRoute() {
|
||||||
@ -249,14 +253,9 @@ async function scrollToBottom(behavior: "smooth" | "auto" = "smooth") {
|
|||||||
@scroll.passive="handleScroll"
|
@scroll.passive="handleScroll"
|
||||||
>
|
>
|
||||||
<div class="conversation-column">
|
<div class="conversation-column">
|
||||||
<button
|
<p v-if="conversationStore.loadingOlder" class="history-loading">
|
||||||
v-if="conversation?.hasMoreMessages"
|
正在加载更早消息…
|
||||||
class="load-older"
|
</p>
|
||||||
type="button"
|
|
||||||
@click="loadOlder"
|
|
||||||
>
|
|
||||||
{{ conversationStore.loadingOlder ? "加载中…" : "加载更早消息" }}
|
|
||||||
</button>
|
|
||||||
<AssistantWelcome
|
<AssistantWelcome
|
||||||
v-if="!conversation?.messages.length"
|
v-if="!conversation?.messages.length"
|
||||||
@select="usePrompt"
|
@select="usePrompt"
|
||||||
@ -440,15 +439,11 @@ async function scrollToBottom(behavior: "smooth" | "auto" = "smooth") {
|
|||||||
gap: 18px;
|
gap: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.load-older {
|
.history-loading {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: 999px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
background: var(--color-surface);
|
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
cursor: pointer;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
|
|||||||
@ -9,13 +9,29 @@ export interface RiskIndicator {
|
|||||||
|
|
||||||
export interface RiskAssessmentRequest {
|
export interface RiskAssessmentRequest {
|
||||||
subjectId: string;
|
subjectId: string;
|
||||||
indicators: RiskIndicator[];
|
indicators?: RiskIndicator[];
|
||||||
|
productCategory?: string | null;
|
||||||
|
materialType?: string | null;
|
||||||
|
supplier?: string | null;
|
||||||
|
factory?: string | null;
|
||||||
|
stageNew?: string | null;
|
||||||
|
defectLevel?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RiskFactorResult extends RiskIndicator {
|
export interface RiskFactorResult extends RiskIndicator {
|
||||||
contribution: number;
|
contribution: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RiskDimensionScore {
|
||||||
|
dimension: string;
|
||||||
|
score: number;
|
||||||
|
weight: number;
|
||||||
|
status?: string;
|
||||||
|
reason?: string | null;
|
||||||
|
sampleSize?: number | null;
|
||||||
|
rawValue?: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RiskAssessmentResult {
|
export interface RiskAssessmentResult {
|
||||||
subjectId: string;
|
subjectId: string;
|
||||||
score: number;
|
score: number;
|
||||||
@ -54,11 +70,7 @@ export interface RiskAssessmentResult {
|
|||||||
source: string | null;
|
source: string | null;
|
||||||
status: string;
|
status: string;
|
||||||
}>;
|
}>;
|
||||||
dimensionScores?: Array<{
|
dimensionScores?: RiskDimensionScore[];
|
||||||
dimension: string;
|
|
||||||
score: number;
|
|
||||||
weight: number;
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModelInfo {
|
export interface ModelInfo {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user