feat(ui): 封装基础 UI 组件库
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
Deploy / Deploy Staging (push) Has been cancelled
Deploy / Build Production Runtime Images (push) Has been cancelled
Deploy / Deploy Production (push) Has been cancelled
Deploy / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
Deploy / Deploy Staging (push) Has been cancelled
Deploy / Build Production Runtime Images (push) Has been cancelled
Deploy / Deploy Production (push) Has been cancelled
Deploy / Production Browser E2E (push) Has been cancelled
封装 10 个 V21 设计系统基础组件,统一使用 CSS 变量: - Button: 6 种变体 (primary/secondary/ghost/text/danger/link) + 3 种尺寸 - Input: 支持 error 状态,含 Search/TextArea/Password 子组件 - Select: V21 圆角下拉样式 - Modal: 含 confirm/info/success/error/warning 快捷方法 - Table: V21 表格样式 - Card: 支持悬浮动效变体 - Tag/Badge: 5 种语义变体 (primary/success/warning/error/info) - Tooltip/Popover: V21 气泡样式 - Form: 支持 compact 紧凑模式 - Pagination: V21 分页样式 所有组件使用 CSS 变量(无硬编码值),支持暗色/亮色主题切换, 响应式布局适配移动端。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* V21 Button 按钮
|
||||
* 封装 Ant Design Button,应用 V21 设计系统样式
|
||||
*/
|
||||
import React from "react";
|
||||
import { Button as AntButton } from "antd";
|
||||
import type { ButtonProps as AntButtonProps } from "antd";
|
||||
import classNames from "classnames";
|
||||
import "./ui.css";
|
||||
|
||||
export type ButtonType =
|
||||
| "primary"
|
||||
| "secondary"
|
||||
| "ghost"
|
||||
| "text"
|
||||
| "danger"
|
||||
| "link";
|
||||
|
||||
export type ButtonSize = "sm" | "md" | "lg";
|
||||
|
||||
export interface ButtonProps extends Omit<AntButtonProps, "type" | "size"> {
|
||||
/** 按钮类型 */
|
||||
buttonType?: ButtonType;
|
||||
/** 按钮尺寸 */
|
||||
buttonSize?: ButtonSize;
|
||||
/** 兼容 antd type(用于直接替换) */
|
||||
type?: AntButtonProps["type"];
|
||||
/** 兼容 antd size */
|
||||
size?: AntButtonProps["size"];
|
||||
}
|
||||
|
||||
const TYPE_CLASS_MAP: Record<ButtonType, string> = {
|
||||
primary: "xx-btn-primary",
|
||||
secondary: "xx-btn-secondary",
|
||||
ghost: "xx-btn-ghost",
|
||||
text: "xx-btn-text",
|
||||
danger: "xx-btn-danger",
|
||||
link: "xx-btn-link",
|
||||
};
|
||||
|
||||
const SIZE_CLASS_MAP: Record<ButtonSize, string> = {
|
||||
sm: "xx-btn-sm",
|
||||
md: "xx-btn-md",
|
||||
lg: "xx-btn-lg",
|
||||
};
|
||||
|
||||
/** 将 buttonType 映射到 antd type */
|
||||
const toAntdType = (bt: ButtonType): AntButtonProps["type"] => {
|
||||
switch (bt) {
|
||||
case "primary":
|
||||
return "primary";
|
||||
case "danger":
|
||||
return "primary";
|
||||
case "link":
|
||||
return "link";
|
||||
case "text":
|
||||
return "text";
|
||||
default:
|
||||
return "default";
|
||||
}
|
||||
};
|
||||
|
||||
const Button: React.FC<ButtonProps> = ({
|
||||
buttonType = "ghost",
|
||||
buttonSize = "md",
|
||||
className,
|
||||
children,
|
||||
type,
|
||||
size,
|
||||
...rest
|
||||
}) => {
|
||||
const antdType = type ?? toAntdType(buttonType);
|
||||
const antdSize = size ?? (buttonSize === "sm" ? "small" : buttonSize === "lg" ? "large" : "middle");
|
||||
|
||||
const v21Class = classNames(
|
||||
"xx-btn",
|
||||
TYPE_CLASS_MAP[buttonType],
|
||||
SIZE_CLASS_MAP[buttonSize],
|
||||
className,
|
||||
);
|
||||
|
||||
return (
|
||||
<AntButton
|
||||
type={antdType}
|
||||
size={antdSize}
|
||||
className={v21Class}
|
||||
danger={buttonType === "danger" ? true : rest.danger}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</AntButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* V21 Card 卡片
|
||||
* 封装 Ant Design Card,应用 V21 设计系统样式
|
||||
*/
|
||||
import React from "react";
|
||||
import { Card as AntCard } from "antd";
|
||||
import type { CardProps as AntCardProps } from "antd";
|
||||
import classNames from "classnames";
|
||||
import "./ui.css";
|
||||
|
||||
export interface CardProps extends AntCardProps {
|
||||
/** 是否启用悬浮效果 */
|
||||
hover?: boolean;
|
||||
}
|
||||
|
||||
const Card: React.FC<CardProps> = ({ className, hover, ...rest }) => {
|
||||
const v21Class = classNames(
|
||||
"xx-card-v21",
|
||||
hover && "xx-card-hover",
|
||||
className,
|
||||
);
|
||||
return <AntCard className={v21Class} {...rest} />;
|
||||
};
|
||||
|
||||
export default Card;
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* V21 Form 表单
|
||||
* 封装 Ant Design Form,应用 V21 设计系统样式
|
||||
*/
|
||||
import React from "react";
|
||||
import { Form as AntForm } from "antd";
|
||||
import type { FormProps as AntFormProps } from "antd";
|
||||
import classNames from "classnames";
|
||||
import "./ui.css";
|
||||
|
||||
export interface FormProps extends AntFormProps {
|
||||
/** 紧凑模式(减小表单项间距) */
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
const Form = ({ className, compact, children, ...rest }: FormProps) => {
|
||||
const v21Class = classNames(
|
||||
"xx-form",
|
||||
compact && "xx-form-compact",
|
||||
className,
|
||||
);
|
||||
return (
|
||||
<AntForm className={v21Class} {...(rest as Omit<FormProps, "className" | "compact" | "children">)}>
|
||||
{children as React.ReactNode}
|
||||
</AntForm>
|
||||
);
|
||||
};
|
||||
|
||||
/** 导出 Form 的子组件(保持 antd API 一致) */
|
||||
export const FormItem = AntForm.Item;
|
||||
export const FormList = AntForm.List;
|
||||
export const FormProvider = AntForm.Provider;
|
||||
|
||||
export default Form;
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* V21 Input 输入框
|
||||
* 封装 Ant Design Input,应用 V21 设计系统样式
|
||||
*/
|
||||
import React from "react";
|
||||
import { Input as AntInput } from "antd";
|
||||
import type { InputProps as AntInputProps } from "antd";
|
||||
import type { InputRef } from "antd/es/input";
|
||||
import classNames from "classnames";
|
||||
import "./ui.css";
|
||||
|
||||
const { Search: AntSearch, TextArea: AntTextArea, Password: AntPassword } = AntInput;
|
||||
|
||||
export interface InputProps extends AntInputProps {
|
||||
/** 是否处于错误状态 */
|
||||
error?: boolean;
|
||||
}
|
||||
|
||||
/** 基础 Input */
|
||||
const Input = React.forwardRef<InputRef, InputProps>(
|
||||
({ className, error, ...rest }, ref) => {
|
||||
const v21Class = classNames(
|
||||
"xx-input",
|
||||
error && "xx-input-error",
|
||||
className,
|
||||
);
|
||||
return <AntInput ref={ref} className={v21Class} {...rest} />;
|
||||
},
|
||||
);
|
||||
Input.displayName = "Input";
|
||||
|
||||
/** Search 搜索框 */
|
||||
const Search: React.FC<
|
||||
React.ComponentProps<typeof AntSearch> & { error?: boolean }
|
||||
> = ({ className, error, ...rest }) => {
|
||||
const v21Class = classNames(
|
||||
"xx-input",
|
||||
"xx-input-search",
|
||||
error && "xx-input-error",
|
||||
className,
|
||||
);
|
||||
return <AntSearch className={v21Class} {...rest} />;
|
||||
};
|
||||
|
||||
/** TextArea 文本域 */
|
||||
const TextArea: React.FC<
|
||||
React.ComponentProps<typeof AntTextArea> & { error?: boolean }
|
||||
> = ({ className, error, ...rest }) => {
|
||||
const v21Class = classNames(
|
||||
"xx-input",
|
||||
error && "xx-input-error",
|
||||
className,
|
||||
);
|
||||
return <AntTextArea className={v21Class} {...rest} />;
|
||||
};
|
||||
|
||||
/** Password 密码框 */
|
||||
const Password = React.forwardRef<
|
||||
InputRef,
|
||||
React.ComponentProps<typeof AntPassword> & { error?: boolean }
|
||||
>(({ className, error, ...rest }, ref) => {
|
||||
const v21Class = classNames(
|
||||
"xx-input",
|
||||
error && "xx-input-error",
|
||||
className,
|
||||
);
|
||||
return <AntPassword ref={ref} className={v21Class} {...rest} />;
|
||||
});
|
||||
Password.displayName = "Password";
|
||||
|
||||
export default Object.assign(Input, {
|
||||
Search,
|
||||
TextArea,
|
||||
Password,
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* V21 Modal 对话框
|
||||
* 封装 Ant Design Modal,应用 V21 设计系统样式
|
||||
*/
|
||||
import React from "react";
|
||||
import { Modal as AntModal } from "antd";
|
||||
import type { ModalProps as AntModalProps } from "antd";
|
||||
import type { ModalFuncProps } from "antd/es/modal";
|
||||
import classNames from "classnames";
|
||||
import "./ui.css";
|
||||
|
||||
export interface ModalProps extends AntModalProps {
|
||||
/** 使用 V21 样式 */
|
||||
v21?: boolean;
|
||||
}
|
||||
|
||||
interface ModalComponent extends React.FC<ModalProps> {
|
||||
confirm: (config: ModalFuncProps) => ReturnType<typeof AntModal.confirm>;
|
||||
info: (config: ModalFuncProps) => ReturnType<typeof AntModal.info>;
|
||||
success: (config: ModalFuncProps) => ReturnType<typeof AntModal.success>;
|
||||
error: (config: ModalFuncProps) => ReturnType<typeof AntModal.error>;
|
||||
warning: (config: ModalFuncProps) => ReturnType<typeof AntModal.warning>;
|
||||
}
|
||||
|
||||
const Modal: ModalComponent = ({
|
||||
className,
|
||||
v21 = true,
|
||||
children,
|
||||
...rest
|
||||
}) => {
|
||||
const v21Class = classNames(v21 && "xx-modal", className);
|
||||
return (
|
||||
<AntModal className={v21Class} {...rest}>
|
||||
{children}
|
||||
</AntModal>
|
||||
);
|
||||
};
|
||||
|
||||
/** 确认对话框快捷方法 */
|
||||
Modal.confirm = (config: ModalFuncProps) => {
|
||||
return AntModal.confirm({
|
||||
...config,
|
||||
className: classNames("xx-modal-confirm", config.className),
|
||||
});
|
||||
};
|
||||
|
||||
Modal.info = (config: ModalFuncProps) => {
|
||||
return AntModal.info({
|
||||
...config,
|
||||
className: classNames("xx-modal-confirm", config.className),
|
||||
});
|
||||
};
|
||||
|
||||
Modal.success = (config: ModalFuncProps) => {
|
||||
return AntModal.success({
|
||||
...config,
|
||||
className: classNames("xx-modal-confirm", config.className),
|
||||
});
|
||||
};
|
||||
|
||||
Modal.error = (config: ModalFuncProps) => {
|
||||
return AntModal.error({
|
||||
...config,
|
||||
className: classNames("xx-modal-confirm", config.className),
|
||||
});
|
||||
};
|
||||
|
||||
Modal.warning = (config: ModalFuncProps) => {
|
||||
return AntModal.warning({
|
||||
...config,
|
||||
className: classNames("xx-modal-confirm", config.className),
|
||||
});
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* V21 Pagination 分页
|
||||
* 封装 Ant Design Pagination,应用 V21 设计系统样式
|
||||
*/
|
||||
import React from "react";
|
||||
import { Pagination as AntPagination } from "antd";
|
||||
import type { PaginationProps as AntPaginationProps } from "antd";
|
||||
import classNames from "classnames";
|
||||
import "./ui.css";
|
||||
|
||||
export interface PaginationProps extends AntPaginationProps {
|
||||
/** 使用 V21 样式 */
|
||||
v21?: boolean;
|
||||
}
|
||||
|
||||
const Pagination: React.FC<PaginationProps> = ({
|
||||
className,
|
||||
v21 = true,
|
||||
...rest
|
||||
}) => {
|
||||
const v21Class = classNames(v21 && "xx-pagination", className);
|
||||
return <AntPagination className={v21Class} {...rest} />;
|
||||
};
|
||||
|
||||
export default Pagination;
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* V21 Select 选择器
|
||||
* 封装 Ant Design Select,应用 V21 设计系统样式
|
||||
*/
|
||||
import React from "react";
|
||||
import { Select as AntSelect } from "antd";
|
||||
import type { SelectProps as AntSelectProps } from "antd";
|
||||
import classNames from "classnames";
|
||||
import "./ui.css";
|
||||
|
||||
export interface SelectProps extends AntSelectProps {
|
||||
/** 自定义弹出层类名 */
|
||||
dropdownClassName?: string;
|
||||
}
|
||||
|
||||
const Select: React.FC<SelectProps> = ({
|
||||
className,
|
||||
dropdownClassName,
|
||||
...rest
|
||||
}) => {
|
||||
const v21Class = classNames("xx-select", className);
|
||||
const v21DropdownClass = classNames(
|
||||
"xx-select-dropdown",
|
||||
dropdownClassName,
|
||||
);
|
||||
return (
|
||||
<AntSelect
|
||||
className={v21Class}
|
||||
popupClassName={v21DropdownClass}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Select;
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* V21 Table 表格
|
||||
* 封装 Ant Design Table,应用 V21 设计系统样式
|
||||
*/
|
||||
import React from "react";
|
||||
import { Table as AntTable } from "antd";
|
||||
import type { TableProps as AntTableProps } from "antd";
|
||||
import classNames from "classnames";
|
||||
import "./ui.css";
|
||||
|
||||
export interface TableProps<RecordType = unknown>
|
||||
extends AntTableProps<RecordType> {
|
||||
/** 使用 V21 样式 */
|
||||
v21?: boolean;
|
||||
}
|
||||
|
||||
function Table<RecordType extends object = Record<string, unknown>>({
|
||||
className,
|
||||
v21 = true,
|
||||
...rest
|
||||
}: TableProps<RecordType>) {
|
||||
const v21Class = classNames(v21 && "xx-table", className);
|
||||
return <AntTable<RecordType> className={v21Class} {...rest} />;
|
||||
}
|
||||
|
||||
export default Table as <RecordType extends object = Record<string, unknown>>(
|
||||
props: TableProps<RecordType> & React.RefAttributes<HTMLDivElement>,
|
||||
) => React.ReactElement;
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* V21 Tag 标签 / Badge 徽标
|
||||
* 封装 Ant Design Tag 和 Badge,应用 V21 设计系统样式
|
||||
*/
|
||||
import React from "react";
|
||||
import { Tag as AntTag, Badge as AntBadge } from "antd";
|
||||
import type { TagProps as AntTagProps, BadgeProps as AntBadgeProps } from "antd";
|
||||
import classNames from "classnames";
|
||||
import "./ui.css";
|
||||
|
||||
export type TagVariant = "primary" | "success" | "warning" | "error" | "info";
|
||||
|
||||
export interface TagProps extends AntTagProps {
|
||||
/** V21 语义变体 */
|
||||
variant?: TagVariant;
|
||||
}
|
||||
|
||||
const VARIANT_CLASS_MAP: Record<TagVariant, string> = {
|
||||
primary: "xx-tag-primary",
|
||||
success: "xx-tag-success",
|
||||
warning: "xx-tag-warning",
|
||||
error: "xx-tag-error",
|
||||
info: "xx-tag-info",
|
||||
};
|
||||
|
||||
/** Tag 标签 */
|
||||
const Tag: React.FC<TagProps> = ({ className, variant, ...rest }) => {
|
||||
const v21Class = classNames(
|
||||
"xx-tag",
|
||||
variant && VARIANT_CLASS_MAP[variant],
|
||||
className,
|
||||
);
|
||||
return <AntTag className={v21Class} {...rest} />;
|
||||
};
|
||||
|
||||
/** Badge 徽标 */
|
||||
const Badge: React.FC<AntBadgeProps> = ({ className, ...rest }) => {
|
||||
const v21Class = classNames("xx-badge", className);
|
||||
return <AntBadge className={v21Class} {...rest} />;
|
||||
};
|
||||
|
||||
export { Tag, Badge };
|
||||
export default Tag;
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* V21 Tooltip 文字提示 / Popover 气泡卡片
|
||||
* 封装 Ant Design Tooltip 和 Popover,应用 V21 设计系统样式
|
||||
*/
|
||||
import React from "react";
|
||||
import {
|
||||
Tooltip as AntTooltip,
|
||||
Popover as AntPopover,
|
||||
} from "antd";
|
||||
import classNames from "classnames";
|
||||
import "./ui.css";
|
||||
|
||||
export type TooltipProps = React.ComponentProps<typeof AntTooltip> & {
|
||||
/** 使用 V21 样式 */
|
||||
v21?: boolean;
|
||||
};
|
||||
|
||||
/** Tooltip 文字提示 */
|
||||
const Tooltip: React.FC<TooltipProps> = ({
|
||||
overlayClassName,
|
||||
v21 = true,
|
||||
...rest
|
||||
}) => {
|
||||
const v21Class = classNames(v21 && "xx-tooltip", overlayClassName);
|
||||
return <AntTooltip overlayClassName={v21Class} {...rest} />;
|
||||
};
|
||||
|
||||
export type PopoverProps = React.ComponentProps<typeof AntPopover> & {
|
||||
/** 使用 V21 样式 */
|
||||
v21?: boolean;
|
||||
};
|
||||
|
||||
/** Popover 气泡卡片 */
|
||||
const Popover: React.FC<PopoverProps> = ({
|
||||
overlayClassName,
|
||||
v21 = true,
|
||||
...rest
|
||||
}) => {
|
||||
const v21Class = classNames(v21 && "xx-popover", overlayClassName);
|
||||
return <AntPopover overlayClassName={v21Class} {...rest} />;
|
||||
};
|
||||
|
||||
export { Tooltip, Popover };
|
||||
export default Tooltip;
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* V21 UI 组件库 - 统一导出
|
||||
*/
|
||||
|
||||
// 组件导出
|
||||
export { default as Button } from "./Button";
|
||||
export type { ButtonProps, ButtonType, ButtonSize } from "./Button";
|
||||
|
||||
export { default as Input } from "./Input";
|
||||
export type { InputProps } from "./Input";
|
||||
|
||||
export { default as Select } from "./Select";
|
||||
export type { SelectProps } from "./Select";
|
||||
|
||||
export { default as Modal } from "./Modal";
|
||||
export type { ModalProps } from "./Modal";
|
||||
|
||||
export { default as Table } from "./Table";
|
||||
export type { TableProps } from "./Table";
|
||||
|
||||
export { default as Card } from "./Card";
|
||||
export type { CardProps } from "./Card";
|
||||
|
||||
export { Tag, Badge } from "./Tag";
|
||||
export type { TagProps, TagVariant } from "./Tag";
|
||||
|
||||
export { Tooltip, Popover } from "./Tooltip";
|
||||
export type { TooltipProps, PopoverProps } from "./Tooltip";
|
||||
|
||||
export { default as Form, FormItem, FormList, FormProvider } from "./Form";
|
||||
export type { FormProps } from "./Form";
|
||||
|
||||
export { default as Pagination } from "./Pagination";
|
||||
export type { PaginationProps } from "./Pagination";
|
||||
@@ -0,0 +1,534 @@
|
||||
/**
|
||||
* V21 UI 组件库 - 组件样式
|
||||
* 统一使用 CSS 变量,支持深色/浅色主题自动切换
|
||||
*/
|
||||
|
||||
/* ============================================================
|
||||
Button 按钮
|
||||
============================================================ */
|
||||
.xx-btn {
|
||||
border-radius: var(--radius-md) !important;
|
||||
font-weight: var(--font-weight-extrabold) !important;
|
||||
transition: var(--transition-all) !important;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.xx-btn-primary {
|
||||
background: var(--gradient-primary) !important;
|
||||
color: var(--text-inverse) !important;
|
||||
border: none !important;
|
||||
box-shadow: var(--shadow-primary) !important;
|
||||
}
|
||||
|
||||
.xx-btn-primary:hover,
|
||||
.xx-btn-primary:focus {
|
||||
background: var(--gradient-primary) !important;
|
||||
box-shadow: var(--shadow-hover) !important;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.xx-btn-secondary {
|
||||
background: var(--secondary-color) !important;
|
||||
color: var(--text-inverse) !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.xx-btn-secondary:hover,
|
||||
.xx-btn-secondary:focus {
|
||||
background: var(--secondary-hover) !important;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.xx-btn-ghost {
|
||||
background: var(--bg-primary) !important;
|
||||
border: 1px solid var(--border-color) !important;
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.xx-btn-ghost:hover,
|
||||
.xx-btn-ghost:focus {
|
||||
border-color: var(--primary-color) !important;
|
||||
color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
.xx-btn-text {
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
color: var(--text-secondary) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.xx-btn-text:hover,
|
||||
.xx-btn-text:focus {
|
||||
background: var(--primary-soft) !important;
|
||||
color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
.xx-btn-danger {
|
||||
background: var(--error-color) !important;
|
||||
color: var(--text-inverse) !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.xx-btn-danger:hover,
|
||||
.xx-btn-danger:focus {
|
||||
background: var(--error-hover) !important;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.xx-btn-link {
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
color: var(--text-link) !important;
|
||||
box-shadow: none !important;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
.xx-btn-link:hover,
|
||||
.xx-btn-link:focus {
|
||||
color: var(--text-link-hover) !important;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 尺寸 */
|
||||
.xx-btn-sm {
|
||||
padding: 6px 14px !important;
|
||||
font-size: var(--font-size-sm) !important;
|
||||
}
|
||||
|
||||
.xx-btn-md {
|
||||
padding: 10px 20px !important;
|
||||
font-size: var(--font-size-base) !important;
|
||||
}
|
||||
|
||||
.xx-btn-lg {
|
||||
padding: 14px 28px !important;
|
||||
font-size: var(--font-size-md) !important;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Input 输入框
|
||||
============================================================ */
|
||||
.xx-input .ant-input,
|
||||
.xx-input.ant-input {
|
||||
border-radius: var(--radius-sm) !important;
|
||||
border-color: var(--border-color) !important;
|
||||
color: var(--text-primary) !important;
|
||||
background: var(--bg-primary) !important;
|
||||
transition: var(--transition-colors) !important;
|
||||
font-size: var(--font-size-base) !important;
|
||||
}
|
||||
|
||||
.xx-input .ant-input:hover,
|
||||
.xx-input.ant-input:hover {
|
||||
border-color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
.xx-input .ant-input:focus,
|
||||
.xx-input.ant-input:focus,
|
||||
.xx-input .ant-input-focused,
|
||||
.xx-input.ant-input-focused {
|
||||
border-color: var(--primary-color) !important;
|
||||
box-shadow: 0 0 0 3px var(--primary-soft) !important;
|
||||
}
|
||||
|
||||
.xx-input .ant-input::placeholder,
|
||||
.xx-input.ant-input::placeholder {
|
||||
color: var(--text-tertiary) !important;
|
||||
}
|
||||
|
||||
/* 错误状态 */
|
||||
.xx-input-error .ant-input,
|
||||
.xx-input-error.ant-input {
|
||||
border-color: var(--error-color) !important;
|
||||
}
|
||||
|
||||
.xx-input-error .ant-input:focus,
|
||||
.xx-input-error.ant-input:focus {
|
||||
box-shadow: 0 0 0 3px var(--error-soft) !important;
|
||||
}
|
||||
|
||||
/* 前缀/后缀图标 */
|
||||
.xx-input .ant-input-prefix,
|
||||
.xx-input .ant-input-suffix {
|
||||
color: var(--text-tertiary) !important;
|
||||
}
|
||||
|
||||
/* 搜索框 */
|
||||
.xx-input-search .ant-input-search-button {
|
||||
border-radius: 0 var(--radius-sm) var(--radius-sm) 0 !important;
|
||||
background: var(--gradient-primary) !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Select 选择器
|
||||
============================================================ */
|
||||
.xx-select .ant-select-selector {
|
||||
border-radius: var(--radius-sm) !important;
|
||||
border-color: var(--border-color) !important;
|
||||
background: var(--bg-primary) !important;
|
||||
color: var(--text-primary) !important;
|
||||
transition: var(--transition-colors) !important;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.xx-select .ant-select-selector:hover {
|
||||
border-color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
.xx-select.ant-select-focused .ant-select-selector {
|
||||
border-color: var(--primary-color) !important;
|
||||
box-shadow: 0 0 0 3px var(--primary-soft) !important;
|
||||
}
|
||||
|
||||
.xx-select .ant-select-selection-placeholder {
|
||||
color: var(--text-tertiary) !important;
|
||||
}
|
||||
|
||||
/* 下拉菜单 */
|
||||
.xx-select-dropdown .ant-select-item {
|
||||
border-radius: var(--radius-xs) !important;
|
||||
color: var(--text-primary) !important;
|
||||
transition: var(--transition-fast) !important;
|
||||
}
|
||||
|
||||
.xx-select-dropdown .ant-select-item-option-active {
|
||||
background: var(--primary-soft) !important;
|
||||
color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
.xx-select-dropdown .ant-select-item-option-selected {
|
||||
background: var(--primary-soft) !important;
|
||||
color: var(--primary-color) !important;
|
||||
font-weight: var(--font-weight-semibold) !important;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Modal 对话框
|
||||
============================================================ */
|
||||
.xx-modal .ant-modal-content {
|
||||
border-radius: var(--radius-xl) !important;
|
||||
background: var(--bg-elevated) !important;
|
||||
box-shadow: var(--shadow-lg) !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.xx-modal .ant-modal-header {
|
||||
background: transparent !important;
|
||||
border-bottom: 1px solid var(--border-light) !important;
|
||||
padding: var(--space-lg) var(--space-xl) !important;
|
||||
}
|
||||
|
||||
.xx-modal .ant-modal-title {
|
||||
font-size: var(--font-size-lg) !important;
|
||||
font-weight: var(--font-weight-bold) !important;
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.xx-modal .ant-modal-close {
|
||||
color: var(--text-tertiary) !important;
|
||||
transition: var(--transition-fast) !important;
|
||||
}
|
||||
|
||||
.xx-modal .ant-modal-close:hover {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.xx-modal .ant-modal-body {
|
||||
padding: var(--space-xl) !important;
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.xx-modal .ant-modal-footer {
|
||||
border-top: 1px solid var(--border-light) !important;
|
||||
padding: var(--space-md) var(--space-xl) !important;
|
||||
}
|
||||
|
||||
/* 确认对话框 */
|
||||
.xx-modal-confirm .ant-modal-content {
|
||||
border-radius: var(--radius-xl) !important;
|
||||
}
|
||||
|
||||
.xx-modal-confirm .ant-modal-confirm-body > .anticon {
|
||||
font-size: 24px !important;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Table 表格
|
||||
============================================================ */
|
||||
.xx-table .ant-table {
|
||||
background: var(--bg-primary) !important;
|
||||
color: var(--text-primary) !important;
|
||||
border-radius: var(--radius-md) !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.xx-table .ant-table-thead > tr > th {
|
||||
background: var(--bg-secondary) !important;
|
||||
color: var(--text-secondary) !important;
|
||||
font-weight: var(--font-weight-semibold) !important;
|
||||
border-bottom: 1px solid var(--border-color) !important;
|
||||
font-size: var(--font-size-sm) !important;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--letter-spacing-wide);
|
||||
}
|
||||
|
||||
.xx-table .ant-table-tbody > tr > td {
|
||||
border-bottom: 1px solid var(--border-light) !important;
|
||||
color: var(--text-primary) !important;
|
||||
transition: var(--transition-fast) !important;
|
||||
}
|
||||
|
||||
.xx-table .ant-table-tbody > tr:hover > td {
|
||||
background: var(--primary-soft) !important;
|
||||
}
|
||||
|
||||
.xx-table .ant-table-tbody > tr:last-child > td {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
/* 排序图标 */
|
||||
.xx-table .ant-table-column-sorter-up.active,
|
||||
.xx-table .ant-table-column-sorter-down.active {
|
||||
color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.xx-table .ant-pagination {
|
||||
padding: var(--space-md) 0 !important;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Card 卡片
|
||||
============================================================ */
|
||||
.xx-card-v21 {
|
||||
background: var(--bg-primary) !important;
|
||||
border: 1px solid var(--border-color) !important;
|
||||
border-radius: var(--radius-xl) !important;
|
||||
box-shadow: var(--shadow-card) !important;
|
||||
transition: var(--transition-all) !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.xx-card-v21 .ant-card-head {
|
||||
border-bottom: 1px solid var(--border-light) !important;
|
||||
padding: 0 var(--space-xl) !important;
|
||||
min-height: 56px !important;
|
||||
}
|
||||
|
||||
.xx-card-v21 .ant-card-head-title {
|
||||
font-size: var(--font-size-lg) !important;
|
||||
font-weight: var(--font-weight-bold) !important;
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.xx-card-v21 .ant-card-body {
|
||||
padding: var(--space-xl) !important;
|
||||
}
|
||||
|
||||
.xx-card-v21 .ant-card-actions {
|
||||
border-top: 1px solid var(--border-light) !important;
|
||||
background: var(--bg-secondary) !important;
|
||||
}
|
||||
|
||||
.xx-card-v21 .ant-card-actions > li {
|
||||
margin: var(--space-sm) 0 !important;
|
||||
}
|
||||
|
||||
/* 悬浮卡片 */
|
||||
.xx-card-hover:hover {
|
||||
box-shadow: var(--shadow-md) !important;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Tag 标签 / Badge 徽标
|
||||
============================================================ */
|
||||
.xx-tag {
|
||||
border-radius: var(--radius-xs) !important;
|
||||
font-size: var(--font-size-sm) !important;
|
||||
font-weight: var(--font-weight-medium) !important;
|
||||
padding: 2px 10px !important;
|
||||
line-height: 1.6 !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.xx-tag-primary {
|
||||
background: var(--primary-soft) !important;
|
||||
color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
.xx-tag-success {
|
||||
background: var(--success-soft) !important;
|
||||
color: var(--success-color) !important;
|
||||
}
|
||||
|
||||
.xx-tag-warning {
|
||||
background: var(--warning-soft) !important;
|
||||
color: var(--warning-color) !important;
|
||||
}
|
||||
|
||||
.xx-tag-error {
|
||||
background: var(--error-soft) !important;
|
||||
color: var(--error-color) !important;
|
||||
}
|
||||
|
||||
.xx-tag-info {
|
||||
background: var(--info-soft) !important;
|
||||
color: var(--info-color) !important;
|
||||
}
|
||||
|
||||
/* Badge */
|
||||
.xx-badge .ant-badge-count {
|
||||
background: var(--gradient-primary) !important;
|
||||
box-shadow: var(--shadow-primary) !important;
|
||||
}
|
||||
|
||||
.xx-badge .ant-badge-status-dot {
|
||||
width: 8px !important;
|
||||
height: 8px !important;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Tooltip 文字提示 / Popover 气泡卡片
|
||||
============================================================ */
|
||||
.xx-tooltip .ant-tooltip-inner {
|
||||
background: var(--color-gray-800) !important;
|
||||
color: var(--text-inverse) !important;
|
||||
border-radius: var(--radius-sm) !important;
|
||||
padding: var(--space-xs) var(--space-sm) !important;
|
||||
font-size: var(--font-size-sm) !important;
|
||||
box-shadow: var(--shadow-sm) !important;
|
||||
}
|
||||
|
||||
.xx-tooltip .ant-tooltip-arrow::before {
|
||||
background: var(--color-gray-800) !important;
|
||||
}
|
||||
|
||||
/* Popover */
|
||||
.xx-popover .ant-popover-inner {
|
||||
background: var(--bg-elevated) !important;
|
||||
border: 1px solid var(--border-color) !important;
|
||||
border-radius: var(--radius-md) !important;
|
||||
box-shadow: var(--shadow-md) !important;
|
||||
}
|
||||
|
||||
.xx-popover .ant-popover-inner-content {
|
||||
color: var(--text-primary) !important;
|
||||
padding: var(--space-md) !important;
|
||||
}
|
||||
|
||||
.xx-popover .ant-popover-title {
|
||||
color: var(--text-primary) !important;
|
||||
font-weight: var(--font-weight-semibold) !important;
|
||||
border-bottom: 1px solid var(--border-light) !important;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Form 表单
|
||||
============================================================ */
|
||||
.xx-form .ant-form-item-label > label {
|
||||
color: var(--text-primary) !important;
|
||||
font-weight: var(--font-weight-medium) !important;
|
||||
font-size: var(--font-size-base) !important;
|
||||
}
|
||||
|
||||
.xx-form .ant-form-item-explain-error {
|
||||
color: var(--error-color) !important;
|
||||
font-size: var(--font-size-sm) !important;
|
||||
}
|
||||
|
||||
.xx-form .ant-form-item {
|
||||
margin-bottom: var(--space-lg) !important;
|
||||
}
|
||||
|
||||
/* 表单项间距紧凑 */
|
||||
.xx-form-compact .ant-form-item {
|
||||
margin-bottom: var(--space-md) !important;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Pagination 分页
|
||||
============================================================ */
|
||||
.xx-pagination .ant-pagination-item {
|
||||
border-radius: var(--radius-xs) !important;
|
||||
border-color: var(--border-color) !important;
|
||||
transition: var(--transition-fast) !important;
|
||||
}
|
||||
|
||||
.xx-pagination .ant-pagination-item a {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.xx-pagination .ant-pagination-item:hover {
|
||||
border-color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
.xx-pagination .ant-pagination-item:hover a {
|
||||
color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
.xx-pagination .ant-pagination-item-active {
|
||||
background: var(--gradient-primary) !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.xx-pagination .ant-pagination-item-active a {
|
||||
color: var(--text-inverse) !important;
|
||||
}
|
||||
|
||||
.xx-pagination .ant-pagination-prev .ant-pagination-item-link,
|
||||
.xx-pagination .ant-pagination-next .ant-pagination-item-link {
|
||||
border-radius: var(--radius-xs) !important;
|
||||
color: var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
.xx-pagination .ant-pagination-disabled .ant-pagination-item-link {
|
||||
color: var(--text-disabled) !important;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
响应式
|
||||
============================================================ */
|
||||
@media (max-width: 768px) {
|
||||
.xx-btn-lg {
|
||||
padding: 12px 20px !important;
|
||||
font-size: var(--font-size-base) !important;
|
||||
}
|
||||
|
||||
.xx-card-v21 .ant-card-body {
|
||||
padding: var(--space-md) !important;
|
||||
}
|
||||
|
||||
.xx-card-v21 .ant-card-head {
|
||||
padding: 0 var(--space-md) !important;
|
||||
}
|
||||
|
||||
.xx-modal .ant-modal-body {
|
||||
padding: var(--space-md) !important;
|
||||
}
|
||||
|
||||
.xx-modal .ant-modal-header {
|
||||
padding: var(--space-md) !important;
|
||||
}
|
||||
|
||||
.xx-form .ant-form-item {
|
||||
margin-bottom: var(--space-md) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.xx-btn-lg {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.xx-btn-md {
|
||||
padding: 8px 16px !important;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user