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