update nginx

Change-Id: Ia5695f05d68dcae340c97a9cdd923ce5b56455d7
diff --git a/nginx.conf b/nginx.conf
index 8673316..90e9a4c 100644
--- a/nginx.conf
+++ b/nginx.conf
@@ -1,7 +1,3 @@
-upstream backend {
-    server backend:5004;  # 保持不变(Docker Compose中服务名即容器间通信域名)
-}
-
 server {
     listen 3004;  # 修改为Docker Compose中映射的前端端口(与docker-compose.yml保持一致)
 
@@ -16,7 +12,7 @@
 
     # 反向代理API请求到后端
     location /api/ {
-        proxy_pass http://backend/;  # 代理路径与后端API前缀匹配
+        proxy_pass http://thunderhub-backend:5004;  # 代理路径与后端API前缀匹配
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
diff --git a/package.json b/package.json
index 4625852..9cf008a 100644
--- a/package.json
+++ b/package.json
@@ -68,6 +68,7 @@
     "antd-style": "^3.6.2",
     "classnames": "^2.5.1",
     "dayjs": "^1.11.13",
+    "dva": "^3.0.0-alpha.1",
     "fabric": "^6.4.0",
     "highlight.js": "^11.10.0",
     "lodash": "^4.17.21",
diff --git a/src/api/system/register.js b/src/api/system/register.js
new file mode 100644
index 0000000..64adc05
--- /dev/null
+++ b/src/api/system/register.js
@@ -0,0 +1,10 @@
+import request from '@/utils/request'
+
+// 用户注册
+export function register(data) {
+  return request({
+    url: '/register',
+    method: 'post',
+    data: data
+  })
+}
\ No newline at end of file
diff --git a/src/pages/register/index.jsx b/src/pages/register/index.jsx
new file mode 100644
index 0000000..a91d63b
--- /dev/null
+++ b/src/pages/register/index.jsx
@@ -0,0 +1,115 @@
+import React, { useState } from 'react'
+import { connect } from 'dva'
+import { history } from 'umi'
+import { Form, Input, Button, message } from 'antd'
+import { UserOutlined, LockOutlined } from '@ant-design/icons'
+import styles from './index.less'
+
+const RegisterPage = ({ dispatch, register }) => {
+  const [form] = Form.useForm()
+  
+  const onFinish = values => {
+    dispatch({
+      type: 'register/submit',
+      payload: values
+    }).then(() => {
+      if (!register.error) {
+        message.success('注册成功')
+        history.push('/user/login')
+      } else {
+        message.error(register.error)
+      }
+    })
+  }
+  
+  return (
+    <div className={styles.container}>
+      <div className={styles.content}>
+        <div className={styles.top}>
+          <div className={styles.header}>
+            <span className={styles.title}>用户注册</span>
+          </div>
+        </div>
+        
+        <div className={styles.main}>
+          <Form
+            form={form}
+            name="register"
+            onFinish={onFinish}
+            scrollToFirstError
+          >
+            <Form.Item
+              name="username"
+              rules={[
+                { required: true, message: '请输入用户名!' },
+                { min: 4, message: '用户名至少4个字符' },
+                { max: 20, message: '用户名最多20个字符' },
+                { pattern: /^[a-zA-Z0-9_]+$/, message: '用户名只能包含字母、数字和下划线' }
+              ]}
+            >
+              <Input
+                prefix={<UserOutlined className={styles.prefixIcon} />}
+                placeholder="用户名"
+                size="large"
+              />
+            </Form.Item>
+            
+            <Form.Item
+              name="password"
+              rules={[
+                { required: true, message: '请输入密码!' },
+                { min: 6, message: '密码至少6个字符' },
+                { max: 20, message: '密码最多20个字符' }
+              ]}
+            >
+              <Input.Password
+                prefix={<LockOutlined className={styles.prefixIcon} />}
+                type="password"
+                placeholder="密码"
+                size="large"
+              />
+            </Form.Item>
+            
+            <Form.Item
+              name="confirmPassword"
+              dependencies={['password']}
+              rules={[
+                { required: true, message: '请确认密码!' },
+                ({ getFieldValue }) => ({
+                  validator(_, value) {
+                    if (!value || getFieldValue('password') === value) {
+                      return Promise.resolve()
+                    }
+                    return Promise.reject(new Error('两次输入的密码不一致!'))
+                  }
+                })
+              ]}
+            >
+              <Input.Password
+                prefix={<LockOutlined className={styles.prefixIcon} />}
+                type="password"
+                placeholder="确认密码"
+                size="large"
+              />
+            </Form.Item>
+            
+            <Form.Item>
+              <Button
+                type="primary"
+                htmlType="submit"
+                size="large"
+                className={styles.submit}
+                loading={register.submitting}
+                block
+              >
+                注册
+              </Button>
+            </Form.Item>
+          </Form>
+        </div>
+      </div>
+    </div>
+  )
+}
+
+export default connect(({ register }) => ({ register }))(RegisterPage)
\ No newline at end of file
diff --git a/src/pages/register/index.less b/src/pages/register/index.less
new file mode 100644
index 0000000..a6337d6
--- /dev/null
+++ b/src/pages/register/index.less
@@ -0,0 +1,52 @@
+.container {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  overflow: auto;
+  background: #f0f2f5;
+  background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
+  background-repeat: no-repeat;
+  background-position: center 110px;
+  background-size: 100%;
+}
+
+.content {
+  flex: 1;
+  padding: 32px 0;
+}
+
+.top {
+  text-align: center;
+}
+
+.header {
+  height: 44px;
+  line-height: 44px;
+  
+  .title {
+    position: relative;
+    top: 2px;
+    color: rgba(0, 0, 0, 0.85);
+    font-weight: 600;
+    font-size: 33px;
+    font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
+  }
+}
+
+.main {
+  width: 368px;
+  margin: 0 auto;
+  
+  @media screen and (max-width: 576px) {
+    width: 95%;
+  }
+  
+  .prefixIcon {
+    color: rgba(0, 0, 0, 0.25);
+  }
+  
+  .submit {
+    width: 100%;
+    margin-top: 24px;
+  }
+}
\ No newline at end of file
diff --git a/src/pages/register/model.js b/src/pages/register/model.js
new file mode 100644
index 0000000..891869e
--- /dev/null
+++ b/src/pages/register/model.js
@@ -0,0 +1,65 @@
+import * as registerApi from '@/api/system/register'
+
+export default {
+  namespace: 'register',
+  
+  state: {
+    submitting: false,
+    error: null
+  },
+  
+  effects: {
+    *submit({ payload }, { call, put }) {
+      yield put({
+        type: 'changeSubmitting',
+        payload: true
+      })
+      
+      try {
+        const response = yield call(registerApi.register, payload)
+        
+        if (response.code === 200) {
+          yield put({
+            type: 'registerSuccess'
+          })
+        } else {
+          yield put({
+            type: 'registerFailure',
+            payload: response.msg
+          })
+        }
+      } catch (error) {
+        yield put({
+          type: 'registerFailure',
+          payload: error.response?.data?.msg || '注册失败'
+        })
+      }
+      
+      yield put({
+        type: 'changeSubmitting',
+        payload: false
+      })
+    }
+  },
+  
+  reducers: {
+    changeSubmitting(state, { payload }) {
+      return {
+        ...state,
+        submitting: payload
+      }
+    },
+    registerSuccess(state) {
+      return {
+        ...state,
+        error: null
+      }
+    },
+    registerFailure(state, { payload }) {
+      return {
+        ...state,
+        error: payload
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/router/index.js b/src/router/index.js
new file mode 100644
index 0000000..8194827
--- /dev/null
+++ b/src/router/index.js
@@ -0,0 +1,9 @@
+import Register from '@/pages/register'
+
+// 在路由配置中添加注册路由
+{
+  path: '/user/register',
+  component; Register,
+  layout; false,
+  hideInMenu; true
+}
\ No newline at end of file
diff --git a/src/utils/request.js b/src/utils/request.js
new file mode 100644
index 0000000..536a3cc
--- /dev/null
+++ b/src/utils/request.js
@@ -0,0 +1,44 @@
+// src/utils/request.js
+import axios from 'axios'
+// import { getToken } from 'src/services/system/auth'
+import { message } from 'antd'
+
+// 创建axios实例
+const service = axios.create({
+  baseURL: process.env.REACT_APP_BASE_API, // api的base_url
+  timeout: 5000 // 请求超时时间
+})
+
+// request拦截器
+service.interceptors.request.use(
+  config => {
+    if (getToken()) {
+      config.headers['Authorization'] = 'Bearer ' + getToken()
+    }
+    return config
+  },
+  error => {
+    console.log(error)
+    Promise.reject(error)
+  }
+)
+
+// respone拦截器
+service.interceptors.response.use(
+  response => {
+    const res = response.data
+    if (res.code !== 200) {
+      message.error(res.msg || 'Error')
+      return Promise.reject(res)
+    } else {
+      return res
+    }
+  },
+  error => {
+    console.log('err' + error)
+    message.error(error.message)
+    return Promise.reject(error)
+  }
+)
+
+export default service
\ No newline at end of file