ð åŒåïŒäž€äžªä¿¡å·
æè¿æåšäžäžªåŒæºé¡¹ç®éçš Cursor èŸ å©åŒå UIïŒåç°äžäžªç°è±¡ïŒAI å 乿»æ¯éŠæš Shadcn/uiã
éèŠè¡šæ Œ â Shadcn Table + TanStack éèŠåŒ¹çª â çŽæ¥ç»äœ Dialog æºç éèŠæ¥æéæ© â ç»å Popover + Calendar
å³äœ¿ææ²¡æå®ïŒå®ä¹åŸåçš Shadcn çæºè¯æ¥åçã
èµ·åææäºææãå ¬åžé¡¹ç®é Element Plus çåºå€§ API æ©å·²æ¯èèè®°å¿ãäœæäžæ¬¡è®© AI ç»è¡šæ Œå è¡å çŒèŸïŒäž€æ¬¡äœéªåœ»åºæ¹åäºæå¯¹"ç»ä»¶åº"è¿ä»¶äºç讀ç¥ã
âš ç¬¬äžæ¬¡æè®¶ïŒAI çªç¶ååäº
| åºæ¯ | Element Plus | Shadcn/ui |
|---|---|---|
| AI æä¹æ¹ç»ä»¶ | 对çé»ç硬ç | çŽæ¥ä¿®æ¹æºç æä»¶ |
| å¹»è§æ åµ | çŒé äžååšçæ¹æ³ïŒeditRow()ã@row-dblclick | 粟åæå ¥ç¶æåæ¢é»èŸ |
| ç±»åæšå¯Œ | æ··çš onMounted / onUpdated | åæ°ç±»åå®å šæ£ç¡® |
| æ ¹æ¬åå | AI åªçå° API å€å£³ | AI çå°å šéšæºç åç¶ææµèœ¬ |
ç»è®ºïŒå¯¹ AI æ¥è¯ŽïŒæºç æ¯äžçå ¬æ°ïŒAPI ææ¡£åªæ¯äºæä¿¡æ¯ã
ðªïž ç¬¬äºæ¬¡æè®¶ïŒæ ·åŒåŒå§æŒç§»
å åšåå顟æŽäžªé¡¹ç®ïŒåç° UI 飿 Œåšææåè£ïŒ
| æ ·åŒå±æ§ | åŒå§å | å åšå |
|---|---|---|
| åè§ç³»ç» | ç»äž 8px | 8pxã4pxãçŽè§éæºç»å |
| å¡çå èŸ¹è· | ç»äž p-6 | p-4ãp-5ãp-6 亀éåºç° |
AI æ¯æ¬¡éœçŽæ¥æ¹ Tailwind ç±»åæ¥æ»¡è¶³çç¢éæ±ââæ¹æé®é¢è²ãè°åŒ¹çªå®œåºŠãå 4px è¡é«ã
Shadcn ææ ·åŒæ§å¶æå®å šäº€äºåºæ¥ïŒäœæ²¡ä¿çä»»äœçºŠæçæ€æ ãèªç±åžŠæ¥äºç²Ÿåä¿®æ¹çèœåïŒä¹åžŠæ¥äºè®Ÿè®¡ç³»ç»çç¢çåã
ð¯ äžäžªæ·±å±ççŸ + å «äžªéèé®é¢
è¿äž€æ¬¡ç»éªæŽé²äºåœåç»ä»¶åºèåŒçæ·±å±ççŸïŒ
äŒ ç»åºçšé»çæ¢çš³å®æ§ïŒæ AI æ¡åšéšå€ïŒShadcn çšçœçæ¢å¯ä¿®æ¹æ§ïŒåŽçºç²äºäžèŽæ§ã
äœé®é¢äžæ¢äºæ€ãæ"ç»ä»¶åº"è¿äžªç©ç§æŸå° AI çŒç çèå ç¯äžå®¡è§ïŒäŒåç°æŽå€å€±æç¹ââè¿äºäžæ¯æäžªåºç猺é·ïŒèæ¯æŽäžªå类讟计å讟ç厩å¡ã
ð¥ æ¡æ¶éå®ïŒåäžä»¶äºïŒN å¥å®ç°
| ç°ç¶ | é®é¢ | |
|---|---|---|
| React | antdãshadcnãmui | åæ APIïŒAI è®°æ·· |
| Vue | element-plusãnaive-ui | è· React å®å šäžéçš |
| Svelte | 莫ç ççæ | èªå·±é 蜮å |
åäžç§è¡äžºæš¡åŒïŒäžæèåã匹çªãè¡šæ ŒïŒïŒè¢«éå€å®ç°äº N æ¬¡ïŒæ¯æ¬¡ç»å®äžäžªæ¡æ¶ãAI 忢äžäžææ¶åç¡®çæ¥å§äžéã
ð¥ çæ¬éå®ïŒçæ¬æ¯ç»äººç±»çïŒäžæ¯ç» AI ç
Element Plus v1 â v2: æ¹äºå€å° APIïŒæ²¡äººè®°åŸæž
ã
AI çè®ç»æ°æ®ïŒæ··åäº v1 å v2 çç¥è¯ã
äœ ç项ç®ïŒè£
äº v2ã
AI åç代ç ïŒå¯èœè°çæ¯ v1 ç APIã
çæ¬å·æ"è§èçæŒè¿"å"å®ç°çåæŽ"ç»åšäžèµ·ïŒAI æ æ³åºåã
ð¥ å¹³å°ç»å®ïŒWeb äŒäºïŒç§»åšç«¯ä»å€ŽåŠ
| è¡äžºïŒäžæéæ© | å®ç°æ¹åŒ |
|---|---|
| Web | div + ç»å¯¹å®äœ + æ»åšå衚 |
| ç§»åšç«¯ | åç Picker / Bottom Sheet |
| CLI | stdin/stdout é项å衚 |
åäžäº€äºè¯ä¹ïŒäžç§å®ç°ïŒäžå¥ç»ä»¶åºãAI èŠè·šå¹³å°ïŒå åŠäžå¥ APIã
ð± 讟å€äžæç¥
æ¡é¢ç«¯å¡çæ hoverïŒè§Šå±äžéèŠãæ¡é¢ç«¯è¡šæ Œå±ç€º 20 åïŒç§»åšç«¯åªèœå±ç€º 3 åã
ç»ä»¶äžäŒåè¯ AI"æåšææºäžåºè¯¥é¿ä»ä¹æ ·"ãAI åªèœçââæ¡é¢ç«¯å¯¹äºïŒç§»åšç«¯å€§æŠçéã
ð è¡äžºè§è猺äœ
ç»ä»¶åºæŽé²çæ¯ä»£ç ïŒexport function Select(props)ã
äžæ¯è¡äžºè§èïŒ"æ ArrowDown é«äº®äžäžäžªéé¡¹ïŒæ Enter éäžïŒæ Escape å ³éã"
AI èœè¯»ä»£ç ïŒäœå®èŠçæ¯è®Ÿè®¡æåŸââä»£ç æ¯"æä¹å"ïŒAI éèŠ"åä»ä¹"å"䞺ä»ä¹"ã
ð éåŒè®Ÿè®¡å³ç
| åŒ | 䞺ä»ä¹ïŒ | åšåªè®°åœçïŒ |
|---|---|---|
padding: 8px | 讟计è¯å®¡å³å®ç | æäººçèåé |
color: #2563eb | åçè² v3 | Figma 泚éé |
radius: 4px vs 8px | ç级åºå | PR review é |
ä»£ç æ¬èº«äžæºåžŠ"䞺ä»ä¹"ãAI ä¿®æ¹æ¶åªèœå€éšæšæïŒæšæéäºå°±åŒå ¥äžäžèŽã
ð ææ¡£åæµè¯çéå€å³åš
äžäžªç»ä»¶ïŒ
å代ç + åææ¡£ + å Storybook + åæµè¯ = 4 仜犻æ£å·¥äœ
å¿ é¡»ä¿æåæ¥ââäœç°å®äžä»æ¥äžåæ¥ãåŠæ AI å·²ç»çæäºä»£ç ïŒå®å°±å ·å€äºçæææ¡£åæµè¯æéçææä¿¡æ¯ïŒäœæ²¡äººè®©å®åã
â¿ æ éç¢æ¯éå åïŒäžæ¯å å»ºå±æ§
æ¯äžªç»ä»¶åºèªå·±å®ç°äžæ¬¡ ARIAãæ¯äžªå¢éäžçº¿ååè¡¥äžéãAI çæç»ä»¶æ¶ïŒæ éç¢æ¯æåæè¢«æ³èµ·çäºã
åŠæè§èæ¬èº«å°±å®ä¹äº"è¿äžªç»ä»¶ç ARIA è§è²æ¯ menu"ïŒAI äžåŒå§å°±èœå对ã
ð å šè²å¯¹æ¯æ»ç»
| 绎床 | åœåç»ä»¶åº | AI éèŠä»ä¹ |
|---|---|---|
| æ¡æ¶ | ç»å®äžäžªïŒåæ¢éåŠ | è¡äžºç¬ç«äºæ¡æ¶ïŒçææ¶éé |
| çæ¬ | çæ¬å·æç»äžå | çæ¬ç»å®è§èïŒäžç»å®å®ç° |
| å¹³å° | Web äžå± | äžå¥è§èïŒå€å¹³å°çæ |
| è®Ÿå€ | æ¡é¢å讟 | 讟å€å·®åŒæŸåŒå£°æ |
| è¡äžº | 代ç å³è§è | è§èç¬ç«äºä»£ç |
| å³ç | èåšè®šè®ºé | çŒç 䞺å¯è¿œæº¯ç纊æ |
| ææ¡£/æµè¯ | æåšç»Žæ€ïŒæ°žè¿æ»å | äžæ¬¡äº§åºïŒå€è§åŸèªåšçæ |
| æ éç¢ | äºå修补 | è§èå 建ïŒçææ¶ä¿è¯ |
ð¡ 代ç è¶äŸ¿å®ïŒä»ä¹è¶è޵ïŒ
AI çŒç æ£åšè®©ä»£ç çèŸ¹é ææ¬è¶è¿äºé¶ã
ååŸå¿« â ååŸå¥œãååŸå¿« = ååŸççé床ä¹å¿«ã
åœä»£ç äžåæ¯çšçŒºèµæºïŒçšçŒºçäžè¥¿åäºïŒ
| è¶æ¥è¶äŸ¿å® | è¶æ¥è¶è޵ |
|---|---|
| åäžäžªç»ä»¶ | å®ä¹"ä»ä¹æ¯å¯¹ç" |
| å€å¶ç²èŽŽ | 绎æ€äžèŽæ§ |
| å åèœ | å®äœçºŠæå蟹ç |
| ä¿® bug | ä¿è¯æ£ç¡®æ§ |
| åææ¡£ | ä¿è¯ææ¡£è·ä»£ç äžèŽ |
ðïž äžäžªå¯èœçæ¶æïŒè§è â çæ
åºäºä»¥äžè¯æïŒäžäžªæ¹åéæžæµ®ç°ïŒ
æç»ä»¶åºä»"代ç å "éæäžº"è§èæè¿°åš + çæåŒæ"
è¿äžæ¯"æç»ä»¶ææå å±"ïŒé£æ¯åšæ§æ¡æ¶éåäŒåïŒïŒèæ¯éæ°å®ä¹ç»ä»¶åºç亀ä»ç©ã
æŒè¿è·¯çº¿åŸ
äŒ ç»ç»ä»¶åº âââ æºç 级ç»ä»¶åº âââ è§è驱åšçæ âââ ç»ä»¶åºæ¶å€±
â â â â
â npm install â æºç å€å¶ â Spec é©±åš â æåŸé©±åš
â é»ç â çœç â 纊æ AI â AI è¶³å€åŒº
â AI ç API â æ ·åŒæŒç§» â è§è=èµäº§ â äžéèŠçºŠæ
â â â â
Element Plus Shadcn/ui è¿äžªæ¶æ æªæ¥ïŒ
æ¶æç€ºæåŸ
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â ð§ è§èå±ïŒSpecïŒ â
â å¯äžéèŠäººå·¥ç»Žæ€åçæ¬åçäžè¥¿ â
â â
â âââââââââââââââ ââââââââââââ ââââââââââââ âââââââââââââââ â
â â è¡äžºè§è â â æ ·åŒè§è â â æ éç¢ â â å¹³å°/è®Ÿå€ â â
â â ç¶æå®ä¹ â â Tokenæ å° â â ARIAè§è² â â éé
è§å â â
â â äºä»¶æµèœ¬ â â 纊æè§å â â é®çäº€äº â â å·®åŒå£°æ â â
â âââââââââââââââ ââââââââââââ ââââââââââââ âââââââââââââââ â
âââââââââââââââââââââââââââ¬âââââââââââââââââââââââââââââââââââââââââ
â 读å
âââââââââââââââââââââââââââŒâââââââââââââââââââââââââââââââââââââââââ
â âïž çæåŒæïŒGeneratorïŒ â
â AI çå·¥äœïŒäžæ¯äººçå·¥äœ â
â â
â 读å Spec + 项ç®äžäžæ â
â â â
â âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ â
â â èŸåºïŒ â â
â â ð ç»ä»¶ä»£ç ïŒ.tsx / .vue / .svelteïŒ â â
â â ð ç±»åå®ä¹ â â
â â 𧪠æµè¯çšäŸ â â
â â ð ææ¡£ / Storybook â â
â âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ â
â â
â 忢平å°/æ¡æ¶ = æ¢äžäžªçæç®æ ïŒSpec äžå â
âââââââââââââââââââââââââââ¬âââââââââââââââââââââââââââââââââââââââââ
â æ£æ¥
âââââââââââââââââââââââââââŒâââââââââââââââââââââââââââââââââââââââââ
â ð¡ïž 纊æå±ïŒGuardrailsïŒ â
â ç¡®ä¿ AI çèŸåºæ²¡æéŸè¶èŸ¹ç â
â â
â â
æ ·åŒå¿
é¡»åŒçš tokenïŒçŠæ¢ç¡¬çŒç â
â â
æ éç¢èŠæ±å¿
须满足ïŒäžéè¿äžæäº€ â
â â
代ç å¿
é¡»éè¿é¡¹ç® lint / type check â
â â
AI äžåŸä¿®æ¹è§è屿件 â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
è¿äžªæ¶æè§£å³äºä»ä¹ïŒ
| é®é¢ | â æ§æš¡åŒ | â æ°æ¶æ |
|---|---|---|
| æ¡æ¶éå® | äžäžªæ¡æ¶äžå¥åº | Spec äžåïŒçæåŒæéé ç®æ æ¡æ¶ |
| çæ¬éå® | åçº§åº = å šå±é£é© | åªæè§èæçæ¬ïŒå®ç°å§ç»çæææ° |
| å¹³å°ç»å® | Web/ç§»åšç«¯/CLI åäžå¥ | äžå¥ SpecïŒäžäžªçæç®æ |
| 讟å€äžæç¥ | ç»ä»¶äžç¥éèªå·±åšåªè¿è¡ | Spec äžå®ä¹è®Ÿå€å·®åŒè§å |
| è¡äžºè§èçŒºäœ | 代ç éæ²¡æè¡äžºå£°æ | Spec 第äžå±å°±æ¯è¡äžºå®ä¹ |
| éåŒè®Ÿè®¡å³ç | èåšè¯å®¡è®šè®ºé | Spec æç¡®è®°åœè®Ÿè®¡çºŠæ |
| ææ¡£æµè¯éå€ | å仜犻æ£å·¥äœåèªåæ¥ | äžæ¬¡çæïŒå䞪èŸåºè§åŸ |
| æ éç¢éå å | äžçº¿åè¡¥ | Spec å 建ïŒçææ¶èªåšå å« |
ð 䌪代ç ïŒè§è vs. å®ç°
以äžå±ç€º"è§è"å"çæ"ä¹éŽçå ³ç³»ââè¿äžåæ¯äŒ ç»ç»ä»¶åºçæ ·åã
è¿æ¯"ç»ä»¶åº"ââäžäžªè§èæä»¶ïŒäžæ¯ä»£ç
// æä»¶äœçœ®ïŒ@specs/dropdown-menu.ts
// åŒåè
ç»Žæ€æ€æä»¶ïŒAI äžå¯ä¿®æ¹
export const dropdownMenuSpec = {
id: 'dropdown-menu',
version: '2.1',
// ââ è¡äžºè§è ââ
states: {
isOpen: { type: 'boolean', default: false },
activeIndex: { type: 'number', default: -1 },
selectedValue: { type: 'string | null', default: null },
},
transitions: [
{ trigger: 'trigger:click', guard: '!isOpen', next: { isOpen: true } },
{ trigger: 'escape:keydown', guard: 'isOpen', next: { isOpen: false, activeIndex: -1 } },
{ trigger: 'arrowdown:keydown', guard: 'isOpen', next: { activeIndex: 'activeIndex + 1' } },
{ trigger: 'arrowup:keydown', guard: 'isOpen', next: { activeIndex: 'activeIndex - 1' } },
{ trigger: 'enter:keydown', guard: 'isOpen', next: { selectedValue: 'options[activeIndex]', isOpen: false } },
{ trigger: 'click-outside', guard: 'isOpen', next: { isOpen: false } },
],
// ââ æ ·åŒè§è ââ
styles: {
container: {
background: 'token.surface',
borderRadius: 'token.radius.md',
shadow: 'token.shadow.lg',
},
trigger: {
height: 'token.size.input',
padding: '0 token.spacing.md',
border: '1px solid token.border.default',
},
option: {
padding: 'token.spacing.sm token.spacing.md',
states: {
hover: { background: 'token.color.primary-50' },
selected: { background: 'token.color.primary', color: 'white' },
disabled: { opacity: '0.5' },
},
},
},
// ââ æ éç¢è§è ââ
accessibility: {
container: { role: 'menu', orientation: 'vertical' },
trigger: { role: 'combobox', ariaHasPopup: 'menu', ariaExpanded: 'isOpen' },
option: { role: 'option', ariaSelected: 'is selected' },
keyboardNav: ['ArrowDown', 'ArrowUp', 'Enter', 'Escape', 'Home', 'End'],
focusManagement: 'restore trigger on close',
},
// ââ å¹³å°éé
è§å ââ
platform: {
web: { renderAs: 'div + absolute positioning' },
mobile: { renderAs: 'bottom-sheet', keyboardNavInapplicable: true },
cli: { renderAs: 'stdin list', selectedIndicator: 'â' },
},
// ââ 纊æïŒAI äžå¯è¿åïŒ ââ
constraints: [
'æææ ·åŒåŒå¿
é¡»åŒçš tokenïŒäžåŸç¡¬çŒç ',
'menu å
³éæ¶å¿
é¡»å°çŠç¹åœè¿ trigger',
'option æ°éè¶
è¿ 10 æ¶å¯çšèææ»åš',
'äžåŸä¿®æ¹ spec æä»¶æ¬èº«',
],
};
è¿æ¯ AI çæçç»ä»¶ââäžæ¯äººåç
// ç®æ æ¡æ¶ïŒReact 18 + Tailwind
// çæèªïŒdropdownMenuSpec
import { useDropdownMenu } from '@headless/dropdown-menu';
import { tokens } from '@/lib/tokens';
import { useClickOutside } from '@/hooks/useClickOutside';
export function DropdownMenu({ options, triggerLabel }: Props) {
const { state, actions } = useDropdownMenu(dropdownMenuSpec);
const ref = useRef<HTMLDivElement>(null);
useClickOutside(ref, () => state.isOpen && actions.close());
return (
<div
ref={ref}
style={{
background: tokens.surface,
borderRadius: tokens.radius.md,
boxShadow: tokens.shadow.lg,
}}
role="menu"
aria-orientation="vertical"
>
<button
onClick={actions.toggle}
style={{
height: tokens.size.input,
padding: `0 ${tokens.spacing.md}`,
border: `1px solid ${tokens.border.default}`,
}}
role="combobox"
aria-haspopup="menu"
aria-expanded={state.isOpen}
>
{state.selectedValue ?? triggerLabel}
</button>
{state.isOpen && (
<div>
{options.map((opt, i) => (
<div
key={opt.value}
role="option"
aria-selected={i === state.activeIndex}
onClick={() => actions.select(opt.value)}
style={{
padding: `${tokens.spacing.sm} ${tokens.spacing.md}`,
...(i === state.activeIndex && {
background: tokens.color.primary,
color: 'white',
}),
}}
>
{opt.label}
</div>
))}
</div>
)}
</div>
);
}
䞀段代ç çå ³ç³»æ¯ïŒè§è â çæïŒäžæ¯"ç»ä»¶åº â å€å¶ä¿®æ¹"ã
â ïž è¿è¿äžæ¯ç»å±
è¿äžªæ¶ææäžäžªéå«åæïŒæä»¬è¿äžå€ä¿¡ä»» AIïŒæä»¥éèŠçšè§èæä»¶æ¥çºŠæå®ã
åŠææå€©æš¡å区倧å°è¯»äžé项ç®å°±èœçè§£äœ ç讟计è¯èšã代ç 飿 Œãäžå¡è¯ä¹ââå®èœä»åå²ä»£ç äžèªåšæšå¯Œåºè§èïŒä» PR review äžåŠä¹ 讟计å³çââé£è§èå±ä¹äŒæ¶å€±ã
AI èœå
â²
â
没æç»ä»¶åº âââââ€ââââââââââââââ â (æªæ¥)
â
è§è驱åšçæ âââââââââ€ââââââ â (è¿äžªæ¶æ)
â
Shadcn æºç æ·èŽ ââââââ€ââ â (ç°åš)
â
Element Plus npm å
ââââ†â (è¿å»)
âââââââââââââââââââââââ⺠æ¶éŽ
屿¶ïŒ"ç»ä»¶åº"è¿äžªç©ç§å°äžå€ååšãUI äžåæ¯ç»è£ åºæ¥çïŒèæ¯æ ¹æ®æåŸå³æ¶çæçã
äœé£äžæ¯ä»å€©ã
ð¯ ä»å€©çå®çšé®é¢
ä»å€©çå®çšé®é¢æ¯ïŒåšäžä»œè§èèœçºŠæ AI çæ¶åïŒæä»¬åºè¯¥æä¹è®Ÿè®¡è¿ä»œè§èïŒ
äžé¢çæ¶æäžæ¯çæ¡ïŒæ¯äžäžªéç¹ãçšæ¥è®šè®ºïŒ
| é®é¢ | äœ ççæ³ïŒ |
|---|---|
| è§èåºè¯¥åå°ä»ä¹ç²åºŠïŒ | |
| åªäºäžè¥¿å¿ 须被纊æïŒ | |
| åªäºåºè¯¥äº€ç» AI èªç±åæ¥ïŒ | |
| è¿å¥æ¶æè·çŽæ¥åä»£ç æ¯ïŒæ¯çäºè¿æ¯å€äºå·¥äœéïŒ |
äœ æä¹çïŒ