修正匯出 excel 異常功能

This commit is contained in:
2025-10-07 13:04:18 +08:00
parent 5324297415
commit 4d8b394eed
2 changed files with 90 additions and 11 deletions

View File

@@ -103,6 +103,7 @@ export default function AdminPage() {
const [statusFilter, setStatusFilter] = useState("all") const [statusFilter, setStatusFilter] = useState("all")
const [visibilityFilter, setVisibilityFilter] = useState("all") const [visibilityFilter, setVisibilityFilter] = useState("all")
const [isExporting, setIsExporting] = useState(false) const [isExporting, setIsExporting] = useState(false)
const [isExportingExcel, setIsExportingExcel] = useState(false)
const [showCategoryGuide, setShowCategoryGuide] = useState(false) const [showCategoryGuide, setShowCategoryGuide] = useState(false)
const [showPrivacyDetails, setShowPrivacyDetails] = useState(false) const [showPrivacyDetails, setShowPrivacyDetails] = useState(false)
@@ -427,6 +428,43 @@ export default function AdminPage() {
} }
} }
// 匯出 Excel
const exportToExcel = async () => {
try {
setIsExportingExcel(true)
const response = await fetch('/api/admin/export-excel', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
data: filteredWishes,
filename: `困擾案例數據_${new Date().toISOString().split('T')[0]}.xlsx`
})
})
if (response.ok) {
const blob = await response.blob()
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `困擾案例數據_${new Date().toISOString().split('T')[0]}.xlsx`
document.body.appendChild(a)
a.click()
window.URL.revokeObjectURL(url)
document.body.removeChild(a)
} else {
throw new Error('匯出失敗')
}
} catch (error) {
console.error('匯出 Excel 失敗:', error)
alert('匯出失敗,請稍後再試')
} finally {
setIsExportingExcel(false)
}
}
useEffect(() => { useEffect(() => {
fetchData() fetchData()
}, []) }, [])
@@ -575,7 +613,7 @@ export default function AdminPage() {
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4"> <div className="grid grid-cols-1 md:grid-cols-5 gap-4">
<div className="relative"> <div className="relative">
<Search className="absolute left-3 top-3 h-4 w-4 text-blue-400" /> <Search className="absolute left-3 top-3 h-4 w-4 text-blue-400" />
<Input <Input
@@ -620,6 +658,19 @@ export default function AdminPage() {
)} )}
CSV CSV
</Button> </Button>
<Button
onClick={exportToExcel}
disabled={isExportingExcel}
className="bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 text-white shadow-lg shadow-blue-500/25"
>
{isExportingExcel ? (
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
) : (
<Download className="w-4 h-4 mr-2" />
)}
Excel
</Button>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>

View File

@@ -29,27 +29,55 @@ export async function POST(request: NextRequest) {
const csvRows = [headers.join(',')] const csvRows = [headers.join(',')]
data.forEach((wish: any) => { data.forEach((wish: any) => {
// 清理和轉義 CSV 數據
const cleanText = (text: string) => {
if (!text) return ''
return text
.replace(/"/g, '""') // 轉義雙引號
.replace(/\r?\n/g, ' ') // 將換行符替換為空格
.replace(/\t/g, ' ') // 將製表符替換為空格
.trim()
}
const row = [ const row = [
wish.id, wish.id,
`"${wish.title.replace(/"/g, '""')}"`, `"${cleanText(wish.title)}"`,
`"${wish.current_pain.replace(/"/g, '""')}"`, `"${cleanText(wish.current_pain)}"`,
`"${wish.expected_solution.replace(/"/g, '""')}"`, `"${cleanText(wish.expected_solution)}"`,
`"${(wish.expected_effect || '').replace(/"/g, '""')}"`, `"${cleanText(wish.expected_effect || '')}"`,
wish.is_public ? '是' : '否', wish.is_public ? '是' : '否',
`"${(wish.email || '').replace(/"/g, '""')}"`, `"${cleanText(wish.email || '')}"`,
wish.status === 'active' ? '活躍' : '非活躍', wish.status === 'active' ? '活躍' : '非活躍',
`"${(wish.category || '未分類').replace(/"/g, '""')}"`, `"${cleanText(wish.category || '未分類')}"`,
wish.priority, wish.priority,
wish.like_count, wish.like_count,
`"${new Date(wish.created_at).toLocaleString('zh-TW')}"`, `"${new Date(wish.created_at).toLocaleString('zh-TW', {
`"${new Date(wish.updated_at).toLocaleString('zh-TW')}"`, year: 'numeric',
`"${wish.user_session.replace(/"/g, '""')}"` month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})}"`,
`"${new Date(wish.updated_at).toLocaleString('zh-TW', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})}"`,
`"${cleanText(wish.user_session)}"`
] ]
csvRows.push(row.join(',')) csvRows.push(row.join(','))
}) })
const csvContent = csvRows.join('\n') const csvContent = csvRows.join('\n')
const csvBuffer = Buffer.from(csvContent, 'utf-8') // 添加 UTF-8 BOM 以確保 Excel 正確識別中文編碼
const csvBuffer = Buffer.concat([
Buffer.from('\uFEFF', 'utf8'), // UTF-8 BOM
Buffer.from(csvContent, 'utf-8')
])
console.log(`✅ 成功生成 CSV 文件: ${data.length} 筆數據`) console.log(`✅ 成功生成 CSV 文件: ${data.length} 筆數據`)