feat: 设置页数据导出新增问卷结果分析导出,生成MD文档

This commit is contained in:
selfrelease
2026-07-28 15:34:11 +08:00
parent 5aa7af9a46
commit 3ae1ea4c6d
+121 -1
View File
@@ -1,13 +1,14 @@
import { useState, useEffect } from 'react'
import { toast } from 'sonner'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { Building2, Users, CreditCard, Plus, Bell, Download, Upload, FileSpreadsheet, Clock, CheckCircle, AlertCircle } from 'lucide-react'
import { Building2, Users, CreditCard, Plus, Bell, Download, Upload, FileSpreadsheet, Clock, CheckCircle, AlertCircle, ClipboardList } from 'lucide-react'
import api from '../lib/api'
import { useAuthStore } from '../store/authStore'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
import { Input, Label, Select } from '../components/ui/Input'
import Modal from '../components/ui/Modal'
import { surveyPages, totalFeatures } from '../data/surveyData'
export default function Settings() {
const queryClient = useQueryClient()
@@ -527,10 +528,129 @@ function ExportSettings() {
<Button variant="secondary" size="sm" onClick={handleExport} disabled={exporting}>
<Download className="w-4 h-4 mr-1" />{exporting ? '导出中...' : '导出选中数据'}
</Button>
{/* 问卷结果分析导出 */}
<div className="border-t border-gray-200 pt-3 mt-3">
<div className="flex items-center gap-2 mb-2">
<ClipboardList className="w-4 h-4 text-primary" />
<h3 className="text-sm font-medium"></h3>
</div>
<p className="text-xs text-gray-500 mb-2"> Markdown </p>
<Button variant="secondary" size="sm" onClick={handleSurveyExport}>
<Download className="w-4 h-4 mr-1" />MD
</Button>
</div>
</div>
)
}
/** 导出问卷结果为 Markdown 文档 */
function handleSurveyExport() {
try {
const raw = localStorage.getItem('survey-results')
if (!raw) {
toast.info('暂无已提交的问卷结果,请先在功能调查问卷中提交')
return
}
const data = JSON.parse(raw)
const items: Array<{ featureId: string; score: number; useful: string; remark: string }> = data.items || []
if (items.length === 0) {
toast.info('问卷结果为空')
return
}
const scoreMap = new Map(items.map(i => [i.featureId, i]))
const usefulLabels: Record<string, string> = { yes: '有用', no: '无用', maybe: '待定', '': '' }
const submittedAt = data.submittedAt ? new Date(data.submittedAt).toLocaleString('zh-CN') : ''
let md = `# 功能调查问卷结果分析\n\n`
md += `> **导出时间:** ${new Date().toLocaleString('zh-CN')}\n`
if (submittedAt) md += `> **提交时间:** ${submittedAt}\n`
md += `> **总功能数:** ${totalFeatures}\n`
md += `> **已评功能数:** ${items.length}\n\n`
const rated = items.filter(i => i.score > 0)
const avgScore = rated.length > 0 ? (rated.reduce((s, i) => s + i.score, 0) / rated.length).toFixed(2) : '0'
const usefulCount = items.filter(i => i.useful === 'yes').length
const notUsefulCount = items.filter(i => i.useful === 'no').length
const maybeCount = items.filter(i => i.useful === 'maybe').length
md += `## 统计概览\n\n`
md += `| 指标 | 数值 |\n|------|------|\n`
md += `| 已评功能数 | ${items.length} / ${totalFeatures} |\n`
md += `| 平均评分 | ${avgScore} / 5 |\n`
md += `| 标记有用 | ${usefulCount} |\n`
md += `| 标记无用 | ${notUsefulCount} |\n`
md += `| 标记待定 | ${maybeCount} |\n\n`
md += `## 评分分布\n\n`
for (let s = 5; s >= 1; s--) {
const count = rated.filter(i => i.score === s).length
const pct = rated.length > 0 ? ((count / rated.length) * 100).toFixed(1) : '0'
md += `- **${s}星**${count} 项(${pct}%\n`
}
md += `\n`
md += `## 详细评分\n\n`
for (const page of surveyPages) {
const pageItems = page.features.filter(f => scoreMap.has(f.id))
if (pageItems.length === 0) continue
md += `### ${page.id}. ${page.name}\n\n`
md += `> 📍 ${page.menu}`
if (page.tab) md += ` | Tab: ${page.tab}`
md += `\n\n`
md += `| # | 功能 | 评分 | 有用性 | 备注 |\n`
md += `|---|------|:----:|:------:|------|\n`
for (const f of pageItems) {
const s = scoreMap.get(f.id)!
const stars = '★'.repeat(s.score) + '☆'.repeat(5 - s.score)
md += `| ${f.id} | ${f.name} | ${stars} (${s.score}) | ${usefulLabels[s.useful] || ''} | ${s.remark || ''} |\n`
}
md += `\n`
}
md += `## 低分功能(≤2分)\n\n`
const lowScore = rated.filter(i => i.score <= 2).sort((a, b) => a.score - b.score)
if (lowScore.length > 0) {
md += `| # | 功能 | 评分 | 备注 |\n|---|------|:----:|------|\n`
for (const item of lowScore) {
const feat = surveyPages.flatMap(p => p.features).find(f => f.id === item.featureId)
md += `| ${item.featureId} | ${feat?.name || item.featureId} | ${item.score} | ${item.remark || ''} |\n`
}
} else {
md += `无低分功能\n`
}
md += `\n`
md += `## 高分功能(≥4分)\n\n`
const highScore = rated.filter(i => i.score >= 4).sort((a, b) => b.score - a.score)
if (highScore.length > 0) {
md += `| # | 功能 | 评分 | 备注 |\n|---|------|:----:|------|\n`
for (const item of highScore) {
const feat = surveyPages.flatMap(p => p.features).find(f => f.id === item.featureId)
md += `| ${item.featureId} | ${feat?.name || item.featureId} | ${item.score} | ${item.remark || ''} |\n`
}
} else {
md += `无高分功能\n`
}
md += `\n---\n*由企业用工专家系统自动生成*\n`
const blob = new Blob([md], { type: 'text/markdown;charset=utf-8' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `survey-results-${new Date().toISOString().slice(0, 10)}.md`
a.click()
URL.revokeObjectURL(url)
toast.success('问卷结果已导出为 Markdown 文档')
} catch {
toast.error('导出问卷结果失败')
}
}
function PlanSettings({ orgData }: { orgData: any }) {
const queryClient = useQueryClient()
const plan = orgData?.plan || 'FREE'