chore: 增加代码规范和提交校验

Made-with: Cursor
This commit is contained in:
2026-04-03 16:23:35 +08:00
parent e48462cf4a
commit 7eed1ad5c6
10 changed files with 2382 additions and 36 deletions

12
.editorconfig Normal file
View File

@@ -0,0 +1,12 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false

5
.husky/pre-commit Normal file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"
yarn lint
yarn format:check

4
.prettierignore Normal file
View File

@@ -0,0 +1,4 @@
dist
node_modules
yarn.lock
*.min.js

7
.prettierrc Normal file
View File

@@ -0,0 +1,7 @@
{
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 100
}

36
eslint.config.js Normal file
View File

@@ -0,0 +1,36 @@
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import react from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import prettier from "eslint-config-prettier";
export default tseslint.config(
{ ignores: ["dist/**", "node_modules/**"] },
{
settings: {
react: { version: "detect" },
},
},
eslint.configs.recommended,
...tseslint.configs.recommended,
react.configs.flat.recommended,
react.configs.flat["jsx-runtime"],
reactHooks.configs.flat.recommended,
{
files: ["**/*.{ts,tsx}"],
plugins: {
"react-refresh": reactRefresh,
},
languageOptions: {
parserOptions: {
ecmaFeatures: { jsx: true },
},
},
rules: {
"react/prop-types": "off",
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
},
},
prettier,
);

View File

@@ -10,7 +10,12 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
"preview": "vite preview",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"prepare": "husky"
},
"dependencies": {
"antd": "^6.3.5",
@@ -19,13 +24,23 @@
"react-router-dom": "^7.13.2"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@tailwindcss/vite": "^4.2.2",
"@types/node": "^25.5.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"eslint": "^9.17.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"husky": "^9.1.7",
"lint-staged": "^16.4.0",
"prettier": "^3.8.1",
"tailwindcss": "^4.2.2",
"typescript": "^6.0.2",
"typescript-eslint": "^8.58.0",
"vite": "^8.0.3",
"vite-plugin-pages": "^0.33.3"
}

View File

@@ -9,6 +9,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
);

View File

@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from "react";
import { Button, Drawer, Input, Layout, List, Space, Typography } from "antd";
import { Button, Drawer, Input, Layout, Space, Typography } from "antd";
import {
MenuFoldOutlined,
MenuUnfoldOutlined,
@@ -9,6 +9,7 @@ import {
} from "@ant-design/icons";
const { Header, Sider, Content } = Layout;
const MOBILE_WIDTH = 750;
const mockMessages = [
{ role: "assistant", content: "你好,我是 ChatOne 助手,有什么可以帮你?" },
@@ -17,9 +18,14 @@ const mockMessages = [
];
export default function HomePage() {
// 桌面端侧边栏折叠状态(移动端不使用该状态)
const [collapsed, setCollapsed] = useState(false);
// 移动端抽屉侧边栏显示状态
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
const [isMobile, setIsMobile] = useState(() => window.innerWidth < 750);
// 当前视口宽度(用于调试响应式是否生效)
const [viewportWidth, setViewportWidth] = useState(0);
// 是否为移动端(小于 750px
const [isMobile, setIsMobile] = useState(false);
const [inputValue, setInputValue] = useState("");
const menuItems = useMemo(
@@ -32,33 +38,46 @@ export default function HomePage() {
);
useEffect(() => {
const onResize = () => {
const mobile = window.innerWidth < 750;
const updateIsMobile = () => {
// 兼容不同浏览器/缩放场景下的视口宽度读取
const viewportWidth = Math.min(
window.innerWidth,
document.documentElement.clientWidth || window.innerWidth,
);
setViewportWidth(viewportWidth);
const mobile = viewportWidth < MOBILE_WIDTH;
setIsMobile(mobile);
if (!mobile) {
// 切回桌面端时关闭移动抽屉,避免状态残留
setMobileSidebarOpen(false);
}
};
window.addEventListener("resize", onResize);
updateIsMobile();
window.addEventListener("resize", updateIsMobile);
window.addEventListener("orientationchange", updateIsMobile);
return () => {
window.removeEventListener("resize", onResize);
window.removeEventListener("resize", updateIsMobile);
window.removeEventListener("orientationchange", updateIsMobile);
};
}, []);
const sidebarContent = (
<>
{/* 侧边栏头部:桌面折叠时显示缩写,其他情况显示完整标题 */}
<div className="h-14 border-b border-gray-100 px-4 flex items-center">
<Typography.Title level={5} style={{ margin: 0 }}>
{collapsed && !isMobile ? "CO" : "ChatOne"}
</Typography.Title>
</div>
<div className="px-2 py-3">
<Space direction="vertical" className="w-full">
<Space orientation="vertical" className="w-full">
{menuItems.map((item) => (
<Button key={item.label} type="text" className="w-full text-left!">
<Space>
{item.icon}
{/* 桌面折叠时隐藏文字;移动端抽屉内始终显示文字 */}
{(!collapsed || isMobile) && <span>{item.label}</span>}
</Space>
</Button>
@@ -70,6 +89,7 @@ export default function HomePage() {
return (
<Layout style={{ minHeight: "100vh" }}>
{/* 桌面端固定侧边栏;移动端改为 Drawer 抽屉 */}
{!isMobile && (
<Sider trigger={null} collapsible collapsed={collapsed} theme="light">
{sidebarContent}
@@ -91,16 +111,24 @@ export default function HomePage() {
}
onClick={() => {
if (isMobile) {
// 移动端点击按钮:打开左侧抽屉
setMobileSidebarOpen(true);
} else {
// 桌面端点击按钮:切换侧边栏折叠
setCollapsed((v) => !v);
}
}}
/>
<Typography.Text type="secondary">ChatOne Web</Typography.Text>
<Space size={12}>
<Typography.Text type="secondary">ChatOne Web</Typography.Text>
<Typography.Text type="secondary">
{`W:${viewportWidth}px / ${isMobile ? "mobile" : "desktop"}`}
</Typography.Text>
</Space>
</Header>
<Content className="bg-gray-50 p-6">
{/* 聊天窗口:消息列表 + 底部输入区 */}
<div className="h-[calc(100vh-112px)] rounded-lg bg-white border border-gray-100 flex flex-col">
<div className="border-b border-gray-100 px-5 py-3">
<Typography.Title level={5} style={{ margin: 0 }}>
@@ -108,24 +136,23 @@ export default function HomePage() {
</Typography.Title>
</div>
<div className="flex-1 overflow-auto px-5 py-4">
<List
split={false}
dataSource={mockMessages}
renderItem={(item) => (
<List.Item>
<div
className={`max-w-[80%] rounded-lg px-3 py-2 ${
item.role === "user"
? "ml-auto bg-blue-50 border border-blue-100"
: "bg-gray-50 border border-gray-100"
}`}
>
<Typography.Text>{item.content}</Typography.Text>
</div>
</List.Item>
)}
/>
<div className="flex-1 overflow-auto px-5 py-4 flex flex-col gap-2">
{mockMessages.map((item, index) => (
<div
key={index}
className={`flex ${item.role === "user" ? "justify-end" : "justify-start"}`}
>
<div
className={`max-w-[80%] rounded-lg px-3 py-2 ${
item.role === "user"
? "bg-blue-50 border border-blue-100"
: "bg-gray-50 border border-gray-100"
}`}
>
<Typography.Text>{item.content}</Typography.Text>
</div>
</div>
))}
</div>
<div className="border-t border-gray-100 p-4">
@@ -148,7 +175,7 @@ export default function HomePage() {
closable
onClose={() => setMobileSidebarOpen(false)}
open={isMobile && mobileSidebarOpen}
width={260}
size={260}
>
{sidebarContent}
</Drawer>

View File

@@ -1,7 +1,4 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
}

2250
yarn.lock

File diff suppressed because it is too large Load Diff