chore: 初始化 React + TypeScript + SCSS + Ant Design 项目

This commit is contained in:
chener committed 2026-11-07 21:24:06 +08:00
commit 99b11d1a17
18 files changed
+3590

No files matched your search

+36
View File
@@ -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
View File
@@ -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>
)
}
+62
View File
@@ -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>
)
}
+19
View File
@@ -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>,
)
+207
View File
@@ -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;
}
+149
View File
@@ -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>
</>
)
}
+31
View File
@@ -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;
}
+43
View File
@@ -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;
+74
View File
@@ -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;
}
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />