综合示例
@@ -88,14 +175,16 @@ const BaseExample = () => {
-
- {profileCards.map(item => (
-
- ))}
+ {renderCardList(basicCards, mode, status)}
+
+
+ 空值与占位示例
+
+ 无头像显示占位图;无简介 / 无更多信息 / 只有姓名时,对应容器整块不渲染;无 status 时不显示状态点。
+
+
+ {renderCardList(emptyStateCards, mode, status)}
);
};
diff --git a/package.json b/package.json
index bba46eb..67ee253 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@kne/react-box",
- "version": "0.1.10",
+ "version": "0.1.11",
"description": "一个轻量级的 React 设计组件库,专注于提供精致美观的信息展示小组件",
"syntax": {
"esmodules": true
diff --git a/src/PersonalCard/icons.js b/src/PersonalCard/icons.js
index aaeade1..2f27753 100644
--- a/src/PersonalCard/icons.js
+++ b/src/PersonalCard/icons.js
@@ -17,3 +17,9 @@ export const QuoteIcon = ({ className = '', size = 24 }) => (
);
+
+export const UserIcon = ({ className = '', size = 24 }) => (
+
+);
diff --git a/src/PersonalCard/index.js b/src/PersonalCard/index.js
index eebb69a..9e3fa27 100644
--- a/src/PersonalCard/index.js
+++ b/src/PersonalCard/index.js
@@ -1,6 +1,6 @@
import React, { Fragment } from 'react';
import style from './style.module.scss';
-import { MailIcon, CallIcon, QuoteIcon } from './icons';
+import { MailIcon, CallIcon, QuoteIcon, UserIcon } from './icons';
/**
* @param {React.ReactNode} [extra] 卡片角落扩展区(如 Checkbox)
@@ -9,6 +9,10 @@ import { MailIcon, CallIcon, QuoteIcon } from './icons';
*/
const PersonalCard = ({ avatar, name, title, description, phone, email, moreInfo = [], status, badge, mode = 'large', extra, footer, selected = false, className }) => {
const statusClass = style[`status-${status}`] || style['status-online'];
+ const validMoreInfo = Array.isArray(moreInfo) ? moreInfo.filter(item => item?.content != null && item.content !== '') : [];
+ const hasMoreInfo = validMoreInfo.length > 0;
+ const hasContact = !!(phone || email);
+ const hasDescription = !!description;
const AvatarWithStatus = ({ size = 'default' }) => {
const sizeClasses = {
@@ -17,10 +21,31 @@ const PersonalCard = ({ avatar, name, title, description, phone, email, moreInfo
small: 'avatar-small',
default: 'avatar-medium'
};
+ const iconSizeMap = {
+ large: 48,
+ medium: 32,
+ small: 24,
+ default: 32
+ };
+ const sizeKey = sizeClasses[size] ? size : 'default';
+
+ const renderAvatarContent = () => {
+ if (typeof avatar === 'function') {
+ return avatar({ className: style['avatar'] });
+ }
+ if (avatar) {
+ return
;
+ }
+ return (
+
+
+
+ );
+ };
return (
- {typeof avatar === 'function' ? avatar({ className: style['avatar'] }) :

}
+ {renderAvatarContent()}
{status &&
}
);
@@ -45,14 +70,13 @@ const PersonalCard = ({ avatar, name, title, description, phone, email, moreInfo
const BasicInfo = ({ showDivider = true }) => {
return (
- Array.isArray(moreInfo) &&
- moreInfo.length > 0 && (
+ hasMoreInfo && (
- {moreInfo.map(({ content }, index) => {
+ {validMoreInfo.map(({ content }, index) => {
return (
{content}
- {showDivider && moreInfo.length - 1 !== index && }
+ {showDivider && validMoreInfo.length - 1 !== index && }
);
})}
@@ -62,10 +86,9 @@ const PersonalCard = ({ avatar, name, title, description, phone, email, moreInfo
};
const InfoGrid = () =>
- Array.isArray(moreInfo) &&
- moreInfo.length > 0 && (
+ hasMoreInfo && (
- {moreInfo.map(({ label, content }, index) => {
+ {validMoreInfo.map(({ label, content }, index) => {
return ;
})}
@@ -112,10 +135,12 @@ const PersonalCard = ({ avatar, name, title, description, phone, email, moreInfo
-
- {(phone || email) && (
+ {hasDescription && (
+
+ )}
+ {hasContact && (
@@ -136,7 +161,7 @@ const PersonalCard = ({ avatar, name, title, description, phone, email, moreInfo
{badge && {badge}}
{title && {title}
}
- {(phone || email) && (
+ {hasContact && (
@@ -144,44 +169,55 @@ const PersonalCard = ({ avatar, name, title, description, phone, email, moreInfo
)}
-
-
-
-
{description}
+ {(hasMoreInfo || hasDescription) && (
+
+
+ {hasDescription && (
+
+ )}
-
+ )}
{renderFooter()}
);
- const renderHorizontal = () => (
-
- {renderExtra()}
-
-
-
- {name} {badge && {badge}}
-
- {title &&
{title}
}
- {(phone || email) && (
-
-
-
-
+ const renderHorizontal = () => {
+ const showRight = hasMoreInfo || hasDescription || footer;
+ return (
+
+ {renderExtra()}
+
+
+
+ {name} {badge && {badge}}
+
+ {title &&
{title}
}
+ {hasContact && (
+
+ )}
+
+ {showRight && (
+
+
+ {hasDescription && (
+
+ )}
+ {renderFooter()}
)}
-
-
- );
+ );
+ };
const renderMode = {
vertical: renderVertical,
diff --git a/src/PersonalCard/style.module.scss b/src/PersonalCard/style.module.scss
index 86fb297..55e4d91 100644
--- a/src/PersonalCard/style.module.scss
+++ b/src/PersonalCard/style.module.scss
@@ -89,6 +89,19 @@
width: 100%;
height: 100%;
object-fit: cover;
+ border-radius: 50%;
+ }
+
+ .avatar-placeholder {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #f1f5f9;
+ color: #cbd5e1;
+ }
+
+ .avatar-placeholder-icon {
+ display: block;
}
.avatar-large {
@@ -146,6 +159,19 @@
flex-direction: row;
}
+ &.card-horizontal-single {
+ .card-left {
+ flex: 1;
+ border-bottom: none;
+ border-right: none;
+
+ @media (min-width: 768px) {
+ border-bottom: none;
+ border-right: none;
+ }
+ }
+ }
+
.card-left {
padding: 2rem;
flex: 0 0 33.333333%;
@@ -165,7 +191,8 @@
width: 8rem;
height: 8rem;
- img {
+ img,
+ .avatar-placeholder {
width: 100%;
height: 100%;
border-radius: 50%;
@@ -340,7 +367,8 @@
border-radius: 50%;
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.08);
- img {
+ img,
+ .avatar-placeholder {
width: 100%;
height: 100%;
border-radius: 50%;
@@ -458,7 +486,8 @@
width: 4rem;
height: 4rem;
- img {
+ img,
+ .avatar-placeholder {
width: 100%;
height: 100%;
border-radius: 50%;
@@ -476,27 +505,36 @@
.minimal-info {
display: flex;
flex-direction: column;
+ justify-content: center;
+ gap: 0.375rem;
+ min-width: 0;
.name-row {
display: flex;
align-items: center;
+ flex-wrap: wrap;
gap: 0.5rem;
- margin-bottom: 0.25rem;
.name {
font-size: 1rem;
+ margin: 0;
+ }
+
+ .badge {
+ margin-left: 0;
}
}
.title {
font-size: 0.75rem;
- margin-bottom: 0.375rem;
+ margin: 0;
}
.contact-row {
display: flex;
flex-direction: column;
- gap: 0.125rem;
+ gap: 0.25rem;
+ margin-top: 0.125rem;
.contact-item {
display: flex;
@@ -506,6 +544,7 @@
.icon {
color: $text-muted;
+ flex-shrink: 0;
}
.email,
@@ -513,6 +552,7 @@
font-size: 0.75rem;
font-weight: 500;
color: #334155;
+ line-height: 1.4;
}
.email {
@@ -583,11 +623,11 @@
padding: 1rem;
.minimal-header {
padding-right: 1.5rem;
- border-right: 1px solid $border-light;
}
.minimal-main {
margin-top: 0;
padding-left: 1.5rem;
+ border-left: 1px solid $border-light;
}
.minimal-footer {
margin-top: 0;