优化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,
|
||||
})
|
||||
}
|
||||
|
||||
// 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())
|
||||
{
|
||||
// 用户相关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(需要登录)
|
||||
auth.GET("/practice/questions", handlers.GetPracticeQuestions) // 获取练习题目列表
|
||||
|
||||
@ -16,7 +16,8 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router-dom": "^6.21.3"
|
||||
"react-router-dom": "^6.21.3",
|
||||
"remark-gfm": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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 { CheckOutlined, CloseOutlined, TrophyOutlined, CommentOutlined, BulbOutlined, FileTextOutlined, ReloadOutlined } from '@ant-design/icons'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import { fetchWithAuth } from '../utils/request'
|
||||
import type { AnswerResult as AnswerResultType } from '../types/question'
|
||||
|
||||
@ -218,21 +219,175 @@ const AnswerResult: React.FC<AnswerResultProps> = ({
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div style={{ color: '#595959' }}>
|
||||
<div style={{ color: '#595959', lineHeight: '1.8' }}>
|
||||
{explanation ? (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
// 自定义markdown组件样式
|
||||
p: ({ children }) => <p style={{ marginBottom: '0.5em', lineHeight: '1.6' }}>{children}</p>,
|
||||
h1: ({ children }) => <h1 style={{ fontSize: '1.5em', marginTop: '0.5em', marginBottom: '0.5em' }}>{children}</h1>,
|
||||
h2: ({ children }) => <h2 style={{ fontSize: '1.3em', marginTop: '0.5em', marginBottom: '0.5em' }}>{children}</h2>,
|
||||
h3: ({ children }) => <h3 style={{ fontSize: '1.1em', marginTop: '0.5em', marginBottom: '0.5em' }}>{children}</h3>,
|
||||
ul: ({ children }) => <ul style={{ marginLeft: '1.5em', marginBottom: '0.5em' }}>{children}</ul>,
|
||||
ol: ({ children }) => <ol style={{ marginLeft: '1.5em', marginBottom: '0.5em' }}>{children}</ol>,
|
||||
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>,
|
||||
pre: ({ children }) => <pre style={{ backgroundColor: '#f5f5f5', padding: '12px', borderRadius: '4px', overflow: 'auto' }}>{children}</pre>,
|
||||
blockquote: ({ children }) => <blockquote style={{ borderLeft: '4px solid #1890ff', paddingLeft: '12px', margin: '0.5em 0', color: '#666' }}>{children}</blockquote>,
|
||||
p: ({ children }) => (
|
||||
<p style={{
|
||||
marginBottom: '1em',
|
||||
lineHeight: '1.8',
|
||||
wordWrap: 'break-word',
|
||||
whiteSpace: 'pre-wrap'
|
||||
}}>
|
||||
{children}
|
||||
</p>
|
||||
),
|
||||
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}
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
|
||||
.userInfo {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
padding: 10px 16px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 16px;
|
||||
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 0 0 1px rgba(0, 0, 0, 0.03);
|
||||
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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
gap: 3px;
|
||||
|
||||
.userNickname {
|
||||
color: #1d1d1f !important;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.userUsername {
|
||||
color: #6e6e73 !important;
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.userType {
|
||||
font-size: 11px;
|
||||
color: #6e6e73;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
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 {
|
||||
FileTextOutlined,
|
||||
CheckCircleOutlined,
|
||||
@ -12,6 +13,9 @@ import {
|
||||
LogoutOutlined,
|
||||
SettingOutlined,
|
||||
UnorderedListOutlined as ListOutlined,
|
||||
LockOutlined,
|
||||
IdcardOutlined,
|
||||
DownOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import * as questionApi from '../api/question'
|
||||
import { fetchWithAuth } from '../utils/request'
|
||||
@ -77,8 +81,13 @@ const Home: React.FC = () => {
|
||||
const navigate = useNavigate()
|
||||
const [userInfo, setUserInfo] = useState<UserInfo | null>(null)
|
||||
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 [userTypeForm] = Form.useForm()
|
||||
const [editProfileForm] = Form.useForm()
|
||||
const [changePasswordForm] = Form.useForm()
|
||||
const [statistics, setStatistics] = useState<Statistics>({
|
||||
total_questions: 0,
|
||||
answered_questions: 0,
|
||||
@ -87,6 +96,22 @@ const Home: React.FC = () => {
|
||||
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 () => {
|
||||
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) => {
|
||||
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 (
|
||||
<div className={styles.container}>
|
||||
{/* 头部 */}
|
||||
@ -210,27 +367,30 @@ const Home: React.FC = () => {
|
||||
</div>
|
||||
{/* 用户信息 */}
|
||||
{userInfo && (
|
||||
<div className={styles.userInfo}>
|
||||
<Space size="middle">
|
||||
<Avatar
|
||||
src={userInfo.avatar || undefined}
|
||||
size={40}
|
||||
icon={<UserOutlined />}
|
||||
/>
|
||||
<div className={styles.userDetails}>
|
||||
<Text strong className={styles.userNickname}>{userInfo.nickname}</Text>
|
||||
<Text type="secondary" className={styles.userUsername}>@{userInfo.username}</Text>
|
||||
</div>
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
icon={<LogoutOutlined />}
|
||||
onClick={handleLogout}
|
||||
>
|
||||
退出
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
<Dropdown menu={{ items: userMenuItems }} trigger={['click']}>
|
||||
<div className={styles.userInfo}>
|
||||
<Space size="middle">
|
||||
<Avatar
|
||||
src={userInfo.avatar || undefined}
|
||||
size={44}
|
||||
icon={<UserOutlined />}
|
||||
/>
|
||||
<div className={styles.userDetails}>
|
||||
<Text strong className={styles.userNickname}>{userInfo.nickname}</Text>
|
||||
<Text type="secondary" className={styles.userUsername}>@{userInfo.username}</Text>
|
||||
<Badge
|
||||
color={userInfo.user_type ? '#52c41a' : '#ff4d4f'}
|
||||
text={
|
||||
<Text className={styles.userType}>
|
||||
{getUserTypeText(userInfo.user_type)}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<DownOutlined style={{ fontSize: '12px', color: '#8c8c8c' }} />
|
||||
</Space>
|
||||
</div>
|
||||
</Dropdown>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -406,6 +566,203 @@ const Home: React.FC = () => {
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
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"
|
||||
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:
|
||||
version "4.6.2"
|
||||
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"
|
||||
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:
|
||||
version "1.1.0"
|
||||
resolved "https://mirrors.yuchat.top/repository/npmjs/math-intrinsics/-/math-intrinsics-1.1.0.tgz"
|
||||
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:
|
||||
version "2.0.2"
|
||||
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"
|
||||
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:
|
||||
version "2.0.1"
|
||||
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-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:
|
||||
version "2.0.1"
|
||||
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:
|
||||
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:
|
||||
version "11.0.0"
|
||||
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"
|
||||
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:
|
||||
version "1.5.1"
|
||||
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