origin/apps/web/src/app/main/plan/weekplan/page.tsx

227 lines
7.2 KiB
TypeScript
Executable File

import { useState } from 'react'
import * as XLSX from 'xlsx'
import { Table, Select, Pagination } from 'antd'
import type { ColumnsType, ColumnType } from 'antd/es/table'
import { UploadOutlined } from '@ant-design/icons'
interface TableData {
key: string
[key: string]: any
}
export default function WeekPlanPage() {
const [data, setData] = useState<TableData[]>([])
const [columns, setColumns] = useState<ColumnsType<TableData>>([])
const [currentPage, setCurrentPage] = useState(1)
const [trainingStatus, setTrainingStatus] = useState<Record<string, string>>({})
const pageSize = 1 // 每页显示一个第一列的唯一值
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]
if (!file) return
const reader = new FileReader()
reader.onload = (e) => {
const data = new Uint8Array(e.target?.result as ArrayBuffer)
const workbook = XLSX.read(data, { type: 'array' })
const firstSheet = workbook.Sheets[workbook.SheetNames[0]]
// 处理合并单元格
if (firstSheet['!merges']) {
firstSheet['!merges'].forEach(merge => {
// 获取合并区域的起始单元格的值
const firstCell = XLSX.utils.encode_cell({ r: merge.s.r, c: merge.s.c })
const firstCellValue = firstSheet[firstCell]?.v
// 将合并区域内的所有单元格设置为相同的值
for (let row = merge.s.r; row <= merge.e.r; row++) {
for (let col = merge.s.c; col <= merge.e.c; col++) {
const cell = XLSX.utils.encode_cell({ r: row, c: col })
if (!firstSheet[cell]) {
firstSheet[cell] = { t: 's', v: firstCellValue }
}
}
}
})
}
const jsonData: any[] = XLSX.utils.sheet_to_json(firstSheet, {
header: 1,
defval: '',
blankrows: false,
raw: false
})
// 处理表头和数据
const headers = jsonData[0]
const tableData: TableData[] = jsonData.slice(1).map((row, index) => ({
key: index.toString(),
...headers.reduce((acc: any, header: string, idx: number) => {
acc[header] = row[idx]
return acc
}, {})
}))
// 创建列配置
const tableColumns: ColumnsType<TableData> = headers.map((header: string, index: number) => ({
title: header,
dataIndex: header,
key: header,
width: 150,
align: 'center',
filterMultiple: true,
filters: Array.from(new Set(tableData.map(item => item[header])))
.filter(Boolean)
.map(value => ({ text: String(value), value: String(value) })),
onFilter: (value, record) => String(record[header]) === value,
...(index < headers.length - 1 && {
render: (text, record, index) => ({
children: text,
props: {
rowSpan: calculateRowSpan(tableData, index, header),
style: {
border: '1px solid #f0f0f0',
borderBottom: '1px solid #f0f0f0'
}
}
})
})
}))
// 添加是否参训列
tableColumns.push({
title: '是否参训',
dataIndex: 'isTraining',
key: 'isTraining',
width: 120,
align: 'center',
render: (_, record) => (
<Select
value={trainingStatus[record.key] || undefined}
onChange={(value) => {
setTrainingStatus(prev => ({
...prev,
[record.key]: value
}))
}}
style={{ width: '100%' }}
options={[
{ value: '参训', label: '参训' },
{ value: '不参训', label: '不参训' }
]}
placeholder="请选择"
/>
)
})
setColumns(tableColumns)
setData(tableData)
}
reader.readAsArrayBuffer(file)
}
// 计算行合并
const calculateRowSpan = (data: TableData[], rowIndex: number, column: string) => {
if (rowIndex === 0) {
let count = 1
for (let i = 1; i < data.length; i++) {
if (data[i][column] === data[rowIndex][column]) {
count++
} else {
break
}
}
return count
}
if (rowIndex > 0 && data[rowIndex][column] === data[rowIndex - 1][column]) {
return 0
}
let count = 1
for (let i = rowIndex + 1; i < data.length; i++) {
if (data[i][column] === data[rowIndex][column]) {
count++
} else {
break
}
}
return count
}
// 修改分页数据获取逻辑
const getPageData = () => {
const firstColumn = columns[0] as ColumnType<TableData>
const firstColumnName = firstColumn?.dataIndex as string
if (!firstColumnName) return []
// 获取第一列的所有唯一值
const uniqueValues = Array.from(
new Set(data.map(item => item[firstColumnName]))
).filter(Boolean)
// 获取当前页应该显示的值
const currentValue = uniqueValues[(currentPage - 1)]
// 返回第一列等于当前值的所有行
return data.filter(item => item[firstColumnName] === currentValue)
}
console.log(data)
const handleSave = () => {
const saveData = data.map(item => ({
...item,
isTraining: trainingStatus[item.key] || ''
}))
console.log('要保存的数据:', saveData)
// 这里添加保存到后端的逻辑
}
return (
<div className="p-4">
<div className="w-full mb-6 p-8 border-2 border-dashed border-gray-300 rounded-lg bg-gray-50 hover:bg-gray-100 transition-colors cursor-pointer relative">
<input
type="file"
accept=".xlsx,.xls"
onChange={handleFileUpload}
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
/>
<div className="flex flex-col items-center justify-center text-gray-600">
<UploadOutlined className="text-3xl mb-2" />
<p className="text-lg font-medium"></p>
<p className="text-sm text-gray-500"> .xlsx, .xls Excel文件</p>
</div>
</div>
{data.length > 0 && (
<>
<Table
columns={columns}
dataSource={getPageData()}
pagination={false}
bordered
style={{
border: '1px solid #f0f0f0'
}}
className="!border-collapse [&_th]:!border [&_td]:!border [&_th]:!border-solid [&_td]:!border-solid [&_th]:!border-[#f0f0f0] [&_td]:!border-[#f0f0f0]"
/>
<div className="flex justify-end mt-4">
<button
onClick={handleSave}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
</button>
</div>
<Pagination
className="mt-4"
current={currentPage}
total={Array.from(new Set(data.map(item =>
item[(columns[0] as ColumnType<TableData>)?.dataIndex as string]
))).filter(Boolean).length}
pageSize={1}
onChange={(page) => {
setCurrentPage(page)
}}
/>
</>
)}
</div>
)
}