优化UI体验和修复Markdown渲染问题
## 主要改进 ### 1. 修复AI解析Markdown渲染混乱 - 安装remark-gfm插件支持GitHub Flavored Markdown - 优化ReactMarkdown组件样式配置 - 支持表格、代码块、引用、列表等完整Markdown语法 - 改进代码块样式,区分行内代码和代码块 - 优化排版,提升可读性 ### 2. 用户界面优化 - 修改个人信息中"昵称"改为"姓名" - 调整答题设置提示位置到第一行 - 去除答题设置提示标题,使界面更简洁 ### 3. 新增用户功能 - 添加修改个人信息功能(姓名、身份类型) - 添加修改密码功能 - 优化用户信息显示和编辑体验 ### 4. 技术改进 - 修复Home.tsx中handleLogout函数声明顺序问题 - 添加更完善的Markdown样式定义 - 优化流式渲染体验 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
b30647d81b
commit
45299b2d7e
@ -244,3 +244,145 @@ func UpdateUserType(c *gin.Context) {
|
|||||||
"data": userInfo,
|
"data": userInfo,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateProfileRequest 更新用户信息请求
|
||||||
|
type UpdateProfileRequest struct {
|
||||||
|
Nickname string `json:"nickname" binding:"required"`
|
||||||
|
UserType string `json:"user_type" binding:"required,oneof=ordinary-person management-person"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateProfile 更新用户信息
|
||||||
|
func UpdateProfile(c *gin.Context) {
|
||||||
|
var req UpdateProfileRequest
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "请求参数错误",
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从上下文获取用户信息(由认证中间件设置)
|
||||||
|
username, exists := c.Get("username")
|
||||||
|
if !exists {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "未授权访问",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
db := database.GetDB()
|
||||||
|
var user models.User
|
||||||
|
if err := db.Where("username = ?", username).First(&user).Error; err != nil {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "用户不存在",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户信息
|
||||||
|
user.Nickname = req.Nickname
|
||||||
|
user.UserType = req.UserType
|
||||||
|
if err := db.Save(&user).Error; err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "更新用户信息失败",
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回更新后的用户信息
|
||||||
|
userInfo := models.UserInfoResponse{
|
||||||
|
Username: user.Username,
|
||||||
|
Avatar: user.Avatar,
|
||||||
|
Nickname: user.Nickname,
|
||||||
|
UserType: user.UserType,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
|
"message": "用户信息更新成功",
|
||||||
|
"data": userInfo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangePasswordRequest 修改密码请求
|
||||||
|
type ChangePasswordRequest struct {
|
||||||
|
OldPassword string `json:"old_password" binding:"required"`
|
||||||
|
NewPassword string `json:"new_password" binding:"required,min=6"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangePassword 修改密码
|
||||||
|
func ChangePassword(c *gin.Context) {
|
||||||
|
var req ChangePasswordRequest
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "请求参数错误,新密码长度至少为6位",
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从上下文获取用户信息(由认证中间件设置)
|
||||||
|
username, exists := c.Get("username")
|
||||||
|
if !exists {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "未授权访问",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
db := database.GetDB()
|
||||||
|
var user models.User
|
||||||
|
if err := db.Where("username = ?", username).First(&user).Error; err != nil {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "用户不存在",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证旧密码
|
||||||
|
if !user.CheckPassword(req.OldPassword) {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "当前密码错误",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新密码
|
||||||
|
if err := user.HashPassword(req.NewPassword); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "密码加密失败",
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除旧的token,强制重新登录
|
||||||
|
user.Token = ""
|
||||||
|
|
||||||
|
if err := db.Save(&user).Error; err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "密码更新失败",
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
|
"message": "密码修改成功,请重新登录",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
4
main.go
4
main.go
@ -40,7 +40,9 @@ func main() {
|
|||||||
auth := api.Group("", middleware.Auth())
|
auth := api.Group("", middleware.Auth())
|
||||||
{
|
{
|
||||||
// 用户相关API
|
// 用户相关API
|
||||||
auth.PUT("/user/type", handlers.UpdateUserType) // 更新用户类型
|
auth.PUT("/user/type", handlers.UpdateUserType) // 更新用户类型
|
||||||
|
auth.PUT("/user/profile", handlers.UpdateProfile) // 更新用户信息
|
||||||
|
auth.PUT("/user/password", handlers.ChangePassword) // 修改密码
|
||||||
|
|
||||||
// 练习题相关API(需要登录)
|
// 练习题相关API(需要登录)
|
||||||
auth.GET("/practice/questions", handlers.GetPracticeQuestions) // 获取练习题目列表
|
auth.GET("/practice/questions", handlers.GetPracticeQuestions) // 获取练习题目列表
|
||||||
|
|||||||
@ -16,7 +16,8 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"react-router-dom": "^6.21.3"
|
"react-router-dom": "^6.21.3",
|
||||||
|
"remark-gfm": "^4.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.11.5",
|
"@types/node": "^20.11.5",
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import React, { useState } from 'react'
|
|||||||
import { Alert, Typography, Card, Space, Progress, Button, Spin } from 'antd'
|
import { Alert, Typography, Card, Space, Progress, Button, Spin } from 'antd'
|
||||||
import { CheckOutlined, CloseOutlined, TrophyOutlined, CommentOutlined, BulbOutlined, FileTextOutlined, ReloadOutlined } from '@ant-design/icons'
|
import { CheckOutlined, CloseOutlined, TrophyOutlined, CommentOutlined, BulbOutlined, FileTextOutlined, ReloadOutlined } from '@ant-design/icons'
|
||||||
import ReactMarkdown from 'react-markdown'
|
import ReactMarkdown from 'react-markdown'
|
||||||
|
import remarkGfm from 'remark-gfm'
|
||||||
import { fetchWithAuth } from '../utils/request'
|
import { fetchWithAuth } from '../utils/request'
|
||||||
import type { AnswerResult as AnswerResultType } from '../types/question'
|
import type { AnswerResult as AnswerResultType } from '../types/question'
|
||||||
|
|
||||||
@ -218,21 +219,175 @@ const AnswerResult: React.FC<AnswerResultProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div style={{ color: '#595959' }}>
|
<div style={{ color: '#595959', lineHeight: '1.8' }}>
|
||||||
{explanation ? (
|
{explanation ? (
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
|
remarkPlugins={[remarkGfm]}
|
||||||
components={{
|
components={{
|
||||||
// 自定义markdown组件样式
|
// 自定义markdown组件样式
|
||||||
p: ({ children }) => <p style={{ marginBottom: '0.5em', lineHeight: '1.6' }}>{children}</p>,
|
p: ({ children }) => (
|
||||||
h1: ({ children }) => <h1 style={{ fontSize: '1.5em', marginTop: '0.5em', marginBottom: '0.5em' }}>{children}</h1>,
|
<p style={{
|
||||||
h2: ({ children }) => <h2 style={{ fontSize: '1.3em', marginTop: '0.5em', marginBottom: '0.5em' }}>{children}</h2>,
|
marginBottom: '1em',
|
||||||
h3: ({ children }) => <h3 style={{ fontSize: '1.1em', marginTop: '0.5em', marginBottom: '0.5em' }}>{children}</h3>,
|
lineHeight: '1.8',
|
||||||
ul: ({ children }) => <ul style={{ marginLeft: '1.5em', marginBottom: '0.5em' }}>{children}</ul>,
|
wordWrap: 'break-word',
|
||||||
ol: ({ children }) => <ol style={{ marginLeft: '1.5em', marginBottom: '0.5em' }}>{children}</ol>,
|
whiteSpace: 'pre-wrap'
|
||||||
li: ({ children }) => <li style={{ marginBottom: '0.3em', lineHeight: '1.6' }}>{children}</li>,
|
}}>
|
||||||
code: ({ children }) => <code style={{ backgroundColor: '#f5f5f5', padding: '2px 6px', borderRadius: '3px', fontSize: '0.9em' }}>{children}</code>,
|
{children}
|
||||||
pre: ({ children }) => <pre style={{ backgroundColor: '#f5f5f5', padding: '12px', borderRadius: '4px', overflow: 'auto' }}>{children}</pre>,
|
</p>
|
||||||
blockquote: ({ children }) => <blockquote style={{ borderLeft: '4px solid #1890ff', paddingLeft: '12px', margin: '0.5em 0', color: '#666' }}>{children}</blockquote>,
|
),
|
||||||
|
h1: ({ children }) => (
|
||||||
|
<h1 style={{
|
||||||
|
fontSize: '1.75em',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginTop: '1em',
|
||||||
|
marginBottom: '0.6em',
|
||||||
|
borderBottom: '2px solid #e8e8e8',
|
||||||
|
paddingBottom: '0.3em'
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</h1>
|
||||||
|
),
|
||||||
|
h2: ({ children }) => (
|
||||||
|
<h2 style={{
|
||||||
|
fontSize: '1.5em',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginTop: '1em',
|
||||||
|
marginBottom: '0.5em',
|
||||||
|
color: '#262626'
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</h2>
|
||||||
|
),
|
||||||
|
h3: ({ children }) => (
|
||||||
|
<h3 style={{
|
||||||
|
fontSize: '1.25em',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginTop: '0.8em',
|
||||||
|
marginBottom: '0.5em',
|
||||||
|
color: '#262626'
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</h3>
|
||||||
|
),
|
||||||
|
ul: ({ children }) => (
|
||||||
|
<ul style={{
|
||||||
|
marginLeft: '1.5em',
|
||||||
|
marginBottom: '1em',
|
||||||
|
paddingLeft: '0.5em',
|
||||||
|
lineHeight: '1.8'
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</ul>
|
||||||
|
),
|
||||||
|
ol: ({ children }) => (
|
||||||
|
<ol style={{
|
||||||
|
marginLeft: '1.5em',
|
||||||
|
marginBottom: '1em',
|
||||||
|
paddingLeft: '0.5em',
|
||||||
|
lineHeight: '1.8'
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</ol>
|
||||||
|
),
|
||||||
|
li: ({ children }) => (
|
||||||
|
<li style={{
|
||||||
|
marginBottom: '0.4em',
|
||||||
|
lineHeight: '1.8'
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</li>
|
||||||
|
),
|
||||||
|
code: ({ children, className }) => {
|
||||||
|
const isInline = !className
|
||||||
|
return isInline ? (
|
||||||
|
<code style={{
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
padding: '2px 6px',
|
||||||
|
borderRadius: '3px',
|
||||||
|
fontSize: '0.9em',
|
||||||
|
fontFamily: 'Consolas, Monaco, "Courier New", monospace',
|
||||||
|
color: '#c7254e'
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</code>
|
||||||
|
) : (
|
||||||
|
<code className={className} style={{
|
||||||
|
display: 'block',
|
||||||
|
fontFamily: 'Consolas, Monaco, "Courier New", monospace'
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</code>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
pre: ({ children }) => (
|
||||||
|
<pre style={{
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
padding: '12px 16px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
overflow: 'auto',
|
||||||
|
marginBottom: '1em',
|
||||||
|
border: '1px solid #e8e8e8'
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</pre>
|
||||||
|
),
|
||||||
|
blockquote: ({ children }) => (
|
||||||
|
<blockquote style={{
|
||||||
|
borderLeft: '4px solid #1890ff',
|
||||||
|
paddingLeft: '16px',
|
||||||
|
margin: '1em 0',
|
||||||
|
color: '#666',
|
||||||
|
fontStyle: 'italic',
|
||||||
|
backgroundColor: '#f0f9ff',
|
||||||
|
padding: '12px 16px',
|
||||||
|
borderRadius: '0 4px 4px 0'
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</blockquote>
|
||||||
|
),
|
||||||
|
table: ({ children }) => (
|
||||||
|
<div style={{ overflowX: 'auto', marginBottom: '1em' }}>
|
||||||
|
<table style={{
|
||||||
|
borderCollapse: 'collapse',
|
||||||
|
width: '100%',
|
||||||
|
border: '1px solid #e8e8e8'
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
th: ({ children }) => (
|
||||||
|
<th style={{
|
||||||
|
border: '1px solid #e8e8e8',
|
||||||
|
padding: '8px 12px',
|
||||||
|
backgroundColor: '#fafafa',
|
||||||
|
textAlign: 'left',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</th>
|
||||||
|
),
|
||||||
|
td: ({ children }) => (
|
||||||
|
<td style={{
|
||||||
|
border: '1px solid #e8e8e8',
|
||||||
|
padding: '8px 12px'
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</td>
|
||||||
|
),
|
||||||
|
strong: ({ children }) => (
|
||||||
|
<strong style={{ fontWeight: 'bold', color: '#262626' }}>{children}</strong>
|
||||||
|
),
|
||||||
|
em: ({ children }) => (
|
||||||
|
<em style={{ fontStyle: 'italic', color: '#595959' }}>{children}</em>
|
||||||
|
),
|
||||||
|
hr: () => (
|
||||||
|
<hr style={{
|
||||||
|
border: 'none',
|
||||||
|
borderTop: '1px solid #e8e8e8',
|
||||||
|
margin: '1.5em 0'
|
||||||
|
}} />
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{explanation}
|
{explanation}
|
||||||
|
|||||||
@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
.userInfo {
|
.userInfo {
|
||||||
background: rgba(255, 255, 255, 0.85);
|
background: rgba(255, 255, 255, 0.85);
|
||||||
padding: 10px 16px;
|
padding: 12px 16px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
backdrop-filter: blur(30px) saturate(180%);
|
backdrop-filter: blur(30px) saturate(180%);
|
||||||
-webkit-backdrop-filter: blur(30px) saturate(180%);
|
-webkit-backdrop-filter: blur(30px) saturate(180%);
|
||||||
@ -52,21 +52,39 @@
|
|||||||
0 1px 3px rgba(0, 0, 0, 0.02),
|
0 1px 3px rgba(0, 0, 0, 0.02),
|
||||||
0 0 0 1px rgba(0, 0, 0, 0.03);
|
0 0 0 1px rgba(0, 0, 0, 0.03);
|
||||||
border: 0.5px solid rgba(0, 0, 0, 0.04);
|
border: 0.5px solid rgba(0, 0, 0, 0.04);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
box-shadow:
|
||||||
|
0 4px 12px rgba(0, 0, 0, 0.06),
|
||||||
|
0 2px 6px rgba(0, 0, 0, 0.04),
|
||||||
|
0 0 0 1px rgba(0, 0, 0, 0.04);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
.userDetails {
|
.userDetails {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 2px;
|
gap: 3px;
|
||||||
|
|
||||||
.userNickname {
|
.userNickname {
|
||||||
color: #1d1d1f !important;
|
color: #1d1d1f !important;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.userUsername {
|
.userUsername {
|
||||||
color: #6e6e73 !important;
|
color: #6e6e73 !important;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.userType {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #6e6e73;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { Card, Statistic, Row, Col, Typography, message, Space, Avatar, Button, Modal, Form, Radio, Alert } from 'antd'
|
import { Card, Statistic, Row, Col, Typography, message, Space, Avatar, Button, Modal, Form, Radio, Alert, Input, Switch, InputNumber, Divider, Badge, Dropdown } from 'antd'
|
||||||
|
import type { MenuProps } from 'antd'
|
||||||
import {
|
import {
|
||||||
FileTextOutlined,
|
FileTextOutlined,
|
||||||
CheckCircleOutlined,
|
CheckCircleOutlined,
|
||||||
@ -12,6 +13,9 @@ import {
|
|||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
UnorderedListOutlined as ListOutlined,
|
UnorderedListOutlined as ListOutlined,
|
||||||
|
LockOutlined,
|
||||||
|
IdcardOutlined,
|
||||||
|
DownOutlined,
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import * as questionApi from '../api/question'
|
import * as questionApi from '../api/question'
|
||||||
import { fetchWithAuth } from '../utils/request'
|
import { fetchWithAuth } from '../utils/request'
|
||||||
@ -77,8 +81,13 @@ const Home: React.FC = () => {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [userInfo, setUserInfo] = useState<UserInfo | null>(null)
|
const [userInfo, setUserInfo] = useState<UserInfo | null>(null)
|
||||||
const [userTypeModalVisible, setUserTypeModalVisible] = useState(false) // 用户类型补充模态框
|
const [userTypeModalVisible, setUserTypeModalVisible] = useState(false) // 用户类型补充模态框
|
||||||
|
const [editProfileVisible, setEditProfileVisible] = useState(false) // 修改用户信息模态框
|
||||||
|
const [changePasswordVisible, setChangePasswordVisible] = useState(false) // 修改密码模态框
|
||||||
|
const [practiceSettingsVisible, setPracticeSettingsVisible] = useState(false) // 答题设置模态框
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [userTypeForm] = Form.useForm()
|
const [userTypeForm] = Form.useForm()
|
||||||
|
const [editProfileForm] = Form.useForm()
|
||||||
|
const [changePasswordForm] = Form.useForm()
|
||||||
const [statistics, setStatistics] = useState<Statistics>({
|
const [statistics, setStatistics] = useState<Statistics>({
|
||||||
total_questions: 0,
|
total_questions: 0,
|
||||||
answered_questions: 0,
|
answered_questions: 0,
|
||||||
@ -87,6 +96,22 @@ const Home: React.FC = () => {
|
|||||||
accuracy: 0,
|
accuracy: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 答题设置状态
|
||||||
|
const [autoNext, setAutoNext] = useState(() => {
|
||||||
|
const saved = localStorage.getItem('autoNextEnabled')
|
||||||
|
return saved !== null ? saved === 'true' : true
|
||||||
|
})
|
||||||
|
|
||||||
|
const [autoNextDelay, setAutoNextDelay] = useState(() => {
|
||||||
|
const saved = localStorage.getItem('autoNextDelay')
|
||||||
|
return saved !== null ? parseInt(saved, 10) : 2
|
||||||
|
})
|
||||||
|
|
||||||
|
const [randomMode, setRandomMode] = useState(() => {
|
||||||
|
const saved = localStorage.getItem('randomModeEnabled')
|
||||||
|
return saved !== null ? saved === 'true' : false
|
||||||
|
})
|
||||||
|
|
||||||
// 加载统计数据
|
// 加载统计数据
|
||||||
const loadStatistics = async () => {
|
const loadStatistics = async () => {
|
||||||
try {
|
try {
|
||||||
@ -154,6 +179,152 @@ const Home: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 修改用户信息
|
||||||
|
const handleEditProfile = () => {
|
||||||
|
if (userInfo) {
|
||||||
|
editProfileForm.setFieldsValue({
|
||||||
|
nickname: userInfo.nickname,
|
||||||
|
user_type: userInfo.user_type,
|
||||||
|
})
|
||||||
|
setEditProfileVisible(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUpdateProfile = async (values: { nickname: string; user_type: string }) => {
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const response = await fetchWithAuth('/api/user/profile', {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(values),
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
// 更新本地存储的用户信息
|
||||||
|
const user = JSON.parse(localStorage.getItem('user') || '{}')
|
||||||
|
user.nickname = data.data.nickname
|
||||||
|
user.user_type = data.data.user_type
|
||||||
|
localStorage.setItem('user', JSON.stringify(user))
|
||||||
|
setUserInfo(user)
|
||||||
|
|
||||||
|
message.success('个人信息更新成功')
|
||||||
|
setEditProfileVisible(false)
|
||||||
|
} else {
|
||||||
|
message.error(data.message || '更新失败')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
message.error('网络错误,请稍后重试')
|
||||||
|
console.error('更新用户信息错误:', err)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改密码
|
||||||
|
const handleChangePassword = async (values: { old_password: string; new_password: string }) => {
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const response = await fetchWithAuth('/api/user/password', {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(values),
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
message.success('密码修改成功,请重新登录')
|
||||||
|
changePasswordForm.resetFields()
|
||||||
|
setChangePasswordVisible(false)
|
||||||
|
// 清除登录信息并跳转到登录页
|
||||||
|
setTimeout(() => {
|
||||||
|
localStorage.removeItem('token')
|
||||||
|
localStorage.removeItem('user')
|
||||||
|
navigate('/login')
|
||||||
|
}, 1000)
|
||||||
|
} else {
|
||||||
|
message.error(data.message || '修改失败')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
message.error('网络错误,请稍后重试')
|
||||||
|
console.error('修改密码错误:', err)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 答题设置相关函数
|
||||||
|
const toggleAutoNext = () => {
|
||||||
|
const newValue = !autoNext
|
||||||
|
setAutoNext(newValue)
|
||||||
|
localStorage.setItem('autoNextEnabled', String(newValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleRandomMode = () => {
|
||||||
|
const newValue = !randomMode
|
||||||
|
setRandomMode(newValue)
|
||||||
|
localStorage.setItem('randomModeEnabled', String(newValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelayChange = (value: number | null) => {
|
||||||
|
if (value !== null && value >= 1 && value <= 10) {
|
||||||
|
setAutoNextDelay(value)
|
||||||
|
localStorage.setItem('autoNextDelay', String(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户类型显示文本
|
||||||
|
const getUserTypeText = (type?: string) => {
|
||||||
|
if (!type) return '未设置'
|
||||||
|
return type === 'ordinary-person' ? '普通涉密人员' : '保密管理人员'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
const handleLogout = () => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确定要退出登录吗?',
|
||||||
|
onOk: () => {
|
||||||
|
localStorage.removeItem('token')
|
||||||
|
localStorage.removeItem('user')
|
||||||
|
setUserInfo(null)
|
||||||
|
message.success('已退出登录')
|
||||||
|
navigate('/login')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户菜单项
|
||||||
|
const userMenuItems: MenuProps['items'] = [
|
||||||
|
{
|
||||||
|
key: 'edit-profile',
|
||||||
|
icon: <IdcardOutlined />,
|
||||||
|
label: '修改个人信息',
|
||||||
|
onClick: handleEditProfile,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'change-password',
|
||||||
|
icon: <LockOutlined />,
|
||||||
|
label: '修改密码',
|
||||||
|
onClick: () => setChangePasswordVisible(true),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'practice-settings',
|
||||||
|
icon: <SettingOutlined />,
|
||||||
|
label: '答题设置',
|
||||||
|
onClick: () => setPracticeSettingsVisible(true),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'logout',
|
||||||
|
icon: <LogoutOutlined />,
|
||||||
|
label: '退出登录',
|
||||||
|
danger: true,
|
||||||
|
onClick: handleLogout,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
// 点击题型卡片
|
// 点击题型卡片
|
||||||
const handleTypeClick = async (type: string) => {
|
const handleTypeClick = async (type: string) => {
|
||||||
try {
|
try {
|
||||||
@ -181,20 +352,6 @@ const Home: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 退出登录
|
|
||||||
const handleLogout = () => {
|
|
||||||
Modal.confirm({
|
|
||||||
title: '确定要退出登录吗?',
|
|
||||||
onOk: () => {
|
|
||||||
localStorage.removeItem('token')
|
|
||||||
localStorage.removeItem('user')
|
|
||||||
setUserInfo(null)
|
|
||||||
message.success('已退出登录')
|
|
||||||
navigate('/login')
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
{/* 头部 */}
|
{/* 头部 */}
|
||||||
@ -210,27 +367,30 @@ const Home: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
{/* 用户信息 */}
|
{/* 用户信息 */}
|
||||||
{userInfo && (
|
{userInfo && (
|
||||||
<div className={styles.userInfo}>
|
<Dropdown menu={{ items: userMenuItems }} trigger={['click']}>
|
||||||
<Space size="middle">
|
<div className={styles.userInfo}>
|
||||||
<Avatar
|
<Space size="middle">
|
||||||
src={userInfo.avatar || undefined}
|
<Avatar
|
||||||
size={40}
|
src={userInfo.avatar || undefined}
|
||||||
icon={<UserOutlined />}
|
size={44}
|
||||||
/>
|
icon={<UserOutlined />}
|
||||||
<div className={styles.userDetails}>
|
/>
|
||||||
<Text strong className={styles.userNickname}>{userInfo.nickname}</Text>
|
<div className={styles.userDetails}>
|
||||||
<Text type="secondary" className={styles.userUsername}>@{userInfo.username}</Text>
|
<Text strong className={styles.userNickname}>{userInfo.nickname}</Text>
|
||||||
</div>
|
<Text type="secondary" className={styles.userUsername}>@{userInfo.username}</Text>
|
||||||
<Button
|
<Badge
|
||||||
type="text"
|
color={userInfo.user_type ? '#52c41a' : '#ff4d4f'}
|
||||||
danger
|
text={
|
||||||
icon={<LogoutOutlined />}
|
<Text className={styles.userType}>
|
||||||
onClick={handleLogout}
|
{getUserTypeText(userInfo.user_type)}
|
||||||
>
|
</Text>
|
||||||
退出
|
}
|
||||||
</Button>
|
/>
|
||||||
</Space>
|
</div>
|
||||||
</div>
|
<DownOutlined style={{ fontSize: '12px', color: '#8c8c8c' }} />
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -406,6 +566,203 @@ const Home: React.FC = () => {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
{/* 修改个人信息模态框 */}
|
||||||
|
<Modal
|
||||||
|
title="修改个人信息"
|
||||||
|
open={editProfileVisible}
|
||||||
|
onCancel={() => setEditProfileVisible(false)}
|
||||||
|
footer={null}
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={editProfileForm}
|
||||||
|
name="editProfile"
|
||||||
|
onFinish={handleUpdateProfile}
|
||||||
|
autoComplete="off"
|
||||||
|
layout="vertical"
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="nickname"
|
||||||
|
label="姓名"
|
||||||
|
rules={[{ required: true, message: '请输入姓名' }]}
|
||||||
|
>
|
||||||
|
<Input prefix={<UserOutlined />} placeholder="请输入姓名" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="user_type"
|
||||||
|
label="身份类型"
|
||||||
|
rules={[{ required: true, message: '请选择身份类型' }]}
|
||||||
|
>
|
||||||
|
<Radio.Group>
|
||||||
|
<Radio value="ordinary-person">普通涉密人员</Radio>
|
||||||
|
<Radio value="management-person">保密管理人员</Radio>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item>
|
||||||
|
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
|
||||||
|
<Button onClick={() => setEditProfileVisible(false)}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" htmlType="submit" loading={loading}>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{/* 修改密码模态框 */}
|
||||||
|
<Modal
|
||||||
|
title="修改密码"
|
||||||
|
open={changePasswordVisible}
|
||||||
|
onCancel={() => setChangePasswordVisible(false)}
|
||||||
|
footer={null}
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<Alert
|
||||||
|
message="密码修改后需要重新登录"
|
||||||
|
type="warning"
|
||||||
|
showIcon
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
/>
|
||||||
|
<Form
|
||||||
|
form={changePasswordForm}
|
||||||
|
name="changePassword"
|
||||||
|
onFinish={handleChangePassword}
|
||||||
|
autoComplete="off"
|
||||||
|
layout="vertical"
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="old_password"
|
||||||
|
label="当前密码"
|
||||||
|
rules={[{ required: true, message: '请输入当前密码' }]}
|
||||||
|
>
|
||||||
|
<Input.Password prefix={<LockOutlined />} placeholder="请输入当前密码" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="new_password"
|
||||||
|
label="新密码"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: '请输入新密码' },
|
||||||
|
{ min: 6, message: '密码长度至少为6位' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input.Password prefix={<LockOutlined />} placeholder="请输入新密码(至少6位)" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="confirm_password"
|
||||||
|
label="确认新密码"
|
||||||
|
dependencies={['new_password']}
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: '请确认新密码' },
|
||||||
|
({ getFieldValue }) => ({
|
||||||
|
validator(_, value) {
|
||||||
|
if (!value || getFieldValue('new_password') === value) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error('两次输入的密码不一致'))
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input.Password prefix={<LockOutlined />} placeholder="请再次输入新密码" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item>
|
||||||
|
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
|
||||||
|
<Button onClick={() => setChangePasswordVisible(false)}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" htmlType="submit" loading={loading}>
|
||||||
|
确认修改
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{/* 答题设置模态框 */}
|
||||||
|
<Modal
|
||||||
|
title="答题设置"
|
||||||
|
open={practiceSettingsVisible}
|
||||||
|
onCancel={() => setPracticeSettingsVisible(false)}
|
||||||
|
footer={[
|
||||||
|
<Button key="close" type="primary" onClick={() => setPracticeSettingsVisible(false)}>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
]}
|
||||||
|
width={480}
|
||||||
|
>
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
||||||
|
<Alert
|
||||||
|
description="这些设置会在答题页面中生效,帮助您更高效地刷题。"
|
||||||
|
type="info"
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<span style={{ fontSize: 15, fontWeight: 500 }}>自动下一题</span>
|
||||||
|
<span style={{ marginLeft: 8, fontSize: 13, color: '#8c8c8c' }}>
|
||||||
|
答对题目后自动跳转到下一题
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12, paddingLeft: 8 }}>
|
||||||
|
<Switch
|
||||||
|
checked={autoNext}
|
||||||
|
onChange={toggleAutoNext}
|
||||||
|
/>
|
||||||
|
<span style={{ fontSize: 14, color: autoNext ? '#52c41a' : '#8c8c8c' }}>
|
||||||
|
{autoNext ? '已开启' : '已关闭'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{autoNext && (
|
||||||
|
<div>
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<span style={{ fontSize: 15, fontWeight: 500 }}>延迟时间</span>
|
||||||
|
<span style={{ marginLeft: 8, fontSize: 13, color: '#8c8c8c' }}>
|
||||||
|
设置答对后等待多久跳转到下一题
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12, paddingLeft: 8 }}>
|
||||||
|
<InputNumber
|
||||||
|
min={1}
|
||||||
|
max={10}
|
||||||
|
value={autoNextDelay}
|
||||||
|
onChange={handleDelayChange}
|
||||||
|
style={{ width: 80 }}
|
||||||
|
/>
|
||||||
|
<span style={{ fontSize: 14 }}>秒后自动跳转</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<span style={{ fontSize: 15, fontWeight: 500 }}>随机题目</span>
|
||||||
|
<span style={{ marginLeft: 8, fontSize: 13, color: '#8c8c8c' }}>
|
||||||
|
开启后点击下一题时随机跳转
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12, paddingLeft: 8 }}>
|
||||||
|
<Switch
|
||||||
|
checked={randomMode}
|
||||||
|
onChange={toggleRandomMode}
|
||||||
|
/>
|
||||||
|
<span style={{ fontSize: 14, color: randomMode ? '#52c41a' : '#8c8c8c' }}>
|
||||||
|
{randomMode ? '已开启' : '已关闭'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
185
web/yarn.lock
185
web/yarn.lock
@ -1338,6 +1338,11 @@ escape-string-regexp@^4.0.0:
|
|||||||
resolved "https://mirrors.yuchat.top/repository/npmjs/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
|
resolved "https://mirrors.yuchat.top/repository/npmjs/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
|
||||||
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
||||||
|
|
||||||
|
escape-string-regexp@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://mirrors.yuchat.top/repository/npmjs/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
|
||||||
|
integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
|
||||||
|
|
||||||
eslint-plugin-react-hooks@^4.6.0:
|
eslint-plugin-react-hooks@^4.6.0:
|
||||||
version "4.6.2"
|
version "4.6.2"
|
||||||
resolved "https://mirrors.yuchat.top/repository/npmjs/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz"
|
resolved "https://mirrors.yuchat.top/repository/npmjs/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz"
|
||||||
@ -1922,11 +1927,26 @@ make-dir@^2.1.0:
|
|||||||
pify "^4.0.1"
|
pify "^4.0.1"
|
||||||
semver "^5.6.0"
|
semver "^5.6.0"
|
||||||
|
|
||||||
|
markdown-table@^3.0.0:
|
||||||
|
version "3.0.4"
|
||||||
|
resolved "https://mirrors.yuchat.top/repository/npmjs/markdown-table/-/markdown-table-3.0.4.tgz#fe44d6d410ff9d6f2ea1797a3f60aa4d2b631c2a"
|
||||||
|
integrity sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==
|
||||||
|
|
||||||
math-intrinsics@^1.1.0:
|
math-intrinsics@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://mirrors.yuchat.top/repository/npmjs/math-intrinsics/-/math-intrinsics-1.1.0.tgz"
|
resolved "https://mirrors.yuchat.top/repository/npmjs/math-intrinsics/-/math-intrinsics-1.1.0.tgz"
|
||||||
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
|
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
|
||||||
|
|
||||||
|
mdast-util-find-and-replace@^3.0.0:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://mirrors.yuchat.top/repository/npmjs/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz#70a3174c894e14df722abf43bc250cbae44b11df"
|
||||||
|
integrity sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==
|
||||||
|
dependencies:
|
||||||
|
"@types/mdast" "^4.0.0"
|
||||||
|
escape-string-regexp "^5.0.0"
|
||||||
|
unist-util-is "^6.0.0"
|
||||||
|
unist-util-visit-parents "^6.0.0"
|
||||||
|
|
||||||
mdast-util-from-markdown@^2.0.0:
|
mdast-util-from-markdown@^2.0.0:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://mirrors.yuchat.top/repository/npmjs/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz#4850390ca7cf17413a9b9a0fbefcd1bc0eb4160a"
|
resolved "https://mirrors.yuchat.top/repository/npmjs/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz#4850390ca7cf17413a9b9a0fbefcd1bc0eb4160a"
|
||||||
@ -1945,6 +1965,71 @@ mdast-util-from-markdown@^2.0.0:
|
|||||||
micromark-util-types "^2.0.0"
|
micromark-util-types "^2.0.0"
|
||||||
unist-util-stringify-position "^4.0.0"
|
unist-util-stringify-position "^4.0.0"
|
||||||
|
|
||||||
|
mdast-util-gfm-autolink-literal@^2.0.0:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://mirrors.yuchat.top/repository/npmjs/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz#abd557630337bd30a6d5a4bd8252e1c2dc0875d5"
|
||||||
|
integrity sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/mdast" "^4.0.0"
|
||||||
|
ccount "^2.0.0"
|
||||||
|
devlop "^1.0.0"
|
||||||
|
mdast-util-find-and-replace "^3.0.0"
|
||||||
|
micromark-util-character "^2.0.0"
|
||||||
|
|
||||||
|
mdast-util-gfm-footnote@^2.0.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://mirrors.yuchat.top/repository/npmjs/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz#7778e9d9ca3df7238cc2bd3fa2b1bf6a65b19403"
|
||||||
|
integrity sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/mdast" "^4.0.0"
|
||||||
|
devlop "^1.1.0"
|
||||||
|
mdast-util-from-markdown "^2.0.0"
|
||||||
|
mdast-util-to-markdown "^2.0.0"
|
||||||
|
micromark-util-normalize-identifier "^2.0.0"
|
||||||
|
|
||||||
|
mdast-util-gfm-strikethrough@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://mirrors.yuchat.top/repository/npmjs/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz#d44ef9e8ed283ac8c1165ab0d0dfd058c2764c16"
|
||||||
|
integrity sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==
|
||||||
|
dependencies:
|
||||||
|
"@types/mdast" "^4.0.0"
|
||||||
|
mdast-util-from-markdown "^2.0.0"
|
||||||
|
mdast-util-to-markdown "^2.0.0"
|
||||||
|
|
||||||
|
mdast-util-gfm-table@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://mirrors.yuchat.top/repository/npmjs/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz#7a435fb6223a72b0862b33afbd712b6dae878d38"
|
||||||
|
integrity sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==
|
||||||
|
dependencies:
|
||||||
|
"@types/mdast" "^4.0.0"
|
||||||
|
devlop "^1.0.0"
|
||||||
|
markdown-table "^3.0.0"
|
||||||
|
mdast-util-from-markdown "^2.0.0"
|
||||||
|
mdast-util-to-markdown "^2.0.0"
|
||||||
|
|
||||||
|
mdast-util-gfm-task-list-item@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://mirrors.yuchat.top/repository/npmjs/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz#e68095d2f8a4303ef24094ab642e1047b991a936"
|
||||||
|
integrity sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/mdast" "^4.0.0"
|
||||||
|
devlop "^1.0.0"
|
||||||
|
mdast-util-from-markdown "^2.0.0"
|
||||||
|
mdast-util-to-markdown "^2.0.0"
|
||||||
|
|
||||||
|
mdast-util-gfm@^3.0.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://mirrors.yuchat.top/repository/npmjs/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz#2cdf63b92c2a331406b0fb0db4c077c1b0331751"
|
||||||
|
integrity sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==
|
||||||
|
dependencies:
|
||||||
|
mdast-util-from-markdown "^2.0.0"
|
||||||
|
mdast-util-gfm-autolink-literal "^2.0.0"
|
||||||
|
mdast-util-gfm-footnote "^2.0.0"
|
||||||
|
mdast-util-gfm-strikethrough "^2.0.0"
|
||||||
|
mdast-util-gfm-table "^2.0.0"
|
||||||
|
mdast-util-gfm-task-list-item "^2.0.0"
|
||||||
|
mdast-util-to-markdown "^2.0.0"
|
||||||
|
|
||||||
mdast-util-mdx-expression@^2.0.0:
|
mdast-util-mdx-expression@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://mirrors.yuchat.top/repository/npmjs/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz#43f0abac9adc756e2086f63822a38c8d3c3a5096"
|
resolved "https://mirrors.yuchat.top/repository/npmjs/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz#43f0abac9adc756e2086f63822a38c8d3c3a5096"
|
||||||
@ -2059,6 +2144,85 @@ micromark-core-commonmark@^2.0.0:
|
|||||||
micromark-util-symbol "^2.0.0"
|
micromark-util-symbol "^2.0.0"
|
||||||
micromark-util-types "^2.0.0"
|
micromark-util-types "^2.0.0"
|
||||||
|
|
||||||
|
micromark-extension-gfm-autolink-literal@^2.0.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://mirrors.yuchat.top/repository/npmjs/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz#6286aee9686c4462c1e3552a9d505feddceeb935"
|
||||||
|
integrity sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==
|
||||||
|
dependencies:
|
||||||
|
micromark-util-character "^2.0.0"
|
||||||
|
micromark-util-sanitize-uri "^2.0.0"
|
||||||
|
micromark-util-symbol "^2.0.0"
|
||||||
|
micromark-util-types "^2.0.0"
|
||||||
|
|
||||||
|
micromark-extension-gfm-footnote@^2.0.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://mirrors.yuchat.top/repository/npmjs/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz#4dab56d4e398b9853f6fe4efac4fc9361f3e0750"
|
||||||
|
integrity sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==
|
||||||
|
dependencies:
|
||||||
|
devlop "^1.0.0"
|
||||||
|
micromark-core-commonmark "^2.0.0"
|
||||||
|
micromark-factory-space "^2.0.0"
|
||||||
|
micromark-util-character "^2.0.0"
|
||||||
|
micromark-util-normalize-identifier "^2.0.0"
|
||||||
|
micromark-util-sanitize-uri "^2.0.0"
|
||||||
|
micromark-util-symbol "^2.0.0"
|
||||||
|
micromark-util-types "^2.0.0"
|
||||||
|
|
||||||
|
micromark-extension-gfm-strikethrough@^2.0.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://mirrors.yuchat.top/repository/npmjs/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz#86106df8b3a692b5f6a92280d3879be6be46d923"
|
||||||
|
integrity sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==
|
||||||
|
dependencies:
|
||||||
|
devlop "^1.0.0"
|
||||||
|
micromark-util-chunked "^2.0.0"
|
||||||
|
micromark-util-classify-character "^2.0.0"
|
||||||
|
micromark-util-resolve-all "^2.0.0"
|
||||||
|
micromark-util-symbol "^2.0.0"
|
||||||
|
micromark-util-types "^2.0.0"
|
||||||
|
|
||||||
|
micromark-extension-gfm-table@^2.0.0:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://mirrors.yuchat.top/repository/npmjs/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz#fac70bcbf51fe65f5f44033118d39be8a9b5940b"
|
||||||
|
integrity sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==
|
||||||
|
dependencies:
|
||||||
|
devlop "^1.0.0"
|
||||||
|
micromark-factory-space "^2.0.0"
|
||||||
|
micromark-util-character "^2.0.0"
|
||||||
|
micromark-util-symbol "^2.0.0"
|
||||||
|
micromark-util-types "^2.0.0"
|
||||||
|
|
||||||
|
micromark-extension-gfm-tagfilter@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://mirrors.yuchat.top/repository/npmjs/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz#f26d8a7807b5985fba13cf61465b58ca5ff7dc57"
|
||||||
|
integrity sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==
|
||||||
|
dependencies:
|
||||||
|
micromark-util-types "^2.0.0"
|
||||||
|
|
||||||
|
micromark-extension-gfm-task-list-item@^2.0.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://mirrors.yuchat.top/repository/npmjs/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz#bcc34d805639829990ec175c3eea12bb5b781f2c"
|
||||||
|
integrity sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==
|
||||||
|
dependencies:
|
||||||
|
devlop "^1.0.0"
|
||||||
|
micromark-factory-space "^2.0.0"
|
||||||
|
micromark-util-character "^2.0.0"
|
||||||
|
micromark-util-symbol "^2.0.0"
|
||||||
|
micromark-util-types "^2.0.0"
|
||||||
|
|
||||||
|
micromark-extension-gfm@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://mirrors.yuchat.top/repository/npmjs/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz#3e13376ab95dd7a5cfd0e29560dfe999657b3c5b"
|
||||||
|
integrity sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==
|
||||||
|
dependencies:
|
||||||
|
micromark-extension-gfm-autolink-literal "^2.0.0"
|
||||||
|
micromark-extension-gfm-footnote "^2.0.0"
|
||||||
|
micromark-extension-gfm-strikethrough "^2.0.0"
|
||||||
|
micromark-extension-gfm-table "^2.0.0"
|
||||||
|
micromark-extension-gfm-tagfilter "^2.0.0"
|
||||||
|
micromark-extension-gfm-task-list-item "^2.0.0"
|
||||||
|
micromark-util-combine-extensions "^2.0.0"
|
||||||
|
micromark-util-types "^2.0.0"
|
||||||
|
|
||||||
micromark-factory-destination@^2.0.0:
|
micromark-factory-destination@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://mirrors.yuchat.top/repository/npmjs/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz#8fef8e0f7081f0474fbdd92deb50c990a0264639"
|
resolved "https://mirrors.yuchat.top/repository/npmjs/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz#8fef8e0f7081f0474fbdd92deb50c990a0264639"
|
||||||
@ -2838,6 +3002,18 @@ react@^18.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
|
|
||||||
|
remark-gfm@^4.0.1:
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://mirrors.yuchat.top/repository/npmjs/remark-gfm/-/remark-gfm-4.0.1.tgz#33227b2a74397670d357bf05c098eaf8513f0d6b"
|
||||||
|
integrity sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==
|
||||||
|
dependencies:
|
||||||
|
"@types/mdast" "^4.0.0"
|
||||||
|
mdast-util-gfm "^3.0.0"
|
||||||
|
micromark-extension-gfm "^3.0.0"
|
||||||
|
remark-parse "^11.0.0"
|
||||||
|
remark-stringify "^11.0.0"
|
||||||
|
unified "^11.0.0"
|
||||||
|
|
||||||
remark-parse@^11.0.0:
|
remark-parse@^11.0.0:
|
||||||
version "11.0.0"
|
version "11.0.0"
|
||||||
resolved "https://mirrors.yuchat.top/repository/npmjs/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1"
|
resolved "https://mirrors.yuchat.top/repository/npmjs/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1"
|
||||||
@ -2859,6 +3035,15 @@ remark-rehype@^11.0.0:
|
|||||||
unified "^11.0.0"
|
unified "^11.0.0"
|
||||||
vfile "^6.0.0"
|
vfile "^6.0.0"
|
||||||
|
|
||||||
|
remark-stringify@^11.0.0:
|
||||||
|
version "11.0.0"
|
||||||
|
resolved "https://mirrors.yuchat.top/repository/npmjs/remark-stringify/-/remark-stringify-11.0.0.tgz#4c5b01dd711c269df1aaae11743eb7e2e7636fd3"
|
||||||
|
integrity sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==
|
||||||
|
dependencies:
|
||||||
|
"@types/mdast" "^4.0.0"
|
||||||
|
mdast-util-to-markdown "^2.0.0"
|
||||||
|
unified "^11.0.0"
|
||||||
|
|
||||||
resize-observer-polyfill@^1.5.1:
|
resize-observer-polyfill@^1.5.1:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://mirrors.yuchat.top/repository/npmjs/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
resolved "https://mirrors.yuchat.top/repository/npmjs/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user