重新构建路由和配置样式文件

This commit is contained in:
2025-03-26 10:04:27 +08:00
parent a42a9990bf
commit 97ccf5a077
141 changed files with 88034 additions and 179 deletions
+75
View File
@@ -0,0 +1,75 @@
import React from 'react';
import { Link } from '@remix-run/react';
type ButtonType = 'primary' | 'default' | 'danger';
type ButtonSize = 'small' | 'medium' | 'large';
interface ButtonProps {
children: React.ReactNode;
type?: ButtonType;
size?: ButtonSize;
to?: string;
icon?: string;
disabled?: boolean;
className?: string;
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
}
export function Button({
children,
type = 'default',
size = 'medium',
to,
icon,
disabled = false,
className = '',
onClick,
...rest
}: ButtonProps & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'type'>) {
const baseClasses = 'ant-btn';
const typeClasses = {
primary: 'ant-btn-primary',
default: 'ant-btn-default',
danger: 'ant-btn-danger'
};
const sizeClasses = {
small: 'ant-btn-sm',
medium: '',
large: 'text-base px-5 py-2.5'
};
const classes = [
baseClasses,
typeClasses[type],
sizeClasses[size],
disabled ? 'opacity-50 cursor-not-allowed' : '',
className
].filter(Boolean).join(' ');
if (to) {
return (
<Link
to={to}
className={classes}
{...(rest as any)}
>
{icon && <i className={`${icon} mr-1.5`}></i>}
{children}
</Link>
);
}
return (
<button
className={classes}
disabled={disabled}
onClick={onClick}
{...rest}
>
{icon && <i className={`${icon} mr-1.5`}></i>}
{children}
</button>
);
}
+44
View File
@@ -0,0 +1,44 @@
import React from 'react';
interface CardProps {
children: React.ReactNode;
title?: React.ReactNode;
icon?: string;
extra?: React.ReactNode;
className?: string;
bodyClassName?: string;
noDivider?: boolean;
}
export function Card({
children,
title,
icon,
extra,
className = '',
bodyClassName = '',
noDivider = true,
}: CardProps) {
return (
<div className={`ant-card ${className} bg-white shadow`}>
{(title || extra) && (
<div className={`flex justify-between items-center px-5 py-3 ${noDivider ? '' : 'border-b border-gray-100'}`}>
{title && (
<div className="card-title !mb-0">
{icon && <i className={`${icon} mr-2`}></i>}
<span>{title}</span>
</div>
)}
{extra && (
<div className="card-extra">
{extra}
</div>
)}
</div>
)}
<div className={`ant-card-body ${bodyClassName}`}>
{children}
</div>
</div>
);
}
+108
View File
@@ -0,0 +1,108 @@
import React from 'react';
interface TableColumn<T> {
title: React.ReactNode;
dataIndex?: keyof T;
key?: string;
width?: number | string;
render?: (value: any, record: T, index: number) => React.ReactNode;
align?: 'left' | 'center' | 'right';
className?: string;
}
interface TableProps<T> {
columns: TableColumn<T>[];
dataSource: T[];
rowKey: keyof T | ((record: T) => string);
loading?: boolean;
bordered?: boolean;
emptyText?: React.ReactNode;
className?: string;
onRow?: (record: T, index: number) => React.HTMLAttributes<HTMLTableRowElement>;
}
export function Table<T extends Record<string, any>>({
columns,
dataSource,
rowKey,
loading = false,
bordered = false,
emptyText = '暂无数据',
className = '',
onRow,
}: TableProps<T>) {
const getRowKey = (record: T, index: number): string => {
if (typeof rowKey === 'function') {
return rowKey(record);
}
return String(record[rowKey]);
};
return (
<div className={`ant-table-wrapper ${className} ${loading ? 'opacity-70' : ''}`}>
<table className="ant-table">
<thead>
<tr>
{columns.map((column, index) => (
<th
key={column.key || column.dataIndex?.toString() || index}
className={column.className}
style={{
width: column.width,
textAlign: column.align || 'left',
}}
>
{column.title}
</th>
))}
</tr>
</thead>
<tbody>
{dataSource.length > 0 ? (
dataSource.map((record, index) => (
<tr
key={getRowKey(record, index)}
{...(onRow ? onRow(record, index) : {})}
>
{columns.map((column, colIndex) => (
<td
key={column.key || column.dataIndex?.toString() || colIndex}
style={{ textAlign: column.align || 'left' }}
>
{column.render
? column.render(
column.dataIndex ? record[column.dataIndex] : undefined,
record,
index
)
: column.dataIndex
? record[column.dataIndex]
: undefined}
</td>
))}
</tr>
))
) : (
<tr>
<td
colSpan={columns.length}
className="py-6 text-center text-gray-500"
>
{emptyText}
</td>
</tr>
)}
</tbody>
</table>
{loading && (
<div className="absolute inset-0 flex items-center justify-center bg-white bg-opacity-60 z-10">
<div className="flex items-center space-x-2">
<div className="w-5 h-5 border-2 border-primary border-t-transparent rounded-full animate-spin"></div>
<span className="text-gray-600">...</span>
</div>
</div>
)}
</div>
);
}
+60
View File
@@ -0,0 +1,60 @@
import React from 'react';
export type TagColor = 'blue' | 'green' | 'cyan' | 'purple' | 'orange' | 'red' | 'default';
interface TagProps {
children: React.ReactNode;
color?: TagColor;
closable?: boolean;
onClose?: (e: React.MouseEvent<HTMLElement>) => void;
className?: string;
}
export function Tag({
children,
color = 'default',
closable = false,
onClose,
className = '',
}: TagProps) {
const baseClasses = 'ant-tag';
const colorClasses = {
default: '',
blue: 'ant-tag-blue',
green: 'ant-tag-green',
cyan: 'ant-tag-cyan',
purple: 'ant-tag-purple',
orange: 'ant-tag-orange',
red: 'ant-tag-red'
};
const classes = [
baseClasses,
colorClasses[color],
className
].filter(Boolean).join(' ');
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onClose?.(e as unknown as React.MouseEvent<HTMLElement>);
}
};
return (
<span className={classes}>
{children}
{closable && (
<i
className="ri-close-line ml-1 cursor-pointer text-opacity-60 hover:text-opacity-100"
onClick={onClose}
onKeyDown={handleKeyDown}
role="button"
tabIndex={0}
aria-label="关闭标签"
></i>
)}
</span>
);
}