Compare commits
No commits in common. "340eaf5bb9917cac45dc3d89e44c5e5bd9415a80" and "c733dc5ffe2e2d507ee416d9cb900bf75c2aa13b" have entirely different histories.
340eaf5bb9
...
c733dc5ffe
222
CLAUDE.md
222
CLAUDE.md
@ -1,5 +1,35 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
### 第一部分:核心编程原则 (Guiding Principles)
|
||||
这是我们合作的顶层思想,指导所有具体的行为。
|
||||
可读性优先 (Readability First):始终牢记“代码是写给人看的,只是恰好机器可以执行”。清晰度高于一切。
|
||||
DRY (Don't Repeat Yourself):绝不复制代码片段。通过抽象(如函数、类、模块)来封装和复用通用逻辑。
|
||||
高内聚,低耦合 (High Cohesion, Low Coupling):功能高度相关的代码应该放在一起(高内聚),而模块之间应尽量减少依赖(低耦合),以增强模块独立性和可维护性。
|
||||
|
||||
### 第二部分:具体执行指令 (Actionable Instructions)
|
||||
这是 Claude 在日常工作中需要严格遵守的具体操作指南。
|
||||
沟通与语言规范
|
||||
默认语言:请默认使用简体中文进行所有交流、解释和思考过程的陈述。
|
||||
代码与术语:所有代码实体(变量名、函数名、类名等)及技术术语(如库名、框架名、设计模式等)必须保持英文原文。
|
||||
注释规范:代码注释应使用中文。
|
||||
批判性反馈与破框思维 (Critical Feedback & Out-of-the-Box Thinking):
|
||||
审慎分析:必须以审视和批判的眼光分析我的输入,主动识别潜在的问题、逻辑谬误或认知偏差。
|
||||
坦率直言:需要明确、直接地指出我思考中的盲点,并提供显著超越我当前思考框架的建议,以挑战我的预设。
|
||||
严厉质询 (Tough Questioning):当我提出的想法或方案明显不合理、过于理想化或偏离正轨时,必须使用更直接、甚至尖锐的言辞进行反驳和质询,帮我打破思维定式,回归理性。
|
||||
开发与调试策略 (Development & Debugging Strategy)
|
||||
坚韧不拔的解决问题 (Tenacious Problem-Solving):当面对编译错误、逻辑不通或多次尝试失败时,绝不允许通过简化或伪造实现来“绕过”问题。
|
||||
逐个击破 (Incremental Debugging):必须坚持对错误和问题进行逐一分析、定位和修复。
|
||||
|
||||
### 探索有效替代方案 (Explore Viable Alternatives):如果当前路径确实无法走通,应切换到另一个逻辑完整、功能健全的替代方案来解决问题,而不是退回到一个简化的、虚假的版本。
|
||||
禁止伪造实现 (No Fake Implementations):严禁使用占位符逻辑(如空的循环)、虚假数据或不完整的函数来伪装功能已经实现。所有交付的代码都必须是意图明确且具备真实逻辑的。
|
||||
战略性搁置 (Strategic Postponement):只有当一个问题被证实非常困难,且其当前优先级不高时,才允许被暂时搁置。搁置时,必须以 TODO 形式在代码中或任务列表中明确标记,并清晰说明遇到的问题。在核心任务完成后,必须回过头来重新审视并解决这些被搁置的问题。
|
||||
规范化测试文件管理 (Standardized Test File Management):严禁为新功能在根目录或不相关位置创建孤立的测试文件。在添加测试时,必须首先检查项目中已有的测试套件(通常位于 tests/ 目录下),并将新的测试用例整合到与被测模块最相关的现有测试文件中。只有当确实没有合适的宿主文件时,才允许在 tests/ 目录下创建符合项目命名规范的新测试文件。
|
||||
项目与代码维护 (Project & Code Maintenance)
|
||||
统一文档维护 (Unified Documentation Maintenance):严禁为每个独立任务(如重构、功能实现)创建新的总结文档(例如 CODE_REFACTORING_SUMMARY.md)。在任务完成后,必须优先检查项目中已有的相关文档(如 README.md、既有的设计文档等),并将新的总结、变更或补充内容直接整合到现有文档中,维护其完整性和时效性。
|
||||
及时清理 (Timely Cleanup):在完成开发任务时,如果发现任何已无用(过时)的代码、文件或注释,应主动提出清理建议。
|
||||
|
||||
## 项目概述
|
||||
|
||||
Coi Admin 是一个基于 Vue3、Vite5、TypeScript 和 Naive UI 的简洁后台管理模板,实现了完整的认证、权限管理、路由管理等功能。
|
||||
@ -1217,198 +1247,6 @@ import { coiMsgError, coiMsgSuccess, coiMsgWarning } from '@/utils/coi'
|
||||
|
||||
**遵循此规范确保所有表格列表页面具有统一的按钮样式和用户体验!**
|
||||
|
||||
## 📋 表格操作按钮标准样式规范(强制要求)
|
||||
|
||||
**所有新建的表格列表页面中的操作按钮都必须严格参考用户管理页面的按钮样式标准,确保界面一致性**
|
||||
|
||||
### 参考标准页面
|
||||
|
||||
- **主要参考**:用户管理页面 `src/views/system/user/index.vue`(1021-1100行)
|
||||
- **辅助参考**:角色管理页面、菜单管理页面
|
||||
|
||||
### 标准按钮实现规范
|
||||
|
||||
**✅ 正确的表格操作列按钮实现方式**:
|
||||
```typescript
|
||||
// 表格列定义 - 操作列
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 280,
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
render: (row) => {
|
||||
const buttons = []
|
||||
|
||||
// 编辑按钮 - 主要操作按钮
|
||||
if (hasPermission('edit')) {
|
||||
buttons.push(h(NButton, {
|
||||
type: 'primary',
|
||||
size: 'small',
|
||||
class: 'action-btn-primary',
|
||||
onClick: () => handleEdit(row),
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
default: () => h(IconParkOutlineEdit)
|
||||
}),
|
||||
default: () => '编辑',
|
||||
}))
|
||||
}
|
||||
|
||||
// 删除按钮 - 危险操作按钮
|
||||
if (hasPermission('delete')) {
|
||||
buttons.push(h(NPopconfirm, {
|
||||
onPositiveClick: () => handleDelete(row.id),
|
||||
negativeText: '取消',
|
||||
positiveText: '确定',
|
||||
}, {
|
||||
default: () => '确定删除此记录吗?',
|
||||
trigger: () => h(NButton, {
|
||||
type: 'error',
|
||||
secondary: true,
|
||||
size: 'small',
|
||||
class: 'action-btn-secondary action-btn-danger',
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
default: () => h(IconParkOutlineDelete),
|
||||
}),
|
||||
default: () => '删除',
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
// 其他功能按钮 - 辅助操作按钮
|
||||
if (hasPermission('other')) {
|
||||
buttons.push(h(NButton, {
|
||||
type: 'warning',
|
||||
secondary: true,
|
||||
size: 'small',
|
||||
class: 'action-btn-secondary action-btn-warning',
|
||||
onClick: () => handleOtherAction(row),
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
default: () => h(IconParkOutlineSetting),
|
||||
}),
|
||||
default: () => '设置',
|
||||
}))
|
||||
}
|
||||
|
||||
return h('div', { class: 'flex items-center justify-center gap-2' }, buttons)
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 按钮样式核心标准
|
||||
|
||||
**1. 按钮基础属性**:
|
||||
- ✅ `size: 'small'` - 统一小尺寸按钮
|
||||
- ✅ 不使用 `round` 属性 - 标准方形按钮
|
||||
- ✅ 必须包含 `class` 样式类用于主题适配
|
||||
|
||||
**2. 按钮类型规范**:
|
||||
- ✅ 主要编辑操作:`type: 'primary'`
|
||||
- ✅ 删除危险操作:`type: 'error', secondary: true`
|
||||
- ✅ 警告类操作:`type: 'warning', secondary: true`
|
||||
- ✅ 信息类操作:`type: 'info', secondary: true`
|
||||
|
||||
**3. 图标规范**:
|
||||
- ✅ 图标尺寸:`size: 14`
|
||||
- ✅ 图标位置调整:`style: 'transform: translateY(-1px)'`
|
||||
- ✅ 图标必须语义化,与操作功能匹配
|
||||
|
||||
**4. 按钮布局规范**:
|
||||
- ✅ 容器样式:`class: 'flex items-center justify-center gap-2'`
|
||||
- ✅ 按钮间距:`gap-2`(8px间距)
|
||||
- ✅ 垂直居中对齐
|
||||
- ✅ 水平居中对齐
|
||||
|
||||
**5. 操作列配置**:
|
||||
- ✅ 列宽度:`width: 280` 或根据按钮数量调整
|
||||
- ✅ 对齐方式:`align: 'center'`
|
||||
- ✅ 固定位置:`fixed: 'right'`
|
||||
|
||||
### 必需的图标导入
|
||||
|
||||
```typescript
|
||||
// 在文件顶部导入所需图标组件
|
||||
import IconParkOutlineEdit from '~icons/icon-park-outline/edit'
|
||||
import IconParkOutlineDelete from '~icons/icon-park-outline/delete'
|
||||
import IconParkOutlineSetting from '~icons/icon-park-outline/setting'
|
||||
import IconParkOutlineRefresh from '~icons/icon-park-outline/refresh'
|
||||
import IconParkOutlineUserPositioning from '~icons/icon-park-outline/user-positioning'
|
||||
```
|
||||
|
||||
### 强制要求检查清单
|
||||
|
||||
开发新的表格列表页面时,必须确保:
|
||||
- [ ] 参考用户管理页面的按钮样式实现
|
||||
- [ ] 按钮使用标准方形样式(不使用 `round` 属性)
|
||||
- [ ] 所有按钮都有图标 + 文字的组合形式
|
||||
- [ ] 图标尺寸和位置调整一致:`size: 14, style: 'transform: translateY(-1px)'`
|
||||
- [ ] 按钮容器使用标准布局:`flex items-center justify-center gap-2`
|
||||
- [ ] 按钮类型和颜色符合功能语义
|
||||
- [ ] 操作列配置符合标准(宽度、对齐、固定位置)
|
||||
- [ ] 删除等危险操作使用确认弹框(NPopconfirm)
|
||||
|
||||
### 严格禁止行为
|
||||
|
||||
- ❌ 偏离用户管理页面的按钮样式标准
|
||||
- ❌ 使用圆角按钮(`round` 属性)
|
||||
- ❌ 按钮缺少图标或文字
|
||||
- ❌ 图标尺寸和位置不统一
|
||||
- ❌ 按钮间距不一致
|
||||
- ❌ 不使用语义化的按钮类型
|
||||
- ❌ 危险操作不使用确认弹框
|
||||
|
||||
### 按钮样式类参考
|
||||
|
||||
```css
|
||||
/* 这些样式类已在用户管理页面中定义 */
|
||||
.action-btn-primary /* 主要按钮样式 */
|
||||
.action-btn-secondary /* 辅助按钮样式 */
|
||||
.action-btn-danger /* 危险按钮样式 */
|
||||
.action-btn-warning /* 警告按钮样式 */
|
||||
.action-btn-info /* 信息按钮样式 */
|
||||
```
|
||||
|
||||
### 典型按钮组合示例
|
||||
|
||||
```typescript
|
||||
// 常见的CRUD操作按钮组合
|
||||
const buttons = []
|
||||
|
||||
// 1. 编辑(主要操作)
|
||||
buttons.push(h(NButton, {
|
||||
type: 'primary',
|
||||
size: 'small',
|
||||
class: 'action-btn-primary'
|
||||
// ... 其他属性
|
||||
}))
|
||||
|
||||
// 2. 删除(危险操作,带确认)
|
||||
buttons.push(h(NPopconfirm, {
|
||||
// ... 确认属性
|
||||
trigger: () => h(NButton, {
|
||||
type: 'error',
|
||||
secondary: true,
|
||||
size: 'small',
|
||||
class: 'action-btn-secondary action-btn-danger'
|
||||
// ... 其他属性
|
||||
})
|
||||
}))
|
||||
|
||||
// 3. 其他功能按钮(辅助操作)
|
||||
buttons.push(h(NButton, {
|
||||
type: 'info',
|
||||
secondary: true,
|
||||
size: 'small',
|
||||
class: 'action-btn-secondary action-btn-info'
|
||||
// ... 其他属性
|
||||
}))
|
||||
```
|
||||
|
||||
**严格遵循用户管理页面的按钮样式标准,确保所有表格操作按钮具有统一的外观和交互体验!**
|
||||
|
||||
## 📝 表单布局紧凑设计规范(强制要求)
|
||||
|
||||
**所有新创建的表单页面和弹框表单都必须严格遵循紧凑布局设计规范,确保界面的一致性和空间利用率**
|
||||
|
||||
@ -1,16 +1,4 @@
|
||||
export const staticRoutes: AppRoute.RowRoute[] = [
|
||||
{
|
||||
name: 'dict-data',
|
||||
path: '/system/dict/data',
|
||||
title: '字典数据详情',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-park-outline:table-file',
|
||||
menuType: '2',
|
||||
componentPath: 'system/dict/data',
|
||||
id: 1001,
|
||||
pid: null,
|
||||
hide: true, // 隐藏在菜单中,只用于路由跳转
|
||||
},
|
||||
// {
|
||||
// name: 'personal-center',
|
||||
// path: '/personal-center',
|
||||
|
||||
@ -1,183 +0,0 @@
|
||||
/**
|
||||
* 字典管理 API 接口
|
||||
*/
|
||||
|
||||
import { request } from '@/service/http'
|
||||
import type {
|
||||
DictDataForm,
|
||||
DictDataOption,
|
||||
DictDataQueryBo,
|
||||
DictDataVo,
|
||||
DictTypeForm,
|
||||
DictTypeOption,
|
||||
DictTypeQueryBo,
|
||||
DictTypeVo,
|
||||
PageDictDataVo,
|
||||
PageDictTypeVo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type {
|
||||
DictDataForm,
|
||||
DictDataOption,
|
||||
DictDataQueryBo,
|
||||
DictDataSearchForm,
|
||||
DictDataVo,
|
||||
DictTypeForm,
|
||||
DictTypeOption,
|
||||
DictTypeQueryBo,
|
||||
DictTypeSearchForm,
|
||||
DictTypeVo,
|
||||
PageDictDataVo,
|
||||
PageDictTypeVo,
|
||||
} from './types'
|
||||
|
||||
export { DictStatus, DictTag } from './types'
|
||||
|
||||
/** ======================= 字典类型管理 API ======================= */
|
||||
|
||||
/**
|
||||
* 分页查询字典类型列表
|
||||
*/
|
||||
export function getDictTypeList(params: DictTypeQueryBo) {
|
||||
return request.Get<Service.ResponseResult<PageDictTypeVo>>('/coder/sysDictType/listPage', {
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有字典类型
|
||||
*/
|
||||
export function getAllDictTypes() {
|
||||
return request.Get<Service.ResponseResult<DictTypeVo[]>>('/coder/sysDictType/list')
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询字典类型详情
|
||||
*/
|
||||
export function getDictTypeById(id: string) {
|
||||
return request.Get<Service.ResponseResult<DictTypeVo>>(`/coder/sysDictType/getById/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增字典类型
|
||||
*/
|
||||
export function addDictType(data: DictTypeForm) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysDictType/add', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改字典类型
|
||||
*/
|
||||
export function updateDictType(data: DictTypeForm) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysDictType/update', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除字典类型
|
||||
*/
|
||||
export function deleteDictType(id: string) {
|
||||
return request.Post<Service.ResponseResult<string>>(`/coder/sysDictType/deleteById/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除字典类型
|
||||
*/
|
||||
export function batchDeleteDictType(ids: string[]) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysDictType/batchDelete', ids)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改字典类型状态
|
||||
*/
|
||||
export function updateDictTypeStatus(dictId: string, dictStatus: string) {
|
||||
return request.Post<Service.ResponseResult<string>>(`/coder/sysDictType/updateStatus/${dictId}/${dictStatus}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询字典类型下拉框选项
|
||||
*/
|
||||
export function getDictTypeOptions() {
|
||||
return request.Get<Service.ResponseResult<DictTypeOption[]>>('/coder/sysDictType/listDictType')
|
||||
}
|
||||
|
||||
/** ======================= 字典数据管理 API ======================= */
|
||||
|
||||
/**
|
||||
* 分页查询字典数据列表
|
||||
*/
|
||||
export function getDictDataList(params: DictDataQueryBo) {
|
||||
return request.Get<Service.ResponseResult<PageDictDataVo>>('/coder/sysDictData/listPage', {
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有字典数据
|
||||
*/
|
||||
export function getAllDictData() {
|
||||
return request.Get<Service.ResponseResult<DictDataVo[]>>('/coder/sysDictData/list')
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询字典数据详情
|
||||
*/
|
||||
export function getDictDataById(id: string) {
|
||||
return request.Get<Service.ResponseResult<DictDataVo>>(`/coder/sysDictData/getById/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增字典数据
|
||||
*/
|
||||
export function addDictData(data: DictDataForm) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysDictData/add', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最新排序号
|
||||
*/
|
||||
export function getDictDataSorted(dictType: string) {
|
||||
return request.Get<Service.ResponseResult<number>>(`/coder/sysDictData/getSorted/${dictType}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改字典数据
|
||||
*/
|
||||
export function updateDictData(data: DictDataForm) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysDictData/update', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除字典数据
|
||||
*/
|
||||
export function deleteDictData(id: string) {
|
||||
return request.Post<Service.ResponseResult<string>>(`/coder/sysDictData/deleteById/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除字典数据
|
||||
*/
|
||||
export function batchDeleteDictData(ids: string[]) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysDictData/batchDelete', ids)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改字典数据状态
|
||||
*/
|
||||
export function updateDictDataStatus(dictId: string, dictStatus: string) {
|
||||
return request.Post<Service.ResponseResult<string>>(`/coder/sysDictData/updateStatus/${dictId}/${dictStatus}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据类型查询字典数据
|
||||
*/
|
||||
export function getDictDataByType(dictType: string) {
|
||||
return request.Get<Service.ResponseResult<DictDataOption[]>>(`/coder/sysDictData/listDataByType/${dictType}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步字典缓存到Redis
|
||||
*/
|
||||
export function syncDictCache() {
|
||||
return request.Get<Service.ResponseResult<string>>('/coder/sysDictData/listDictCacheRedis')
|
||||
}
|
||||
@ -1,145 +0,0 @@
|
||||
/**
|
||||
* 字典管理相关的类型定义
|
||||
*/
|
||||
|
||||
/** 字典类型相关类型定义 */
|
||||
|
||||
// 字典类型实体
|
||||
export interface DictTypeVo {
|
||||
dictId: string
|
||||
dictType: string
|
||||
dictName: string
|
||||
dictStatus: string
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
// 字典类型查询参数
|
||||
export interface DictTypeQueryBo {
|
||||
pageNo?: number
|
||||
pageSize?: number
|
||||
dictName?: string
|
||||
dictType?: string
|
||||
dictStatus?: string
|
||||
}
|
||||
|
||||
// 字典类型搜索表单
|
||||
export interface DictTypeSearchForm {
|
||||
dictName?: string
|
||||
dictType?: string
|
||||
dictStatus?: string
|
||||
}
|
||||
|
||||
// 字典类型表单
|
||||
export interface DictTypeForm {
|
||||
dictId?: string
|
||||
dictType: string
|
||||
dictName: string
|
||||
dictStatus: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
/** 字典数据相关类型定义 */
|
||||
|
||||
// 字典数据实体
|
||||
export interface DictDataVo {
|
||||
dictId: string
|
||||
dictLabel: string
|
||||
dictValue: string
|
||||
dictType: string
|
||||
dictStatus: string
|
||||
dictTag: string
|
||||
dictColor?: string
|
||||
sorted: number
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
// 字典数据查询参数
|
||||
export interface DictDataQueryBo {
|
||||
pageNo?: number
|
||||
pageSize?: number
|
||||
dictType?: string
|
||||
dictLabel?: string
|
||||
dictStatus?: string
|
||||
}
|
||||
|
||||
// 字典数据搜索表单
|
||||
export interface DictDataSearchForm {
|
||||
dictType?: string
|
||||
dictLabel?: string
|
||||
dictStatus?: string
|
||||
}
|
||||
|
||||
// 字典数据表单
|
||||
export interface DictDataForm {
|
||||
dictId?: string
|
||||
dictLabel: string
|
||||
dictValue: string
|
||||
dictType: string
|
||||
dictStatus: string
|
||||
dictTag: string
|
||||
dictColor?: string
|
||||
sorted: number
|
||||
remark?: string
|
||||
}
|
||||
|
||||
/** 分页结果类型 */
|
||||
|
||||
// 字典类型分页结果
|
||||
export interface PageDictTypeVo {
|
||||
records: DictTypeVo[]
|
||||
total: number
|
||||
size: number
|
||||
current: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
// 字典数据分页结果
|
||||
export interface PageDictDataVo {
|
||||
records: DictDataVo[]
|
||||
total: number
|
||||
size: number
|
||||
current: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
/** 选项类型 */
|
||||
|
||||
// 字典类型下拉选项
|
||||
export interface DictTypeOption {
|
||||
dictType: string
|
||||
dictName: string
|
||||
}
|
||||
|
||||
// 字典数据选项
|
||||
export interface DictDataOption {
|
||||
dictLabel: string
|
||||
dictValue: string
|
||||
dictType: string
|
||||
dictTag: string
|
||||
dictColor?: string
|
||||
}
|
||||
|
||||
/** 状态常量 */
|
||||
|
||||
// 字典状态枚举
|
||||
export const DictStatus = {
|
||||
NORMAL: '0', // 正常
|
||||
DISABLED: '1', // 停用
|
||||
} as const
|
||||
|
||||
// 字典标签类型枚举
|
||||
export const DictTag = {
|
||||
PRIMARY: 'primary',
|
||||
SUCCESS: 'success',
|
||||
INFO: 'info',
|
||||
WARNING: 'warning',
|
||||
ERROR: 'error',
|
||||
} as const
|
||||
@ -49,13 +49,6 @@ export async function handleResponseError(response: Response) {
|
||||
|
||||
Object.assign(error, { code: errorCode, message })
|
||||
|
||||
// 检查是否是401未授权错误,直接执行登出
|
||||
if (response.status === 401) {
|
||||
const authStore = useAuthStore()
|
||||
authStore.logout()
|
||||
return error
|
||||
}
|
||||
|
||||
showError(error)
|
||||
|
||||
return error
|
||||
@ -76,21 +69,6 @@ export function handleBusinessError(data: Record<string, any>, config: Required<
|
||||
data: data.data,
|
||||
}
|
||||
|
||||
// 检查是否是token失效的业务错误
|
||||
const errorMessage = data[msgKey] || ''
|
||||
const isTokenExpired = errorMessage.includes('token 无效')
|
||||
|| errorMessage.includes('Token无效')
|
||||
|| errorMessage.includes('未提供有效的Token')
|
||||
|| errorMessage.includes('登录已过期')
|
||||
|| errorMessage.includes('未登录')
|
||||
|
||||
if (isTokenExpired) {
|
||||
// Token失效,执行登出逻辑
|
||||
const authStore = useAuthStore()
|
||||
authStore.logout()
|
||||
return error
|
||||
}
|
||||
|
||||
showError(error)
|
||||
|
||||
return error
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,899 +0,0 @@
|
||||
<template>
|
||||
<div class="dict-management p-1 bg-gray-50 h-screen flex flex-col">
|
||||
<!-- 搜索表单和字典表格 -->
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-100 flex-1 flex flex-col overflow-hidden">
|
||||
<!-- 搜索表单 -->
|
||||
<div class="px-4 py-2 border-b border-gray-100">
|
||||
<n-form
|
||||
ref="searchFormRef"
|
||||
:model="searchForm"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
class="search-form"
|
||||
>
|
||||
<n-grid :cols="4" :x-gap="8" :y-gap="4">
|
||||
<n-grid-item>
|
||||
<n-form-item label="字典名称" path="dictName">
|
||||
<n-input
|
||||
v-model:value="searchForm.dictName"
|
||||
placeholder="请输入字典名称"
|
||||
clearable
|
||||
@keydown.enter="handleSearch"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
|
||||
<n-grid-item>
|
||||
<n-form-item label="字典类型" path="dictType">
|
||||
<n-input
|
||||
v-model:value="searchForm.dictType"
|
||||
placeholder="请输入字典类型"
|
||||
clearable
|
||||
@keydown.enter="handleSearch"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
|
||||
<n-grid-item>
|
||||
<n-form-item label="状态" path="dictStatus">
|
||||
<n-select
|
||||
v-model:value="searchForm.dictStatus"
|
||||
placeholder="请选择状态"
|
||||
clearable
|
||||
:options="statusOptions"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
|
||||
<n-grid-item>
|
||||
<n-form-item label="" path="">
|
||||
<NSpace>
|
||||
<NButton type="primary" @click="handleSearch">
|
||||
<template #icon>
|
||||
<NIcon><icon-park-outline:search /></NIcon>
|
||||
</template>
|
||||
搜索
|
||||
</NButton>
|
||||
<NButton @click="handleReset">
|
||||
<template #icon>
|
||||
<NIcon><icon-park-outline:refresh /></NIcon>
|
||||
</template>
|
||||
重置
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</n-form>
|
||||
</div>
|
||||
|
||||
<!-- 表格头部操作栏 -->
|
||||
<div class="flex items-center justify-between px-4 py-2 border-b border-gray-100">
|
||||
<div class="flex items-center gap-4">
|
||||
<NButton
|
||||
v-permission="'system:dict:add'"
|
||||
type="primary"
|
||||
class="px-3 flex items-center"
|
||||
@click="handleAdd"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon class="mr-1" style="transform: translateY(-1px)">
|
||||
<icon-park-outline:plus />
|
||||
</NIcon>
|
||||
</template>
|
||||
新增
|
||||
</NButton>
|
||||
|
||||
<NButton
|
||||
v-permission="'system:dict:delete'"
|
||||
type="error"
|
||||
:disabled="selectedRows.length === 0"
|
||||
class="px-3 flex items-center"
|
||||
@click="handleBatchDelete"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon class="mr-1" style="transform: translateY(-1px)">
|
||||
<icon-park-outline:delete />
|
||||
</NIcon>
|
||||
</template>
|
||||
批量删除
|
||||
</NButton>
|
||||
|
||||
<NButton
|
||||
v-permission="'system:dict:update'"
|
||||
type="info"
|
||||
class="px-3 flex items-center"
|
||||
@click="handleSyncCache"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon class="mr-1" style="transform: translateY(-1px)">
|
||||
<icon-park-outline:refresh />
|
||||
</NIcon>
|
||||
</template>
|
||||
同步缓存
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格内容 -->
|
||||
<div class="table-wrapper flex-1 overflow-auto pt-4">
|
||||
<!-- 数据表格 -->
|
||||
<n-data-table
|
||||
v-if="tableData.length > 0 || loading"
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:row-key="rowKey"
|
||||
:checked-row-keys="checkedRowKeys"
|
||||
:bordered="false"
|
||||
:single-line="false"
|
||||
:scroll-x="1450"
|
||||
size="medium"
|
||||
class="custom-table"
|
||||
@update:checked-row-keys="handleRowSelectionChange"
|
||||
/>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<CoiEmpty
|
||||
v-else
|
||||
type="nodata"
|
||||
title="暂无数据"
|
||||
description="当前没有字典类型数据"
|
||||
:show-action="true"
|
||||
>
|
||||
<template #action>
|
||||
<NButton
|
||||
type="primary"
|
||||
size="medium"
|
||||
round
|
||||
@click="handleAdd"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon>
|
||||
<icon-park-outline:plus />
|
||||
</NIcon>
|
||||
</template>
|
||||
新增字典类型
|
||||
</NButton>
|
||||
</template>
|
||||
</CoiEmpty>
|
||||
</div>
|
||||
|
||||
<!-- 分页器 -->
|
||||
<div v-if="tableData.length > 0" class="border-t border-gray-100">
|
||||
<CoiPagination
|
||||
v-model:page="pagination.page"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:item-count="pagination.itemCount"
|
||||
:show-size-picker="true"
|
||||
@update:page="handlePageChange"
|
||||
@update:page-size="handlePageSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑弹框 -->
|
||||
<CoiDialog
|
||||
ref="formDialogRef"
|
||||
:title="modalTitle"
|
||||
:width="800"
|
||||
height="auto"
|
||||
confirm-text="确定"
|
||||
cancel-text="取消"
|
||||
@coi-confirm="handleSubmit"
|
||||
@coi-cancel="handleCancel"
|
||||
>
|
||||
<template #content>
|
||||
<div class="px-3 py-2">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-placement="left"
|
||||
label-width="90px"
|
||||
require-mark-placement="right-hanging"
|
||||
class="compact-form"
|
||||
>
|
||||
<n-grid :cols="2" :x-gap="10">
|
||||
<n-grid-item>
|
||||
<n-form-item label="字典名称" path="dictName" class="mb-2">
|
||||
<n-input
|
||||
v-model:value="formData.dictName"
|
||||
placeholder="请输入字典名称"
|
||||
maxlength="50"
|
||||
show-count
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="字典类型" path="dictType" class="mb-2">
|
||||
<n-input
|
||||
v-model:value="formData.dictType"
|
||||
placeholder="请输入字典类型"
|
||||
:disabled="isEdit"
|
||||
maxlength="50"
|
||||
show-count
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="状态" path="dictStatus" class="mb-2">
|
||||
<n-select
|
||||
v-model:value="formData.dictStatus"
|
||||
placeholder="请选择状态"
|
||||
:options="statusOptions"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
<n-form-item label="备注" path="remark" class="mb-2">
|
||||
<n-input
|
||||
v-model:value="formData.remark"
|
||||
type="textarea"
|
||||
placeholder="请输入备注信息"
|
||||
:rows="3"
|
||||
maxlength="500"
|
||||
show-count
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</div>
|
||||
</template>
|
||||
</CoiDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, h, onMounted, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { DataTableColumns, FormInst, FormRules } from 'naive-ui'
|
||||
import { NButton, NIcon, NPopconfirm, NSpace, NSwitch } from 'naive-ui'
|
||||
import CoiDialog from '@/components/common/CoiDialog.vue'
|
||||
import CoiEmpty from '@/components/common/CoiEmpty.vue'
|
||||
import CoiPagination from '@/components/common/CoiPagination.vue'
|
||||
import { usePermission } from '@/hooks/usePermission'
|
||||
import { coiMsgBox, coiMsgError, coiMsgSuccess, coiMsgWarning } from '@/utils/coi'
|
||||
import {
|
||||
addDictType,
|
||||
batchDeleteDictType,
|
||||
deleteDictType,
|
||||
DictStatus,
|
||||
getDictTypeList,
|
||||
syncDictCache,
|
||||
updateDictType,
|
||||
updateDictTypeStatus,
|
||||
} from '@/service/api/system/dict'
|
||||
import type {
|
||||
DictTypeForm,
|
||||
DictTypeSearchForm,
|
||||
DictTypeVo,
|
||||
} from '@/service/api/system/dict'
|
||||
|
||||
// 图标导入
|
||||
import IconParkOutlineDelete from '~icons/icon-park-outline/delete'
|
||||
import IconParkOutlineEdit from '~icons/icon-park-outline/edit'
|
||||
|
||||
// 路由和权限验证
|
||||
const router = useRouter()
|
||||
const { hasPermission } = usePermission()
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const tableData = ref<DictTypeVo[]>([])
|
||||
const selectedRows = ref<DictTypeVo[]>([])
|
||||
const checkedRowKeys = ref<string[]>([])
|
||||
const searchFormRef = ref<FormInst>()
|
||||
const formRef = ref<FormInst>()
|
||||
const formDialogRef = ref()
|
||||
const isEdit = ref(false)
|
||||
const modalTitle = ref('')
|
||||
|
||||
// 搜索表单数据
|
||||
const searchForm = reactive<DictTypeSearchForm>({
|
||||
dictName: '',
|
||||
dictType: '',
|
||||
dictStatus: '',
|
||||
})
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive<DictTypeForm>({
|
||||
dictId: '',
|
||||
dictType: '',
|
||||
dictName: '',
|
||||
dictStatus: DictStatus.NORMAL,
|
||||
remark: '',
|
||||
})
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
itemCount: 0,
|
||||
})
|
||||
|
||||
// 分页处理函数
|
||||
function handlePageChange(page: number) {
|
||||
pagination.page = page
|
||||
getTableData()
|
||||
}
|
||||
|
||||
function handlePageSizeChange(pageSize: number) {
|
||||
pagination.pageSize = pageSize
|
||||
pagination.page = 1
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 状态选项
|
||||
const statusOptions = [
|
||||
{ label: '正常', value: DictStatus.NORMAL },
|
||||
{ label: '停用', value: DictStatus.DISABLED },
|
||||
]
|
||||
|
||||
// 表单验证规则
|
||||
const rules: FormRules = {
|
||||
dictName: [
|
||||
{ required: true, message: '请输入字典名称', trigger: ['blur', 'input'] },
|
||||
{ min: 1, max: 50, message: '字典名称长度在1到50个字符', trigger: ['blur', 'input'] },
|
||||
],
|
||||
dictType: [
|
||||
{ required: true, message: '请输入字典类型', trigger: ['blur', 'input'] },
|
||||
{ min: 1, max: 50, message: '字典类型长度在1到50个字符', trigger: ['blur', 'input'] },
|
||||
{ pattern: /^[a-z_]\w*$/i, message: '字典类型只能包含字母、数字、下划线,且不能以数字开头', trigger: ['blur', 'input'] },
|
||||
],
|
||||
dictStatus: [
|
||||
{ required: true, message: '请选择状态', trigger: ['blur', 'change'] },
|
||||
],
|
||||
}
|
||||
|
||||
// 表格行键
|
||||
const rowKey = (row: DictTypeVo) => row.dictId
|
||||
|
||||
// 表格列配置
|
||||
const columns: DataTableColumns<DictTypeVo> = [
|
||||
{ type: 'selection' },
|
||||
{
|
||||
title: '序号',
|
||||
key: 'index',
|
||||
width: 70,
|
||||
align: 'center',
|
||||
render: (_, index) => {
|
||||
return (pagination.page - 1) * pagination.pageSize + index + 1
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '字典名称',
|
||||
key: 'dictName',
|
||||
width: 150,
|
||||
ellipsis: { tooltip: true },
|
||||
},
|
||||
{
|
||||
title: '字典类型',
|
||||
key: 'dictType',
|
||||
width: 150,
|
||||
ellipsis: { tooltip: true },
|
||||
render: (row) => {
|
||||
return h('a', {
|
||||
class: 'dict-type-clickable-link',
|
||||
style: {
|
||||
color: '#326C72',
|
||||
cursor: 'pointer',
|
||||
textDecoration: 'none',
|
||||
fontWeight: '500',
|
||||
transition: 'all 0.2s ease',
|
||||
display: 'inline-block',
|
||||
},
|
||||
onClick: (e: MouseEvent) => {
|
||||
e.preventDefault()
|
||||
handleViewDictData(row)
|
||||
},
|
||||
onMouseenter: (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement
|
||||
target.style.color = '#4A9BA1'
|
||||
target.style.textDecoration = 'underline'
|
||||
},
|
||||
onMouseleave: (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement
|
||||
target.style.color = '#326C72'
|
||||
target.style.textDecoration = 'none'
|
||||
},
|
||||
}, row.dictType)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '字典状态',
|
||||
key: 'dictStatus',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
render: (row) => {
|
||||
return h('div', { class: 'flex items-center justify-center' }, [
|
||||
h(NPopconfirm, {
|
||||
onPositiveClick: () => handleStatusChange(row),
|
||||
negativeText: '取消',
|
||||
positiveText: '确定',
|
||||
}, {
|
||||
default: () => `确定要${row.dictStatus === DictStatus.NORMAL ? '停用' : '启用'}字典类型「${row.dictName}」吗?`,
|
||||
trigger: () => h(NSwitch, {
|
||||
value: row.dictStatus === DictStatus.NORMAL,
|
||||
size: 'small',
|
||||
}),
|
||||
}),
|
||||
])
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
key: 'remark',
|
||||
width: 150,
|
||||
ellipsis: { tooltip: true },
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createTime',
|
||||
width: 170,
|
||||
ellipsis: { tooltip: true },
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
key: 'updateTime',
|
||||
width: 170,
|
||||
ellipsis: { tooltip: true },
|
||||
render: (row) => {
|
||||
return h('span', { class: 'text-gray-500 text-sm' }, row.updateTime || '-')
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '修改人',
|
||||
key: 'updateBy',
|
||||
width: 120,
|
||||
ellipsis: { tooltip: true },
|
||||
render: (row) => {
|
||||
return h('span', { class: 'text-gray-600' }, row.updateBy || '-')
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 180,
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
render: (row) => {
|
||||
const buttons = []
|
||||
|
||||
// 编辑按钮
|
||||
if (hasPermission('system:dict:update')) {
|
||||
buttons.push(h(NButton, {
|
||||
type: 'primary',
|
||||
size: 'small',
|
||||
class: 'action-btn-primary',
|
||||
onClick: () => handleEdit(row),
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
default: () => h(IconParkOutlineEdit),
|
||||
}),
|
||||
default: () => '编辑',
|
||||
}))
|
||||
}
|
||||
|
||||
// 删除按钮
|
||||
if (hasPermission('system:dict:delete')) {
|
||||
buttons.push(h(NPopconfirm, {
|
||||
onPositiveClick: () => handleDelete(row),
|
||||
negativeText: '取消',
|
||||
positiveText: '确定',
|
||||
}, {
|
||||
default: () => '确定删除此字典类型吗?删除后将同时删除相关的字典数据!',
|
||||
trigger: () => h(NButton, {
|
||||
type: 'error',
|
||||
secondary: true,
|
||||
size: 'small',
|
||||
class: 'action-btn-secondary action-btn-danger',
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
default: () => h(IconParkOutlineDelete),
|
||||
}),
|
||||
default: () => '删除',
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
return h('div', { class: 'flex items-center justify-center gap-2' }, buttons)
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
// 计算属性
|
||||
const _hasSearchConditions = computed(() => {
|
||||
return !!(searchForm.dictName || searchForm.dictType || searchForm.dictStatus)
|
||||
})
|
||||
|
||||
// 获取表格数据
|
||||
async function getTableData() {
|
||||
try {
|
||||
loading.value = true
|
||||
const params = {
|
||||
pageNo: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
dictName: searchForm.dictName || undefined,
|
||||
dictType: searchForm.dictType || undefined,
|
||||
dictStatus: searchForm.dictStatus || undefined,
|
||||
}
|
||||
|
||||
const { data, isSuccess } = await getDictTypeList(params)
|
||||
|
||||
if (isSuccess && data) {
|
||||
tableData.value = data.records
|
||||
pagination.itemCount = data.total
|
||||
}
|
||||
else {
|
||||
tableData.value = []
|
||||
pagination.itemCount = 0
|
||||
}
|
||||
}
|
||||
catch {
|
||||
coiMsgError('获取数据失败')
|
||||
tableData.value = []
|
||||
pagination.itemCount = 0
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function handleSearch() {
|
||||
pagination.page = 1
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
function handleReset() {
|
||||
searchForm.dictName = ''
|
||||
searchForm.dictType = ''
|
||||
searchForm.dictStatus = ''
|
||||
pagination.page = 1
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 新增
|
||||
function handleAdd() {
|
||||
isEdit.value = false
|
||||
modalTitle.value = '新增字典类型'
|
||||
|
||||
// 重置表单数据
|
||||
Object.assign(formData, {
|
||||
dictId: '',
|
||||
dictType: '',
|
||||
dictName: '',
|
||||
dictStatus: DictStatus.NORMAL,
|
||||
remark: '',
|
||||
})
|
||||
|
||||
formDialogRef.value?.coiOpen()
|
||||
}
|
||||
|
||||
// 编辑
|
||||
function handleEdit(row: DictTypeVo) {
|
||||
isEdit.value = true
|
||||
modalTitle.value = '编辑字典类型'
|
||||
|
||||
// 填充表单数据
|
||||
Object.assign(formData, {
|
||||
dictId: row.dictId,
|
||||
dictType: row.dictType,
|
||||
dictName: row.dictName,
|
||||
dictStatus: row.dictStatus,
|
||||
remark: row.remark || '',
|
||||
})
|
||||
|
||||
formDialogRef.value?.coiOpen()
|
||||
}
|
||||
|
||||
// 删除
|
||||
async function handleDelete(row: DictTypeVo) {
|
||||
try {
|
||||
const { isSuccess } = await deleteDictType(row.dictId)
|
||||
|
||||
if (isSuccess) {
|
||||
coiMsgSuccess('删除成功')
|
||||
getTableData()
|
||||
}
|
||||
else {
|
||||
coiMsgError('删除失败')
|
||||
}
|
||||
}
|
||||
catch {
|
||||
coiMsgError('删除失败,请检查网络连接')
|
||||
}
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
async function handleBatchDelete() {
|
||||
if (checkedRowKeys.value.length === 0) {
|
||||
coiMsgWarning('请选择要删除的数据')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await coiMsgBox(`确定删除选中的${checkedRowKeys.value.length}条记录吗?删除后将同时删除相关的字典数据!`, '批量删除确认')
|
||||
|
||||
const { isSuccess } = await batchDeleteDictType(checkedRowKeys.value)
|
||||
|
||||
if (isSuccess) {
|
||||
coiMsgSuccess('批量删除成功')
|
||||
checkedRowKeys.value = []
|
||||
selectedRows.value = []
|
||||
getTableData()
|
||||
}
|
||||
else {
|
||||
coiMsgError('批量删除失败')
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// 用户取消删除
|
||||
}
|
||||
}
|
||||
|
||||
// 状态切换
|
||||
async function handleStatusChange(row: DictTypeVo) {
|
||||
const newStatus = row.dictStatus === DictStatus.NORMAL ? DictStatus.DISABLED : DictStatus.NORMAL
|
||||
|
||||
try {
|
||||
const { isSuccess } = await updateDictTypeStatus(row.dictId, newStatus)
|
||||
|
||||
if (isSuccess) {
|
||||
coiMsgSuccess('状态修改成功')
|
||||
getTableData()
|
||||
}
|
||||
else {
|
||||
coiMsgError('状态修改失败')
|
||||
}
|
||||
}
|
||||
catch {
|
||||
coiMsgError('状态修改失败,请检查网络连接')
|
||||
}
|
||||
}
|
||||
|
||||
// 同步缓存
|
||||
async function handleSyncCache() {
|
||||
try {
|
||||
loading.value = true
|
||||
const { isSuccess } = await syncDictCache()
|
||||
|
||||
if (isSuccess) {
|
||||
coiMsgSuccess('缓存同步成功')
|
||||
}
|
||||
else {
|
||||
coiMsgError('缓存同步失败')
|
||||
}
|
||||
}
|
||||
catch {
|
||||
coiMsgError('缓存同步失败,请检查网络连接')
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 行选择变化
|
||||
function handleRowSelectionChange(rowKeys: string[]) {
|
||||
checkedRowKeys.value = rowKeys
|
||||
selectedRows.value = tableData.value.filter(row => rowKeys.includes(row.dictId))
|
||||
}
|
||||
|
||||
// 表单提交
|
||||
async function handleSubmit() {
|
||||
if (!formRef.value)
|
||||
return
|
||||
|
||||
try {
|
||||
// 先进行表单验证
|
||||
await formRef.value.validate()
|
||||
}
|
||||
catch {
|
||||
// 表单验证失败,提示用户检查填写内容
|
||||
coiMsgWarning('验证失败,请检查填写内容')
|
||||
return
|
||||
}
|
||||
|
||||
// 表单验证通过,执行API调用
|
||||
try {
|
||||
const submitData = { ...formData }
|
||||
|
||||
const { isSuccess } = isEdit.value ? await updateDictType(submitData) : await addDictType(submitData)
|
||||
|
||||
if (isSuccess) {
|
||||
coiMsgSuccess(isEdit.value ? '修改成功' : '新增成功')
|
||||
formDialogRef.value?.coiClose()
|
||||
getTableData()
|
||||
}
|
||||
else {
|
||||
coiMsgError(isEdit.value ? '修改失败,请稍后重试' : '新增失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error) {
|
||||
coiMsgError(error.message || (isEdit.value ? '修改失败,请检查网络连接' : '新增失败,请检查网络连接'))
|
||||
}
|
||||
else {
|
||||
coiMsgError(isEdit.value ? '修改失败,请检查网络连接' : '新增失败,请检查网络连接')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 查看字典数据
|
||||
function handleViewDictData(row: DictTypeVo) {
|
||||
router.push({
|
||||
path: '/system/dict/data',
|
||||
query: {
|
||||
dictType: row.dictType,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 取消操作
|
||||
function handleCancel() {
|
||||
formDialogRef.value?.coiClose()
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dict-management {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
.custom-table :deep(.n-data-table-td) {
|
||||
padding: 8px 16px !important;
|
||||
font-size: 14px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.custom-table :deep(.n-data-table-th) {
|
||||
padding: 12px 16px !important;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
background-color: #fafafa;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.custom-table :deep(.n-data-table-tr:hover .n-data-table-td) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
.action-btn-primary {
|
||||
background-color: #1890ff;
|
||||
border-color: #1890ff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn-secondary {
|
||||
background-color: transparent;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.action-btn-secondary.action-btn-danger {
|
||||
color: #ff4d4f;
|
||||
border-color: #ff4d4f;
|
||||
}
|
||||
|
||||
.action-btn-secondary.action-btn-danger:hover {
|
||||
background-color: #ff4d4f;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.custom-table :deep(.n-data-table-base-table) {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.custom-table :deep(.n-data-table-base-table)::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.custom-table :deep(.n-data-table-base-table)::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.custom-table :deep(.n-data-table-base-table)::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.custom-table :deep(.n-data-table-base-table)::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
|
||||
/* 紧凑表单样式 */
|
||||
.compact-form :deep(.n-form-item) {
|
||||
margin-bottom: 8px !important;
|
||||
}
|
||||
|
||||
.compact-form :deep(.n-form-item .n-form-item-feedback-wrapper) {
|
||||
min-height: 0 !important;
|
||||
padding-top: 2px !important;
|
||||
}
|
||||
|
||||
.compact-form :deep(.n-form-item .n-form-item-label) {
|
||||
padding-bottom: 2px !important;
|
||||
}
|
||||
|
||||
.compact-form :deep(.n-input),
|
||||
.compact-form :deep(.n-input-number),
|
||||
.compact-form :deep(.n-select),
|
||||
.compact-form :deep(.n-cascader),
|
||||
.compact-form :deep(.n-radio-group) {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.compact-form :deep(.n-input .n-input__input-el),
|
||||
.compact-form :deep(.n-input-number .n-input__input-el) {
|
||||
padding: 2px 1px !important;
|
||||
min-height: 32px !important;
|
||||
}
|
||||
|
||||
.compact-form :deep(.n-select .n-base-selection) {
|
||||
min-height: 32px !important;
|
||||
padding: 2px 1px !important;
|
||||
}
|
||||
|
||||
.compact-form :deep(.n-select .n-base-selection .n-base-selection-label) {
|
||||
padding: 0 6px !important;
|
||||
min-height: 30px !important;
|
||||
line-height: 30px !important;
|
||||
}
|
||||
|
||||
.compact-form :deep(.n-cascader .n-cascader-trigger) {
|
||||
min-height: 32px !important;
|
||||
padding: 2px 1px !important;
|
||||
}
|
||||
|
||||
/* 字典类型可点击链接样式 - 使用青绿色系配色 */
|
||||
.custom-table :deep(.dict-type-clickable-link) {
|
||||
color: #326C72 !important;
|
||||
cursor: pointer !important;
|
||||
text-decoration: none !important;
|
||||
font-weight: 500 !important;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
.custom-table :deep(.dict-type-clickable-link:hover) {
|
||||
color: #4A9BA1 !important;
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
|
||||
.custom-table :deep(.dict-type-clickable-link:active) {
|
||||
color: #275A5F !important;
|
||||
}
|
||||
|
||||
/* 针对表格单元格内容的强制样式 */
|
||||
.custom-table :deep(.n-data-table-td .dict-type-clickable-link) {
|
||||
color: #326C72 !important;
|
||||
cursor: pointer !important;
|
||||
text-decoration: none !important;
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
|
||||
/* 更加具体的选择器以确保样式生效 */
|
||||
.dict-management .custom-table :deep(.n-data-table-tbody .n-data-table-tr .n-data-table-td .dict-type-clickable-link) {
|
||||
color: #326C72 !important;
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
</style>
|
||||
@ -518,7 +518,6 @@ const columns: DataTableColumns<SysFileVo> = [
|
||||
buttons.push(h(NButton, {
|
||||
type: 'primary',
|
||||
size: 'small',
|
||||
class: 'action-btn-primary',
|
||||
onClick: () => handleDownload(row),
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
@ -538,9 +537,7 @@ const columns: DataTableColumns<SysFileVo> = [
|
||||
default: () => '确定删除此文件吗?',
|
||||
trigger: () => h(NButton, {
|
||||
type: 'error',
|
||||
secondary: true,
|
||||
size: 'small',
|
||||
class: 'action-btn-secondary action-btn-danger',
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
default: () => h(IconParkOutlineDelete),
|
||||
@ -975,84 +972,4 @@ onMounted(() => {
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 统一按钮样式系统 - 支持动态主题色彩 */
|
||||
.action-btn-primary {
|
||||
background: var(--primary-color) !important;
|
||||
border-color: var(--primary-color) !important;
|
||||
color: #ffffff !important;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
box-shadow: 0 2px 4px var(--primary-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-primary:hover {
|
||||
background: var(--primary-color-hover) !important;
|
||||
border-color: var(--primary-color-hover) !important;
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 4px 8px var(--primary-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-primary:active {
|
||||
background: var(--primary-color-pressed) !important;
|
||||
border-color: var(--primary-color-pressed) !important;
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary:hover {
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.12) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary:active {
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
.action-btn-danger {
|
||||
border-color: var(--error-color) !important;
|
||||
color: var(--error-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-danger:hover {
|
||||
border-color: var(--error-color-hover) !important;
|
||||
color: var(--error-color-hover) !important;
|
||||
background: var(--error-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-warning {
|
||||
border-color: var(--warning-color) !important;
|
||||
color: var(--warning-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-warning:hover {
|
||||
border-color: var(--warning-color-hover) !important;
|
||||
color: var(--warning-color-hover) !important;
|
||||
background: var(--warning-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-info {
|
||||
border-color: var(--info-color) !important;
|
||||
color: var(--info-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-info:hover {
|
||||
border-color: var(--info-color-hover) !important;
|
||||
color: var(--info-color-hover) !important;
|
||||
background: var(--info-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-success {
|
||||
border-color: var(--success-color) !important;
|
||||
color: var(--success-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-success:hover {
|
||||
border-color: var(--success-color-hover) !important;
|
||||
color: var(--success-color-hover) !important;
|
||||
background: var(--success-color-suppl) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -370,9 +370,7 @@ const columns: DataTableColumns<LoginLogVo> = [
|
||||
default: () => '确定删除此登录记录吗?',
|
||||
trigger: () => h(NButton, {
|
||||
type: 'error',
|
||||
secondary: true,
|
||||
size: 'small',
|
||||
class: 'action-btn-secondary action-btn-danger',
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
default: () => h(IconParkOutlineDelete),
|
||||
@ -382,7 +380,7 @@ const columns: DataTableColumns<LoginLogVo> = [
|
||||
}))
|
||||
}
|
||||
|
||||
return h('div', { class: 'flex items-center justify-center gap-2' }, buttons)
|
||||
return h('div', { class: 'flex items-center justify-center gap-1' }, buttons)
|
||||
},
|
||||
},
|
||||
]
|
||||
@ -645,32 +643,6 @@ onMounted(() => {
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
/* 统一按钮样式系统 - 支持动态主题色彩 */
|
||||
.action-btn-secondary {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary:hover {
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.12) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary:active {
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
.action-btn-danger {
|
||||
border-color: var(--error-color) !important;
|
||||
color: var(--error-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-danger:hover {
|
||||
border-color: var(--error-color-hover) !important;
|
||||
color: var(--error-color-hover) !important;
|
||||
background: var(--error-color-suppl) !important;
|
||||
}
|
||||
|
||||
/* 登录日志详情样式 */
|
||||
.login-log-detail :deep(.n-descriptions-item-label) {
|
||||
font-weight: 600;
|
||||
|
||||
@ -835,9 +835,7 @@ const columns: DataTableColumns<MenuVo> = [
|
||||
if (row.menuType !== '3' && hasButton(PERMISSIONS.MENU.ADD)) {
|
||||
buttons.push(h(NButton, {
|
||||
type: 'success',
|
||||
secondary: true,
|
||||
size: 'small',
|
||||
class: 'action-btn-secondary action-btn-success',
|
||||
onClick: () => handleAddChild(row),
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
@ -852,7 +850,6 @@ const columns: DataTableColumns<MenuVo> = [
|
||||
buttons.push(h(NButton, {
|
||||
type: 'primary',
|
||||
size: 'small',
|
||||
class: 'action-btn-primary',
|
||||
onClick: () => handleEdit(row),
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
@ -872,9 +869,7 @@ const columns: DataTableColumns<MenuVo> = [
|
||||
default: () => '确定删除此菜单吗?',
|
||||
trigger: () => h(NButton, {
|
||||
type: 'error',
|
||||
secondary: true,
|
||||
size: 'small',
|
||||
class: 'action-btn-secondary action-btn-danger',
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
default: () => h(IconParkOutlineDelete),
|
||||
@ -884,7 +879,7 @@ const columns: DataTableColumns<MenuVo> = [
|
||||
}))
|
||||
}
|
||||
|
||||
return h('div', { class: 'flex items-center justify-center gap-2' }, buttons)
|
||||
return h('div', { class: 'flex items-center justify-center gap-1' }, buttons)
|
||||
},
|
||||
},
|
||||
]
|
||||
@ -1768,84 +1763,4 @@ onMounted(() => {
|
||||
.custom-table :deep(.n-data-table-base-table)::-webkit-scrollbar-thumb:hover {
|
||||
background: #adb5bd;
|
||||
}
|
||||
|
||||
/* 统一按钮样式系统 - 支持动态主题色彩 */
|
||||
.action-btn-primary {
|
||||
background: var(--primary-color) !important;
|
||||
border-color: var(--primary-color) !important;
|
||||
color: #ffffff !important;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
box-shadow: 0 2px 4px var(--primary-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-primary:hover {
|
||||
background: var(--primary-color-hover) !important;
|
||||
border-color: var(--primary-color-hover) !important;
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 4px 8px var(--primary-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-primary:active {
|
||||
background: var(--primary-color-pressed) !important;
|
||||
border-color: var(--primary-color-pressed) !important;
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary:hover {
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.12) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary:active {
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
.action-btn-success {
|
||||
border-color: var(--success-color) !important;
|
||||
color: var(--success-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-success:hover {
|
||||
border-color: var(--success-color-hover) !important;
|
||||
color: var(--success-color-hover) !important;
|
||||
background: var(--success-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-danger {
|
||||
border-color: var(--error-color) !important;
|
||||
color: var(--error-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-danger:hover {
|
||||
border-color: var(--error-color-hover) !important;
|
||||
color: var(--error-color-hover) !important;
|
||||
background: var(--error-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-warning {
|
||||
border-color: var(--warning-color) !important;
|
||||
color: var(--warning-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-warning:hover {
|
||||
border-color: var(--warning-color-hover) !important;
|
||||
color: var(--warning-color-hover) !important;
|
||||
background: var(--warning-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-info {
|
||||
border-color: var(--info-color) !important;
|
||||
color: var(--info-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-info:hover {
|
||||
border-color: var(--info-color-hover) !important;
|
||||
color: var(--info-color-hover) !important;
|
||||
background: var(--info-color-suppl) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -460,7 +460,6 @@ const columns: DataTableColumns<OperLogVo> = [
|
||||
buttons.push(h(NButton, {
|
||||
type: 'primary',
|
||||
size: 'small',
|
||||
class: 'action-btn-primary',
|
||||
onClick: () => handleViewDetail(row),
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
@ -479,9 +478,7 @@ const columns: DataTableColumns<OperLogVo> = [
|
||||
default: () => '确定删除此操作记录吗?',
|
||||
trigger: () => h(NButton, {
|
||||
type: 'error',
|
||||
secondary: true,
|
||||
size: 'small',
|
||||
class: 'action-btn-secondary action-btn-danger',
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
default: () => h(IconParkOutlineDelete),
|
||||
@ -491,7 +488,7 @@ const columns: DataTableColumns<OperLogVo> = [
|
||||
}))
|
||||
}
|
||||
|
||||
return h('div', { class: 'flex items-center justify-center gap-2' }, buttons)
|
||||
return h('div', { class: 'flex items-center justify-center gap-1' }, buttons)
|
||||
},
|
||||
},
|
||||
]
|
||||
@ -808,53 +805,6 @@ onMounted(() => {
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
/* 统一按钮样式系统 - 支持动态主题色彩 */
|
||||
.action-btn-primary {
|
||||
background: var(--primary-color) !important;
|
||||
border-color: var(--primary-color) !important;
|
||||
color: #ffffff !important;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
box-shadow: 0 2px 4px var(--primary-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-primary:hover {
|
||||
background: var(--primary-color-hover) !important;
|
||||
border-color: var(--primary-color-hover) !important;
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 4px 8px var(--primary-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-primary:active {
|
||||
background: var(--primary-color-pressed) !important;
|
||||
border-color: var(--primary-color-pressed) !important;
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary:hover {
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.12) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary:active {
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
.action-btn-danger {
|
||||
border-color: var(--error-color) !important;
|
||||
color: var(--error-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-danger:hover {
|
||||
border-color: var(--error-color-hover) !important;
|
||||
color: var(--error-color-hover) !important;
|
||||
background: var(--error-color-suppl) !important;
|
||||
}
|
||||
|
||||
/* 操作日志详情样式 */
|
||||
.oper-log-detail :deep(.n-descriptions-item-label) {
|
||||
font-weight: 600;
|
||||
|
||||
@ -218,7 +218,6 @@
|
||||
size="tiny"
|
||||
circle
|
||||
type="primary"
|
||||
class="action-btn-primary"
|
||||
@click.stop="handleDownload(picture)"
|
||||
>
|
||||
<template #icon>
|
||||
@ -233,8 +232,6 @@
|
||||
size="tiny"
|
||||
circle
|
||||
type="error"
|
||||
secondary
|
||||
class="action-btn-secondary action-btn-danger"
|
||||
@click.stop="handleDelete(picture.pictureId!)"
|
||||
>
|
||||
<template #icon>
|
||||
@ -659,7 +656,6 @@ const columns: DataTableColumns<SysPictureVo> = [
|
||||
buttons.push(h(NButton, {
|
||||
type: 'primary',
|
||||
size: 'small',
|
||||
class: 'action-btn-primary',
|
||||
onClick: () => handleDownload(row),
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
@ -678,9 +674,7 @@ const columns: DataTableColumns<SysPictureVo> = [
|
||||
default: () => '确定删除此图片吗?',
|
||||
trigger: () => h(NButton, {
|
||||
type: 'error',
|
||||
secondary: true,
|
||||
size: 'small',
|
||||
class: 'action-btn-secondary action-btn-danger',
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
default: () => h(IconParkOutlineDelete),
|
||||
@ -690,7 +684,7 @@ const columns: DataTableColumns<SysPictureVo> = [
|
||||
}))
|
||||
}
|
||||
|
||||
return h('div', { class: 'flex items-center justify-center gap-2' }, buttons)
|
||||
return h('div', { class: 'flex items-center justify-center gap-1' }, buttons)
|
||||
},
|
||||
},
|
||||
]
|
||||
@ -1133,84 +1127,4 @@ onMounted(() => {
|
||||
.picture-card img {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
/* 统一按钮样式系统 - 支持动态主题色彩 */
|
||||
.action-btn-primary {
|
||||
background: var(--primary-color) !important;
|
||||
border-color: var(--primary-color) !important;
|
||||
color: #ffffff !important;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
box-shadow: 0 2px 4px var(--primary-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-primary:hover {
|
||||
background: var(--primary-color-hover) !important;
|
||||
border-color: var(--primary-color-hover) !important;
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 4px 8px var(--primary-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-primary:active {
|
||||
background: var(--primary-color-pressed) !important;
|
||||
border-color: var(--primary-color-pressed) !important;
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary:hover {
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.12) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary:active {
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
.action-btn-danger {
|
||||
border-color: var(--error-color) !important;
|
||||
color: var(--error-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-danger:hover {
|
||||
border-color: var(--error-color-hover) !important;
|
||||
color: var(--error-color-hover) !important;
|
||||
background: var(--error-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-warning {
|
||||
border-color: var(--warning-color) !important;
|
||||
color: var(--warning-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-warning:hover {
|
||||
border-color: var(--warning-color-hover) !important;
|
||||
color: var(--warning-color-hover) !important;
|
||||
background: var(--warning-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-info {
|
||||
border-color: var(--info-color) !important;
|
||||
color: var(--info-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-info:hover {
|
||||
border-color: var(--info-color-hover) !important;
|
||||
color: var(--info-color-hover) !important;
|
||||
background: var(--info-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-success {
|
||||
border-color: var(--success-color) !important;
|
||||
color: var(--success-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-success:hover {
|
||||
border-color: var(--success-color-hover) !important;
|
||||
color: var(--success-color-hover) !important;
|
||||
background: var(--success-color-suppl) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -616,7 +616,6 @@ const columns: DataTableColumns<RoleVo> = [
|
||||
buttons.push(h(NButton, {
|
||||
type: 'primary',
|
||||
size: 'small',
|
||||
class: 'action-btn-primary',
|
||||
onClick: () => handleEdit(row),
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
@ -636,9 +635,7 @@ const columns: DataTableColumns<RoleVo> = [
|
||||
default: () => row.roleId === 1 ? '超级管理员角色不可删除' : '确定删除此角色吗?',
|
||||
trigger: () => h(NButton, {
|
||||
type: 'error',
|
||||
secondary: true,
|
||||
size: 'small',
|
||||
class: 'action-btn-secondary action-btn-danger',
|
||||
disabled: row.roleId === 1,
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
@ -653,9 +650,7 @@ const columns: DataTableColumns<RoleVo> = [
|
||||
if (hasButton(PERMISSIONS.ROLE.MENU) && row.roleId !== 1) {
|
||||
buttons.push(h(NButton, {
|
||||
type: 'warning',
|
||||
secondary: true,
|
||||
size: 'small',
|
||||
class: 'action-btn-secondary action-btn-warning',
|
||||
onClick: () => handleAssignMenu(row),
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
@ -665,7 +660,7 @@ const columns: DataTableColumns<RoleVo> = [
|
||||
}))
|
||||
}
|
||||
|
||||
return h('div', { class: 'flex items-center justify-center gap-2' }, buttons)
|
||||
return h('div', { class: 'flex items-center justify-center gap-1' }, buttons)
|
||||
},
|
||||
},
|
||||
]
|
||||
@ -1588,73 +1583,4 @@ onMounted(() => {
|
||||
min-height: 30px !important;
|
||||
line-height: 30px !important;
|
||||
}
|
||||
|
||||
/* 统一按钮样式系统 - 支持动态主题色彩 */
|
||||
.action-btn-primary {
|
||||
background: var(--primary-color) !important;
|
||||
border-color: var(--primary-color) !important;
|
||||
color: #ffffff !important;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
box-shadow: 0 2px 4px var(--primary-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-primary:hover {
|
||||
background: var(--primary-color-hover) !important;
|
||||
border-color: var(--primary-color-hover) !important;
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 4px 8px var(--primary-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-primary:active {
|
||||
background: var(--primary-color-pressed) !important;
|
||||
border-color: var(--primary-color-pressed) !important;
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary:hover {
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.12) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary:active {
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
.action-btn-danger {
|
||||
border-color: var(--error-color) !important;
|
||||
color: var(--error-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-danger:hover {
|
||||
border-color: var(--error-color-hover) !important;
|
||||
color: var(--error-color-hover) !important;
|
||||
background: var(--error-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-warning {
|
||||
border-color: var(--warning-color) !important;
|
||||
color: var(--warning-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-warning:hover {
|
||||
border-color: var(--warning-color-hover) !important;
|
||||
color: var(--warning-color-hover) !important;
|
||||
background: var(--warning-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-info {
|
||||
border-color: var(--info-color) !important;
|
||||
color: var(--info-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-info:hover {
|
||||
border-color: var(--info-color-hover) !important;
|
||||
color: var(--info-color-hover) !important;
|
||||
background: var(--info-color-suppl) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1031,7 +1031,6 @@ const columns: DataTableColumns<UserVo> = [
|
||||
buttons.push(h(NButton, {
|
||||
type: 'primary',
|
||||
size: 'small',
|
||||
class: 'action-btn-primary',
|
||||
onClick: () => handleEdit(row),
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
@ -1051,9 +1050,7 @@ const columns: DataTableColumns<UserVo> = [
|
||||
default: () => '确定删除此用户吗?',
|
||||
trigger: () => h(NButton, {
|
||||
type: 'error',
|
||||
secondary: true,
|
||||
size: 'small',
|
||||
class: 'action-btn-secondary action-btn-danger',
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
default: () => h(IconParkOutlineDelete),
|
||||
@ -1067,9 +1064,7 @@ const columns: DataTableColumns<UserVo> = [
|
||||
if (hasButton(PERMISSIONS.USER.RESET_PWD)) {
|
||||
buttons.push(h(NButton, {
|
||||
type: 'warning',
|
||||
secondary: true,
|
||||
size: 'small',
|
||||
class: 'action-btn-secondary action-btn-warning',
|
||||
onClick: () => handleResetPassword(row),
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
@ -1083,9 +1078,7 @@ const columns: DataTableColumns<UserVo> = [
|
||||
if (hasButton(PERMISSIONS.USER.ROLE)) {
|
||||
buttons.push(h(NButton, {
|
||||
type: 'info',
|
||||
secondary: true,
|
||||
size: 'small',
|
||||
class: 'action-btn-secondary action-btn-info',
|
||||
onClick: () => handleAssignRole(row),
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14, style: 'transform: translateY(-1px)' }, {
|
||||
@ -1095,7 +1088,7 @@ const columns: DataTableColumns<UserVo> = [
|
||||
}))
|
||||
}
|
||||
|
||||
return h('div', { class: 'flex items-center justify-center gap-2' }, buttons)
|
||||
return h('div', { class: 'flex items-center justify-center gap-1' }, buttons)
|
||||
},
|
||||
},
|
||||
]
|
||||
@ -2264,73 +2257,4 @@ onBeforeUnmount(() => {
|
||||
min-height: 30px !important;
|
||||
line-height: 30px !important;
|
||||
}
|
||||
|
||||
/* 统一按钮样式系统 - 支持动态主题色彩 */
|
||||
.action-btn-primary {
|
||||
background: var(--primary-color) !important;
|
||||
border-color: var(--primary-color) !important;
|
||||
color: #ffffff !important;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
box-shadow: 0 2px 4px var(--primary-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-primary:hover {
|
||||
background: var(--primary-color-hover) !important;
|
||||
border-color: var(--primary-color-hover) !important;
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 4px 8px var(--primary-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-primary:active {
|
||||
background: var(--primary-color-pressed) !important;
|
||||
border-color: var(--primary-color-pressed) !important;
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary:hover {
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.12) !important;
|
||||
}
|
||||
|
||||
.action-btn-secondary:active {
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
.action-btn-danger {
|
||||
border-color: var(--error-color) !important;
|
||||
color: var(--error-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-danger:hover {
|
||||
border-color: var(--error-color-hover) !important;
|
||||
color: var(--error-color-hover) !important;
|
||||
background: var(--error-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-warning {
|
||||
border-color: var(--warning-color) !important;
|
||||
color: var(--warning-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-warning:hover {
|
||||
border-color: var(--warning-color-hover) !important;
|
||||
color: var(--warning-color-hover) !important;
|
||||
background: var(--warning-color-suppl) !important;
|
||||
}
|
||||
|
||||
.action-btn-info {
|
||||
border-color: var(--info-color) !important;
|
||||
color: var(--info-color) !important;
|
||||
}
|
||||
|
||||
.action-btn-info:hover {
|
||||
border-color: var(--info-color-hover) !important;
|
||||
color: var(--info-color-hover) !important;
|
||||
background: var(--info-color-suppl) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user