Compare commits

..

No commits in common. "340eaf5bb9917cac45dc3d89e44c5e5bd9415a80" and "c733dc5ffe2e2d507ee416d9cb900bf75c2aa13b" have entirely different histories.

14 changed files with 36 additions and 3029 deletions

222
CLAUDE.md
View File

@ -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'
// ... 其他属性
}))
```
**严格遵循用户管理页面的按钮样式标准,确保所有表格操作按钮具有统一的外观和交互体验!**
## 📝 表单布局紧凑设计规范(强制要求)
**所有新创建的表单页面和弹框表单都必须严格遵循紧凑布局设计规范,确保界面的一致性和空间利用率**

View File

@ -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',

View File

@ -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')
}

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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>

View File

@ -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;

View File

@ -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>

View File

@ -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>

View File

@ -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>