feat(broadcast): finish multi-role insight experience
This commit is contained in:
@@ -14,10 +14,14 @@
|
||||
<text class="message-title">{{ item.title }}</text>
|
||||
<view class="detail-tags">
|
||||
<text v-if="item.requiredRead" class="detail-tag required">必读消息</text>
|
||||
<text v-if="item.targeted" class="detail-tag related">与我有关{{ item.targetReason ? ` · ${item.targetReason}` : '' }}</text>
|
||||
<text v-if="item.targeted" class="detail-tag related">定向提醒{{ item.targetReason ? ` · ${item.targetReason}` : '' }}</text>
|
||||
</view>
|
||||
<text class="message-time">发布时间:{{ displayTime(item.publishedAt) }}</text>
|
||||
<text class="message-content">{{ item.content }}</text>
|
||||
<view class="detail-actions">
|
||||
<button class="ask-message-button" @click="askAboutMessage">问这条消息</button>
|
||||
<text class="ask-message-note">将在“问”中围绕本条消息继续咨询</text>
|
||||
</view>
|
||||
<view v-if="item.attachment" class="attachment-card">
|
||||
<view class="attachment-head">
|
||||
<view>
|
||||
@@ -30,19 +34,67 @@
|
||||
<button class="download-button" :disabled="downloading" @click="downloadAttachment">
|
||||
{{ downloading ? '正在读取…' : '查看原文件' }}
|
||||
</button>
|
||||
</view>
|
||||
<view class="detail-actions">
|
||||
<button class="ask-message-button" @click="askAboutMessage">问这条消息</button>
|
||||
<text class="ask-message-note">将在“问”中围绕本条消息继续咨询</text>
|
||||
<view v-if="item.attachment.perspectives.length" class="perspective-panel">
|
||||
<view class="perspective-heading">
|
||||
<view>
|
||||
<text class="perspective-kicker">
|
||||
{{ selectedPerspective?.code === item.attachment.defaultPerspectiveCode ? '我的岗位视角' : '多岗位解读' }}
|
||||
</text>
|
||||
<text class="perspective-title">{{ selectedPerspective?.label }}</text>
|
||||
</view>
|
||||
<text v-if="item.attachment.insightStatus === 'PARTIAL'" class="insight-state partial">部分完成</text>
|
||||
</view>
|
||||
<scroll-view class="perspective-tabs" scroll-x>
|
||||
<view class="perspective-tab-row" role="group" aria-label="岗位解读切换">
|
||||
<button
|
||||
v-for="perspective in item.attachment.perspectives"
|
||||
:key="perspective.code"
|
||||
class="perspective-tab"
|
||||
:class="{ active: selectedPerspective?.code === perspective.code }"
|
||||
:aria-pressed="selectedPerspective?.code === perspective.code"
|
||||
@click="selectPerspective(perspective.code)"
|
||||
>{{ perspective.label }}</button>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<text v-if="selectedPerspective?.summary" class="perspective-summary">{{ selectedPerspective.summary }}</text>
|
||||
<view v-for="section in perspectiveSections" :key="section.label" class="perspective-section">
|
||||
<text class="perspective-section-title">{{ section.label }}</text>
|
||||
<text v-for="entry in section.items" :key="entry" class="perspective-item">· {{ entry }}</text>
|
||||
</view>
|
||||
<button
|
||||
v-if="selectedPerspective?.evidence.length"
|
||||
class="evidence-toggle"
|
||||
:aria-expanded="evidenceExpanded"
|
||||
@click="evidenceExpanded = !evidenceExpanded"
|
||||
>
|
||||
{{ evidenceExpanded ? '收起原文依据' : `查看原文依据(${selectedPerspective.evidence.length})` }}
|
||||
</button>
|
||||
<view v-if="evidenceExpanded" class="evidence-list">
|
||||
<view v-for="evidence in selectedPerspective?.evidence" :key="`${evidence.paragraphIndex}-${evidence.quote}`" class="evidence-item">
|
||||
<text class="evidence-index">第 {{ evidence.paragraphIndex }} 段</text>
|
||||
<text class="evidence-quote">“{{ evidence.quote }}”</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else-if="item.attachment.insightStatus === 'PENDING'" class="insight-state-card" role="status">
|
||||
<text>AI 正在从不同岗位解读,不影响查看原文件</text>
|
||||
<button class="insight-refresh" @click="load">刷新解读</button>
|
||||
</view>
|
||||
<view v-else-if="item.attachment.insightStatus === 'FAILED'" class="insight-state-card failed">
|
||||
多岗位解读暂不可用,可继续查看原文件或咨询
|
||||
</view>
|
||||
<view v-else-if="item.attachment.insightStatus === 'NOT_PROCESSED'" class="insight-state-card">
|
||||
该文件暂未生成多岗位解读
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import type { BroadcastMessage } from '@/services/broadcast';
|
||||
import type { BroadcastAttachment, BroadcastMessage } from '@/services/broadcast';
|
||||
import {
|
||||
clearBroadcastQuestionContext,
|
||||
getBroadcastMessage,
|
||||
@@ -60,6 +112,19 @@ const item = ref<BroadcastMessage | null>(null);
|
||||
const loading = ref(false);
|
||||
const message = ref('');
|
||||
const downloading = ref(false);
|
||||
const selectedPerspectiveCode = ref('');
|
||||
const evidenceExpanded = ref(false);
|
||||
const selectedPerspective = computed(() =>
|
||||
item.value?.attachment?.perspectives.find((perspective) => perspective.code === selectedPerspectiveCode.value)
|
||||
|| item.value?.attachment?.perspectives[0]
|
||||
|| null
|
||||
);
|
||||
const perspectiveSections = computed(() => selectedPerspective.value ? [
|
||||
{ label: '关注重点', items: selectedPerspective.value.concerns },
|
||||
{ label: '岗位影响', items: selectedPerspective.value.impacts },
|
||||
{ label: '需要行动', items: selectedPerspective.value.actions },
|
||||
{ label: '风险提醒', items: selectedPerspective.value.risks }
|
||||
].filter((section) => section.items.length) : []);
|
||||
|
||||
const goBack = () => uni.navigateBack({ delta: 1, fail: () => uni.switchTab({ url: '/pages/user/today/index' }) });
|
||||
const displayTime = (value: string) => value ? value.replace('T', ' ').slice(0, 16) : '';
|
||||
@@ -70,6 +135,17 @@ const formatFileSize = (bytes: number) => {
|
||||
if (bytes < 1024 * 1024) return `${Math.max(1, Math.round(bytes / 1024))} KB`;
|
||||
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
||||
};
|
||||
const selectPerspective = (code: string) => {
|
||||
selectedPerspectiveCode.value = code;
|
||||
evidenceExpanded.value = false;
|
||||
};
|
||||
const selectInitialPerspective = (attachment?: BroadcastAttachment) => {
|
||||
const perspectives = attachment?.perspectives || [];
|
||||
const preferred = perspectives.some((perspective) => perspective.code === attachment?.defaultPerspectiveCode)
|
||||
? attachment?.defaultPerspectiveCode
|
||||
: perspectives[0]?.code;
|
||||
selectPerspective(preferred || '');
|
||||
};
|
||||
|
||||
const downloadAttachment = async () => {
|
||||
if (!item.value?.attachment || downloading.value) return;
|
||||
@@ -130,6 +206,7 @@ const load = async () => {
|
||||
message.value = '';
|
||||
try {
|
||||
const next = await getBroadcastMessage(id.value);
|
||||
selectInitialPerspective(next.attachment);
|
||||
item.value = next;
|
||||
try {
|
||||
await markBroadcastMessageRead(id.value);
|
||||
@@ -179,6 +256,30 @@ onLoad((options) => {
|
||||
.attachment-name { display: block; margin-top: 4px; overflow-wrap: anywhere; color: var(--ink); font-size: 14px; font-weight: 900; }
|
||||
.attachment-size { flex: none; color: var(--muted); font-size: 12px; }
|
||||
.attachment-summary { overflow-wrap: anywhere; color: #596273; font-size: 13px; line-height: 1.65; }
|
||||
.perspective-panel { display: grid; gap: 12px; margin-top: 4px; padding-top: 14px; border-top: 1px solid #f0e3e5; }
|
||||
.perspective-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
||||
.perspective-kicker { display: block; color: var(--brand); font-size: 11px; font-weight: 900; }
|
||||
.perspective-title { display: block; margin-top: 3px; color: var(--ink); font-size: 17px; font-weight: 900; }
|
||||
.insight-state { flex: none; padding: 3px 7px; border-radius: 999px; font-size: 11px; font-weight: 800; }
|
||||
.insight-state.partial { background: #fff4df; color: #986000; }
|
||||
.perspective-tabs { width: 100%; white-space: nowrap; }
|
||||
.perspective-tab-row { display: inline-flex; gap: 8px; padding-bottom: 2px; }
|
||||
.perspective-tab { display: inline-flex; min-height: 34px; align-items: center; justify-content: center; padding: 0 12px; border: 1px solid #e5e7eb; border-radius: 999px; background: #fff; color: #596273; font-size: 12px; font-weight: 800; line-height: 1; }
|
||||
.perspective-tab::after, .evidence-toggle::after { border: 0; }
|
||||
.perspective-tab.active { border-color: #f0a6ac; background: #fff0f1; color: var(--brand); }
|
||||
.perspective-summary { color: var(--ink); font-size: 14px; font-weight: 700; line-height: 1.65; }
|
||||
.perspective-section { display: grid; gap: 5px; }
|
||||
.perspective-section-title { color: #30343b; font-size: 13px; font-weight: 900; }
|
||||
.perspective-item { overflow-wrap: anywhere; color: #596273; font-size: 13px; line-height: 1.55; }
|
||||
.evidence-toggle { display: flex; min-height: 36px; align-items: center; justify-content: center; padding: 0; border-radius: 10px; background: #f4f5f7; color: #596273; font-size: 12px; font-weight: 800; }
|
||||
.evidence-list { display: grid; gap: 8px; }
|
||||
.evidence-item { display: grid; gap: 4px; padding: 10px; border-left: 3px solid #d9dde4; background: #f7f8fa; }
|
||||
.evidence-index { color: #7a8290; font-size: 11px; font-weight: 800; }
|
||||
.evidence-quote { overflow-wrap: anywhere; color: #596273; font-size: 12px; line-height: 1.55; }
|
||||
.insight-state-card { display: grid; gap: 8px; padding: 10px 12px; border-radius: 10px; background: #f4f5f7; color: #596273; font-size: 12px; line-height: 1.55; }
|
||||
.insight-state-card.failed { background: #fff4f4; color: #9d2730; }
|
||||
.insight-refresh { display: flex; min-height: 34px; align-items: center; justify-content: center; padding: 0; border-radius: 9px; background: #fff; color: var(--brand); font-size: 12px; font-weight: 800; }
|
||||
.insight-refresh::after { border: 0; }
|
||||
.download-button { display: flex; min-height: 40px; align-items: center; justify-content: center; padding: 0; border: 1px solid #f0a6ac; border-radius: 12px; background: #fff; color: var(--brand); font-size: 13px; font-weight: 900; }
|
||||
.download-button::after { border: 0; }
|
||||
.detail-actions { display: grid; gap: 8px; margin-top: 8px; padding-top: 16px; border-top: 1px solid var(--employee-line, #ebedf0); }
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<view class="message-tags">
|
||||
<text class="message-state" :class="{ read: item.read }">{{ item.read ? '已读' : '未读' }}</text>
|
||||
<text v-if="item.requiredRead" class="message-tag required">必读</text>
|
||||
<text v-if="item.targeted" class="message-tag related">与我有关</text>
|
||||
<text v-if="item.targeted" class="message-tag related">定向提醒</text>
|
||||
</view>
|
||||
<text class="message-time">{{ displayTime(item.publishedAt) }}</text>
|
||||
</view>
|
||||
@@ -90,7 +90,7 @@ const moreAvailable = ref(false);
|
||||
const scope = ref<BroadcastMessageScope>('ALL');
|
||||
const scopeOptions: Array<{ value: BroadcastMessageScope; label: string }> = [
|
||||
{ value: 'ALL', label: '全部' },
|
||||
{ value: 'RELATED', label: '与我有关' },
|
||||
{ value: 'RELATED', label: '定向提醒' },
|
||||
{ value: 'REQUIRED_UNREAD', label: '必读未读' }
|
||||
];
|
||||
const requests = createPageRequestScope();
|
||||
|
||||
Reference in New Issue
Block a user