feat: 图标系统与共享组件
- Icons.jsx:Appica SVG 图标统一封装(零 emoji,名称映射) - PageHeader.jsx:两页共享页眉(标题/副标题/时钟/导航) - CompanySearch.jsx:入驻企业 Autocomplete 搜索(Appica)
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Autocomplete,
|
||||
AutocompleteInput,
|
||||
AutocompleteContent,
|
||||
AutocompleteEmpty,
|
||||
AutocompleteList,
|
||||
AutocompleteItem,
|
||||
} from '@appica/ui-react/autocomplete';
|
||||
import { BuildingSkyscraper, BuildingCommunity, UsersGroup, Rocket, Search } from '@appica/icons-react';
|
||||
|
||||
/* =========================================================
|
||||
入驻企业搜索(Appica Autocomplete)
|
||||
按名称搜索企业,显示区域/行业/所在房间
|
||||
========================================================= */
|
||||
|
||||
const ZONE_META = {
|
||||
'加速区': { icon: Rocket, color: '#4c8dff' },
|
||||
'国际区': { icon: BuildingCommunity, color: '#22d3ee' },
|
||||
'成长区': { icon: UsersGroup, color: '#34d399' },
|
||||
'OPC 空间': { icon: BuildingSkyscraper, color: '#fbbf24' },
|
||||
'孵化加速区': { icon: Rocket, color: '#4c8dff' },
|
||||
'国际创客区': { icon: BuildingCommunity, color: '#22d3ee' },
|
||||
'成长苗圃区': { icon: UsersGroup, color: '#34d399' },
|
||||
};
|
||||
|
||||
export default function CompanySearch({ companies }) {
|
||||
// value 存字符串(企业名),onValueChange 收到的是 item 对象 → 取 name
|
||||
const [value, setValue] = useState('');
|
||||
const selected = companies.find((c) => c.name === value);
|
||||
|
||||
return (
|
||||
<div className="bd-search">
|
||||
<Autocomplete
|
||||
items={companies}
|
||||
value={value}
|
||||
onValueChange={(v) => setValue(typeof v === 'string' ? v : (v && v.name) || '')}
|
||||
itemToStringValue={(item) => (item && item.name) || ''}
|
||||
clearable
|
||||
icon
|
||||
size="md"
|
||||
>
|
||||
<AutocompleteInput
|
||||
placeholder="搜索入驻企业…"
|
||||
aria-label="搜索入驻企业"
|
||||
startSlot={selected
|
||||
? (() => {
|
||||
const meta = ZONE_META[selected.zone];
|
||||
const Ico = meta ? meta.icon : BuildingSkyscraper;
|
||||
return <Ico size={16} style={{ color: meta ? meta.color : '#8aa0c0' }} aria-hidden />;
|
||||
})()
|
||||
: <Search size={16} aria-hidden />}
|
||||
/>
|
||||
<AutocompleteContent
|
||||
side="bottom"
|
||||
align="end"
|
||||
sideOffset={6}
|
||||
className="bd-search-pop"
|
||||
positionerProps={{ className: 'bd-search-pos' }}
|
||||
>
|
||||
<AutocompleteEmpty>未找到匹配企业</AutocompleteEmpty>
|
||||
<AutocompleteList>
|
||||
{(item) => {
|
||||
const meta = ZONE_META[item.zone];
|
||||
const ZoneIco = meta ? meta.icon : BuildingSkyscraper;
|
||||
return (
|
||||
<AutocompleteItem key={item.name} value={item}>
|
||||
<span className="bd-search-item">
|
||||
<span className="bd-search-zone" style={{ color: meta ? meta.color : '#8aa0c0' }}>
|
||||
<ZoneIco size={15} data-icon="start" aria-hidden />
|
||||
</span>
|
||||
<span className="bd-search-info">
|
||||
<b>{item.name}</b>
|
||||
<em>{item.zone} · {item.industry}</em>
|
||||
</span>
|
||||
<span className="bd-search-room">{item.room}</span>
|
||||
</span>
|
||||
</AutocompleteItem>
|
||||
);
|
||||
}}
|
||||
</AutocompleteList>
|
||||
</AutocompleteContent>
|
||||
</Autocomplete>
|
||||
|
||||
{selected && (
|
||||
<div className="bd-search-result">
|
||||
<div className="bd-search-result-head">
|
||||
<span className="bd-search-result-name">{selected.name}</span>
|
||||
<span className="bd-search-result-zone" style={{ color: (ZONE_META[selected.zone] || {}).color }}>
|
||||
{selected.zone}
|
||||
</span>
|
||||
</div>
|
||||
<div className="bd-search-result-meta">
|
||||
<span>行业 <b>{selected.industry}</b></span>
|
||||
<span>房间 <b>{selected.room}</b></span>
|
||||
<span>状态 <b className="ok">在驻</b></span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/* =========================================================
|
||||
图标库:Appica Icons(@appica/icons-react)
|
||||
禁止使用表情图标,统一从这里引用 SVG 图标
|
||||
========================================================= */
|
||||
import {
|
||||
Bolt, Wrench, BuildingSkyscraper, Users, UsersGroup, Coin, Radio, Trophy,
|
||||
Flask, BuildingFactory, Diamond, GraduationCap, ChartLine, ChartArea,
|
||||
Clock, Robot, Broadcast, Message, Briefcase, Flame, Leaf, Star,
|
||||
Check, Gauge, Cpu, Server, Database, Sparkles, Bulb, Settings,
|
||||
Heart, Pill, Dna, Microscope, Atom, MapPin, Flag, Award, Medal,
|
||||
History, Drone, Plane, Rocket, Satellite, Wallet, UserPlus, Activity,
|
||||
} from '@appica/icons-react';
|
||||
|
||||
/* 名称 → 图标组件映射(推荐的调用方式:icon="bolt") */
|
||||
const NAME_MAP = {
|
||||
bolt: Bolt,
|
||||
wrench: Wrench,
|
||||
building: BuildingSkyscraper,
|
||||
'building-skyscraper': BuildingSkyscraper,
|
||||
'building-community': UsersGroup,
|
||||
users: Users,
|
||||
'users-group': UsersGroup,
|
||||
coin: Coin,
|
||||
radio: Radio,
|
||||
trophy: Trophy,
|
||||
flask: Flask,
|
||||
factory: BuildingFactory,
|
||||
'building-factory': BuildingFactory,
|
||||
diamond: Diamond,
|
||||
graduation: GraduationCap,
|
||||
'graduation-cap': GraduationCap,
|
||||
'chart-line': ChartLine,
|
||||
'chart-area': ChartArea,
|
||||
clock: Clock,
|
||||
robot: Robot,
|
||||
broadcast: Broadcast,
|
||||
message: Message,
|
||||
briefcase: Briefcase,
|
||||
flame: Flame,
|
||||
leaf: Leaf,
|
||||
star: Star,
|
||||
check: Check,
|
||||
gauge: Gauge,
|
||||
cpu: Cpu,
|
||||
server: Server,
|
||||
database: Database,
|
||||
sparkles: Sparkles,
|
||||
bulb: Bulb,
|
||||
settings: Settings,
|
||||
heart: Heart,
|
||||
pill: Pill,
|
||||
dna: Dna,
|
||||
microscope: Microscope,
|
||||
atom: Atom,
|
||||
'map-pin': MapPin,
|
||||
flag: Flag,
|
||||
award: Award,
|
||||
medal: Medal,
|
||||
history: History,
|
||||
drone: Drone,
|
||||
plane: Plane,
|
||||
rocket: Rocket,
|
||||
satellite: Satellite,
|
||||
wallet: Wallet,
|
||||
'user-plus': UserPlus,
|
||||
activity: Activity,
|
||||
};
|
||||
|
||||
/**
|
||||
* Icon —— 统一图标组件
|
||||
* @param {string} name 图标标识(emoji 兼容 或 名称,如 "bolt" / "chart-line")
|
||||
* @param {number|string} size
|
||||
* @param {number} strokeWidth
|
||||
* @param {string} className
|
||||
*/
|
||||
export default function Icon({ name, size = 18, strokeWidth = 1.6, className = '', ...rest }) {
|
||||
const Comp = NAME_MAP[name] || null;
|
||||
if (!Comp) return null;
|
||||
return (
|
||||
<Comp
|
||||
size={size}
|
||||
strokeWidth={strokeWidth}
|
||||
className={`appica-ico${className ? ` ${className}` : ''}`}
|
||||
aria-hidden="true"
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/* 独立导出的常用图标(组件内直接引用) */
|
||||
export {
|
||||
Bolt, Wrench, BuildingSkyscraper, Users, Coin, Radio, Trophy,
|
||||
Flask, BuildingFactory, Diamond, GraduationCap, ChartLine, ChartArea,
|
||||
Clock, Robot, Broadcast, Message, Briefcase, Flame, Leaf, Star,
|
||||
Check, Gauge, Cpu, Server, Database, Sparkles, Bulb, Settings,
|
||||
Heart, Pill, Dna, Microscope, Atom, MapPin, Flag, Award, Medal,
|
||||
History, Drone, Plane, Rocket, Satellite, Wallet, UserPlus, Activity,
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import Icon from './Icons';
|
||||
|
||||
/* =========================================================
|
||||
共享页眉 —— 两个大屏页面(数据大屏 / 数字孪生)统一风格
|
||||
仅通过 props 区分:导航目标、状态文案
|
||||
========================================================= */
|
||||
|
||||
export default function PageHeader({ nav, status = '数据实时' }) {
|
||||
const navigate = useNavigate();
|
||||
const [now, setNow] = useState(new Date());
|
||||
|
||||
useEffect(() => {
|
||||
const iv = setInterval(() => setNow(new Date()), 1000);
|
||||
return () => clearInterval(iv);
|
||||
}, []);
|
||||
|
||||
const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
||||
const week = ['日', '一', '二', '三', '四', '五', '六'][now.getDay()];
|
||||
const timeStr = now.toLocaleTimeString('zh-CN', { hour12: false });
|
||||
|
||||
return (
|
||||
<header className="bd-header">
|
||||
<div className="bd-header-left">
|
||||
<span className="bd-logo">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinejoin="round">
|
||||
<path d="M3 21V9l6-5 6 5v12" />
|
||||
<path d="M9 21v-6h6v6" />
|
||||
<path d="M15 9.5V21" />
|
||||
</svg>
|
||||
</span>
|
||||
<div className="bd-header-titles">
|
||||
<h1 className="bd-header-title">昆明市大学生创业园 <span className="bd-header-title-accent">· OPC 智能园区数字运营中心</span></h1>
|
||||
<div className="bd-header-sub">云南省首家政府主办大学生创业孵化园区 · 空间+孵化+融资+政策+资源+AI赋能+综合服务 七位一体</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bd-header-right">
|
||||
{nav && (
|
||||
<button
|
||||
className="bd-nav-hint"
|
||||
onClick={() => navigate(nav.to)}
|
||||
title={nav.title}
|
||||
>
|
||||
<Icon name={nav.icon || 'chevron-right'} size={14} />
|
||||
{nav.label}
|
||||
</button>
|
||||
)}
|
||||
<div className="bd-live">
|
||||
<span className="bd-live-dot" />
|
||||
{status}
|
||||
</div>
|
||||
<div className="bd-clock">
|
||||
<div className="bd-clock-time">{timeStr}</div>
|
||||
<div className="bd-clock-date">{dateStr} 周{week}</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user