chore: 初始化 React + TypeScript + SCSS + Ant Design 项目
This commit is contained in:
commit
99b11d1a17
18 files changed
+3590
No files matched your search
+33
@@ -0,0 +1,33 @@
|
||||
# 依赖
|
||||
node_modules/
|
||||
|
||||
# 构建产物
|
||||
dist/
|
||||
dist-ssr/
|
||||
*.local
|
||||
|
||||
# 日志
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# 编辑器与系统文件
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# 测试与缓存
|
||||
coverage/
|
||||
.vite/
|
||||
.eslintcache
|
||||
*.tsbuildinfo
|
||||
@@ -0,0 +1,77 @@
|
||||
# Antd Starter
|
||||
|
||||
React + TypeScript + SCSS + Ant Design 的前端起始项目,使用 Vite 构建。示例页面演示了 Ant Design 组件、SCSS 变量与 mixin、CSS Modules 以及一键切换的暗色主题。
|
||||
|
||||
## 技术栈
|
||||
|
||||
| 能力 | 选型 | 版本 |
|
||||
| --- | --- | --- |
|
||||
| 构建工具 | Vite | 8.x |
|
||||
| 框架 | React | 19.x |
|
||||
| 语言 | TypeScript(严格模式) | 7.x |
|
||||
| 样式 | Sass + CSS Modules | 1.x |
|
||||
| 组件库 | Ant Design(含 @ant-design/icons) | 6.x |
|
||||
|
||||
## 快速开始
|
||||
|
||||
需要 Node.js 20.19+ 或 22.12+(Vite 8 的要求)。
|
||||
|
||||
```bash
|
||||
npm install # 安装依赖
|
||||
npm run dev # 启动开发服务器,默认 http://localhost:5173
|
||||
```
|
||||
|
||||
其他脚本:
|
||||
|
||||
```bash
|
||||
npm run build # 类型检查 + 生产构建,输出到 dist/
|
||||
npm run preview # 本地预览 dist/ 构建结果
|
||||
npm run typecheck # 仅做 TypeScript 类型检查
|
||||
```
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
.
|
||||
├─ public/ # 静态资源,原样拷贝到构建产物
|
||||
├─ src/
|
||||
│ ├─ components/ # 可复用业务组件
|
||||
│ │ └─ DemoForm.tsx # Ant Design 表单示例
|
||||
│ ├─ pages/ # 页面
|
||||
│ │ ├─ HomePage.tsx
|
||||
│ │ └─ HomePage.module.scss
|
||||
│ ├─ styles/
|
||||
│ │ ├─ _variables.scss # 颜色、间距、圆角、断点等变量
|
||||
│ │ ├─ _mixins.scss # 响应式、布局等 mixin
|
||||
│ │ └─ global.scss # 全局基础样式
|
||||
│ ├─ App.tsx # 主题 / 语言包 Provider 与页面外壳
|
||||
│ ├─ App.module.scss
|
||||
│ ├─ main.tsx # 入口
|
||||
│ └─ vite-env.d.ts
|
||||
├─ index.html
|
||||
├─ tsconfig.json
|
||||
└─ vite.config.ts
|
||||
```
|
||||
|
||||
## 约定与说明
|
||||
|
||||
**路径别名**:`@` 指向 `src`,在 TypeScript(`tsconfig.json` 的 `paths`)和 Vite(`resolve.alias`)中都已配置,例如 `import DemoForm from '@/components/DemoForm'`。
|
||||
|
||||
**样式**:
|
||||
|
||||
- 全局变量用 `@use '../styles/variables' as *;` 引入,不要用已废弃的 `@import`。
|
||||
- 组件级样式使用 CSS Modules(`*.module.scss`),类名通过 `styles.xxx` 访问,避免全局污染。
|
||||
- 主题色通过 CSS 变量(`--app-bg`、`--app-text` 等)下发,定义在 `App.module.scss` 的 `.app` 与 `.app.dark` 中,自定义样式可随 antd 主题一起切换。
|
||||
|
||||
**Ant Design**:
|
||||
|
||||
- `ConfigProvider` 统一主题 token 与中文语言包(`antd/locale/zh_CN`)。
|
||||
- 主题切换使用 `theme.defaultAlgorithm` / `theme.darkAlgorithm`。
|
||||
- 通过 `App` 组件提供的 `App.useApp()` 获取 `message`、`notification`、`modal`,保证它们跟随当前主题上下文。
|
||||
- 组件按需引入,例如 `import { Button, Form } from 'antd'`。
|
||||
|
||||
**构建拆分**:`vite.config.ts` 使用 Rolldown 的 `codeSplitting` 把 `react`、`antd` 拆成独立 chunk,便于长期缓存;antd 依赖体积较大,已相应放宽 chunk 体积告警阈值。
|
||||
|
||||
## 下一步
|
||||
|
||||
把 `src/pages/HomePage.tsx` 和 `src/components/DemoForm.tsx` 换成自己的页面即可。如需路由、状态管理或请求库,可自行添加 React Router、Zustand、TanStack Query 等依赖。
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="React + TypeScript + SCSS + Ant Design 起始项目" />
|
||||
<title>React + TS + SCSS + Ant Design</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+2695
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "demo",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"description": "React + TypeScript + SCSS + Ant Design 前端项目",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.3.4",
|
||||
"antd": "^6.7.0",
|
||||
"react": "^19.3.1",
|
||||
"react-dom": "^19.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^26.6.6",
|
||||
"@types/react": "^19.3.0",
|
||||
"@types/react-dom": "^19.3.0",
|
||||
"@vitejs/plugin-react": "^6.2.1",
|
||||
"sass": "^1.108.1",
|
||||
"typescript": "^7.0.2",
|
||||
"vite": "^8.3.6"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#5b8ff9" />
|
||||
<stop offset="100%" stop-color="#38bdf8" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="64" height="64" rx="16" fill="url(#g)" />
|
||||
<path
|
||||
d="M20 44 30 20h4l10 24h-5.2l-2.2-5.8H27.4L25.2 44H20zm9.2-10.2h5.6L32 26.4l-2.8 7.4z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 440 B |
@@ -0,0 +1,36 @@
|
||||
@use './styles/variables' as *;
|
||||
|
||||
// 应用外壳:主题色通过 CSS 变量下发,结构样式在 pages 目录
|
||||
.app {
|
||||
--app-bg: #f5f7fb;
|
||||
--app-surface: #ffffff;
|
||||
--app-border: rgba(15, 23, 42, 0.08);
|
||||
--app-text: #1f2430;
|
||||
--app-text-muted: #5b6577;
|
||||
--app-header: rgba(255, 255, 255, 0.72);
|
||||
--app-glow: rgba(91, 143, 249, 0.18);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// antd 的 App 组件会额外包一层 div,这里直接用视口高度保证铺满
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
color: var(--app-text);
|
||||
background:
|
||||
radial-gradient(1200px 600px at 12% -10%, var(--app-glow), transparent 60%),
|
||||
radial-gradient(900px 500px at 100% 0%, rgba(56, 189, 248, 0.14), transparent 55%),
|
||||
var(--app-bg);
|
||||
transition:
|
||||
background-color $transition-base,
|
||||
color $transition-base;
|
||||
|
||||
&.dark {
|
||||
--app-bg: #0b1020;
|
||||
--app-surface: #121a2e;
|
||||
--app-border: rgba(148, 163, 184, 0.18);
|
||||
--app-text: #e5e9f2;
|
||||
--app-text-muted: #93a0b8;
|
||||
--app-header: rgba(11, 16, 32, 0.72);
|
||||
--app-glow: rgba(91, 143, 249, 0.22);
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
|
||||
import { App as AntdApp, ConfigProvider, theme as antdTheme } from 'antd'
|
||||
import type { ThemeConfig } from 'antd'
|
||||
import zhCN from 'antd/locale/zh_CN'
|
||||
|
||||
import HomePage from '@/pages/HomePage'
|
||||
|
||||
import styles from './App.module.scss'
|
||||
|
||||
const FONT_FAMILY =
|
||||
"-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif"
|
||||
|
||||
export default function App() {
|
||||
const [isDark, setIsDark] = useState(false)
|
||||
|
||||
const themeConfig = useMemo<ThemeConfig>(
|
||||
() => ({
|
||||
algorithm: isDark ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm,
|
||||
token: {
|
||||
colorPrimary: '#5b8ff9',
|
||||
colorInfo: '#5b8ff9',
|
||||
borderRadius: 10,
|
||||
fontFamily: FONT_FAMILY,
|
||||
},
|
||||
}),
|
||||
[isDark],
|
||||
)
|
||||
|
||||
return (
|
||||
<ConfigProvider locale={zhCN} theme={themeConfig}>
|
||||
<AntdApp>
|
||||
<div className={`${styles.app} ${isDark ? styles.dark : ''}`}>
|
||||
<HomePage isDark={isDark} onToggleTheme={() => setIsDark((value) => !value)} />
|
||||
</div>
|
||||
</AntdApp>
|
||||
</ConfigProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { App as AntdApp, Button, Col, DatePicker, Form, Input, Row, Select, Space } from 'antd'
|
||||
|
||||
interface FormValues {
|
||||
name: string
|
||||
role: string
|
||||
startDate?: unknown
|
||||
}
|
||||
|
||||
const ROLE_OPTIONS = [
|
||||
{ value: 'frontend', label: '前端工程师' },
|
||||
{ value: 'backend', label: '后端工程师' },
|
||||
{ value: 'designer', label: '交互设计师' },
|
||||
]
|
||||
|
||||
export default function DemoForm() {
|
||||
const { message } = AntdApp.useApp()
|
||||
const [form] = Form.useForm<FormValues>()
|
||||
|
||||
const handleFinish = (values: FormValues) => {
|
||||
console.log('表单提交:', values)
|
||||
void message.success('提交成功,数据已输出到控制台')
|
||||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
requiredMark={false}
|
||||
initialValues={{ role: 'frontend' }}
|
||||
onFinish={handleFinish}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col xs={24} md={8}>
|
||||
<Form.Item name="name" label="姓名" rules={[{ required: true, message: '请输入姓名' }]}>
|
||||
<Input placeholder="例如:张三" allowClear />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
|
||||
<Col xs={24} md={8}>
|
||||
<Form.Item name="role" label="角色">
|
||||
<Select options={ROLE_OPTIONS} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
|
||||
<Col xs={24} md={8}>
|
||||
<Form.Item name="startDate" label="计划开始日期">
|
||||
<DatePicker style={{ width: '100%' }} placeholder="选择日期" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item style={{ marginBottom: 0 }}>
|
||||
<Space>
|
||||
<Button type="primary" htmlType="submit">
|
||||
提交
|
||||
</Button>
|
||||
<Button onClick={() => form.resetFields()}>重置</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
import 'antd/dist/reset.css'
|
||||
import '@/styles/global.scss'
|
||||
|
||||
import App from './App'
|
||||
|
||||
const container = document.getElementById('root')
|
||||
|
||||
if (!container) {
|
||||
throw new Error('找不到 #root 挂载节点,请检查 index.html')
|
||||
}
|
||||
|
||||
createRoot(container).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
@@ -0,0 +1,207 @@
|
||||
@use '../styles/variables' as *;
|
||||
@use '../styles/mixins' as *;
|
||||
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: $space-4;
|
||||
padding: $space-3 $space-5;
|
||||
background: var(--app-header);
|
||||
border-bottom: 1px solid var(--app-border);
|
||||
backdrop-filter: saturate(180%) blur(12px);
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: $space-3;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.brandMark {
|
||||
@include flex-center;
|
||||
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 10px;
|
||||
background: $gradient-brand;
|
||||
box-shadow: $shadow-sm;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.brandText {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.brandName {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.brandTag {
|
||||
color: var(--app-text-muted);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: $space-3;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
max-width: 1120px;
|
||||
margin: 0 auto;
|
||||
padding: $space-7 $space-5;
|
||||
}
|
||||
|
||||
.hero {
|
||||
@include surface(var(--app-surface), var(--app-border));
|
||||
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: $space-7;
|
||||
box-shadow: $shadow-md;
|
||||
|
||||
// 右上角装饰光斑
|
||||
&::after {
|
||||
position: absolute;
|
||||
inset: -140px -140px auto auto;
|
||||
width: 380px;
|
||||
height: 380px;
|
||||
background: radial-gradient(circle, rgba(91, 143, 249, 0.32), transparent 68%);
|
||||
content: '';
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: $space-1;
|
||||
padding: 2px 10px;
|
||||
border-radius: $radius-pill;
|
||||
background: $color-primary-soft;
|
||||
color: $color-primary;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: $space-4 0 $space-3;
|
||||
font-size: clamp(26px, 4.2vw, 42px);
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.gradientText {
|
||||
background: $gradient-brand;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
-webkit-background-clip: text;
|
||||
}
|
||||
|
||||
.lead {
|
||||
max-width: 62ch;
|
||||
margin: 0 0 $space-5;
|
||||
color: var(--app-text-muted);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.heroActions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: $space-3;
|
||||
}
|
||||
|
||||
// 技术栈标签行
|
||||
.stack {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: $space-2;
|
||||
margin-top: $space-5;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
margin: $space-7 0 $space-4;
|
||||
font-size: 18px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: $space-4;
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@include respond-to($bp-md) {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
@include respond-to($bp-lg) {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
@include surface(var(--app-surface), var(--app-border));
|
||||
|
||||
padding: $space-5;
|
||||
transition:
|
||||
transform $transition-base,
|
||||
box-shadow $transition-base;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: $shadow-md;
|
||||
}
|
||||
}
|
||||
|
||||
.cardIcon {
|
||||
@include flex-center;
|
||||
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-bottom: $space-3;
|
||||
border-radius: $radius-md;
|
||||
background: $color-primary-soft;
|
||||
color: $color-primary;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.cardTitle {
|
||||
margin: 0 0 $space-1;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.cardDesc {
|
||||
margin: 0;
|
||||
color: var(--app-text-muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.panel {
|
||||
@include surface(var(--app-surface), var(--app-border));
|
||||
|
||||
margin-top: $space-6;
|
||||
padding: $space-5;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: $space-5;
|
||||
border-top: 1px solid var(--app-border);
|
||||
color: var(--app-text-muted);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
import {
|
||||
ApiOutlined,
|
||||
BgColorsOutlined,
|
||||
CodeOutlined,
|
||||
LayoutOutlined,
|
||||
MoonOutlined,
|
||||
RocketOutlined,
|
||||
SunOutlined,
|
||||
ThunderboltOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { App as AntdApp, Button, Switch, Tag } from 'antd'
|
||||
|
||||
import DemoForm from '@/components/DemoForm'
|
||||
|
||||
import styles from './HomePage.module.scss'
|
||||
|
||||
const STACK = ['Vite 8', 'React 19', 'TypeScript 7', 'Sass', 'Ant Design 6', 'CSS Modules']
|
||||
|
||||
const FEATURES: { icon: ReactNode; title: string; desc: string }[] = [
|
||||
{
|
||||
icon: <RocketOutlined />,
|
||||
title: '极速构建',
|
||||
desc: '基于 Vite 8,冷启动与热更新都很快;@ 别名已指向 src 目录。',
|
||||
},
|
||||
{
|
||||
icon: <CodeOutlined />,
|
||||
title: '类型安全',
|
||||
desc: 'TypeScript 严格模式,npm run typecheck 即可做全量类型检查。',
|
||||
},
|
||||
{
|
||||
icon: <BgColorsOutlined />,
|
||||
title: '样式方案',
|
||||
desc: 'Sass 配合 CSS Modules,内置颜色、间距变量与响应式 mixin。',
|
||||
},
|
||||
{
|
||||
icon: <LayoutOutlined />,
|
||||
title: '组件库',
|
||||
desc: 'Ant Design 6 组件开箱即用,ConfigProvider 统一主题与中文语言包。',
|
||||
},
|
||||
{
|
||||
icon: <ThunderboltOutlined />,
|
||||
title: '暗色主题',
|
||||
desc: '一键切换 antd 算法,自定义样式通过 CSS 变量同步跟随。',
|
||||
},
|
||||
{
|
||||
icon: <ApiOutlined />,
|
||||
title: '目录约定',
|
||||
desc: 'pages 放页面、components 放复用组件、styles 放全局样式。',
|
||||
},
|
||||
]
|
||||
|
||||
interface HomePageProps {
|
||||
isDark: boolean
|
||||
onToggleTheme: () => void
|
||||
}
|
||||
|
||||
export default function HomePage({ isDark, onToggleTheme }: HomePageProps) {
|
||||
const { message } = AntdApp.useApp()
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className={styles.header}>
|
||||
<div className={styles.brand}>
|
||||
<span className={styles.brandMark}>A</span>
|
||||
<span className={styles.brandText}>
|
||||
<span className={styles.brandName}>Antd Starter</span>
|
||||
<span className={styles.brandTag}>React · TypeScript · SCSS</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.actions}>
|
||||
<Switch
|
||||
checked={isDark}
|
||||
onChange={onToggleTheme}
|
||||
checkedChildren={<MoonOutlined />}
|
||||
unCheckedChildren={<SunOutlined />}
|
||||
aria-label="切换暗色主题"
|
||||
/>
|
||||
<Button href="https://ant.design/components/overview-cn" target="_blank" rel="noreferrer">
|
||||
文档
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className={styles.main}>
|
||||
<section className={styles.hero}>
|
||||
<span className={styles.eyebrow}>Get started</span>
|
||||
|
||||
<h1 className={styles.title}>
|
||||
一个开箱即用的 <span className={styles.gradientText}>React + TypeScript</span> 起始项目
|
||||
</h1>
|
||||
|
||||
<p className={styles.lead}>
|
||||
已内置 Vite 构建、Sass 预处理器与 Ant Design 组件库,支持暗色主题与中文语言包。
|
||||
删掉示例内容,就可以直接开始写业务代码。
|
||||
</p>
|
||||
|
||||
<div className={styles.heroActions}>
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
icon={<RocketOutlined />}
|
||||
onClick={() => {
|
||||
void message.success('环境已就绪,开始写代码吧 🎉')
|
||||
}}
|
||||
>
|
||||
开始开发
|
||||
</Button>
|
||||
<Button size="large" href="https://vite.dev/guide/" target="_blank" rel="noreferrer">
|
||||
Vite 文档
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className={styles.stack}>
|
||||
{STACK.map((item) => (
|
||||
<Tag key={item} color="blue">
|
||||
{item}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<h2 className={styles.sectionTitle}>项目内置能力</h2>
|
||||
<div className={styles.grid}>
|
||||
{FEATURES.map((feature) => (
|
||||
<article className={styles.card} key={feature.title}>
|
||||
<div className={styles.cardIcon}>{feature.icon}</div>
|
||||
<h3 className={styles.cardTitle}>{feature.title}</h3>
|
||||
<p className={styles.cardDesc}>{feature.desc}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<section className={styles.panel}>
|
||||
<h2 className={styles.sectionTitle} style={{ marginTop: 0 }}>
|
||||
表单示例
|
||||
</h2>
|
||||
<DemoForm />
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer className={styles.footer}>
|
||||
React + TypeScript + SCSS + Ant Design · 由 Vite 驱动
|
||||
</footer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// 可复用的 SCSS mixin
|
||||
// 使用方式:@use './mixins' as *;
|
||||
|
||||
@use './variables' as *;
|
||||
|
||||
/// 不小于指定断点生效(移动优先)
|
||||
@mixin respond-to($breakpoint) {
|
||||
@media (min-width: $breakpoint) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/// 卡片类表面的基础样式,颜色交给调用方
|
||||
@mixin surface($background, $border) {
|
||||
background: $background;
|
||||
border: 1px solid $border;
|
||||
border-radius: $radius-lg;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
@mixin truncate {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// 全局设计变量:颜色、间距、圆角、断点等
|
||||
// 使用方式:@use './variables' as *;
|
||||
|
||||
// 字体
|
||||
$font-sans:
|
||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB',
|
||||
'Microsoft YaHei', 'Helvetica Neue', Arial, sans-serif;
|
||||
$font-mono: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
||||
|
||||
// 品牌色
|
||||
$color-primary: #5b8ff9;
|
||||
$color-primary-soft: rgba(91, 143, 249, 0.12);
|
||||
$color-accent: #38bdf8;
|
||||
$gradient-brand: linear-gradient(135deg, #5b8ff9 0%, #38bdf8 100%);
|
||||
|
||||
// 间距
|
||||
$space-1: 4px;
|
||||
$space-2: 8px;
|
||||
$space-3: 12px;
|
||||
$space-4: 16px;
|
||||
$space-5: 24px;
|
||||
$space-6: 32px;
|
||||
$space-7: 48px;
|
||||
|
||||
// 圆角
|
||||
$radius-sm: 8px;
|
||||
$radius-md: 12px;
|
||||
$radius-lg: 20px;
|
||||
$radius-pill: 999px;
|
||||
|
||||
// 阴影
|
||||
$shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06);
|
||||
$shadow-md: 0 8px 24px -8px rgba(16, 24, 40, 0.16);
|
||||
$shadow-lg: 0 24px 48px -24px rgba(16, 24, 40, 0.28);
|
||||
|
||||
// 动效
|
||||
$ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
$transition-base: 180ms $ease-out;
|
||||
|
||||
// 响应式断点
|
||||
$bp-sm: 576px;
|
||||
$bp-md: 768px;
|
||||
$bp-lg: 1024px;
|
||||
@@ -0,0 +1,74 @@
|
||||
@use './variables' as *;
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: $font-sans;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #1f2430;
|
||||
background: #f5f7fb;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $color-primary;
|
||||
text-decoration: none;
|
||||
transition: color $transition-base;
|
||||
|
||||
&:hover {
|
||||
color: $color-accent;
|
||||
}
|
||||
}
|
||||
|
||||
code,
|
||||
kbd,
|
||||
pre {
|
||||
font-family: $font-mono;
|
||||
font-size: 0.92em;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: $color-primary-soft;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid $color-primary;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
// 细滚动条(WebKit)
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(100, 116, 139, 0.35);
|
||||
border: 3px solid transparent;
|
||||
border-radius: $radius-pill;
|
||||
background-clip: content-box;
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"types": ["vite/client", "node"],
|
||||
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"useDefineForClassFields": true,
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 5173,
|
||||
open: false,
|
||||
},
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
sourcemap: false,
|
||||
// antd 依赖本身较大,单独成包后放宽此告警阈值
|
||||
chunkSizeWarningLimit: 700,
|
||||
// 把体积较大的第三方依赖拆成独立 chunk,便于长期缓存
|
||||
rolldownOptions: {
|
||||
output: {
|
||||
codeSplitting: {
|
||||
groups: [
|
||||
{
|
||||
name: 'react-vendor',
|
||||
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
|
||||
},
|
||||
{
|
||||
name: 'antd-vendor',
|
||||
test: /[\\/]node_modules[\\/](antd|@ant-design|@rc-component|rc-[^\\/]+)[\\/]/,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in new issue
Block a user