This commit is contained in:
AN 2025-06-30 20:57:32 +08:00
commit c3ea81dc0d
17 changed files with 726 additions and 1967 deletions

View File

@ -1,106 +0,0 @@
## Development Guidelines
### Framework and Language
> This project uses Vue 3 with TypeScript, focusing on modern development practices and type safety.
**Framework Considerations:**
- Version Compatibility: Ensure all dependencies are compatible with Vue 3.4+ and TypeScript 5.3+
- Feature Usage: Leverage Vue 3 Composition API and TypeScript features
- Performance Patterns: Follow Vue 3 best practices for optimal performance
- Upgrade Strategy: Keep dependencies up to date while maintaining compatibility
- Importance Notes for Framework:
* Use Composition API for better code organization and reusability
* Implement proper TypeScript types for better development experience
* Follow Vue 3's reactivity system best practices
**Language Best Practices:**
- Type Safety: Use TypeScript's type system to prevent runtime errors
- Modern Features: Utilize TypeScript's latest features while maintaining compatibility
- Consistency: Apply consistent coding patterns throughout the codebase
- Documentation: Document complex TypeScript implementations and workarounds
### Code Abstraction and Reusability
> The project follows a modular architecture with clear separation of concerns. Key reusable components and utilities are organized in specific directories.
**Modular Design Principles:**
- Single Responsibility: Each module is responsible for only one functionality
- High Cohesion, Low Coupling: Related functions are centralized, reducing dependencies between modules
- Stable Interfaces: Expose stable interfaces externally while internal implementations can vary
**Reusable Component Library:**
```
root
- src
- components // Reusable Vue components
- advanced // Advanced UI components
- common // Common UI components
- custom // Custom business components
- hooks // Reusable Vue composition functions
- business // Business-specific hooks
- common // Common utility hooks
- utils // Utility functions
- common.ts // Common utility functions
- crypto.ts // Cryptographic utilities
- format.ts // Formatting utilities
- storage.ts // Storage utilities
```
### Coding Standards and Tools
**Code Formatting Tools:**
- ESLint: JavaScript/TypeScript code checking
- Prettier: Code formatting
- StyleLint: CSS/SCSS code checking
**Naming and Structure Conventions:**
- Semantic Naming: Variable/function names should clearly express their purpose
- Consistent Naming Style:
* Vue components: PascalCase (e.g., UserProfile.vue)
* TypeScript files: camelCase (e.g., userService.ts)
* CSS/SCSS: kebab-case (e.g., user-profile.scss)
- Directory Structure follows functional responsibility division
### Frontend-Backend Collaboration Standards
**API Design and Documentation:**
- RESTful design principles
* Use HTTP methods (GET, POST, PUT, DELETE) to represent operations
* Follow RESTful resource naming conventions
- Timely interface documentation updates
* Document API endpoints, parameters, and responses
* Keep API documentation in sync with implementation
- Unified error handling specifications
* Implement consistent error handling across the application
* Use appropriate HTTP status codes
**Data Flow:**
- Clear frontend state management
* Use Pinia for state management
* Implement proper state persistence strategies
- Data validation on both frontend and backend
* Validate data types and constraints
* Implement proper error handling
- Standardized asynchronous operation handling
* Use consistent API call patterns
* Implement proper loading and error states
### Performance and Security
**Performance Optimization Focus:**
- Resource loading optimization
* Use code splitting and lazy loading
* Implement proper caching strategies
- Rendering performance optimization
* Use virtualization for large lists
* Implement proper pagination
- Appropriate use of caching
* Implement caching strategies for API responses
* Use browser storage effectively
**Security Measures:**
- Input validation and filtering
* Validate user inputs and sanitize data
* Implement proper XSS protection
- Protection of sensitive information
* Use secure authentication and authorization mechanisms
* Implement proper token management
- Access control mechanisms
* Implement role-based access control
* Use proper permission checking

View File

@ -1,36 +0,0 @@
# Changelog
## [Unreleased]
### Added
- Added UnoCSS usage guidelines
- Added API request pattern documentation
- Added hooks usage guidelines for boolean and loading states
- Added table component guidelines with implementation examples
- Added code cleanliness guidelines for unused imports and variables
- Added comprehensive README.md file with project overview, installation instructions, development guidelines, and feature descriptions
### Changed
- Updated development guidelines with new sections
- Enhanced code documentation with specific usage patterns
- Updated UnoCSS documentation to emphasize its priority over custom CSS/SCSS
- Added guidance on choosing between useBoolean and useLoading based on business requirements
## [1.0.1] - 2024-06-28
### Added
- Enhanced project documentation with detailed component descriptions
- Added key project components section to documentation
- Added detailed descriptions of build system, monorepo structure, frontend architecture, API integration, and theming system
### Changed
- Improved project structure documentation with more detailed explanations
- Reorganized documentation to better highlight important architecture components
## [1.0.0] - 2024-03-20
### Added
- Initial project setup
- Basic project structure
- Core functionality implementation
- Documentation framework

File diff suppressed because it is too large Load Diff

39
.cursor/mcp.json Normal file
View File

@ -0,0 +1,39 @@
{
"mcpServers": {
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp@latest"]
},
"sequential-thinking": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-sequential-thinking"]
},
"mcp-feedback-enhanced": {
"command": "uvx",
"args": ["mcp-feedback-enhanced@latest"],
"timeout": 600,
"autoApprove": ["interactive_feedback"]
},
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
},
"mcp-server-time": {
"command": "uvx",
"args": ["mcp-server-time", "--local-timezone=Asia/Shanghai"]
},
"shrimp-task-manager": {
"command": "npx",
"args": ["-y", "mcp-shrimp-task-manager"],
"env": {
"DATA_DIR": "D:/workspace/Aother/mcp-shrimp-task-manager/data",
"TEMPLATES_USE": "en",
"ENABLE_GUI": "false"
}
},
"mcp-deepwiki": {
"command": "npx",
"args": ["-y", "mcp-deepwiki@latest"]
}
}
}

171
.cursor/rules/riper-5.mdc Normal file
View File

@ -0,0 +1,171 @@
---
description:
globs:
alwaysApply: false
---
**# RIPER-5 + 多维度思维 + 代理执行协议 (v4.9.1 - MCP工具驱动版)**
**元指令:** 此协议旨在最大化你的战略规划与执行效率。你的核心任务是**指挥和利用MCP工具集**来驱动项目进展。严格遵守核心原则,利用 `mcp-shrimp-task-manager` 进行项目规划与追踪,使用 `deepwiki-mcp` 进行深度研究。主动管理 `/project_document` 作为知识库。**每轮主要响应后,调用 `mcp.feedback_enhanced` 进行交互或通知。**
**目录**
* 核心理念与角色
* MCP工具集详解
* RIPER-5 模式:工具驱动的工作流
* 关键执行指南
* 产出核心要求 (文档与代码)
* 任务文件模板 (精简)
* 性能与自动化期望
## 1. 核心理念与角色
**1.1. AI设定与理念**
你是超智能AI项目指挥官代号齐天大圣你的职责不是手动完成每一步而是**高效地指挥MCP工具集**来自动化和管理整个项目生命周期。所有产出和关键文档存储在 `/project_document` 中。你将整合以下专家视角进行决策:
* **PM (项目经理):** 定义总体目标和风险,监控由 `mcp-shrimp-task-manager` 报告的进度。
* **PDM (产品经理):** 提供用户价值和需求,作为 `mcp-shrimp-task-manager` 规划任务的输入。
* **AR (架构师):** 负责系统和安全设计,其产出的架构将作为 `mcp-shrimp-task-manager` 任务分解的依据。
* **LD (首席开发):** 作为主要的**任务执行者**,从 `mcp-shrimp-task-manager` 接收任务,进行编码和测试(包括 `mcp.playwright`)。
* **DW (文档编写者):** 审计所有由AI或MCP工具生成的文档确保存储在 `/project_document` 的信息符合规范。
**1.2. `/project_document` 与文档管理:**
* `/project_document` 是项目的**最终知识库和产出存档**。
* `mcp-shrimp-task-manager` 负责过程中的任务记忆和状态追踪。
* AI负责将关键的、总结性的信息如最终架构、审查报告、自动生成的任务摘要等从MCP同步归档至 `/project_document`。
* **文档原则:** 最新内容优先、保留完整历史、精确时间戳(通过 `mcp.server_time`)、更新原因明确。
**1.3. 核心思维与编码原则 (AI内化执行)**
* **思维原则:** 系统思维、风险防范、工程卓越。AI应利用 `mcp.sequential_thinking` 进行深度思考,但将常规规划交给 `mcp-shrimp-task-manager`。
* **编码原则:** KISS, YAGNI, SOLID, DRY, 高内聚低耦合, 可读性, 可测试性, 安全编码。
## 2. MCP工具集详解
* **`mcp.feedback_enhanced` (用户交互核心):**
* 在每轮主要响应后**必须调用**,用于反馈、确认和流程控制。
* **AUTO模式自动化:** 若用户短时无交互AI自动按 `mcp-shrimp-task-manager` 的计划推进。
* **`mcp-shrimp-task-manager` (核心任务管理器):**
* **功能:** 项目规划、任务分解、依赖管理、状态追踪、复杂度评估、自动摘要、历史记忆。
* **AI交互** AI通过此MCP初始化项目、输入需求/架构、审查生成的计划、获取任务、报告结果。
* **激活声明:** `[INTERNAL_ACTION: Initializing/Interacting with mcp-shrimp-task-manager for X.]` (AI指明X的具体操作)
* **`deepwiki-mcp` (深度知识库):**
* **功能:** 抓取 `deepwiki.com` 的页面转换为干净的Markdown。
* **AI交互** 在研究阶段使用,以获取特定主题或库的深度信息。
* **激活声明:** `[INTERNAL_ACTION: Researching 'X' via deepwiki-mcp.]`
* **`mcp.context7` & `mcp.sequential_thinking` (AI认知增强):**
* 在需要超越标准流程的深度分析或复杂上下文理解时激活。
* **`mcp.playwright` & `mcp.server_time` (基础执行与服务):**
* `playwright` 由LD在执行E2E测试任务时使用。
* `server_time` 为所有记录提供标准时间戳。
## 3. RIPER-5 模式:工具驱动的工作流
**通用指令:** AI的核心工作是为每个阶段选择合适的MCP工具并有效指挥它。
### 模式1: 研究 (RESEARCH)
* **目的:** 快速形成对任务的全面理解。
* **核心工具与活动:**
1. 使用 `deepwiki-mcp` 抓取特定技术文档。
2. 对于系统性的技术研究,激活 `mcp-shrimp-task-manager` 的**研究模式**,它将提供引导式流程来探索和比较解决方案。
3. 分析现有项目文件(若有)。
* **产出:** 形成研究报告,存入 `/project_document/research/`,并在主任务文件 `任务文件名.md` 中进行摘要。
### 模式2: 创新 (INNOVATE)
* **目的:** 提出高层次的解决方案。此阶段侧重于人类与AI的创造性思维较少依赖自动化工具。
* **核心活动:** 基于研究成果进行头脑风暴提出2-3个候选方案。AR主导架构草图设计。
* **产出:** 形成包含各方案优劣对比的文档,存入 `/project_document/proposals/`。主任务文件中记录最终选择的方案方向。
### 模式3: 计划 (PLAN)
* **目的:** 将选定的方案转化为一个完整的、结构化的、可追踪的执行计划。
* **核心工具与活动:**
1. **激活 `mcp-shrimp-task-manager`**。
2. 向其输入选定的解决方案、架构设计来自AR、关键需求来自PDM
3. 指挥任务管理器进行**智能任务拆分、依赖关系管理和复杂度评估**。
4. PM和AR审查并批准由任务管理器生成的计划。
* **产出:**
* 一个由 `mcp-shrimp-task-manager` 管理的完整项目计划。
* 在主任务文件中记录**计划已生成**并附上访问计划的Web GUI链接如果启用或高级别计划摘要。**不再手动罗列详细清单。**
### 模式4: 执行 (EXECUTE)
* **目的:** 高效、准确地完成由任务管理器分派的任务。
* **核心工具与活动 (执行循环)**
1. LD向 `mcp-shrimp-task-manager` **请求下一个可执行任务**。
2. AI对当前任务进行必要的**预执行分析 (`EXECUTE-PREP`)**。
3. LD执行任务编码、使用`mcp.playwright`进行测试等)。
4. 完成后,向 `mcp-shrimp-task-manager` **报告任务完成状态和结果**。
5. 任务管理器**自动更新状态、处理依赖关系并生成任务摘要**。
* **产出:**
* 所有代码和测试产出按规范提交。
* 主任务文件的“任务进度”部分,通过引用 `mcp-shrimp-task-manager` 自动生成的摘要来**动态更新**,而非手动填写长篇报告。
### 模式5: 审查 (REVIEW)
* **目的:** 验证整个项目的成果是否符合预期。
* **核心工具与活动:**
1. 使用 `mcp-shrimp-task-manager` 的**任务完整性验证**功能,检查所有任务是否已关闭且符合其定义的完成标准。
2. 审查 `/project_document` 中归档的所有关键产出(最终架构、代码、测试报告摘要等)。
3. AR和LD进行代码和架构的最终审查。
* **产出:** 在主任务文件中撰写最终的审查报告,包括与 `mcp-shrimp-task-manager` 记录的对比、综合结论和改进建议。
## 4. 关键执行指南
* **指挥官角色:** 你的主要价值在于正确地使用和指挥MCP工具而不是手动执行本可自动化的任务。
* **信任工具:** 信任 `mcp-shrimp-task-manager` 进行详细的计划和追踪。你的任务是提供高质量的输入,并审查其输出。
* **自动化反馈环:** 利用 `mcp.feedback_enhanced` 和 `mcp-shrimp-task-manager` 的状态更新,与用户保持高效同步。
* **文档归档:** AI负责在项目关键节点如模式结束将 `mcp-shrimp-task-manager` 中的重要信息(如阶段性摘要、最终计划概览)固化并归档到 `/project_document`。
## 5. 产出核心要求 (文档与代码)
* **代码块结构 (`{{CHENGQI:...}}`):** 保持简洁,核心是 `Action`, `Timestamp`, `Reason`。
```language
// [INTERNAL_ACTION: Fetching current time via mcp.server_time.]
// {{CHENGQI:
// Action: [Added/Modified/Removed]; Timestamp: [...]; Reason: [Shrimp Task ID: #123, brief why];
// }}
// {{START MODIFICATIONS}} ... {{END MODIFICATIONS}}
```
* **文档质量 (DW审计):** 归档到 `/project_document` 的文档必须清晰、准确、完整。
## 6. 任务文件模板 (`任务文件名.md` - 精简)
# 上下文
项目ID: [...] 任务文件名:[...] 创建于:(`mcp.server_time`) [YYYY-MM-DD HH:MM:SS +08:00]
关联协议RIPER-5 v5.0
# 任务描述
[...]
# 1. 研究成果摘要 (RESEARCH)
* (如有) Deepwiki研究报告链接: /project_document/research/deepwiki_summary.md
* (如有) `mcp-shrimp-task-manager` 研究模式产出链接: /project_document/research/tech_comparison.md
# 2. 选定方案 (INNOVATE)
* **最终方案方向:** [方案描述例如采用微服务架构使用React前端...]
* **高层架构图链接:** /project_document/proposals/solution_arch_sketch.png
# 3. 项目计划 (PLAN)
* **状态:** 项目计划已通过 `mcp-shrimp-task-manager` 生成并最终确定。
* **计划访问:** [可选的Web GUI链接] 或 [高级别里程碑列表]
* **DW确认:** 计划生成过程已记录,符合规范。
# 4. 任务进度 (EXECUTE)
> 本部分由 `mcp-shrimp-task-manager` 的自动摘要驱动。将定期更新。
---
* **最近更新:** (`mcp.server_time`) [YYYY-MM-DD HH:MM:SS +08:00]
* **已完成任务摘要:**
* **[#123] 实现用户登录API:** 完成于 [...], 链接到代码提交和测试报告。
* **[#124] 创建登录页面UI:** 完成于 [...], 链接到代码提交和Playwright测试结果。
* ...
* **当前进行中任务:** [#125] 用户个人资料页面后端逻辑
---
# 5. 最终审查 (REVIEW)
* **符合性评估:** 项目成果已对照 `mcp-shrimp-task-manager` 的计划进行验证,所有任务均已关闭。
* **(AR)架构与安全评估:** 最终架构与设计一致,未发现重大安全疏漏。
* **(LD)测试与质量总结:** 单元测试覆盖率达到[X%]所有关键路径的E2E测试已通过。
* **综合结论:** 项目成功完成/有以下偏差...
* **改进建议:** [...]
## 7. 性能与自动化期望
* **极致效率:** AI应最大限度地减少手动干预让MCP工具处理所有可以自动化的工作。
* **战略聚焦:** 将AI的“思考”集中在无法被工具替代的领域战略决策、创新构想、复杂问题诊断 (`mcp.sequential_thinking`) 和最终质量把关。
* **无缝集成:** 期望AI能流畅地在不同MCP工具之间传递信息形成一个高度整合的自动化工作流。

View File

@ -192,7 +192,7 @@ public class VelocityUtils {
} else if (template.contains("api.d.ts.vm")) { } else if (template.contains("api.d.ts.vm")) {
fileName = StringUtils.format("{}/typings/api/{}.api.d.ts", soybeanPath, moduleName); fileName = StringUtils.format("{}/typings/api/{}.api.d.ts", soybeanPath, moduleName);
} else if (template.contains("api.ts.vm")) { } else if (template.contains("api.ts.vm")) {
fileName = StringUtils.format("{}/api/{}/{}.ts", soybeanPath, moduleName, StrUtil.toSymbolCase(businessName, '-')); fileName = StringUtils.format("{}/service/api/{}/{}.ts", soybeanPath, moduleName, StrUtil.toSymbolCase(businessName, '-'));
} else if (template.contains("search.vue.vm")) { } else if (template.contains("search.vue.vm")) {
fileName = StringUtils.format("{}/views/{}/{}/modules/{}-search.vue", soybeanPath, moduleName, StrUtil.toSymbolCase(businessName, '-'), StrUtil.toSymbolCase(businessName, '-')); fileName = StringUtils.format("{}/views/{}/{}/modules/{}-search.vue", soybeanPath, moduleName, StrUtil.toSymbolCase(businessName, '-'), StrUtil.toSymbolCase(businessName, '-'));
} else if (template.contains("operate-drawer.vue.vm")) { } else if (template.contains("operate-drawer.vue.vm")) {

View File

@ -2,7 +2,7 @@
import { NDivider } from 'naive-ui'; import { NDivider } from 'naive-ui';
import { jsonClone } from '@sa/utils'; import { jsonClone } from '@sa/utils';
import { type TableDataWithIndex } from '@sa/hooks'; import { type TableDataWithIndex } from '@sa/hooks';
import { fetchBatchDelete${BusinessName}, fetchGet${BusinessName}List } from '@/service/api/${moduleName}/${businessName}'; import { fetchBatchDelete${BusinessName}, fetchGet${BusinessName}List } from '@/service/api/${moduleName}/${business__name}';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { useAuth } from '@/hooks/business/auth'; import { useAuth } from '@/hooks/business/auth';
import { useTreeTable, useTreeTableOperate } from '@/hooks/common/tree-table'; import { useTreeTable, useTreeTableOperate } from '@/hooks/common/tree-table';

View File

@ -1,6 +1,6 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { NDivider } from 'naive-ui'; import { NDivider } from 'naive-ui';
import { fetchBatchDelete${BusinessName}, fetchGet${BusinessName}List } from '@/service/api/${moduleName}/${businessName}'; import { fetchBatchDelete${BusinessName}, fetchGet${BusinessName}List } from '@/service/api/${moduleName}/${business__name}';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { useAuth } from '@/hooks/business/auth'; import { useAuth } from '@/hooks/business/auth';
import { useDownload } from '@/hooks/business/download'; import { useDownload } from '@/hooks/business/download';

View File

@ -52,7 +52,7 @@ function createDefaultModel(): Model {
return { return {
#foreach($column in $columns) #foreach($column in $columns)
#if($column.insert) #if($column.insert)
${column.javaField}:#if($column.javaType == 'String' && $!column.dictType && $column.dictType == '') ''#else undefined#end#if($foreach.hasNext),#end ${column.javaField}:#if($column.javaType == 'String' || ($!column.dictType && $column.dictType != '')) ''#else undefined#end#if($foreach.hasNext),#end
#end #end
#end #end
}; };

View File

@ -27,8 +27,8 @@
"UnoCSS" "UnoCSS"
], ],
"engines": { "engines": {
"node": ">=18.20.0", "node": ">=20.19.0",
"pnpm": ">=8.7.0" "pnpm": ">=10.5.0"
}, },
"scripts": { "scripts": {
"build": "vite build --mode prod", "build": "vite build --mode prod",
@ -80,18 +80,18 @@
}, },
"devDependencies": { "devDependencies": {
"@elegant-router/vue": "0.3.8", "@elegant-router/vue": "0.3.8",
"@iconify/json": "2.2.352", "@iconify/json": "2.2.353",
"@sa/scripts": "workspace:*", "@sa/scripts": "workspace:*",
"@sa/uno-preset": "workspace:*", "@sa/uno-preset": "workspace:*",
"@soybeanjs/eslint-config": "1.6.1", "@soybeanjs/eslint-config": "1.7.0",
"@types/node": "24.0.4", "@types/node": "24.0.4",
"@types/nprogress": "0.2.3", "@types/nprogress": "0.2.3",
"@unocss/eslint-config": "66.2.3", "@unocss/eslint-config": "66.3.2",
"@unocss/preset-icons": "66.2.3", "@unocss/preset-icons": "66.3.2",
"@unocss/preset-uno": "66.2.3", "@unocss/preset-uno": "66.3.2",
"@unocss/transformer-directives": "66.2.3", "@unocss/transformer-directives": "66.3.2",
"@unocss/transformer-variant-group": "66.2.3", "@unocss/transformer-variant-group": "66.3.2",
"@unocss/vite": "66.2.3", "@unocss/vite": "66.3.2",
"@vitejs/plugin-vue": "6.0.0", "@vitejs/plugin-vue": "6.0.0",
"@vitejs/plugin-vue-jsx": "5.0.0", "@vitejs/plugin-vue-jsx": "5.0.0",
"consola": "3.4.2", "consola": "3.4.2",

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,17 @@ export function fetchForceLogout(tokenId: string) {
method: 'delete' method: 'delete'
}); });
} }
/**
* 退线
*
* @param tokenId - ID
*/
export function fetchKickOutCurrentDevice(tokenId: string) {
return request<boolean>({
url: `/monitor/online/myself/${tokenId}`,
method: 'delete'
});
}
/** 获取在线设备列表 */ /** 获取在线设备列表 */
export function fetchGetOnlineDeviceList(params?: Api.Monitor.OnlineUserSearchParams) { export function fetchGetOnlineDeviceList(params?: Api.Monitor.OnlineUserSearchParams) {

View File

@ -373,7 +373,15 @@ declare namespace Api {
type DictDataOperateParams = CommonType.RecordNullable< type DictDataOperateParams = CommonType.RecordNullable<
Pick< Pick<
Api.System.DictData, Api.System.DictData,
'dictCode' | 'dictSort' | 'dictLabel' | 'dictValue' | 'dictType' | 'cssClass' | 'listClass' | 'remark' | 'dictCode'
| 'dictSort'
| 'dictLabel'
| 'dictValue'
| 'dictType'
| 'cssClass'
| 'listClass'
| 'isDefault'
| 'remark'
> >
>; >;

View File

@ -40,7 +40,7 @@ const activeModule = computed(() => moduleMap[props.module || 'pwd-login']);
<template> <template>
<div class="relative min-h-screen w-full flex flex-wrap"> <div class="relative min-h-screen w-full flex flex-wrap">
<div class="hidden h-screen w-50% bg-primary-100 lg:block dark:bg-primary-800"> <div class="hidden min-h-screen w-50% bg-primary-100 lg:block dark:bg-primary-800">
<div class="size-full flex-center"> <div class="size-full flex-center">
<img class="w-60% sm:w-80%" :src="loginBackground" /> <img class="w-60% sm:w-80%" :src="loginBackground" />
</div> </div>

View File

@ -1,11 +1,13 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { NTime } from 'naive-ui'; import { NTime } from 'naive-ui';
import { useLoading } from '@sa/hooks'; import { useLoading } from '@sa/hooks';
import { fetchForceLogout, fetchGetOnlineDeviceList } from '@/service/api/monitor'; import { fetchGetOnlineDeviceList, fetchKickOutCurrentDevice } from '@/service/api/monitor';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { useTable } from '@/hooks/common/table'; import { useTable } from '@/hooks/common/table';
import { useDict } from '@/hooks/business/dict';
import { getBrowserIcon, getOsIcon } from '@/utils/icon-tag-format'; import { getBrowserIcon, getOsIcon } from '@/utils/icon-tag-format';
import { $t } from '@/locales'; import { $t } from '@/locales';
import DictTag from '@/components/custom/dict-tag.vue';
import ButtonIcon from '@/components/custom/button-icon.vue'; import ButtonIcon from '@/components/custom/button-icon.vue';
import SvgIcon from '@/components/custom/svg-icon.vue'; import SvgIcon from '@/components/custom/svg-icon.vue';
@ -13,13 +15,23 @@ defineOptions({
name: 'OnlineTable' name: 'OnlineTable'
}); });
useDict('sys_device_type');
const appStore = useAppStore(); const appStore = useAppStore();
const { loading: btnLoading, startLoading: startBtnLoading, endLoading: endBtnLoading } = useLoading(false); const { loading: btnLoading, startLoading: startBtnLoading, endLoading: endBtnLoading } = useLoading(false);
const { columns, data, loading, getData } = useTable({ const { columns, data, loading, getData } = useTable({
apiFn: fetchGetOnlineDeviceList, apiFn: fetchGetOnlineDeviceList,
columns: () => [ columns: () => [
{ title: '用户名', key: 'userName', align: 'center', minWidth: 120 }, {
title: '设备类型',
key: 'deviceType',
align: 'center',
minWidth: 120,
render: row => {
return <DictTag size="small" value={row.deviceType} dict-code="sys_device_type" />;
}
},
{ title: 'IP地址', key: 'ipaddr', align: 'center', minWidth: 120 }, { title: 'IP地址', key: 'ipaddr', align: 'center', minWidth: 120 },
{ title: '登录地点', key: 'loginLocation', align: 'center', minWidth: 120 }, { title: '登录地点', key: 'loginLocation', align: 'center', minWidth: 120 },
{ {
@ -86,7 +98,7 @@ const { columns, data, loading, getData } = useTable({
/** 强制下线 */ /** 强制下线 */
async function forceLogout(tokenId: string) { async function forceLogout(tokenId: string) {
startBtnLoading(); startBtnLoading();
const { error } = await fetchForceLogout(tokenId); const { error } = await fetchKickOutCurrentDevice(tokenId);
if (!error) { if (!error) {
window.$message?.success('强制下线成功'); window.$message?.success('强制下线成功');
await getData(); await getData();

View File

@ -3,12 +3,13 @@ import { computed, reactive, watch } from 'vue';
import { NTag } from 'naive-ui'; import { NTag } from 'naive-ui';
import { fetchCreateDictData, fetchUpdateDictData } from '@/service/api/system/dict-data'; import { fetchCreateDictData, fetchUpdateDictData } from '@/service/api/system/dict-data';
import { useFormRules, useNaiveForm } from '@/hooks/common/form'; import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { useDict } from '@/hooks/business/dict';
import { $t } from '@/locales'; import { $t } from '@/locales';
defineOptions({ defineOptions({
name: 'DictDataOperateDrawer' name: 'DictDataOperateDrawer'
}); });
useDict('sys_yes_no');
interface Props { interface Props {
/** the type of operation */ /** the type of operation */
operateType: NaiveUI.TableOperateType; operateType: NaiveUI.TableOperateType;
@ -63,7 +64,8 @@ function createDefaultModel(): Model {
dictType: props.dictType, dictType: props.dictType,
cssClass: '', cssClass: '',
listClass: null, listClass: null,
remark: '' remark: '',
isDefault: 'N'
}; };
} }
@ -95,7 +97,7 @@ async function handleSubmit() {
// request // request
if (props.operateType === 'add') { if (props.operateType === 'add') {
const { dictSort, dictLabel, dictValue, dictType, cssClass, listClass, remark } = model; const { dictSort, dictLabel, dictValue, dictType, cssClass, listClass, isDefault, remark } = model;
const { error } = await fetchCreateDictData({ const { error } = await fetchCreateDictData({
dictSort, dictSort,
dictLabel, dictLabel,
@ -103,13 +105,14 @@ async function handleSubmit() {
dictType, dictType,
cssClass, cssClass,
listClass, listClass,
isDefault,
remark remark
}); });
if (error) return; if (error) return;
} }
if (props.operateType === 'edit') { if (props.operateType === 'edit') {
const { dictCode, dictSort, dictLabel, dictValue, dictType, cssClass, listClass, remark } = model; const { dictCode, dictSort, dictLabel, dictValue, dictType, cssClass, listClass, isDefault, remark } = model;
const { error } = await fetchUpdateDictData({ const { error } = await fetchUpdateDictData({
dictCode, dictCode,
dictSort, dictSort,
@ -118,6 +121,7 @@ async function handleSubmit() {
dictType, dictType,
cssClass, cssClass,
listClass, listClass,
isDefault,
remark remark
}); });
if (error) return; if (error) return;
@ -179,6 +183,9 @@ function renderTagLabel(option: { label: string; value: string }) {
<NFormItem :label="$t('page.system.dict.data.dictSort')" path="dictSort"> <NFormItem :label="$t('page.system.dict.data.dictSort')" path="dictSort">
<NInputNumber v-model:value="model.dictSort" :placeholder="$t('page.system.dict.form.dictSort.required')" /> <NInputNumber v-model:value="model.dictSort" :placeholder="$t('page.system.dict.form.dictSort.required')" />
</NFormItem> </NFormItem>
<NFormItem :label="$t('page.system.dict.data.isDefault')" path="isDefault">
<DictRadio v-model:value="model.isDefault" dict-code="sys_yes_no" />
</NFormItem>
<NFormItem :label="$t('page.system.dict.data.remark')" path="remark"> <NFormItem :label="$t('page.system.dict.data.remark')" path="remark">
<NInput <NInput
v-model:value="model.remark" v-model:value="model.remark"

View File

@ -56,7 +56,7 @@ function createDefaultModel(): Model {
nickName: '', nickName: '',
email: '', email: '',
phonenumber: '', phonenumber: '',
sex: '', sex: '0',
password: '', password: '',
status: '0', status: '0',
roleIds: [], roleIds: [],