227 lines
7.2 KiB
TypeScript
Executable File
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>
|
|
)
|
|
} |