ReportStudio.vue 5.96 KB
<script setup lang="ts">
import { computed } from 'vue'

import ReportPreview from '@/components/report/ReportPreview.vue'
import ReportTaskBoard from '@/components/report/ReportTaskBoard.vue'
import type { ReportStatusPayload, ReportTask } from '@/types'
import { statusTone } from '@/utils/format'

const props = defineProps<{
  status: ReportStatusPayload
  selectedTask: ReportTask | null
  selectedTaskId: string
  customTemplate: string
  previewUrl: string
  loading: boolean
  generating: boolean
}>()

const emit = defineEmits<{
  'update:selectedTaskId': [value: string]
  'update:customTemplate': [value: string]
  refresh: []
  generate: []
  download: [kind: 'html' | 'md' | 'pdf', taskId: string]
}>()

const templateModel = computed({
  get: () => props.customTemplate,
  set: (value: string) => emit('update:customTemplate', value),
})

const canDownload = computed(() => props.selectedTask?.status === 'completed')
</script>

<template>
  <section class="report-studio">
    <div class="report-layout">
      <ReportTaskBoard
        :tasks="status.tasks"
        :selected-task-id="selectedTaskId"
        @update:selected-task-id="$emit('update:selectedTaskId', $event)"
        @download="(kind, taskId) => $emit('download', kind, taskId)"
      />

      <div class="report-main">
        <div class="editor-shelf archive-panel">
          <div class="editor-shelf__head">
            <div>
              <p class="archive-kicker">Template Selection</p>
              <h3 class="archive-card-title">交付编排与编辑焦点</h3>
              <p class="archive-copy">
                可在这里补充模板片段或额外交付要求;未填写时,系统继续使用默认场馆运营反馈模板。
              </p>
            </div>
            <div class="editor-shelf__actions">
              <span class="archive-chip" :data-tone="status.engines_ready ? 'success' : 'running'">
                <span class="archive-chip__dot" />
                {{ status.engines_ready ? 'Inputs Ready' : 'Awaiting Engine Output' }}
              </span>
              <button class="archive-button--ghost" type="button" :disabled="loading" @click="$emit('refresh')">刷新状态</button>
              <button class="archive-button" type="button" :disabled="generating || !status.initialized" @click="$emit('generate')">
                {{ generating ? '生成中' : '发起报告' }}
              </button>
            </div>
          </div>

          <label class="editor-input">
            <span class="archive-kicker">Editorial Focus</span>
            <textarea
              class="archive-textarea"
              :value="templateModel"
              placeholder="例如:优先突出排队体验、动线效率、评论情绪分歧,以及当前场馆与竞品之间的差异化表现。"
              @input="templateModel = ($event.target as HTMLTextAreaElement).value"
            />
          </label>
        </div>

        <ReportPreview
          :selected-task="selectedTask"
          :preview-url="previewUrl"
        />
      </div>
    </div>

    <div class="report-dock">
      <div class="report-dock__downloads">
        <button
          class="archive-button--quiet"
          type="button"
          :disabled="!canDownload"
          @click="selectedTask && $emit('download', 'pdf', selectedTask.task_id)"
        >
          PDF
        </button>
        <button
          class="archive-button--quiet"
          type="button"
          :disabled="!canDownload"
          @click="selectedTask && $emit('download', 'html', selectedTask.task_id)"
        >
          HTML
        </button>
        <button
          class="archive-button--quiet"
          type="button"
          :disabled="!canDownload"
          @click="selectedTask && $emit('download', 'md', selectedTask.task_id)"
        >
          MD
        </button>
      </div>

      <div class="report-dock__status">
        <span class="archive-chip" :data-tone="statusTone(selectedTask?.status || 'pending')">
          <span class="archive-chip__dot" />
          {{ selectedTask?.status || 'waiting' }}
        </span>
        <strong>{{ selectedTask?.progress ?? 0 }}%</strong>
      </div>

      <button class="archive-button" type="button" :disabled="generating || !status.initialized" @click="$emit('generate')">
        {{ generating ? '分析中' : '重新生成报告' }}
      </button>
    </div>
  </section>
</template>

<style scoped>
.report-studio,
.report-layout,
.report-main,
.editor-shelf,
.editor-shelf__actions {
  display: grid;
  gap: 18px;
}

.report-layout {
  grid-template-columns: 320px minmax(0, 1fr);
  align-items: start;
}

.report-main {
  min-width: 0;
}

.editor-shelf__head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 18px;
  align-items: start;
}

.editor-shelf__actions {
  grid-auto-flow: column;
  align-items: center;
}

.editor-input {
  display: grid;
  gap: 10px;
}

.editor-input .archive-textarea {
  min-height: 140px;
}

.report-dock {
  position: sticky;
  bottom: 12px;
  z-index: 2;
  display: flex;
  justify-content: space-between;
  gap: 18px;
  align-items: center;
  padding: 16px 18px;
  border: 1px solid rgba(24, 35, 31, 0.1);
  border-radius: 22px;
  background: rgba(255, 250, 244, 0.92);
  box-shadow: var(--shadow-soft);
  backdrop-filter: blur(16px);
}

.report-dock__downloads,
.report-dock__status {
  display: flex;
  gap: 10px;
  align-items: center;
}

.report-dock__downloads .archive-button--quiet {
  min-height: 34px;
  padding-inline: 12px;
}

.report-dock__status strong {
  font-family: var(--font-display);
  font-size: 28px;
  color: var(--primary);
}

@media (max-width: 1360px) {
  .report-layout,
  .editor-shelf__head {
    grid-template-columns: 1fr;
  }

  .editor-shelf__actions {
    grid-auto-flow: row;
    justify-items: start;
  }
}

@media (max-width: 820px) {
  .report-dock {
    flex-direction: column;
    align-items: stretch;
  }

  .report-dock__downloads,
  .report-dock__status {
    justify-content: flex-start;
    flex-wrap: wrap;
  }
}
</style>