feat: improve model catalog aggregation
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { format } from 'date-fns';
|
||||
import ConfigProvider from 'antd/es/config-provider';
|
||||
import DatePicker from 'antd/es/date-picker';
|
||||
import zhCN from 'antd/es/locale/zh_CN';
|
||||
import dayjs, { type Dayjs } from 'dayjs';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
import { CalendarIcon, X } from 'lucide-react';
|
||||
import { Button } from './button';
|
||||
import { Calendar } from './calendar';
|
||||
@@ -7,7 +13,11 @@ import { Input } from './input';
|
||||
import { Label } from './label';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from './popover';
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
dayjs.locale('zh-cn');
|
||||
|
||||
export function DateTimePicker(props: {
|
||||
clearLabel?: string;
|
||||
disabled?: boolean;
|
||||
placeholder?: string;
|
||||
value: string;
|
||||
@@ -54,7 +64,7 @@ export function DateTimePicker(props: {
|
||||
时间
|
||||
<Input type="time" value={timeValue} onChange={(event) => updateTime(event.target.value)} />
|
||||
</Label>
|
||||
<Button type="button" variant="ghost" size="icon" title="清除有效期" disabled={!props.value} onClick={() => props.onChange('')}>
|
||||
<Button type="button" variant="ghost" size="icon" title={props.clearLabel ?? '清除有效期'} disabled={!props.value} onClick={() => props.onChange('')}>
|
||||
<X size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -63,9 +73,68 @@ export function DateTimePicker(props: {
|
||||
);
|
||||
}
|
||||
|
||||
export function DateTimeRangePicker(props: {
|
||||
disabled?: boolean;
|
||||
from: string;
|
||||
fromPlaceholder?: string;
|
||||
to: string;
|
||||
toPlaceholder?: string;
|
||||
onChange: (value: { from: string; to: string }) => void;
|
||||
}) {
|
||||
const value = useMemo<[Dayjs | null, Dayjs | null] | null>(() => {
|
||||
const from = parseDayjsDateTime(props.from);
|
||||
const to = parseDayjsDateTime(props.to);
|
||||
return from || to ? [from, to] : null;
|
||||
}, [props.from, props.to]);
|
||||
|
||||
return (
|
||||
<ConfigProvider locale={zhCN} theme={dateRangePickerTheme}>
|
||||
<DatePicker.RangePicker
|
||||
allowClear
|
||||
className="shDateRangePicker"
|
||||
classNames={{ popup: { root: 'shDateRangePickerPopup' } }}
|
||||
disabled={props.disabled}
|
||||
format={rangeDateTimeFormat}
|
||||
needConfirm
|
||||
placeholder={[props.fromPlaceholder ?? '开始日期', props.toPlaceholder ?? '结束日期']}
|
||||
showTime={{ format: 'HH:mm:ss' }}
|
||||
size="middle"
|
||||
style={{ width: '100%' }}
|
||||
value={value}
|
||||
onChange={(nextValue) => {
|
||||
props.onChange({
|
||||
from: nextValue?.[0] ? nextValue[0].format(rangeDateTimeFormat) : '',
|
||||
to: nextValue?.[1] ? nextValue[1].format(rangeDateTimeFormat) : '',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</ConfigProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const rangeDateTimeFormat = 'YYYY-MM-DD HH:mm:ss';
|
||||
const rangeParseFormats = [rangeDateTimeFormat, 'YYYY-MM-DDTHH:mm:ss', 'YYYY-MM-DDTHH:mm', 'YYYY-MM-DD'];
|
||||
const dateRangePickerTheme = {
|
||||
token: {
|
||||
colorPrimary: '#18181b',
|
||||
colorPrimaryHover: '#27272a',
|
||||
colorBorder: '#e4e4e7',
|
||||
colorText: '#27272a',
|
||||
colorTextPlaceholder: '#71717a',
|
||||
colorBgContainer: '#ffffff',
|
||||
colorBgElevated: '#ffffff',
|
||||
colorFillSecondary: '#f4f4f5',
|
||||
borderRadius: 6,
|
||||
controlHeight: 36,
|
||||
fontFamily: 'inherit',
|
||||
fontSize: 14,
|
||||
},
|
||||
};
|
||||
|
||||
function parseLocalDateTime(value: string) {
|
||||
if (!value) return undefined;
|
||||
const date = new Date(value);
|
||||
const normalized = value.includes('T') ? value : value.replace(' ', 'T');
|
||||
const date = new Date(normalized);
|
||||
return Number.isNaN(date.getTime()) ? undefined : date;
|
||||
}
|
||||
|
||||
@@ -82,6 +151,14 @@ function formatTimeValue(date: Date) {
|
||||
return `${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||
}
|
||||
|
||||
function parseDayjsDateTime(value: string): Dayjs | null {
|
||||
if (!value) return null;
|
||||
const parsed = dayjs(value, rangeParseFormats, true);
|
||||
if (parsed.isValid()) return parsed;
|
||||
const fallback = dayjs(value);
|
||||
return fallback.isValid() ? fallback : null;
|
||||
}
|
||||
|
||||
function pad(value: number) {
|
||||
return String(value).padStart(2, '0');
|
||||
}
|
||||
|
||||
@@ -1,9 +1,40 @@
|
||||
import * as React from 'react';
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
export function Table(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
export type TableDensity = 'standard' | 'compact';
|
||||
|
||||
export interface TableProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
density?: TableDensity;
|
||||
}
|
||||
|
||||
export function Table(props: TableProps) {
|
||||
const { className, density = 'standard', ...rest } = props;
|
||||
return <div className={cn('shTable', density === 'compact' ? 'shTableCompact' : 'shTableStandard', className)} role="table" {...rest} />;
|
||||
}
|
||||
|
||||
export function TableViewportLayout(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
const { className, ...rest } = props;
|
||||
return <div className={cn('shTable', className)} role="table" {...rest} />;
|
||||
return <div className={cn('shTableViewportLayout', className)} {...rest} />;
|
||||
}
|
||||
|
||||
export function TableToolbar(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
const { className, ...rest } = props;
|
||||
return <div className={cn('shTableToolbar', className)} {...rest} />;
|
||||
}
|
||||
|
||||
export function TableSummary(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
const { className, ...rest } = props;
|
||||
return <div className={cn('shTableSummary', className)} {...rest} />;
|
||||
}
|
||||
|
||||
export function TableFooter(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
const { className, ...rest } = props;
|
||||
return <div className={cn('shTableFooter', className)} {...rest} />;
|
||||
}
|
||||
|
||||
export function TablePageActions(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
const { className, ...rest } = props;
|
||||
return <div className={cn('shTablePageActions', className)} {...rest} />;
|
||||
}
|
||||
|
||||
export function TableRow(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
@@ -11,14 +42,14 @@ export function TableRow(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return <div className={cn('shTableRow', className)} role="row" {...rest} />;
|
||||
}
|
||||
|
||||
export function TableHead(props: React.HTMLAttributes<HTMLSpanElement>) {
|
||||
export function TableHead(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
const { className, ...rest } = props;
|
||||
return <span className={cn('shTableHead', className)} role="columnheader" {...rest} />;
|
||||
return <div className={cn('shTableHead', className)} role="columnheader" {...rest} />;
|
||||
}
|
||||
|
||||
export function TableCell(props: React.HTMLAttributes<HTMLSpanElement>) {
|
||||
export function TableCell(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
const { className, ...rest } = props;
|
||||
return <span className={cn('shTableCell', className)} role="cell" {...rest} />;
|
||||
return <div className={cn('shTableCell', className)} role="cell" {...rest} />;
|
||||
}
|
||||
|
||||
export function EmptyState(props: { title: string; description?: string }) {
|
||||
|
||||
Reference in New Issue
Block a user