with one click
前端页面开发。当用户需要开发 Web 应用、创建 UI 组件、实现交互功能或优化前端性能时使用此技能。
npx skills add https://github.com/NeverSight/learn-skills.dev --skill frontend-developmentCopy and paste this command into Claude Code to install the skill
前端页面开发。当用户需要开发 Web 应用、创建 UI 组件、实现交互功能或优化前端性能时使用此技能。
npx skills add https://github.com/NeverSight/learn-skills.dev --skill frontend-developmentCopy and paste this command into Claude Code to install the skill
Generate AI videos with Luma Dream Machine via AceDataCloud API. Use when creating videos from text prompts, generating videos from reference images, extending existing videos, or any video generation task with Luma. Supports text-to-video, image-to-video, and video extension.
Single-pass post-processing, URP Renderer Features, and mobile-safe screen effects for Unity
GPU architecture, precision types, fillrate, overdraw, baked lighting, and LOD optimization for Unity mobile/WebGL shaders
Node budgets, custom lighting, sub-graphs, precision control, and mobile workflows for Unity Shader Graph
Channel packing, variant reduction, shader_feature vs multi_compile, and build size optimization for Unity
Mobile-optimized water shaders with depth coloring, foam, Gerstner waves, refraction, and caustics for Unity URP
| name | frontend-development |
| description | 前端页面开发。当用户需要开发 Web 应用、创建 UI 组件、实现交互功能或优化前端性能时使用此技能。 |
| allowed-tools | Bash, Read, Write, Edit, Glob, Grep |
此技能专门用于前端 Web 开发,包括:
src/
├── components/ # 可复用组件
│ ├── Button/
│ ├── Input/
│ └── Modal/
├── pages/ # 页面组件
│ ├── Home/
│ ├── Login/
│ └── Dashboard/
├── hooks/ # 自定义 Hooks
├── utils/ # 工具函数
├── services/ # API 服务
├── store/ # 状态管理
├── styles/ # 全局样式
├── types/ # TypeScript 类型
└── constants/ # 常量定义
import React, { useState } from 'react';
import styled from 'styled-components';
interface ButtonProps {
variant?: 'primary' | 'secondary';
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
onClick?: () => void;
children: React.ReactNode;
}
const StyledButton = styled.button<ButtonProps>`
padding: ${props => {
switch (props.size) {
case 'small': return '8px 16px';
case 'large': return '16px 32px';
default: return '12px 24px';
}
}};
background: ${props =>
props.variant === 'primary' ? '#2196F3' : '#757575'
};
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
transition: all 0.2s;
&:hover:not(:disabled) {
opacity: 0.9;
transform: translateY(-2px);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`;
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'medium',
disabled = false,
onClick,
children
}) => {
return (
<StyledButton
variant={variant}
size={size}
disabled={disabled}
onClick={onClick}
>
{children}
</StyledButton>
);
};
<template>
<button
:class="buttonClasses"
:disabled="disabled"
@click="handleClick"
>
<slot />
</button>
</template>
<script setup lang="ts">
import { computed } from 'vue';
interface Props {
variant?: 'primary' | 'secondary';
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
variant: 'primary',
size: 'medium',
disabled: false
});
const emit = defineEmits<{
click: [];
}>();
const buttonClasses = computed(() => [
'btn',
`btn-${props.variant}`,
`btn-${props.size}`,
{ 'btn-disabled': props.disabled }
]);
const handleClick = () => {
if (!props.disabled) {
emit('click');
}
};
</script>
<style scoped>
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
transition: all 0.2s;
}
.btn-primary {
background: #2196F3;
color: white;
}
.btn-secondary {
background: #757575;
color: white;
}
.btn-small {
padding: 8px 16px;
font-size: 14px;
}
.btn-large {
padding: 16px 32px;
font-size: 18px;
}
.btn:hover:not(.btn-disabled) {
opacity: 0.9;
transform: translateY(-2px);
}
.btn-disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
import { useState, useEffect } from 'react';
interface FetchState<T> {
data: T | null;
loading: boolean;
error: Error | null;
}
export function useFetch<T>(url: string): FetchState<T> {
const [state, setState] = useState<FetchState<T>>({
data: null,
loading: true,
error: null
});
useEffect(() => {
let cancelled = false;
const fetchData = async () => {
try {
setState(prev => ({ ...prev, loading: true }));
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (!cancelled) {
setState({ data, loading: false, error: null });
}
} catch (error) {
if (!cancelled) {
setState({
data: null,
loading: false,
error: error as Error
});
}
}
};
fetchData();
return () => {
cancelled = true;
};
}, [url]);
return state;
}
import create from 'zustand';
import { persist } from 'zustand/middleware';
interface User {
id: string;
name: string;
email: string;
}
interface AuthState {
user: User | null;
token: string | null;
isAuthenticated: boolean;
login: (user: User, token: string) => void;
logout: () => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
user: null,
token: null,
isAuthenticated: false,
login: (user, token) =>
set({ user, token, isAuthenticated: true }),
logout: () =>
set({ user: null, token: null, isAuthenticated: false })
}),
{
name: 'auth-storage'
}
)
);
/* Mobile First */
/* Mobile: 默认样式 */
.container {
padding: 16px;
}
/* Tablet: >= 768px */
@media (min-width: 768px) {
.container {
padding: 24px;
}
}
/* Desktop: >= 1024px */
@media (min-width: 1024px) {
.container {
padding: 32px;
max-width: 1200px;
margin: 0 auto;
}
}
/* Large Desktop: >= 1440px */
@media (min-width: 1440px) {
.container {
max-width: 1400px;
}
}
.flex-container {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: 16px;
flex-wrap: wrap;
}
@media (max-width: 768px) {
.flex-container {
flex-direction: column;
}
}
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
padding: 24px;
}
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';
describe('Button', () => {
it('renders correctly', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('is disabled when disabled prop is true', () => {
render(<Button disabled>Click me</Button>);
expect(screen.getByText('Click me')).toBeDisabled();
});
});
import { test, expect } from '@playwright/test';
test('user can login', async ({ page }) => {
await page.goto('http://localhost:3000/login');
await page.fill('input[name="email"]', 'user@example.com');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('http://localhost:3000/dashboard');
await expect(page.locator('h1')).toContainText('Dashboard');
});
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
ui: ['@mui/material']
}
}
},
minify: 'terser',
terserOptions: {
compress: {
drop_console: true
}
}
}
});
# .env.production
VITE_API_URL=https://api.production.com
VITE_APP_NAME=My App