Compare commits
No commits in common. "75560e0c4b7ec4c4eb533c7d678c8cff1dcc0970" and "340eaf5bb9917cac45dc3d89e44c5e5bd9415a80" have entirely different histories.
75560e0c4b
...
340eaf5bb9
92
CLAUDE.md
92
CLAUDE.md
@ -59,14 +59,6 @@ pnpm sizecheck
|
||||
- **UnoCSS 66.2.0** 原子化CSS
|
||||
- **Alova 3.3.2** HTTP客户端
|
||||
|
||||
## 开发规范补充
|
||||
|
||||
### 字典数据使用约定
|
||||
- **严禁写死常量**:凡是来源于数据库的枚举/状态类数据(启用/停用、成功/失败、性别、角色/菜单/文件服务等),必须通过后端字典接口 `listDataByType` 获取,不得在前端硬编码。
|
||||
- **统一入口**:所有页面应使用 `useDict`/`useDictStore`、`DictTag` 与 `getSelectOptions` 等工具消费字典,确保新增字典值时页面自动生效。
|
||||
- **缓存刷新**:对字典类型或字典数据进行增删改操作后,务必调用 `dictStore.invalidate` 失效前端缓存,确保其它模块能够获取最新字典信息。
|
||||
- **新增字典类型**:若发现现有字典无法覆盖业务,需要先在后端补充字典类型及数据,再在前端按照上述方式接入,不得继续使用手工数组。
|
||||
|
||||
### 目录结构要点
|
||||
```
|
||||
src/
|
||||
@ -175,7 +167,7 @@ src/
|
||||
## ⚠️ 严格禁止事项
|
||||
|
||||
**1. 消息提示**
|
||||
```text
|
||||
```typescript
|
||||
// ❌ 严禁使用Element Plus原生消息
|
||||
// ✅ 必须使用项目封装的消息函数
|
||||
import { coiMsgError, coiMsgSuccess } from '@/utils/coi.ts'
|
||||
@ -187,7 +179,7 @@ coiMsgError('操作失败')
|
||||
```
|
||||
|
||||
**2. 类型定义**
|
||||
```text
|
||||
```typescript
|
||||
// ❌ 严禁使用any类型
|
||||
const response: any = await getList()
|
||||
|
||||
@ -196,7 +188,7 @@ const response: Result<ResponseVo[]> = await getList()
|
||||
```
|
||||
|
||||
**3. 数据访问**
|
||||
```text
|
||||
```typescript
|
||||
// ❌ 错误的数据访问
|
||||
const data = response
|
||||
|
||||
@ -1237,10 +1229,74 @@ import { coiMsgError, coiMsgSuccess, coiMsgWarning } from '@/utils/coi'
|
||||
### 标准按钮实现规范
|
||||
|
||||
**✅ 正确的表格操作列按钮实现方式**:
|
||||
- 编辑按钮:`type: 'primary'`、`size: 'small'`、`class: 'action-btn-primary'`,图标固定为 `IconParkOutlineEdit`。
|
||||
- 删除按钮:使用 `NPopconfirm` 包裹 `type: 'error'`、`secondary: true`、`size: 'small'` 的按钮,图标用 `IconParkOutlineDelete`,确认文案统一为“确定删除此记录吗?”。
|
||||
- 其它功能按钮:根据语义选择 `type: 'warning'`、`'info'` 等,并附加 `secondary: true` 与对应语义图标(如 `IconParkOutlineSetting`)。
|
||||
- 操作列返回 `div.flex.items-center.justify-center.gap-2` 布局,按钮顺序遵循“编辑→删除→其他”。
|
||||
```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)
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 按钮样式核心标准
|
||||
|
||||
@ -1325,7 +1381,7 @@ const buttons = []
|
||||
buttons.push(h(NButton, {
|
||||
type: 'primary',
|
||||
size: 'small',
|
||||
class: 'action-btn-primary',
|
||||
class: 'action-btn-primary'
|
||||
// ... 其他属性
|
||||
}))
|
||||
|
||||
@ -1336,7 +1392,7 @@ buttons.push(h(NPopconfirm, {
|
||||
type: 'error',
|
||||
secondary: true,
|
||||
size: 'small',
|
||||
class: 'action-btn-secondary action-btn-danger',
|
||||
class: 'action-btn-secondary action-btn-danger'
|
||||
// ... 其他属性
|
||||
})
|
||||
}))
|
||||
@ -1346,7 +1402,7 @@ buttons.push(h(NButton, {
|
||||
type: 'info',
|
||||
secondary: true,
|
||||
size: 'small',
|
||||
class: 'action-btn-secondary action-btn-info',
|
||||
class: 'action-btn-secondary action-btn-info'
|
||||
// ... 其他属性
|
||||
}))
|
||||
```
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
<template>
|
||||
<n-tag
|
||||
v-if="option"
|
||||
:type="tagType"
|
||||
:style="tagStyle"
|
||||
:size="size"
|
||||
:bordered="!option.dictColor"
|
||||
class="dict-tag"
|
||||
>
|
||||
<slot :option="option" :label="option.dictLabel">
|
||||
{{ option.dictLabel }}
|
||||
</slot>
|
||||
</n-tag>
|
||||
<span v-else class="dict-tag__placeholder">
|
||||
<slot name="placeholder" :value="value">
|
||||
{{ fallbackLabel }}
|
||||
</slot>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { TagProps } from 'naive-ui'
|
||||
import { useDict } from '@/hooks'
|
||||
|
||||
type DictValue = string | number | boolean | null | undefined
|
||||
|
||||
interface DictTagProps {
|
||||
dictType: string
|
||||
value: DictValue
|
||||
size?: NonNullable<TagProps['size']>
|
||||
fallbackLabel?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<DictTagProps>(), {
|
||||
size: 'small',
|
||||
fallbackLabel: '-',
|
||||
})
|
||||
|
||||
const { getDictOption, getDictLabel } = useDict(computed(() => [props.dictType]))
|
||||
|
||||
const option = computed(() => getDictOption(props.dictType, props.value))
|
||||
|
||||
const tagType = computed<TagProps['type']>(() => {
|
||||
if (!option.value)
|
||||
return 'default'
|
||||
|
||||
return option.value.dictColor ? 'default' : (option.value.dictTag as TagProps['type']) ?? 'default'
|
||||
})
|
||||
|
||||
const tagStyle = computed(() => {
|
||||
if (!option.value?.dictColor)
|
||||
return undefined
|
||||
|
||||
return {
|
||||
borderColor: option.value.dictColor,
|
||||
backgroundColor: option.value.dictColor,
|
||||
color: '#fff',
|
||||
} satisfies Record<string, string>
|
||||
})
|
||||
|
||||
const fallbackLabel = computed(() => getDictLabel(props.dictType, props.value, props.fallbackLabel))
|
||||
|
||||
const value = computed(() => props.value)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dict-tag__placeholder {
|
||||
color: var(--n-text-color);
|
||||
}
|
||||
</style>
|
||||
@ -1,3 +1,2 @@
|
||||
export * from './useBoolean'
|
||||
export * from './usePermission'
|
||||
export * from './useDict'
|
||||
|
||||
@ -1,75 +0,0 @@
|
||||
import type { MaybeRef } from 'vue'
|
||||
import { computed, unref, watch } from 'vue'
|
||||
import { toSelectOptions } from '@/utils/dict'
|
||||
import { useDictStore } from '@/store'
|
||||
import type { DictDataOption } from '@/service/api/system/dict'
|
||||
|
||||
interface UseDictOptions {
|
||||
/** 是否在创建时立即加载,默认为 true */
|
||||
immediate?: boolean
|
||||
/** 监听类型变化时是否强制刷新 */
|
||||
force?: boolean
|
||||
}
|
||||
|
||||
export function useDict(dictTypes: MaybeRef<string[] | undefined>, options: UseDictOptions = {}) {
|
||||
const dictStore = useDictStore()
|
||||
|
||||
const normalizedTypes = computed(() => {
|
||||
const value = unref(dictTypes) ?? []
|
||||
return value.filter((item): item is string => Boolean(item))
|
||||
})
|
||||
|
||||
const load = async (force = false) => {
|
||||
const types = normalizedTypes.value
|
||||
if (!types.length)
|
||||
return
|
||||
|
||||
await dictStore.fetchDicts(types, force)
|
||||
}
|
||||
|
||||
watch(
|
||||
normalizedTypes,
|
||||
(types) => {
|
||||
if (!types.length)
|
||||
return
|
||||
void dictStore.fetchDicts(types, options.force ?? false)
|
||||
},
|
||||
{ immediate: options.immediate ?? true },
|
||||
)
|
||||
|
||||
const dictOptions = computed<Record<string, DictDataOption[]>>(() => {
|
||||
const result: Record<string, DictDataOption[]> = {}
|
||||
normalizedTypes.value.forEach((type) => {
|
||||
result[type] = dictStore.getDictOptions(type)
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
const isLoading = computed(() => normalizedTypes.value.some(type => dictStore.isLoading(type)))
|
||||
|
||||
const getDictLabel = (dictType: string, value: unknown, fallback?: string) =>
|
||||
dictStore.getDictLabel(dictType, value, fallback)
|
||||
|
||||
const getDictOption = (dictType: string, value: unknown) =>
|
||||
dictStore.getDictOption(dictType, value)
|
||||
|
||||
const getSelectOptions = (dictType: string) => toSelectOptions(dictStore.getDictOptions(dictType))
|
||||
|
||||
const reload = async (targetTypes?: string[]) => {
|
||||
const types = targetTypes && targetTypes.length ? targetTypes : normalizedTypes.value
|
||||
if (!types.length)
|
||||
return
|
||||
|
||||
await dictStore.fetchDicts(types, true)
|
||||
}
|
||||
|
||||
return {
|
||||
dictOptions,
|
||||
isLoading,
|
||||
load,
|
||||
reload,
|
||||
getDictLabel,
|
||||
getDictOption,
|
||||
getSelectOptions,
|
||||
}
|
||||
}
|
||||
@ -1,102 +0,0 @@
|
||||
import { getDictDataByType } from '@/service/api/system/dict'
|
||||
import type { DictDataOption } from '@/service/api/system/dict'
|
||||
|
||||
type DictValue = string | number | boolean | null | undefined
|
||||
|
||||
export const useDictStore = defineStore('dict-store', () => {
|
||||
const dictMap = ref<Record<string, DictDataOption[]>>({})
|
||||
const loadingMap = ref<Record<string, boolean>>({})
|
||||
const pendingMap: Record<string, Promise<DictDataOption[]>> = {}
|
||||
|
||||
function getDictOptions(dictType: string) {
|
||||
return dictMap.value[dictType] ?? []
|
||||
}
|
||||
|
||||
function getDictOption(dictType: string, value: DictValue) {
|
||||
if (value === undefined || value === null)
|
||||
return undefined
|
||||
|
||||
const target = String(value)
|
||||
return getDictOptions(dictType).find(option => option.dictValue === target)
|
||||
}
|
||||
|
||||
function getDictLabel(dictType: string, value: DictValue, fallback?: string) {
|
||||
const option = getDictOption(dictType, value)
|
||||
if (option)
|
||||
return option.dictLabel
|
||||
|
||||
if (fallback !== undefined)
|
||||
return fallback
|
||||
|
||||
if (value === undefined || value === null || value === '')
|
||||
return ''
|
||||
|
||||
return String(value)
|
||||
}
|
||||
|
||||
async function fetchDict(dictType: string, force = false) {
|
||||
if (!dictType)
|
||||
return [] as DictDataOption[]
|
||||
|
||||
if (!force && dictMap.value[dictType])
|
||||
return dictMap.value[dictType]
|
||||
|
||||
if (!force && pendingMap[dictType])
|
||||
return pendingMap[dictType]
|
||||
|
||||
const promise = (async () => {
|
||||
loadingMap.value[dictType] = true
|
||||
try {
|
||||
const { isSuccess, data } = await getDictDataByType(dictType)
|
||||
if (isSuccess && Array.isArray(data))
|
||||
dictMap.value[dictType] = data
|
||||
else if (!dictMap.value[dictType])
|
||||
dictMap.value[dictType] = []
|
||||
|
||||
return dictMap.value[dictType]
|
||||
}
|
||||
finally {
|
||||
loadingMap.value[dictType] = false
|
||||
delete pendingMap[dictType]
|
||||
}
|
||||
})()
|
||||
|
||||
if (!force)
|
||||
pendingMap[dictType] = promise
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
async function fetchDicts(dictTypes: string[], force = false) {
|
||||
const uniqueTypes = Array.from(new Set(dictTypes.filter(Boolean)))
|
||||
await Promise.all(uniqueTypes.map(type => fetchDict(type, force)))
|
||||
}
|
||||
|
||||
function isLoading(dictType: string) {
|
||||
return Boolean(loadingMap.value[dictType])
|
||||
}
|
||||
|
||||
function invalidate(dictType?: string) {
|
||||
if (dictType) {
|
||||
delete dictMap.value[dictType]
|
||||
delete loadingMap.value[dictType]
|
||||
delete pendingMap[dictType]
|
||||
}
|
||||
else {
|
||||
dictMap.value = {}
|
||||
loadingMap.value = {}
|
||||
Object.keys(pendingMap).forEach(key => delete pendingMap[key])
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dictMap,
|
||||
fetchDict,
|
||||
fetchDicts,
|
||||
getDictOptions,
|
||||
getDictOption,
|
||||
getDictLabel,
|
||||
isLoading,
|
||||
invalidate,
|
||||
}
|
||||
})
|
||||
@ -5,7 +5,6 @@ export * from './app/index'
|
||||
export * from './auth'
|
||||
export * from './router'
|
||||
export * from './tab'
|
||||
export * from './dict'
|
||||
|
||||
// 安装pinia全局状态库
|
||||
export function installPinia(app: App) {
|
||||
|
||||
@ -1,65 +0,0 @@
|
||||
import type { DictDataOption } from '@/service/api/system/dict'
|
||||
|
||||
export type DictValue = string | number | boolean | null | undefined
|
||||
|
||||
/**
|
||||
* 将字典选项转换为 Naive UI Select 所需结构
|
||||
*/
|
||||
export function toSelectOptions(dictOptions: DictDataOption[] = []) {
|
||||
return dictOptions.map(option => ({
|
||||
label: option.dictLabel,
|
||||
value: option.dictValue,
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过字典值获取对应的完整选项对象
|
||||
*/
|
||||
export function findDictOption(dictOptions: DictDataOption[] = [], value: DictValue) {
|
||||
if (value === undefined || value === null)
|
||||
return undefined
|
||||
|
||||
const target = String(value)
|
||||
return dictOptions.find(option => option.dictValue === target)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典标签,找不到时返回默认值或原始值
|
||||
*/
|
||||
export function findDictLabel(dictOptions: DictDataOption[] = [], value: DictValue, fallback?: string) {
|
||||
const target = findDictOption(dictOptions, value)
|
||||
if (target)
|
||||
return target.dictLabel
|
||||
|
||||
if (fallback !== undefined)
|
||||
return fallback
|
||||
|
||||
if (value === undefined || value === null || value === '')
|
||||
return ''
|
||||
|
||||
return String(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典颜色配置
|
||||
*/
|
||||
export function findDictColor(dictOptions: DictDataOption[] = [], value: DictValue) {
|
||||
const target = findDictOption(dictOptions, value)
|
||||
if (!target)
|
||||
return undefined
|
||||
|
||||
return {
|
||||
tag: target.dictTag,
|
||||
color: target.dictColor,
|
||||
label: target.dictLabel,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字典数组转为值 -> 选项的 Map,方便重复查询
|
||||
*/
|
||||
export function createDictMap(dictOptions: DictDataOption[] = []) {
|
||||
const map = new Map<string, DictDataOption>()
|
||||
dictOptions.forEach(option => map.set(option.dictValue, option))
|
||||
return map
|
||||
}
|
||||
@ -97,7 +97,13 @@
|
||||
|
||||
<div class="flex justify-between items-center py-2">
|
||||
<span class="text-gray-600">状态</span>
|
||||
<DictTag dict-type="sys_switch_status" :value="personalData.userStatus || '0'" />
|
||||
<n-tag
|
||||
:type="(personalData.userStatus || '0') === '0' ? 'info' : 'error'"
|
||||
:style="(personalData.userStatus || '0') === '0' ? { backgroundColor: '#6366f1', color: 'white', border: 'none' } : {}"
|
||||
size="small"
|
||||
>
|
||||
{{ getStatusText(personalData.userStatus || '0') }}
|
||||
</n-tag>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center py-2">
|
||||
@ -254,14 +260,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
import { coiMsgError, coiMsgSuccess, coiMsgWarning } from '@/utils/coi'
|
||||
import { getPersonalData, updateBasicData, updatePassword, uploadAvatar } from '@/service/api/personal'
|
||||
import type { PersonalDataVo, UpdatePasswordBo, UpdatePersonalBo } from '@/service/api/personal'
|
||||
import { serviceConfig } from '@/../service.config'
|
||||
import DictTag from '@/components/common/DictTag.vue'
|
||||
import { useDict } from '@/hooks'
|
||||
|
||||
// 获取用户信息
|
||||
const authStore = useAuthStore()
|
||||
@ -339,12 +343,29 @@ const passwordRules = {
|
||||
],
|
||||
}
|
||||
|
||||
const { getSelectOptions, getDictLabel } = useDict(['sys_user_sex', 'sys_switch_status'])
|
||||
// 性别选项
|
||||
const genderOptions = [
|
||||
{ label: '男', value: '1' },
|
||||
{ label: '女', value: '2' },
|
||||
{ label: '未知', value: '3' },
|
||||
]
|
||||
|
||||
const genderOptions = computed(() => getSelectOptions('sys_user_sex'))
|
||||
// 状态选项
|
||||
const statusOptions = [
|
||||
{ label: '启用', value: '0' },
|
||||
{ label: '停用', value: '1' },
|
||||
]
|
||||
|
||||
// 获取性别显示文字
|
||||
function getGenderText(sex: string) {
|
||||
return getDictLabel('sys_user_sex', sex, '未知')
|
||||
const option = genderOptions.find(item => item.value === sex)
|
||||
return option?.label || '未知'
|
||||
}
|
||||
|
||||
// 获取状态显示文字
|
||||
function getStatusText(status: string) {
|
||||
const option = statusOptions.find(item => item.value === status)
|
||||
return option?.label || '未知'
|
||||
}
|
||||
|
||||
// 获取角色名称列表
|
||||
|
||||
@ -327,8 +327,7 @@ import { NButton, NIcon, NInputNumber, NPopconfirm, NSpace, NSwitch, NTag } from
|
||||
import CoiDialog from '@/components/common/CoiDialog.vue'
|
||||
import CoiEmpty from '@/components/common/CoiEmpty.vue'
|
||||
import CoiPagination from '@/components/common/CoiPagination.vue'
|
||||
import { useDict, usePermission } from '@/hooks'
|
||||
import { useDictStore } from '@/store'
|
||||
import { usePermission } from '@/hooks/usePermission'
|
||||
import { coiMsgBox, coiMsgError, coiMsgSuccess, coiMsgWarning } from '@/utils/coi'
|
||||
import {
|
||||
addDictData,
|
||||
@ -375,9 +374,6 @@ const currentDictType = ref<DictTypeOption | null>(null)
|
||||
const dictTypeOptions = ref<{ label: string, value: string }[]>([])
|
||||
const isFromTypeClick = ref(false)
|
||||
|
||||
const dictStore = useDictStore()
|
||||
const { getSelectOptions, getDictLabel } = useDict(['sys_switch_status', 'sys_tag_type'])
|
||||
|
||||
// 搜索表单数据
|
||||
const searchForm = reactive<DictDataSearchForm>({
|
||||
dictType: '',
|
||||
@ -418,8 +414,18 @@ function handlePageSizeChange(pageSize: number) {
|
||||
}
|
||||
|
||||
// 状态选项
|
||||
const statusOptions = computed(() => getSelectOptions('sys_switch_status'))
|
||||
const tagOptions = computed(() => getSelectOptions('sys_tag_type'))
|
||||
const statusOptions = [
|
||||
{ label: '正常', value: DictStatus.NORMAL },
|
||||
{ label: '停用', value: DictStatus.DISABLED },
|
||||
]
|
||||
|
||||
// 标签类型选项
|
||||
const tagOptions = [
|
||||
{ label: 'primary', value: DictTag.PRIMARY },
|
||||
{ label: 'success', value: DictTag.SUCCESS },
|
||||
{ label: 'info', value: DictTag.INFO },
|
||||
{ label: 'warning', value: DictTag.WARNING },
|
||||
]
|
||||
|
||||
// 表单验证规则
|
||||
const rules: FormRules = {
|
||||
@ -544,7 +550,7 @@ const columns: DataTableColumns<DictDataVo> = [
|
||||
negativeText: '取消',
|
||||
positiveText: '确定',
|
||||
}, {
|
||||
default: () => `确定要将字典数据「${row.dictLabel}」状态切换为「${getDictLabel('sys_switch_status', row.dictStatus === DictStatus.NORMAL ? '1' : '0')}」吗?`,
|
||||
default: () => `确定要${row.dictStatus === DictStatus.NORMAL ? '停用' : '启用'}字典数据「${row.dictLabel}」吗?`,
|
||||
trigger: () => h(NSwitch, {
|
||||
value: row.dictStatus === DictStatus.NORMAL,
|
||||
size: 'small',
|
||||
@ -816,7 +822,6 @@ async function handleDelete(row: DictDataVo) {
|
||||
|
||||
if (isSuccess) {
|
||||
coiMsgSuccess('删除成功')
|
||||
dictStore.invalidate(row.dictType)
|
||||
getTableData()
|
||||
}
|
||||
else {
|
||||
@ -844,8 +849,6 @@ async function handleBatchDelete() {
|
||||
coiMsgSuccess('批量删除成功')
|
||||
checkedRowKeys.value = []
|
||||
selectedRows.value = []
|
||||
if (searchForm.dictType)
|
||||
dictStore.invalidate(searchForm.dictType)
|
||||
getTableData()
|
||||
}
|
||||
else {
|
||||
@ -865,9 +868,7 @@ async function handleStatusChange(row: DictDataVo) {
|
||||
const { isSuccess } = await updateDictDataStatus(row.dictId, newStatus)
|
||||
|
||||
if (isSuccess) {
|
||||
const statusLabel = getDictLabel('sys_switch_status', newStatus, newStatus === DictStatus.NORMAL ? '启用' : '停用')
|
||||
coiMsgSuccess(`状态已更新为「${statusLabel}」`)
|
||||
dictStore.invalidate(row.dictType)
|
||||
coiMsgSuccess('状态修改成功')
|
||||
getTableData()
|
||||
}
|
||||
else {
|
||||
@ -887,7 +888,6 @@ async function handleSyncCache() {
|
||||
|
||||
if (isSuccess) {
|
||||
coiMsgSuccess('缓存同步成功')
|
||||
dictStore.invalidate()
|
||||
}
|
||||
else {
|
||||
coiMsgError('缓存同步失败')
|
||||
@ -936,8 +936,6 @@ async function handleSubmit() {
|
||||
if (isSuccess) {
|
||||
coiMsgSuccess(isEdit.value ? '修改成功' : '新增成功')
|
||||
formDialogRef.value?.coiClose()
|
||||
if (submitData.dictType)
|
||||
dictStore.invalidate(submitData.dictType)
|
||||
getTableData()
|
||||
}
|
||||
else {
|
||||
|
||||
@ -251,8 +251,7 @@ 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 { useDict, usePermission } from '@/hooks'
|
||||
import { useDictStore } from '@/store'
|
||||
import { usePermission } from '@/hooks/usePermission'
|
||||
import { coiMsgBox, coiMsgError, coiMsgSuccess, coiMsgWarning } from '@/utils/coi'
|
||||
import {
|
||||
addDictType,
|
||||
@ -277,7 +276,6 @@ import IconParkOutlineEdit from '~icons/icon-park-outline/edit'
|
||||
// 路由和权限验证
|
||||
const router = useRouter()
|
||||
const { hasPermission } = usePermission()
|
||||
const { getSelectOptions, getDictLabel } = useDict(['sys_switch_status'])
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
@ -289,7 +287,6 @@ const formRef = ref<FormInst>()
|
||||
const formDialogRef = ref()
|
||||
const isEdit = ref(false)
|
||||
const modalTitle = ref('')
|
||||
const dictStore = useDictStore()
|
||||
|
||||
// 搜索表单数据
|
||||
const searchForm = reactive<DictTypeSearchForm>({
|
||||
@ -327,7 +324,10 @@ function handlePageSizeChange(pageSize: number) {
|
||||
}
|
||||
|
||||
// 状态选项
|
||||
const statusOptions = computed(() => getSelectOptions('sys_switch_status'))
|
||||
const statusOptions = [
|
||||
{ label: '正常', value: DictStatus.NORMAL },
|
||||
{ label: '停用', value: DictStatus.DISABLED },
|
||||
]
|
||||
|
||||
// 表单验证规则
|
||||
const rules: FormRules = {
|
||||
@ -411,12 +411,10 @@ const columns: DataTableColumns<DictTypeVo> = [
|
||||
negativeText: '取消',
|
||||
positiveText: '确定',
|
||||
}, {
|
||||
default: () => `确定要将字典类型「${row.dictName}」状态切换为「${getDictLabel('sys_switch_status', row.dictStatus === DictStatus.NORMAL ? '1' : '0')}」吗?`,
|
||||
default: () => `确定要${row.dictStatus === DictStatus.NORMAL ? '停用' : '启用'}字典类型「${row.dictName}」吗?`,
|
||||
trigger: () => h(NSwitch, {
|
||||
value: row.dictStatus === DictStatus.NORMAL,
|
||||
size: 'small',
|
||||
checkedChildren: getDictLabel('sys_switch_status', '0', '启用'),
|
||||
uncheckedChildren: getDictLabel('sys_switch_status', '1', '停用'),
|
||||
}),
|
||||
}),
|
||||
])
|
||||
@ -597,7 +595,6 @@ async function handleDelete(row: DictTypeVo) {
|
||||
|
||||
if (isSuccess) {
|
||||
coiMsgSuccess('删除成功')
|
||||
dictStore.invalidate()
|
||||
getTableData()
|
||||
}
|
||||
else {
|
||||
@ -625,7 +622,6 @@ async function handleBatchDelete() {
|
||||
coiMsgSuccess('批量删除成功')
|
||||
checkedRowKeys.value = []
|
||||
selectedRows.value = []
|
||||
dictStore.invalidate()
|
||||
getTableData()
|
||||
}
|
||||
else {
|
||||
@ -645,9 +641,7 @@ async function handleStatusChange(row: DictTypeVo) {
|
||||
const { isSuccess } = await updateDictTypeStatus(row.dictId, newStatus)
|
||||
|
||||
if (isSuccess) {
|
||||
const statusLabel = getDictLabel('sys_switch_status', newStatus, newStatus === DictStatus.NORMAL ? '启用' : '停用')
|
||||
coiMsgSuccess(`状态已更新为「${statusLabel}」`)
|
||||
dictStore.invalidate()
|
||||
coiMsgSuccess('状态修改成功')
|
||||
getTableData()
|
||||
}
|
||||
else {
|
||||
@ -667,7 +661,6 @@ async function handleSyncCache() {
|
||||
|
||||
if (isSuccess) {
|
||||
coiMsgSuccess('缓存同步成功')
|
||||
dictStore.invalidate()
|
||||
}
|
||||
else {
|
||||
coiMsgError('缓存同步失败')
|
||||
@ -711,7 +704,6 @@ async function handleSubmit() {
|
||||
if (isSuccess) {
|
||||
coiMsgSuccess(isEdit.value ? '修改成功' : '新增成功')
|
||||
formDialogRef.value?.coiClose()
|
||||
dictStore.invalidate()
|
||||
getTableData()
|
||||
}
|
||||
else {
|
||||
|
||||
@ -72,7 +72,7 @@
|
||||
v-model:value="searchForm.fileService"
|
||||
placeholder="请选择存储类型"
|
||||
clearable
|
||||
:options="getSelectOptions('sys_file_service')"
|
||||
:options="FILE_SERVICE_DB_OPTIONS"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
@ -215,7 +215,11 @@
|
||||
<n-select
|
||||
v-model:value="uploadForm.fileService"
|
||||
placeholder="请选择文件服务"
|
||||
:options="getSelectOptions('sys_file_service')"
|
||||
:options="[
|
||||
{ label: 'LOCAL', value: 'LOCAL' },
|
||||
{ label: 'OSS', value: 'OSS' },
|
||||
{ label: 'MINIO', value: 'MINIO' },
|
||||
]"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
@ -280,8 +284,7 @@ import CoiEmpty from '@/components/common/CoiEmpty.vue'
|
||||
import CoiPagination from '@/components/common/CoiPagination.vue'
|
||||
import CoiImageViewer from '@/components/common/CoiImageViewer.vue'
|
||||
import { coiMsgBox, coiMsgError, coiMsgSuccess, coiMsgWarning } from '@/utils/coi'
|
||||
import DictTag from '@/components/common/DictTag.vue'
|
||||
import { useDict, usePermission } from '@/hooks'
|
||||
import { usePermission } from '@/hooks/usePermission'
|
||||
import { PERMISSIONS } from '@/constants/permissions'
|
||||
import {
|
||||
batchDeleteSysFiles,
|
||||
@ -292,6 +295,7 @@ import {
|
||||
import type { SysFileQueryBo, SysFileSearchForm, SysFileVo } from '@/service/api/system/file'
|
||||
import IconParkOutlineDelete from '~icons/icon-park-outline/delete'
|
||||
import IconParkOutlineDownload from '~icons/icon-park-outline/download'
|
||||
import { FILE_SERVICE_DB_OPTIONS } from '@/service/api/system/file/types'
|
||||
|
||||
// 文件分类图标组件
|
||||
const StarIcon = () => h('span', { class: 'text-yellow-500' }, '✨')
|
||||
@ -317,27 +321,6 @@ const fileCategories = [
|
||||
|
||||
// 权限验证
|
||||
const { hasPermission } = usePermission()
|
||||
const { getSelectOptions } = useDict(['sys_file_service'])
|
||||
|
||||
const storageValueToType: Record<string, string> = {
|
||||
1: 'LOCAL',
|
||||
2: 'MINIO',
|
||||
3: 'OSS',
|
||||
}
|
||||
|
||||
const storageTypeToValue: Record<string, string> = {
|
||||
LOCAL: '1',
|
||||
MINIO: '2',
|
||||
OSS: '3',
|
||||
}
|
||||
|
||||
function mapServiceValueToType(value: string | null | undefined) {
|
||||
const str = value != null ? String(value) : '1'
|
||||
if (storageValueToType[str])
|
||||
return storageValueToType[str]
|
||||
const upper = str.toUpperCase()
|
||||
return storageValueToType[storageTypeToValue[upper] ?? '1'] ?? 'LOCAL'
|
||||
}
|
||||
|
||||
// 响应式数据
|
||||
const searchFormRef = ref<FormInst>()
|
||||
@ -393,7 +376,7 @@ const isConfirmDisabled = computed(() => {
|
||||
|
||||
// 上传表单数据
|
||||
const uploadForm = ref({
|
||||
fileService: '1',
|
||||
fileService: 'LOCAL',
|
||||
filePath: '',
|
||||
})
|
||||
|
||||
@ -498,8 +481,19 @@ const columns: DataTableColumns<SysFileVo> = [
|
||||
title: '文件服务类型',
|
||||
key: 'fileService',
|
||||
align: 'center',
|
||||
width: 140,
|
||||
render: row => h(DictTag, { dictType: 'sys_file_service', value: row.fileService }),
|
||||
width: 120,
|
||||
render: (row) => {
|
||||
const serviceMap: Record<string, { type: 'success' | 'info' | 'warning', text: string }> = {
|
||||
1: { type: 'success', text: '本地存储' },
|
||||
2: { type: 'info', text: 'MinIO存储' },
|
||||
3: { type: 'warning', text: '阿里云OSS' },
|
||||
}
|
||||
const config = serviceMap[row.fileService] || { type: 'info', text: '未知' }
|
||||
return h(NTag, {
|
||||
type: config.type,
|
||||
size: 'small',
|
||||
}, { default: () => config.text })
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
@ -787,8 +781,7 @@ async function customUpload({ file, onProgress, onFinish, onError }: any) {
|
||||
onProgress({ percent: 10 })
|
||||
|
||||
// 调用上传API,传递选择的存储类型
|
||||
const storageType = mapServiceValueToType(uploadForm.value.fileService)
|
||||
const result = await uploadFile(fileObj, folderName, 2, '-1', storageType)
|
||||
const result = await uploadFile(fileObj, folderName, 2, '-1', uploadForm.value.fileService)
|
||||
|
||||
// 检查业务逻辑是否成功
|
||||
if (result.isSuccess === false) {
|
||||
|
||||
@ -51,7 +51,10 @@
|
||||
v-model:value="searchForm.loginStatus"
|
||||
placeholder="请选择登录状态"
|
||||
clearable
|
||||
:options="loginStatusOptions"
|
||||
:options="[
|
||||
{ label: '成功', value: '0' },
|
||||
{ label: '失败', value: '1' },
|
||||
]"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
@ -193,13 +196,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, h, nextTick, onMounted, ref } from 'vue'
|
||||
import { h, nextTick, onMounted, ref } from 'vue'
|
||||
import type { DataTableColumns, FormInst } from 'naive-ui'
|
||||
import { NButton, NIcon, NPopconfirm, NSpace } from 'naive-ui'
|
||||
import { NButton, NIcon, NPopconfirm, NSpace, NTag } from 'naive-ui'
|
||||
import IconParkOutlineDelete from '~icons/icon-park-outline/delete'
|
||||
import CoiEmpty from '@/components/common/CoiEmpty.vue'
|
||||
import CoiPagination from '@/components/common/CoiPagination.vue'
|
||||
import DictTag from '@/components/common/DictTag.vue'
|
||||
import {
|
||||
batchDeleteLoginLog,
|
||||
deleteLoginLog,
|
||||
@ -208,21 +210,10 @@ import {
|
||||
import type { LoginLogVo } from '@/service/api/system/loginlog'
|
||||
import { coiMsgBox, coiMsgError, coiMsgSuccess, coiMsgWarning } from '@/utils/coi'
|
||||
import { PERMISSIONS } from '@/constants/permissions'
|
||||
import { useDict, usePermission } from '@/hooks'
|
||||
import { usePermission } from '@/hooks/usePermission'
|
||||
|
||||
// 权限相关
|
||||
const { hasPermission } = usePermission()
|
||||
const { dictOptions } = useDict(['sys_common_status'])
|
||||
|
||||
const loginStatusOptions = computed(() => {
|
||||
const options = dictOptions.sys_common_status || []
|
||||
return options
|
||||
.filter(option => option.dictValue === '1' || option.dictValue === '2')
|
||||
.map(option => ({
|
||||
label: option.dictLabel,
|
||||
value: option.dictValue === '1' ? '0' : '1',
|
||||
}))
|
||||
})
|
||||
|
||||
// 搜索表单数据
|
||||
interface LoginLogSearchForm {
|
||||
@ -329,9 +320,17 @@ const columns: DataTableColumns<LoginLogVo> = [
|
||||
{
|
||||
title: '登录状态',
|
||||
key: 'loginStatus',
|
||||
width: 110,
|
||||
width: 100,
|
||||
align: 'center',
|
||||
render: row => h(DictTag, { dictType: 'sys_common_status', value: row.loginStatus === '0' ? '1' : '2' }),
|
||||
render: (row) => {
|
||||
const isSuccess = row.loginStatus === '0'
|
||||
return h(NTag, {
|
||||
type: isSuccess ? 'success' : 'error',
|
||||
size: 'small',
|
||||
}, {
|
||||
default: () => isSuccess ? '操作成功' : '操作失败',
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '登录信息',
|
||||
|
||||
@ -40,7 +40,10 @@
|
||||
v-model:value="searchForm.menuStatus"
|
||||
placeholder="请选择菜单状态"
|
||||
clearable
|
||||
:options="getSelectOptions('sys_switch_status')"
|
||||
:options="[
|
||||
{ label: '启用', value: '0' },
|
||||
{ label: '停用', value: '1' },
|
||||
]"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
@ -232,7 +235,7 @@
|
||||
:style="formData.parentId === '0' ? { color: themeColors.primary, fontWeight: '500' } : {}"
|
||||
@click="handleSelectParent('0', '最顶级菜单')"
|
||||
>
|
||||
<NRadio
|
||||
<n-radio
|
||||
:checked="formData.parentId === '0'"
|
||||
style="pointer-events: none;"
|
||||
/>
|
||||
@ -260,7 +263,7 @@
|
||||
@click="handleSelectParent(menu.value, menu.label)"
|
||||
@mouseenter="handleMenuHover(menu)"
|
||||
>
|
||||
<NRadio
|
||||
<n-radio
|
||||
:checked="formData.parentId === menu.value"
|
||||
style="pointer-events: none;"
|
||||
/>
|
||||
@ -300,7 +303,7 @@
|
||||
:style="formData.parentId === submenu.value ? { color: themeColors.primary, fontWeight: '500' } : {}"
|
||||
@click="handleSelectParent(submenu.value, submenu.label)"
|
||||
>
|
||||
<NRadio
|
||||
<n-radio
|
||||
:checked="formData.parentId === submenu.value"
|
||||
style="pointer-events: none;"
|
||||
/>
|
||||
@ -322,18 +325,20 @@
|
||||
|
||||
<!-- 菜单类型 -->
|
||||
<n-form-item label="菜单类型" path="menuType" class="mb-2">
|
||||
<NRadioGroup
|
||||
<n-radio-group
|
||||
v-model:value="formData.menuType"
|
||||
@update:value="handleMenuTypeChange"
|
||||
>
|
||||
<NRadio
|
||||
v-for="item in dictOptions.sys_menu_type || []"
|
||||
:key="item.dictValue"
|
||||
:value="item.dictValue"
|
||||
>
|
||||
{{ item.dictLabel }}
|
||||
</NRadio>
|
||||
</NRadioGroup>
|
||||
<n-radio value="1">
|
||||
目录
|
||||
</n-radio>
|
||||
<n-radio value="2">
|
||||
菜单
|
||||
</n-radio>
|
||||
<n-radio value="3">
|
||||
按钮
|
||||
</n-radio>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<!-- 菜单图标 -->
|
||||
@ -387,14 +392,14 @@
|
||||
<n-grid :cols="2" :x-gap="10" class="mb-2">
|
||||
<n-grid-item>
|
||||
<n-form-item label="是否隐藏" path="isHide">
|
||||
<NRadioGroup v-model:value="formData.isHide">
|
||||
<NRadio value="0">
|
||||
<n-radio-group v-model:value="formData.isHide">
|
||||
<n-radio value="0">
|
||||
是
|
||||
</NRadio>
|
||||
<NRadio value="1">
|
||||
</n-radio>
|
||||
<n-radio value="1">
|
||||
否
|
||||
</NRadio>
|
||||
</NRadioGroup>
|
||||
</n-radio>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
@ -433,14 +438,14 @@
|
||||
<n-grid :cols="2" :x-gap="10" class="mb-2">
|
||||
<n-grid-item>
|
||||
<n-form-item label="是否缓存" path="isKeepAlive">
|
||||
<NRadioGroup v-model:value="formData.isKeepAlive">
|
||||
<NRadio value="0">
|
||||
<n-radio-group v-model:value="formData.isKeepAlive">
|
||||
<n-radio value="0">
|
||||
是
|
||||
</NRadio>
|
||||
<NRadio value="1">
|
||||
</n-radio>
|
||||
<n-radio value="1">
|
||||
否
|
||||
</NRadio>
|
||||
</NRadioGroup>
|
||||
</n-radio>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
@ -457,26 +462,26 @@
|
||||
<n-grid :cols="2" :x-gap="10" class="mb-2">
|
||||
<n-grid-item>
|
||||
<n-form-item label="是否展开" path="isSpread">
|
||||
<NRadioGroup v-model:value="formData.isSpread">
|
||||
<NRadio value="0">
|
||||
<n-radio-group v-model:value="formData.isSpread">
|
||||
<n-radio value="0">
|
||||
是
|
||||
</NRadio>
|
||||
<NRadio value="1">
|
||||
</n-radio>
|
||||
<n-radio value="1">
|
||||
否
|
||||
</NRadio>
|
||||
</NRadioGroup>
|
||||
</n-radio>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="是否固钉" path="isAffix">
|
||||
<NRadioGroup v-model:value="formData.isAffix">
|
||||
<NRadio value="0">
|
||||
<n-radio-group v-model:value="formData.isAffix">
|
||||
<n-radio value="0">
|
||||
是
|
||||
</NRadio>
|
||||
<NRadio value="1">
|
||||
</n-radio>
|
||||
<n-radio value="1">
|
||||
否
|
||||
</NRadio>
|
||||
</NRadioGroup>
|
||||
</n-radio>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
@ -487,26 +492,26 @@
|
||||
<n-grid :cols="2" :x-gap="10" class="mb-2">
|
||||
<n-grid-item>
|
||||
<n-form-item label="是否展开" path="isSpread">
|
||||
<NRadioGroup v-model:value="formData.isSpread">
|
||||
<NRadio value="0">
|
||||
<n-radio-group v-model:value="formData.isSpread">
|
||||
<n-radio value="0">
|
||||
是
|
||||
</NRadio>
|
||||
<NRadio value="1">
|
||||
</n-radio>
|
||||
<n-radio value="1">
|
||||
否
|
||||
</NRadio>
|
||||
</NRadioGroup>
|
||||
</n-radio>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="是否固钉" path="isAffix">
|
||||
<NRadioGroup v-model:value="formData.isAffix">
|
||||
<NRadio value="0">
|
||||
<n-radio-group v-model:value="formData.isAffix">
|
||||
<n-radio value="0">
|
||||
是
|
||||
</NRadio>
|
||||
<NRadio value="1">
|
||||
</n-radio>
|
||||
<n-radio value="1">
|
||||
否
|
||||
</NRadio>
|
||||
</NRadioGroup>
|
||||
</n-radio>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
@ -530,7 +535,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, h, nextTick, onMounted, ref } from 'vue'
|
||||
import type { DataTableColumns, FormInst, FormRules } from 'naive-ui'
|
||||
import { NButton, NIcon, NPopconfirm, NRadio, NRadioGroup, NSpace, NSwitch, NTag } from 'naive-ui'
|
||||
import { NButton, NIcon, NPopconfirm, NSpace, NSwitch, NTag } from 'naive-ui'
|
||||
import IconParkOutlineDelete from '~icons/icon-park-outline/delete'
|
||||
import IconParkOutlineEdit from '~icons/icon-park-outline/edit'
|
||||
import IconParkOutlinePlus from '~icons/icon-park-outline/plus'
|
||||
@ -538,9 +543,8 @@ import CoiDialog from '@/components/common/CoiDialog.vue'
|
||||
import CoiEmpty from '@/components/common/CoiEmpty.vue'
|
||||
import CoiIcon from '@/components/common/CoiIcon.vue'
|
||||
import IconSelect from '@/components/common/IconSelect.vue'
|
||||
import DictTag from '@/components/common/DictTag.vue'
|
||||
import { PERMISSIONS } from '@/constants/permissions'
|
||||
import { useDict, usePermission } from '@/hooks'
|
||||
import { usePermission } from '@/hooks'
|
||||
import { coiMsgBox, coiMsgError, coiMsgSuccess, coiMsgWarning } from '@/utils/coi'
|
||||
import {
|
||||
addMenu,
|
||||
@ -583,11 +587,6 @@ const selectedRows = ref<MenuVo[]>([])
|
||||
const expandedKeys = ref<string[]>([])
|
||||
const isAllExpanded = ref(true)
|
||||
|
||||
const { dictOptions, getSelectOptions, getDictLabel } = useDict([
|
||||
'sys_menu_type',
|
||||
'sys_switch_status',
|
||||
])
|
||||
|
||||
// 弹框相关
|
||||
const menuDialogRef = ref()
|
||||
const formRef = ref<FormInst>()
|
||||
@ -717,7 +716,15 @@ const columns: DataTableColumns<MenuVo> = [
|
||||
key: 'menuType',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
render: row => h(DictTag, { dictType: 'sys_menu_type', value: row.menuType }),
|
||||
render: (row) => {
|
||||
const typeMap = {
|
||||
1: { label: '目录', color: 'primary' },
|
||||
2: { label: '菜单', color: 'info' },
|
||||
3: { label: '按钮', color: 'warning' },
|
||||
}
|
||||
const type = typeMap[Number(row.menuType) as keyof typeof typeMap]
|
||||
return h(NTag, { type: type.color }, { default: () => type.label })
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '展开/折叠',
|
||||
@ -811,8 +818,6 @@ const columns: DataTableColumns<MenuVo> = [
|
||||
render: (row) => {
|
||||
return h(NSwitch, {
|
||||
value: row.menuStatus === '0',
|
||||
checkedChildren: getDictLabel('sys_switch_status', '0', '启用'),
|
||||
uncheckedChildren: getDictLabel('sys_switch_status', '1', '停用'),
|
||||
onUpdateValue: value => handleStatusChange(row, value ? '0' : '1'),
|
||||
})
|
||||
},
|
||||
@ -1310,8 +1315,7 @@ async function handleStatusChange(menu: MenuVo, status: string) {
|
||||
// 更新本地数据的状态
|
||||
updateMenuStatusInData(tableData.value, menu.menuId, status)
|
||||
|
||||
const statusLabel = getDictLabel('sys_switch_status', status, status === '0' ? '启用' : '停用')
|
||||
coiMsgSuccess(`状态已更新为「${statusLabel}」`)
|
||||
coiMsgSuccess('状态修改成功')
|
||||
}
|
||||
catch {
|
||||
coiMsgError('状态修改失败')
|
||||
|
||||
@ -40,7 +40,10 @@
|
||||
v-model:value="searchForm.operStatus"
|
||||
placeholder="请选择操作状态"
|
||||
clearable
|
||||
:options="operStatusOptions"
|
||||
:options="[
|
||||
{ label: '操作成功', value: '0' },
|
||||
{ label: '操作失败', value: '1' },
|
||||
]"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
@ -230,7 +233,9 @@
|
||||
{{ currentLogDetail.costTime || '-' }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="操作状态">
|
||||
<DictTag dict-type="sys_common_status" :value="currentLogDetail.operStatus === '0' ? '1' : '2'" />
|
||||
<NTag :type="currentLogDetail.operStatus === '0' ? 'success' : 'error'" size="small">
|
||||
{{ currentLogDetail.operStatus === '0' ? '操作成功' : '操作失败' }}
|
||||
</NTag>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item v-if="currentLogDetail.operParam" label="请求参数" :span="2">
|
||||
<div class="json-container">
|
||||
@ -250,7 +255,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, h, nextTick, onMounted, ref } from 'vue'
|
||||
import { h, nextTick, onMounted, ref } from 'vue'
|
||||
import type { DataTableColumns, FormInst } from 'naive-ui'
|
||||
import { NButton, NIcon, NPopconfirm, NSpace, NTag } from 'naive-ui'
|
||||
import IconParkOutlineDelete from '~icons/icon-park-outline/delete'
|
||||
@ -258,7 +263,6 @@ import IconParkOutlinePreviewOpen from '~icons/icon-park-outline/preview-open'
|
||||
import CoiDialog from '@/components/common/CoiDialog.vue'
|
||||
import CoiEmpty from '@/components/common/CoiEmpty.vue'
|
||||
import CoiPagination from '@/components/common/CoiPagination.vue'
|
||||
import DictTag from '@/components/common/DictTag.vue'
|
||||
import {
|
||||
batchDeleteOperLog,
|
||||
clearOperLog,
|
||||
@ -269,21 +273,10 @@ import {
|
||||
import type { OperLogSearchForm, OperLogVo } from '@/service/api/system/operlog'
|
||||
import { coiMsgBox, coiMsgError, coiMsgSuccess, coiMsgWarning } from '@/utils/coi'
|
||||
import { PERMISSIONS } from '@/constants/permissions'
|
||||
import { useDict, usePermission } from '@/hooks'
|
||||
import { usePermission } from '@/hooks/usePermission'
|
||||
|
||||
// 权限相关
|
||||
const { hasButton } = usePermission()
|
||||
const { dictOptions } = useDict(['sys_oper_type', 'sys_common_status'])
|
||||
|
||||
const operStatusOptions = computed(() => {
|
||||
const options = dictOptions.sys_common_status || []
|
||||
return options
|
||||
.filter(option => option.dictValue === '1' || option.dictValue === '2')
|
||||
.map(option => ({
|
||||
label: option.dictLabel,
|
||||
value: option.dictValue === '1' ? '0' : '1',
|
||||
}))
|
||||
})
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
@ -346,9 +339,16 @@ const columns: DataTableColumns<OperLogVo> = [
|
||||
{
|
||||
title: '操作类型',
|
||||
key: 'operType',
|
||||
width: 110,
|
||||
width: 90,
|
||||
align: 'center',
|
||||
render: row => h(DictTag, { dictType: 'sys_oper_type', value: row.operType }),
|
||||
render: (row) => {
|
||||
return h(NTag, {
|
||||
type: 'primary',
|
||||
size: 'small',
|
||||
}, {
|
||||
default: () => row.operType,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作人员[登录名/用户名]',
|
||||
@ -419,7 +419,15 @@ const columns: DataTableColumns<OperLogVo> = [
|
||||
key: 'operStatus',
|
||||
width: 90,
|
||||
align: 'center',
|
||||
render: row => h(DictTag, { dictType: 'sys_common_status', value: row.operStatus === '0' ? '1' : '2' }),
|
||||
render: (row) => {
|
||||
const isSuccess = row.operStatus === '0'
|
||||
return h(NTag, {
|
||||
type: isSuccess ? 'success' : 'error',
|
||||
size: 'small',
|
||||
}, {
|
||||
default: () => isSuccess ? '操作成功' : '操作失败',
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作时间',
|
||||
|
||||
@ -72,7 +72,7 @@
|
||||
v-model:value="searchForm.pictureService"
|
||||
placeholder="请选择存储类型"
|
||||
clearable
|
||||
:options="getSelectOptions('sys_file_service')"
|
||||
:options="PICTURE_SERVICE_OPTIONS"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
@ -258,9 +258,7 @@
|
||||
<div><span class="text-gray-400">新名称:</span> {{ picture.newName }}</div>
|
||||
<div><span class="text-gray-400">文件大小:</span> {{ picture.pictureSize }}</div>
|
||||
<div><span class="text-gray-400">文件后缀:</span> {{ picture.pictureSuffix?.toUpperCase() }}</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="text-gray-400">服务类型:</span> <DictTag dict-type="sys_file_service" :value="mapStorageTypeToValue(picture.pictureService)" />
|
||||
</div>
|
||||
<div><span class="text-gray-400">服务类型:</span> {{ getPictureServiceText(picture.pictureService) }}</div>
|
||||
<div><span class="text-gray-400">创建时间:</span> {{ picture.createTime ? new Date(picture.createTime).toLocaleString() : '--' }}</div>
|
||||
<div><span class="text-gray-400">创建者:</span> {{ picture.createBy || '--' }}</div>
|
||||
</div>
|
||||
@ -350,7 +348,11 @@
|
||||
<n-select
|
||||
v-model:value="uploadForm.pictureService"
|
||||
placeholder="请选择服务类型"
|
||||
:options="getSelectOptions('sys_file_service')"
|
||||
:options="[
|
||||
{ label: 'LOCAL', value: 'LOCAL' },
|
||||
{ label: 'OSS', value: 'OSS' },
|
||||
{ label: 'MINIO', value: 'MINIO' },
|
||||
]"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
@ -415,13 +417,13 @@ import CoiDialog from '@/components/common/CoiDialog.vue'
|
||||
import CoiPagination from '@/components/common/CoiPagination.vue'
|
||||
import CoiImageViewer from '@/components/common/CoiImageViewer.vue'
|
||||
import { coiMsgBox, coiMsgError, coiMsgSuccess, coiMsgWarning } from '@/utils/coi'
|
||||
import DictTag from '@/components/common/DictTag.vue'
|
||||
import { useDict, usePermission } from '@/hooks'
|
||||
import { usePermission } from '@/hooks/usePermission'
|
||||
import { PERMISSIONS } from '@/constants/permissions'
|
||||
import {
|
||||
batchDeleteSysPictures,
|
||||
deleteSysPicture,
|
||||
getSysPictureList,
|
||||
PICTURE_SERVICE_OPTIONS,
|
||||
PICTURE_TYPE_OPTIONS,
|
||||
uploadPicture,
|
||||
} from '@/service/api/system/picture'
|
||||
@ -456,35 +458,6 @@ const uploadPictureTypeOptions = PICTURE_TYPE_OPTIONS.filter(option => option.va
|
||||
|
||||
// 权限验证
|
||||
const { hasPermission } = usePermission()
|
||||
const { getSelectOptions } = useDict(['sys_file_service'])
|
||||
|
||||
const storageValueToType: Record<string, string> = {
|
||||
1: 'LOCAL',
|
||||
2: 'MINIO',
|
||||
3: 'OSS',
|
||||
}
|
||||
|
||||
const storageTypeToValue: Record<string, string> = {
|
||||
LOCAL: '1',
|
||||
MINIO: '2',
|
||||
OSS: '3',
|
||||
}
|
||||
|
||||
function mapServiceValueToType(value: string | null | undefined) {
|
||||
const str = value != null ? String(value) : '1'
|
||||
if (storageValueToType[str])
|
||||
return storageValueToType[str]
|
||||
const upper = str.toUpperCase()
|
||||
return storageValueToType[storageTypeToValue[upper] ?? '1'] ?? 'LOCAL'
|
||||
}
|
||||
|
||||
function mapStorageTypeToValue(type: string | null | undefined) {
|
||||
const str = type != null ? String(type) : '1'
|
||||
if (storageValueToType[str])
|
||||
return str
|
||||
const upper = str.toUpperCase()
|
||||
return storageTypeToValue[upper] ?? '1'
|
||||
}
|
||||
|
||||
// 响应式数据
|
||||
const searchFormRef = ref<FormInst>()
|
||||
@ -541,7 +514,7 @@ const isConfirmDisabled = computed(() => {
|
||||
|
||||
// 上传表单数据
|
||||
const uploadForm = ref({
|
||||
pictureService: '1',
|
||||
pictureService: 'LOCAL',
|
||||
pictureType: '9', // 默认其他分类
|
||||
picturePath: '',
|
||||
})
|
||||
@ -649,9 +622,20 @@ const columns: DataTableColumns<SysPictureVo> = [
|
||||
{
|
||||
title: '服务类型',
|
||||
key: 'pictureService',
|
||||
width: 130,
|
||||
width: 120,
|
||||
align: 'center',
|
||||
render: row => h(DictTag, { dictType: 'sys_file_service', value: mapStorageTypeToValue(row.pictureService) }),
|
||||
render: (row) => {
|
||||
const serviceMap: Record<string, { type: 'success' | 'info' | 'warning', text: string }> = {
|
||||
1: { type: 'success', text: '本地存储' },
|
||||
2: { type: 'info', text: 'MinIO存储' },
|
||||
3: { type: 'warning', text: '阿里云OSS' },
|
||||
}
|
||||
const config = serviceMap[row.pictureService] || { type: 'info', text: '未知' }
|
||||
return h(NTag, {
|
||||
type: config.type,
|
||||
size: 'small',
|
||||
}, { default: () => config.text })
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
@ -853,12 +837,21 @@ function handleDownload(row: SysPictureVo) {
|
||||
}
|
||||
|
||||
// 获取图片服务类型文本
|
||||
function getPictureServiceText(serviceType: string): string {
|
||||
const serviceMap: Record<string, string> = {
|
||||
1: '本地存储',
|
||||
2: 'MinIO存储',
|
||||
3: '阿里云OSS',
|
||||
}
|
||||
return serviceMap[serviceType] || '未知'
|
||||
}
|
||||
|
||||
// 图片上传相关函数
|
||||
|
||||
// 打开上传弹框
|
||||
function handleUpload() {
|
||||
uploadForm.value = {
|
||||
pictureService: '1',
|
||||
pictureService: 'LOCAL',
|
||||
pictureType: selectedCategory.value === '0' ? '9' : selectedCategory.value, // 根据当前分类设置默认值
|
||||
picturePath: '',
|
||||
}
|
||||
@ -929,8 +922,7 @@ async function customUpload({ file, onProgress, onFinish, onError }: any) {
|
||||
onProgress({ percent: 10 })
|
||||
|
||||
// 调用上传API - 使用选择的分类和存储类型
|
||||
const storageType = mapServiceValueToType(uploadForm.value.pictureService)
|
||||
const result = await uploadPicture(fileObj, uploadForm.value.pictureType, 2, storageType)
|
||||
const result = await uploadPicture(fileObj, uploadForm.value.pictureType, 2, uploadForm.value.pictureService)
|
||||
|
||||
// 检查业务逻辑是否成功
|
||||
if (result.isSuccess === false) {
|
||||
|
||||
@ -61,7 +61,10 @@
|
||||
v-model:value="searchForm.roleStatus"
|
||||
placeholder="请选择角色状态"
|
||||
clearable
|
||||
:options="getSelectOptions('sys_switch_status')"
|
||||
:options="[
|
||||
{ label: '启用', value: '0' },
|
||||
{ label: '停用', value: '1' },
|
||||
]"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
@ -266,15 +269,14 @@
|
||||
<n-grid :cols="2" :x-gap="10">
|
||||
<n-grid-item>
|
||||
<n-form-item label="角色状态" path="roleStatus" class="mb-2">
|
||||
<NRadioGroup v-model:value="formData.roleStatus">
|
||||
<NRadio
|
||||
v-for="item in dictOptions.sys_switch_status || []"
|
||||
:key="item.dictValue"
|
||||
:value="item.dictValue"
|
||||
>
|
||||
{{ item.dictLabel }}
|
||||
</NRadio>
|
||||
</NRadioGroup>
|
||||
<n-select
|
||||
v-model:value="formData.roleStatus"
|
||||
placeholder="请选择角色状态"
|
||||
:options="[
|
||||
{ label: '启用', value: '0' },
|
||||
{ label: '停用', value: '1' },
|
||||
]"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
@ -418,14 +420,13 @@
|
||||
<script setup lang="ts">
|
||||
import { h, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import type { DataTableColumns, FormInst } from 'naive-ui'
|
||||
import { NButton, NIcon, NPopconfirm, NRadio, NRadioGroup, NSpace, NSwitch, NTag, NTree } from 'naive-ui'
|
||||
import { NButton, NIcon, NPopconfirm, NSpace, NSwitch, NTag, NTree } from 'naive-ui'
|
||||
import IconParkOutlineEditOne from '~icons/icon-park-outline/edit-one'
|
||||
import IconParkOutlineDelete from '~icons/icon-park-outline/delete'
|
||||
import IconParkOutlineKey from '~icons/icon-park-outline/key'
|
||||
import CoiDialog from '@/components/common/CoiDialog.vue'
|
||||
import CoiEmpty from '@/components/common/CoiEmpty.vue'
|
||||
import CoiPagination from '@/components/common/CoiPagination.vue'
|
||||
import DictTag from '@/components/common/DictTag.vue'
|
||||
import {
|
||||
addRole,
|
||||
batchDeleteRoles,
|
||||
@ -445,11 +446,10 @@ import {
|
||||
import type { MenuPermissionData, MenuVo } from '@/service/api/system/menu'
|
||||
import { coiMsgBox, coiMsgError, coiMsgSuccess, coiMsgWarning } from '@/utils/coi'
|
||||
import { PERMISSIONS } from '@/constants/permissions'
|
||||
import { useDict, usePermission } from '@/hooks'
|
||||
import { usePermission } from '@/hooks/usePermission'
|
||||
|
||||
// 权限相关
|
||||
const { hasButton } = usePermission()
|
||||
const { dictOptions, getSelectOptions, getDictLabel } = useDict(['sys_switch_status'])
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
@ -546,27 +546,28 @@ const columns: DataTableColumns<RoleVo> = [
|
||||
key: 'roleCode',
|
||||
width: 150,
|
||||
align: 'center',
|
||||
render: row => h(NTag, { type: 'primary', size: 'small' }, { default: () => row.roleCode }),
|
||||
render: (row) => {
|
||||
return h(NTag, { type: 'primary', size: 'small' }, { default: () => row.roleCode })
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '角色状态',
|
||||
key: 'roleStatus',
|
||||
width: 120,
|
||||
width: 100,
|
||||
align: 'center',
|
||||
render: (row) => {
|
||||
return h('div', { class: 'flex items-center justify-center gap-2' }, [
|
||||
h(DictTag, { dictType: 'sys_switch_status', value: row.roleStatus }),
|
||||
return h('div', { class: 'flex items-center justify-center' }, [
|
||||
h(NPopconfirm, {
|
||||
onPositiveClick: () => handleToggleStatus(row),
|
||||
negativeText: '取消',
|
||||
positiveText: '确定',
|
||||
}, {
|
||||
default: () => `确定要将角色「${row.roleName}」状态切换为「${getDictLabel('sys_switch_status', row.roleStatus === '0' ? '1' : '0')}」吗?`,
|
||||
default: () => `确定要${row.roleStatus === '0' ? '停用' : '启用'}角色「${row.roleName}」吗?`,
|
||||
trigger: () => h(NSwitch, {
|
||||
value: row.roleStatus === '0',
|
||||
size: 'small',
|
||||
checkedChildren: getDictLabel('sys_switch_status', '0', '启用'),
|
||||
uncheckedChildren: getDictLabel('sys_switch_status', '1', '停用'),
|
||||
checkedChildren: '启用',
|
||||
uncheckedChildren: '停用',
|
||||
loading: false,
|
||||
}),
|
||||
}),
|
||||
@ -965,7 +966,7 @@ async function handleBatchDelete() {
|
||||
async function handleToggleStatus(role: RoleVo) {
|
||||
try {
|
||||
const newStatus = role.roleStatus === '0' ? '1' : '0'
|
||||
const statusText = getDictLabel('sys_switch_status', newStatus, newStatus === '0' ? '启用' : '停用')
|
||||
const statusText = newStatus === '0' ? '启用' : '停用'
|
||||
|
||||
const { isSuccess } = await updateRoleStatus(role.roleId, newStatus)
|
||||
if (isSuccess) {
|
||||
|
||||
@ -72,7 +72,10 @@
|
||||
v-model:value="searchForm.userStatus"
|
||||
placeholder="请选择用户状态"
|
||||
clearable
|
||||
:options="getSelectOptions('sys_switch_status')"
|
||||
:options="[
|
||||
{ label: '启用', value: '0' },
|
||||
{ label: '停用', value: '1' },
|
||||
]"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
@ -310,7 +313,11 @@
|
||||
<n-select
|
||||
v-model:value="formData.userType"
|
||||
placeholder="请选择用户类型"
|
||||
:options="getSelectOptions('sys_user_type')"
|
||||
:options="[
|
||||
{ label: '系统用户', value: '1' },
|
||||
{ label: '注册用户', value: '2' },
|
||||
{ label: '微信用户', value: '3' },
|
||||
]"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
@ -320,7 +327,11 @@
|
||||
<n-select
|
||||
v-model:value="formData.sex"
|
||||
placeholder="请选择性别"
|
||||
:options="getSelectOptions('sys_user_sex')"
|
||||
:options="[
|
||||
{ label: '男', value: '1' },
|
||||
{ label: '女', value: '2' },
|
||||
{ label: '未知', value: '3' },
|
||||
]"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
@ -356,15 +367,14 @@
|
||||
|
||||
<n-grid-item>
|
||||
<n-form-item label="用户状态" path="userStatus" class="mb-2">
|
||||
<NRadioGroup v-model:value="formData.userStatus">
|
||||
<NRadio
|
||||
v-for="item in dictOptions.sys_switch_status || []"
|
||||
:key="item.dictValue"
|
||||
:value="item.dictValue"
|
||||
>
|
||||
{{ item.dictLabel }}
|
||||
</NRadio>
|
||||
</NRadioGroup>
|
||||
<n-radio-group v-model:value="formData.userStatus">
|
||||
<n-radio value="0">
|
||||
启用
|
||||
</n-radio>
|
||||
<n-radio value="1">
|
||||
停用
|
||||
</n-radio>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
|
||||
@ -575,8 +585,11 @@
|
||||
{{ currentAvatarUser?.avatar ? '用户头像' : '默认头像' }}
|
||||
</p>
|
||||
<div class="flex items-center justify-center gap-4 text-xs text-gray-400">
|
||||
<span>用户类型: {{ getDictLabel('sys_user_type', currentAvatarUser?.userType, '--') }}</span>
|
||||
<span>状态: {{ getDictLabel('sys_switch_status', currentAvatarUser?.userStatus, '--') }}</span>
|
||||
<span>用户类型: {{
|
||||
currentAvatarUser?.userType === '1' ? '系统用户'
|
||||
: currentAvatarUser?.userType === '2' ? '注册用户' : '微信用户'
|
||||
}}</span>
|
||||
<span>状态: {{ currentAvatarUser?.userStatus === '0' ? '启用' : '停用' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -691,7 +704,7 @@
|
||||
<script setup lang="ts">
|
||||
import { h, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import type { DataTableColumns, FormInst } from 'naive-ui'
|
||||
import { NButton, NCheckbox, NDropdown, NIcon, NPopconfirm, NProgress, NRadio, NRadioGroup, NSpace, NSwitch, NTag, NUpload, NUploadDragger } from 'naive-ui'
|
||||
import { NButton, NCheckbox, NDropdown, NIcon, NPopconfirm, NProgress, NSpace, NSwitch, NTag, NUpload, NUploadDragger } from 'naive-ui'
|
||||
import IconParkOutlineEditOne from '~icons/icon-park-outline/edit-one'
|
||||
import IconParkOutlineDelete from '~icons/icon-park-outline/delete'
|
||||
import IconParkOutlineRefresh from '~icons/icon-park-outline/refresh'
|
||||
@ -702,7 +715,6 @@ import IconParkOutlineFileCodeOne from '~icons/icon-park-outline/file-code-one'
|
||||
import CoiDialog from '@/components/common/CoiDialog.vue'
|
||||
import CoiEmpty from '@/components/common/CoiEmpty.vue'
|
||||
import CoiPagination from '@/components/common/CoiPagination.vue'
|
||||
import DictTag from '@/components/common/DictTag.vue'
|
||||
import {
|
||||
addUser,
|
||||
batchDeleteUsers,
|
||||
@ -727,7 +739,7 @@ import {
|
||||
import type { RoleVo } from '@/service/api/system/role'
|
||||
import { coiMsgBox, coiMsgError, coiMsgInfo, coiMsgSuccess, coiMsgWarning } from '@/utils/coi'
|
||||
import { PERMISSIONS } from '@/constants/permissions'
|
||||
import { useDict, usePermission } from '@/hooks'
|
||||
import { usePermission } from '@/hooks/usePermission'
|
||||
|
||||
// 权限相关
|
||||
const { hasButton } = usePermission()
|
||||
@ -778,12 +790,6 @@ const uploadProgress = ref(0)
|
||||
const progressInterval = ref<NodeJS.Timeout | null>(null)
|
||||
const createdBlobUrls = ref<string[]>([])
|
||||
|
||||
const { dictOptions, getSelectOptions, getDictLabel } = useDict([
|
||||
'sys_user_type',
|
||||
'sys_user_sex',
|
||||
'sys_switch_status',
|
||||
])
|
||||
|
||||
// 分页数据
|
||||
const pagination = ref({
|
||||
page: 1,
|
||||
@ -950,14 +956,33 @@ const columns: DataTableColumns<UserVo> = [
|
||||
key: 'userType',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
render: row => h(DictTag, { dictType: 'sys_user_type', value: row.userType }),
|
||||
render: (row) => {
|
||||
const typeMap: Record<string, { label: string, type: any }> = {
|
||||
1: { label: '系统用户', type: 'primary' },
|
||||
2: { label: '注册用户', type: 'info' },
|
||||
3: { label: '微信用户', type: 'warning' },
|
||||
}
|
||||
const config = typeMap[row.userType] || { label: '未知', type: 'default' }
|
||||
return h(NTag, { type: config.type, size: 'small' }, { default: () => config.label })
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
key: 'sex',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
render: row => h(DictTag, { dictType: 'sys_user_sex', value: row.sex }),
|
||||
render: (row) => {
|
||||
const sexMap: Record<string, { label: string, icon: string, color: string }> = {
|
||||
1: { label: '男', icon: '♂', color: 'text-blue-500' },
|
||||
2: { label: '女', icon: '♀', color: 'text-pink-500' },
|
||||
3: { label: '未知', icon: '?', color: 'text-gray-400' },
|
||||
}
|
||||
const config = sexMap[row.sex || '3']
|
||||
return h('div', { class: `flex items-center justify-center gap-1 ${config.color}` }, [
|
||||
h('span', { class: 'text-lg' }, config.icon),
|
||||
h('span', { class: 'text-xs' }, config.label),
|
||||
])
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '用户状态',
|
||||
@ -965,21 +990,18 @@ const columns: DataTableColumns<UserVo> = [
|
||||
width: 100,
|
||||
align: 'center',
|
||||
render: (row) => {
|
||||
const nextStatus = row.userStatus === '0' ? '1' : '0'
|
||||
const enableLabel = getDictLabel('sys_switch_status', '0', '启用')
|
||||
const disableLabel = getDictLabel('sys_switch_status', '1', '停用')
|
||||
return h('div', { class: 'flex items-center justify-center' }, [
|
||||
h(NPopconfirm, {
|
||||
onPositiveClick: () => handleToggleStatus(row),
|
||||
negativeText: '取消',
|
||||
positiveText: '确定',
|
||||
}, {
|
||||
default: () => `确定要将用户「${row.userName}」状态切换为「${getDictLabel('sys_switch_status', nextStatus)}」吗?`,
|
||||
default: () => `确定要${row.userStatus === '0' ? '停用' : '启用'}用户「${row.userName}」吗?`,
|
||||
trigger: () => h(NSwitch, {
|
||||
value: row.userStatus === '0',
|
||||
size: 'small',
|
||||
checkedChildren: enableLabel,
|
||||
uncheckedChildren: disableLabel,
|
||||
checkedChildren: '启用',
|
||||
uncheckedChildren: '停用',
|
||||
loading: false,
|
||||
}),
|
||||
}),
|
||||
@ -1335,7 +1357,7 @@ async function handleBatchDelete() {
|
||||
async function handleToggleStatus(user: UserVo) {
|
||||
try {
|
||||
const newStatus = user.userStatus === '0' ? '1' : '0'
|
||||
const statusText = getDictLabel('sys_switch_status', newStatus, newStatus === '0' ? '启用' : '停用')
|
||||
const statusText = newStatus === '0' ? '启用' : '停用'
|
||||
|
||||
const { isSuccess } = await updateUserStatus(user.userId, newStatus)
|
||||
if (isSuccess) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user