feat(admin): Add user management and upgrade to module permission system

Features - User Management (Phase 4.1):
- Database: Add user_modules table for fine-grained module permissions
- Database: Add 4 user permissions (view/create/edit/delete) to role_permissions
- Backend: UserService (780 lines) - CRUD with tenant isolation
- Backend: UserController + UserRoutes (648 lines) - 13 API endpoints
- Backend: Batch import users from Excel
- Frontend: UserListPage (412 lines) - list/filter/search/pagination
- Frontend: UserFormPage (341 lines) - create/edit with module config
- Frontend: UserDetailPage (393 lines) - details/tenant/module management
- Frontend: 3 modal components (592 lines) - import/assign/configure
- API: GET/POST/PUT/DELETE /api/admin/users/* endpoints

Architecture Upgrade - Module Permission System:
- Backend: Add getUserModules() method in auth.service
- Backend: Login API returns modules array in user object
- Frontend: AuthContext adds hasModule() method
- Frontend: Navigation filters modules based on user.modules
- Frontend: RouteGuard checks requiredModule instead of requiredVersion
- Frontend: Remove deprecated version-based permission system
- UX: Only show accessible modules in navigation (clean UI)
- UX: Smart redirect after login (avoid 403 for regular users)

Fixes:
- Fix UTF-8 encoding corruption in ~100 docs files
- Fix pageSize type conversion in userService (String to Number)
- Fix authUser undefined error in TopNavigation
- Fix login redirect logic with role-based access check
- Update Git commit guidelines v1.2 with UTF-8 safety rules

Database Changes:
- CREATE TABLE user_modules (user_id, tenant_id, module_code, is_enabled)
- ADD UNIQUE CONSTRAINT (user_id, tenant_id, module_code)
- INSERT 4 permissions + role assignments
- UPDATE PUBLIC tenant with 8 module subscriptions

Technical:
- Backend: 5 new files (~2400 lines)
- Frontend: 10 new files (~2500 lines)
- Docs: 1 development record + 2 status updates + 1 guideline update
- Total: ~4900 lines of code

Status: User management 100% complete, module permission system operational
This commit is contained in:
2026-01-16 13:42:10 +08:00
parent 98d862dbd4
commit 66255368b7
560 changed files with 70424 additions and 52353 deletions

View File

@@ -1,7 +1,7 @@
# AIA V2.1 前端组件设计
> **版本**V2.1
> **创建日期**�026-01-11
> **创建日期**2026-01-11
> **技术栈**React 19 + TypeScript 5 + Ant Design 6 + Ant Design X 2.1
---
@@ -11,45 +11,45 @@
```
frontend-v2/src/modules/aia/
├── pages/
� ├── Dashboard.tsx # 智能体大厅(首页�
â”? └── Workspace.tsx # 对è¯<EFBFBD>工作å<EFBFBD>?
│ ├── Dashboard.tsx # 智能体大厅(首页)
│ └── Workspace.tsx # 对话工作台
├── components/
� ├── AgentPipeline/
â”? â”? ├── index.tsx # 5阶段æµ<EFBFBD>水线容å™?
â”? â”? ├── StageColumn.tsx # å<EFBFBD>•阶段åˆ
â”? â”? └── AgentCard.tsx # 智能体å<EFBFBD>¡ç‰?
� ├── IntentSearch/
â”? â”? ├── index.tsx # æ„<EFBFBD>徿<EFBFBD>œç´¢æ¡?
� � └── SuggestionDropdown.tsx # 建议下拉�
� ├── ConversationList/
â”? â”? ├── index.tsx # 历å<EFBFBD>²ä¼šè¯<EFBFBD>列表
â”? â”? └── ConversationItem.tsx # 会è¯<EFBFBD>é¡?
� ├── MessageList/
â”? â”? ├── index.tsx # 消æ<EFBFBD>¯åˆ—表
â”? â”? ├── UserMessage.tsx # 用户消æ<EFBFBD>¯
â”? â”? ├── AssistantMessage.tsx # AIåžå¤<EFBFBD>
â”? â”? └── ThinkingBlock.tsx # 深度æ€<EFBFBD>考折å<EFBFBD> å<EFBFBD>
� ├── Attachment/
� � ├── AttachmentUpload.tsx # 附件上传
â”? â”? ├── AttachmentCard.tsx # 附件å<EFBFBD>¡ç‰‡
� � └── AttachmentPreview.tsx # 附件预览
� ├── SlashCommands/
â”? â”? └── index.tsx # å¿«æ<EFBFBD>·æŒ‡ä»¤è<EFBFBD>œå<EFBFBD>
� └── ActionBar/
â”? └── index.tsx # 结果æ“<EFBFBD>作æ ?
│ ├── AgentPipeline/
│ │ ├── index.tsx # 5阶段流水线容器
│ │ ├── StageColumn.tsx # 单阶段列
│ │ └── AgentCard.tsx # 智能体卡片
│ ├── IntentSearch/
│ │ ├── index.tsx # 意图搜索框
│ │ └── SuggestionDropdown.tsx # 建议下拉框
│ ├── ConversationList/
│ │ ├── index.tsx # 历史会话列表
│ │ └── ConversationItem.tsx # 会话项
│ ├── MessageList/
│ │ ├── index.tsx # 消息列表
│ │ ├── UserMessage.tsx # 用户消息
│ │ ├── AssistantMessage.tsx # AI回复
│ │ └── ThinkingBlock.tsx # 深度思考折叠块
│ ├── Attachment/
│ │ ├── AttachmentUpload.tsx # 附件上传
│ │ ├── AttachmentCard.tsx # 附件卡片
│ │ └── AttachmentPreview.tsx # 附件预览
│ ├── SlashCommands/
│ │ └── index.tsx # 快捷指令菜单
│ └── ActionBar/
└── index.tsx # 结果操作栏
├── hooks/
â”? ├── useConversation.ts # 对è¯<EFBFBD>管ç<EFBFBD>
â”? ├── useAgents.ts # 智能体数æ<EFBFBD>?
â”? ├── useIntentRouter.ts # æ„<EFBFBD>å¾è·¯ç”±
â”? ├── useStreamMessage.ts # æµ<EFBFBD>å¼<EFBFBD>消æ<EFBFBD>¯
� └── useAttachment.ts # 附件上传
│ ├── useConversation.ts # 对话管理
│ ├── useAgents.ts # 智能体数据
│ ├── useIntentRouter.ts # 意图路由
│ ├── useStreamMessage.ts # 流式消息
│ └── useAttachment.ts # 附件上传
├── api/
â”? └── index.ts # API å°<EFBFBD>装
│ └── index.ts # API 封装
├── types/
� └── index.ts # TypeScript 类型
│ └── index.ts # TypeScript 类型
├── styles/
â”? ├── dashboard.module.css # Dashboard æ ·å¼<EFBFBD>
â”? └── workspace.module.css # Workspace æ ·å¼<EFBFBD>
│ ├── dashboard.module.css # Dashboard 样式
│ └── workspace.module.css # Workspace 样式
└── index.tsx # 模块入口 + 路由
```
@@ -68,7 +68,7 @@ frontend-v2/src/modules/aia/
--stage-write: #F59E0B; /* 橙色 - 论文撰写 */
--stage-publish: #EF4444; /* 红色 - 成果发布 */
/* 功能�*/
/* 功能色 */
--ai-assistant: #6366F1; /* AI助手主色 */
--thinking-bg: #F3F4F6; /* 思考块背景 */
--thinking-border: #E5E7EB; /* 思考块边框 */
@@ -108,7 +108,7 @@ frontend-v2/src/modules/aia/
## 📄 页面设计
### 1. Dashboard(智能体大厅�
### 1. Dashboard(智能体大厅)
```tsx
// pages/Dashboard.tsx
@@ -119,7 +119,7 @@ import { AgentPipeline } from '../components/AgentPipeline';
export const Dashboard: React.FC = () => {
return (
<div className={styles.dashboard}>
{/* 顶部æ„<EFBFBD>徿<EFBFBD>œç´¢æ¡?*/}
{/* 顶部意图搜索框 */}
<header className={styles.header}>
<h1>AI </h1>
<p></p>
@@ -135,14 +135,14 @@ export const Dashboard: React.FC = () => {
};
```
**布局特点**�
**布局特点**
- 顶部居中大搜索框
- 5阶段æµ<EFBFBD>水线横å<EFBFBD>平铺(桌é<EFBFBD>¢ï¼? 纵å<C2B5>滚动(移动)
- Gemini 风格大留�
- 5阶段流水线横向平铺(桌面)/ 纵向滚动(移动)
- Gemini 风格大留白
---
### 2. Workspace(对è¯<EFBFBD>工作å<EFBFBD>°ï¼?
### 2. Workspace(对话工作台)
```tsx
// pages/Workspace.tsx
@@ -201,7 +201,7 @@ export const Workspace: React.FC = () => {
## 🧩 组件详细设计
### 1. AgentPipelineï¼?阶段æµ<C3A6>水线)
### 1. AgentPipeline5阶段流水线
```tsx
// components/AgentPipeline/index.tsx
@@ -238,7 +238,7 @@ export const AgentPipeline: React.FC<AgentPipelineProps> = ({ onAgentClick }) =>
};
```
**æ ·å¼<EFBFBD>特ç¹**ï¼?
**样式特点**
```css
.pipeline {
display: flex;
@@ -258,7 +258,7 @@ export const AgentPipeline: React.FC<AgentPipelineProps> = ({ onAgentClick }) =>
---
### 2. IntentSearch(æ„<EFBFBD>徿<EFBFBD>œç´¢æ¡†ï¼?
### 2. IntentSearch(意图搜索框)
```tsx
// components/IntentSearch/index.tsx
@@ -307,14 +307,14 @@ export const IntentSearch: React.FC = () => {
---
### 3. ThinkingBlock(深度æ€<EFBFBD>考折å<EFBFBD> å<EFBFBD>—ï¼?
### 3. ThinkingBlock(深度思考折叠块)
```tsx
// components/MessageList/ThinkingBlock.tsx
interface ThinkingBlockProps {
content: string;
duration?: number; // æ€<EFBFBD>考耗时(ç§ï¼?
duration?: number; // 思考耗时(秒)
isStreaming?: boolean; // 是否正在生成
}
@@ -328,7 +328,7 @@ export const ThinkingBlock: React.FC<ThinkingBlockProps> = ({
useEffect(() => {
if (!isStreaming && expanded) {
// 完æˆ<EFBFBD>å<EFBFBD>?1.5s 自动收起
// 完成后 1.5s 自动收起
const timer = setTimeout(() => setExpanded(false), 1500);
return () => clearTimeout(timer);
}
@@ -344,7 +344,7 @@ export const ThinkingBlock: React.FC<ThinkingBlockProps> = ({
{isStreaming ? <LoadingOutlined spin /> : <BulbOutlined />}
</span>
<span className={styles.title}>
{isStreaming ? '正在深度æ€<EFBFBD>è€?..' : `已深度æ€<EFBFBD>è€?(耗时 ${duration?.toFixed(1)}s)`}
{isStreaming ? '正在深度思考...' : `已深度思考 (耗时 ${duration?.toFixed(1)}s)`}
</span>
<span className={styles.expandIcon}>
{expanded ? <UpOutlined /> : <DownOutlined />}
@@ -363,7 +363,7 @@ export const ThinkingBlock: React.FC<ThinkingBlockProps> = ({
};
```
**æ ·å¼<EFBFBD>**ï¼?
**样式**
```css
.thinkingBlock {
background: var(--thinking-bg);
@@ -412,18 +412,18 @@ export const AttachmentUpload: React.FC<AttachmentUploadProps> = ({
const handleUpload = async (file: File) => {
if (attachments.length >= maxCount) {
message.error(`最多上�${maxCount} 个附件`);
message.error(`最多上传 ${maxCount} 个附件`);
return false;
}
// 文件类型校验
const allowedTypes = ['application/pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'text/plain', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
if (!allowedTypes.includes(file.type)) {
message.error('仅支æŒ?PDFã€<C3A3>Wordã€<C3A3>TXTã€<C3A3>Excel 文件');
message.error('仅支持 PDF、Word、TXT、Excel 文件');
return false;
}
// æ‡ä»¶å¤§å°<EFBFBD>校验ï¼?0MBï¼?
// 文件大小校验20MB
if (file.size > 20 * 1024 * 1024) {
message.error('文件大小不能超过 20MB');
return false;
@@ -448,7 +448,7 @@ export const AttachmentUpload: React.FC<AttachmentUploadProps> = ({
</Button>
</Upload>
{/* 已上传附件列�*/}
{/* 已上传附件列表 */}
<div className={styles.attachmentList}>
{attachments.map(att => (
<AttachmentCard
@@ -483,10 +483,10 @@ interface SlashCommandsProps {
}
const commands: SlashCommand[] = [
{ key: 'polish', icon: 'âœ?, label: 'æ¦è²', description: 'ä¼˜åŒææœ¬è¡¨è¾¾' },
{ key: 'polish', icon: '✨', label: '润色', description: '优化文本表达' },
{ key: 'expand', icon: '📝', label: '扩写', description: '扩展内容细节' },
{ key: 'translate', icon: '🌐', label: '翻译', description: '中英互译' },
{ key: 'export', icon: 'ðŸ', label: '导åºWord', description: '导åºä¸?Word ææ¡£' },
{ key: 'export', icon: '📄', label: '导出Word', description: '导出为 Word 文档' },
];
export const SlashCommands: React.FC<SlashCommandsProps> = ({
@@ -544,7 +544,7 @@ export const SlashCommands: React.FC<SlashCommandsProps> = ({
---
### 6. ActionBar(结果æ“<EFBFBD>作æ <EFBFBD>ï¼?
### 6. ActionBar(结果操作栏)
```tsx
// components/ActionBar/index.tsx
@@ -623,7 +623,7 @@ export function useConversation(conversationId?: string): UseConversationReturn
}
}, [conversationId]);
// å<EFBFBD>é€<EFBFBD>消æ<EFBFBD>¯ï¼ˆæµ<EFBFBD>å¼<EFBFBD>ï¼?
// 发送消息(流式)
const sendMessage = async (content: string, attachmentIds?: string[]) => {
setIsLoading(true);
@@ -637,7 +637,7 @@ export function useConversation(conversationId?: string): UseConversationReturn
};
setMessages(prev => [...prev, userMessage]);
// åˆ<EFBFBD>å§åŒ?AI 消æ<CB86>¯
// 初始化 AI 消息
const aiMessage: Message = {
id: `temp-ai-${Date.now()}`,
role: 'assistant',
@@ -769,7 +769,7 @@ export function useStreamMessage() {
---
## 📱 å“<C3A5>应å¼<C3A5>设è®?
## 📱 响应式设计
### 断点策略
@@ -783,19 +783,19 @@ const breakpoints = {
};
```
### Dashboard å“<EFBFBD>应å¼?
### Dashboard 响应式
| 断点 | 布局 |
|------|------|
| `< 768px` | 流水线纵向滚动,卡片单列 |
| `â‰?768px` | æµ<EFBFBD>水线横å<EFBFBD>?5 åˆ?|
| `768px` | 流水线横向 5 列 |
### Workspace å“<EFBFBD>应å¼?
### Workspace 响应式
| 断点 | 布局 |
|------|------|
| `< 768px` | 侧边栏隐藏(抽屉滑出),输入框键盘适配 |
| `â‰?768px` | ä¾§è¾¹æ <EFBFBD>åºå®šæ˜¾ç¤?240px |
| `768px` | 侧边栏固定显示 240px |
---
@@ -883,4 +883,3 @@ export interface SlashCommand {