Compare commits
7 Commits
main
...
d2ab3d7bba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2ab3d7bba | ||
|
|
cf0509ccd3 | ||
|
|
cd86729115 | ||
|
|
527b1b7837 | ||
|
|
af567310df | ||
|
|
e0c3a26ca1 | ||
|
|
6693653c94 |
@@ -29,28 +29,34 @@ k8s:deployment:create # K8S-部署-创建
|
||||
|
||||
### 前端示例
|
||||
```typescript
|
||||
// src/client/utils/logger.ts
|
||||
// 在需要使用日志的文件中直接引入debug
|
||||
import debug from 'debug';
|
||||
|
||||
export const logger = {
|
||||
error: debug('frontend:error'),
|
||||
api: debug('frontend:api'),
|
||||
auth: debug('frontend:auth'),
|
||||
ui: debug('frontend:ui')
|
||||
};
|
||||
// 按需定义命名空间
|
||||
const errorLogger = debug('frontend:error');
|
||||
const apiLogger = debug('frontend:api');
|
||||
const authLogger = debug('frontend:auth');
|
||||
const uiLogger = debug('frontend:ui');
|
||||
|
||||
// 使用示例
|
||||
errorLogger('用户登录失败: %s', error.message);
|
||||
apiLogger('API请求: %s', url);
|
||||
```
|
||||
|
||||
### 后端示例
|
||||
```typescript
|
||||
// src/server/utils/logger.ts
|
||||
// 在需要使用日志的文件中直接引入debug
|
||||
import debug from 'debug';
|
||||
|
||||
export const logger = {
|
||||
error: debug('backend:error'),
|
||||
api: debug('backend:api'),
|
||||
db: debug('backend:db'),
|
||||
middleware: debug('backend:middleware')
|
||||
};
|
||||
// 按需定义命名空间
|
||||
const errorLogger = debug('backend:error');
|
||||
const apiLogger = debug('backend:api');
|
||||
const dbLogger = debug('backend:db');
|
||||
const middlewareLogger = debug('backend:middleware');
|
||||
|
||||
// 使用示例
|
||||
errorLogger('数据库连接失败: %s', error.message);
|
||||
dbLogger('查询执行: %s', sql);
|
||||
```
|
||||
|
||||
## 5. 最佳实践
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
```typescript
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
import { z } from 'zod';
|
||||
import { z } from '@hono/zod-openapi';
|
||||
|
||||
@Entity('table_name') // 使用小写下划线命名表名
|
||||
export class EntityName {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"start": "export NODE_ENV='production' && node dist-server/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.0.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@hono/node-server": "^1.14.3",
|
||||
|
||||
1767
pnpm-lock.yaml
generated
1767
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,14 @@
|
||||
import React from 'react';
|
||||
import { useRouteError, useNavigate } from 'react-router';
|
||||
import { Alert, Button } from 'antd';
|
||||
import { useTheme } from '../hooks/ThemeProvider';
|
||||
|
||||
export const ErrorPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const { isDark } = useTheme();
|
||||
const error = useRouteError() as any;
|
||||
const errorMessage = error?.statusText || error?.message || '未知错误';
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center flex-grow p-4"
|
||||
style={{ color: isDark ? '#fff' : 'inherit' }}
|
||||
>
|
||||
<div className="max-w-3xl w-full">
|
||||
<h1 className="text-2xl font-bold mb-4">发生错误</h1>
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { Button } from 'antd';
|
||||
import { useTheme } from '../hooks/ThemeProvider';
|
||||
|
||||
export const NotFoundPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const { isDark } = useTheme();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center flex-grow p-4"
|
||||
style={{ color: isDark ? '#fff' : 'inherit' }}
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center flex-grow p-4">
|
||||
<div className="max-w-3xl w-full">
|
||||
<h1 className="text-2xl font-bold mb-4">404 - 页面未找到</h1>
|
||||
<p className="mb-6 text-gray-600 dark:text-gray-300">
|
||||
|
||||
@@ -114,6 +114,7 @@ export const MainLayout = () => {
|
||||
collapsed={collapsed}
|
||||
width={240}
|
||||
className="custom-sider"
|
||||
theme='light'
|
||||
style={{
|
||||
overflow: 'auto',
|
||||
height: '100vh',
|
||||
@@ -122,6 +123,8 @@ export const MainLayout = () => {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
zIndex: 100,
|
||||
transition: 'all 0.2s ease',
|
||||
boxShadow: '2px 0 8px 0 rgba(29, 35, 41, 0.05)',
|
||||
}}
|
||||
>
|
||||
<div className="p-4">
|
||||
@@ -156,12 +159,9 @@ export const MainLayout = () => {
|
||||
</Sider>
|
||||
|
||||
<Layout style={{ marginLeft: collapsed ? 80 : 240, transition: 'margin-left 0.2s' }}>
|
||||
<Header className="p-0 flex justify-between items-center"
|
||||
<div className="sticky top-0 z-50 bg-white shadow-sm transition-all duration-200 h-16 flex items-center justify-between pl-2"
|
||||
style={{
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 99,
|
||||
boxShadow: '0 1px 4px rgba(0,21,41,0.08)',
|
||||
boxShadow: '0 1px 4px rgba(0,21,41,0.08)'
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@@ -191,10 +191,10 @@ export const MainLayout = () => {
|
||||
</Space>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
</Header>
|
||||
</div>
|
||||
|
||||
<Content className="m-6" style={{ overflow: 'initial' }}>
|
||||
<div className="site-layout-content p-6 rounded-lg">
|
||||
<Content className="m-6" style={{ overflow: 'initial', transition: 'all 0.2s ease' }}>
|
||||
<div className="site-layout-content p-6 rounded-lg bg-white shadow-sm">
|
||||
<Outlet />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
Input,
|
||||
Button,
|
||||
Card,
|
||||
message,
|
||||
App,
|
||||
} from 'antd';
|
||||
import {
|
||||
UserOutlined,
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
|
||||
// 登录页面
|
||||
export const LoginPage = () => {
|
||||
const { message } = App.useApp();
|
||||
const { login } = useAuth();
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -46,7 +47,7 @@ export const LoginPage = () => {
|
||||
// 登录成功后跳转到管理后台首页
|
||||
navigate('/admin/dashboard');
|
||||
} catch (error: any) {
|
||||
message.error(error.response?.data?.error || '登录失败');
|
||||
message.error(error instanceof Error ? error.message : '登录失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { getGlobalConfig } from '../utils/utils'
|
||||
|
||||
@@ -22,20 +21,20 @@ const Home = () => {
|
||||
|
||||
{/* 管理入口按钮 */}
|
||||
<div className="space-y-4">
|
||||
<Link
|
||||
to="/admin"
|
||||
<a
|
||||
href="/admin"
|
||||
className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-lg font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
进入管理后台
|
||||
</Link>
|
||||
</a>
|
||||
|
||||
{/* 移动端入口按钮 */}
|
||||
<Link
|
||||
to="/mobile"
|
||||
<a
|
||||
href="/mobile"
|
||||
className="w-full flex justify-center py-3 px-4 border border-blue-600 rounded-md shadow-sm text-lg font-medium text-blue-600 bg-white hover:bg-blue-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
进入移动端
|
||||
</Link>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createRoute, OpenAPIHono } from '@hono/zod-openapi'
|
||||
import { AuthService } from '../../../modules/auth/auth.service'
|
||||
import { UserService } from '../../../modules/users/user.service'
|
||||
import { z } from 'zod'
|
||||
import { z } from '@hono/zod-openapi'
|
||||
import { ErrorSchema } from '../../../utils/errorHandler'
|
||||
import { AppDataSource } from '../../../data-source'
|
||||
import { AuthContext } from '../../../types/context'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
|
||||
import { z } from 'zod'
|
||||
import { z } from '@hono/zod-openapi'
|
||||
import { AuthContext } from '@/server/types/context';
|
||||
import { authMiddleware } from '@/server/middleware/auth.middleware';
|
||||
import { AppDataSource } from '@/server/data-source';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createRoute, OpenAPIHono } from '@hono/zod-openapi'
|
||||
import { AuthService } from '../../../modules/auth/auth.service'
|
||||
import { UserService } from '../../../modules/users/user.service'
|
||||
import { z } from 'zod'
|
||||
import { z } from '@hono/zod-openapi'
|
||||
import { AppDataSource } from '../../../data-source'
|
||||
import { ErrorSchema } from '../../../utils/errorHandler'
|
||||
import { AuthContext } from '../../../types/context'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
|
||||
import { UserService } from '@/server/modules/users/user.service';
|
||||
import { z } from 'zod';
|
||||
import { z } from '@hono/zod-openapi';
|
||||
import { authMiddleware } from '@/server/middleware/auth.middleware';
|
||||
import { ErrorSchema } from '@/server/utils/errorHandler';
|
||||
import { AppDataSource } from '@/server/data-source';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
|
||||
import { UserService } from '@/server/modules/users/user.service';
|
||||
import { z } from 'zod';
|
||||
import { z } from '@hono/zod-openapi';
|
||||
import { authMiddleware } from '@/server/middleware/auth.middleware';
|
||||
import { ErrorSchema } from '@/server/utils/errorHandler';
|
||||
import { AppDataSource } from '@/server/data-source';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
|
||||
import { UserService } from '@/server/modules/users/user.service';
|
||||
import { z } from 'zod';
|
||||
import { z } from '@hono/zod-openapi';
|
||||
import { authMiddleware } from '@/server/middleware/auth.middleware';
|
||||
import { ErrorSchema } from '@/server/utils/errorHandler';
|
||||
import { AppDataSource } from '@/server/data-source';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
|
||||
import { UserService } from '../../modules/users/user.service';
|
||||
import { z } from 'zod';
|
||||
import { z } from '@hono/zod-openapi';
|
||||
import { authMiddleware } from '../../middleware/auth.middleware';
|
||||
import { ErrorSchema } from '../../utils/errorHandler';
|
||||
import { AppDataSource } from '../../data-source';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
|
||||
import { UserService } from '../../modules/users/user.service';
|
||||
import { z } from 'zod';
|
||||
import { z } from '@hono/zod-openapi';
|
||||
import { authMiddleware } from '../../middleware/auth.middleware';
|
||||
import { ErrorSchema } from '../../utils/errorHandler';
|
||||
import { AppDataSource } from '../../data-source';
|
||||
|
||||
@@ -17,6 +17,6 @@ export const AppDataSource = new DataSource({
|
||||
User, Role
|
||||
],
|
||||
migrations: [],
|
||||
synchronize: process.env.DB_SYNCHRONIZE === "true",
|
||||
synchronize: process.env.DB_SYNCHRONIZE !== "false",
|
||||
logging: process.env.DB_LOGGING === "true",
|
||||
});
|
||||
@@ -1,9 +1,18 @@
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { UserService } from '../users/user.service';
|
||||
import { UserEntity as User } from '../users/user.entity';
|
||||
import { DisabledStatus } from '@/share/types';
|
||||
import debug from 'debug';
|
||||
|
||||
const logger = {
|
||||
info: debug('backend:auth:info'),
|
||||
error: debug('backend:auth:error')
|
||||
}
|
||||
|
||||
const JWT_SECRET = 'your-secret-key'; // 生产环境应使用环境变量
|
||||
const JWT_EXPIRES_IN = '7d'; // 7天有效期
|
||||
const ADMIN_USERNAME = 'admin';
|
||||
const ADMIN_PASSWORD = 'admin123';
|
||||
|
||||
export class AuthService {
|
||||
private userService: UserService;
|
||||
@@ -12,8 +21,33 @@ export class AuthService {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
async ensureAdminExists(): Promise<User> {
|
||||
try {
|
||||
let admin = await this.userService.getUserByUsername(ADMIN_USERNAME);
|
||||
if (!admin) {
|
||||
logger.info('Admin user not found, creating default admin account');
|
||||
admin = await this.userService.createUser({
|
||||
username: ADMIN_USERNAME,
|
||||
password: ADMIN_PASSWORD,
|
||||
nickname: '系统管理员',
|
||||
isDisabled: DisabledStatus.ENABLED
|
||||
});
|
||||
logger.info('Default admin account created successfully');
|
||||
}
|
||||
return admin;
|
||||
} catch (error) {
|
||||
logger.error('Failed to ensure admin account exists:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async login(username: string, password: string): Promise<{ token: string; user: User }> {
|
||||
try {
|
||||
// 确保admin用户存在
|
||||
if (username === ADMIN_USERNAME) {
|
||||
await this.ensureAdminExists();
|
||||
}
|
||||
|
||||
const user = await this.userService.getUserByUsername(username);
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
@@ -27,7 +61,7 @@ export class AuthService {
|
||||
const token = this.generateToken(user);
|
||||
return { token, user };
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
logger.error('Login error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
import { z } from 'zod';
|
||||
import { z } from '@hono/zod-openapi';
|
||||
|
||||
export type Permission = string;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Context } from 'hono'
|
||||
import { z } from 'zod'
|
||||
import { z } from '@hono/zod-openapi'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
|
||||
export const ErrorSchema = z.object({
|
||||
|
||||
@@ -1 +1,55 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
/* 全局滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
|
||||
/* 响应式断点 */
|
||||
@media (max-width: 768px) {
|
||||
.custom-sider {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.site-layout-content {
|
||||
padding: 1rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 全局过渡效果 */
|
||||
.transition-all {
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
/* 内容区域阴影优化 */
|
||||
.shadow-sm {
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 按钮悬停效果 */
|
||||
.ant-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* 卡片样式优化 */
|
||||
.ant-card {
|
||||
border-radius: 8px;
|
||||
}
|
||||
@@ -1,12 +1,8 @@
|
||||
import reactStack from 'hono-vite-react-stack-node'
|
||||
import { defineConfig } from 'vite'
|
||||
import i18nextLoader from 'vite-plugin-i18next-loader'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
i18nextLoader({
|
||||
paths: ['src/client/i18n/locales']
|
||||
}),
|
||||
reactStack({
|
||||
minify: false,
|
||||
port: 8080
|
||||
|
||||
Reference in New Issue
Block a user