mirror of
https://github.com/chansee97/nova-admin.git
synced 2025-09-18 03:39:58 +08:00
feat: 增加字典管理角色管理
This commit is contained in:
parent
3124d0b923
commit
53282f9453
@ -45,7 +45,7 @@ const propOverrides = {
|
||||
cols: 4,
|
||||
},
|
||||
ProModalForm: {
|
||||
labelWidth: 120,
|
||||
labelWidth: 100,
|
||||
labelPlacement: 'left',
|
||||
preset: 'card',
|
||||
},
|
||||
|
@ -6,7 +6,7 @@ interface UserQueryParams {
|
||||
pageSize?: number
|
||||
username?: string
|
||||
gender?: 'male' | 'female' | 'unknown'
|
||||
userStatus?: number
|
||||
status?: number
|
||||
deptId?: number
|
||||
}
|
||||
|
||||
|
@ -8,5 +8,5 @@ export enum Regex {
|
||||
|
||||
Email = '^(([^<>()[\\]\\\\.,;:\\s@"]+(\\.[^<>()[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$',
|
||||
|
||||
RouteName = '^[\\w_!@#$%^&*~-]+$',
|
||||
Phone = '^1[3-9]\d{9}$',
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
/** Gender */
|
||||
export enum Gender {
|
||||
male,
|
||||
female,
|
||||
}
|
@ -1,2 +1 @@
|
||||
export * from './Regex'
|
||||
export * from './User'
|
||||
|
44
src/typings/entities/dict.d.ts
vendored
44
src/typings/entities/dict.d.ts
vendored
@ -3,14 +3,18 @@
|
||||
/** 数据库表字段 */
|
||||
namespace Entity {
|
||||
interface DictType {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id?: number
|
||||
/**
|
||||
* 字典名称
|
||||
*/
|
||||
dictName: string
|
||||
name: string
|
||||
/**
|
||||
* 字典类型
|
||||
*/
|
||||
dictType: string
|
||||
type: string
|
||||
/**
|
||||
* 备注信息
|
||||
*/
|
||||
@ -19,21 +23,29 @@ namespace Entity {
|
||||
* 状态
|
||||
*/
|
||||
status?: number
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime?: string
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
interface DictData {
|
||||
/**
|
||||
* 样式属性
|
||||
* 主键ID
|
||||
*/
|
||||
cssClass?: string
|
||||
id?: number
|
||||
/**
|
||||
* 字典标签
|
||||
* 字典名称
|
||||
*/
|
||||
dictLabel: string
|
||||
name: string
|
||||
/**
|
||||
* 字典排序
|
||||
*/
|
||||
dictSort?: number
|
||||
sort?: number
|
||||
/**
|
||||
* 字典类型
|
||||
*/
|
||||
@ -41,15 +53,7 @@ namespace Entity {
|
||||
/**
|
||||
* 字典键值
|
||||
*/
|
||||
dictValue: string
|
||||
/**
|
||||
* 是否默认
|
||||
*/
|
||||
isDefault?: number
|
||||
/**
|
||||
* 表格回显样式
|
||||
*/
|
||||
listClass?: string
|
||||
value: string
|
||||
/**
|
||||
* 备注信息
|
||||
*/
|
||||
@ -58,5 +62,13 @@ namespace Entity {
|
||||
* 状态
|
||||
*/
|
||||
status?: number
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime?: string
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime?: string
|
||||
}
|
||||
}
|
||||
|
4
src/typings/entities/role.d.ts
vendored
4
src/typings/entities/role.d.ts
vendored
@ -8,7 +8,7 @@ namespace Entity {
|
||||
/**
|
||||
* 角色ID
|
||||
*/
|
||||
roleId: number
|
||||
id: number
|
||||
/**
|
||||
* 菜单ID数组
|
||||
*/
|
||||
@ -28,7 +28,7 @@ namespace Entity {
|
||||
/**
|
||||
* 角色状态
|
||||
*/
|
||||
roleStatus?: number
|
||||
status?: number
|
||||
/**
|
||||
* 显示顺序
|
||||
*/
|
||||
|
8
src/typings/entities/user.d.ts
vendored
8
src/typings/entities/user.d.ts
vendored
@ -4,7 +4,7 @@
|
||||
namespace Entity {
|
||||
interface User {
|
||||
/** 用户id */
|
||||
userId: number
|
||||
id: number
|
||||
/** 部门id */
|
||||
deptId?: any
|
||||
/** 用户名 */
|
||||
@ -22,13 +22,9 @@ namespace Entity {
|
||||
/** 头像 */
|
||||
avatar?: string
|
||||
/** 用户状态 */
|
||||
userStatus: number
|
||||
/** 创建人 */
|
||||
createBy?: string
|
||||
status: number
|
||||
/** 创建时间 */
|
||||
createTime: string
|
||||
/** 更新人 */
|
||||
updateBy?: string
|
||||
/** 更新时间 */
|
||||
updateTime: string
|
||||
/** 备注 */
|
||||
|
@ -72,24 +72,23 @@ export function createAlovaInstance(
|
||||
const { status } = response
|
||||
let errorMessage = ''
|
||||
|
||||
const res = await response.clone().json()
|
||||
if (status === 200) {
|
||||
// 返回blob数据
|
||||
if (method.meta?.isBlob)
|
||||
return response.blob()
|
||||
|
||||
// 返回json数据
|
||||
const apiData = await response.json()
|
||||
// 请求成功
|
||||
if (apiData[_backendConfig.codeKey] === _backendConfig.successCode)
|
||||
return apiData
|
||||
if (res[_backendConfig.codeKey] === _backendConfig.successCode)
|
||||
return res
|
||||
|
||||
// 业务请求失败
|
||||
errorMessage = apiData[_backendConfig.msgKey]
|
||||
errorMessage = res[_backendConfig.msgKey]
|
||||
}
|
||||
else {
|
||||
// 接口请求失败
|
||||
const errorCode = response.status as ErrorStatus
|
||||
errorMessage = ERROR_STATUS[errorCode] || ERROR_STATUS.default
|
||||
errorMessage = res[_backendConfig.msgKey] || ERROR_STATUS[errorCode] || ERROR_STATUS.default
|
||||
}
|
||||
window.$message?.error(errorMessage)
|
||||
throw new Error(errorMessage)
|
||||
|
@ -49,7 +49,7 @@ const rules = computed(() => {
|
||||
})
|
||||
const formValue = ref({
|
||||
account: 'admin',
|
||||
pwd: '12345',
|
||||
pwd: '123456',
|
||||
captcha: '',
|
||||
})
|
||||
const isRemember = ref(false)
|
||||
|
@ -185,7 +185,7 @@ function handleAddTable() {
|
||||
<template #icon>
|
||||
<icon-park-outline-add-one />
|
||||
</template>
|
||||
新建
|
||||
新增
|
||||
</NButton>
|
||||
<NButton strong secondary>
|
||||
<template #icon>
|
||||
|
@ -1,144 +1,98 @@
|
||||
import type { DataTableColumns } from 'naive-ui'
|
||||
import { NButton, NFlex, NPopconfirm } from 'naive-ui'
|
||||
import CopyText from '@/components/custom/CopyText.vue'
|
||||
import { NButton, NPopconfirm, NSpace } from 'naive-ui'
|
||||
import type { ProSearchFormColumns } from 'pro-naive-ui'
|
||||
import { renderProCopyableText, renderProTags } from 'pro-naive-ui'
|
||||
|
||||
// 字典类型columns配置函数
|
||||
interface DictTypeColumnActions {
|
||||
onView: (code: string) => void
|
||||
onEdit: (row: Entity.DictType) => void
|
||||
onDelete: (id: number) => void
|
||||
}
|
||||
|
||||
export function createDictTypeColumns(actions: DictTypeColumnActions): DataTableColumns<Entity.DictType> {
|
||||
return [
|
||||
{
|
||||
title: '字典项',
|
||||
key: 'dictName',
|
||||
},
|
||||
{
|
||||
title: '字典码',
|
||||
key: 'dictType',
|
||||
render: (row) => {
|
||||
return (
|
||||
<CopyText value={row.dictType} />
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
render: (row) => {
|
||||
return (
|
||||
<span>{row.status === 1 ? '正常' : '停用'}</span>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
align: 'center',
|
||||
render: (row) => {
|
||||
return (
|
||||
<NFlex justify="center">
|
||||
<NButton
|
||||
size="small"
|
||||
onClick={() => actions.onView(row.dictType)}
|
||||
>
|
||||
查看字典
|
||||
</NButton>
|
||||
<NButton
|
||||
size="small"
|
||||
onClick={() => actions.onEdit(row)}
|
||||
>
|
||||
编辑
|
||||
</NButton>
|
||||
<NPopconfirm onPositiveClick={() => actions.onDelete(row.id!)}>
|
||||
{{
|
||||
default: () => (
|
||||
<span>
|
||||
确认删除字典类型
|
||||
<b>{row.dictName}</b>
|
||||
{' '}
|
||||
?
|
||||
</span>
|
||||
),
|
||||
trigger: () => <NButton size="small" type="error">删除</NButton>,
|
||||
}}
|
||||
</NPopconfirm>
|
||||
</NFlex>
|
||||
)
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// 字典数据columns配置函数
|
||||
interface DictDataColumnActions {
|
||||
onEdit: (row: Entity.DictData) => void
|
||||
onDelete: (id: number) => void
|
||||
}
|
||||
|
||||
export function createDictDataColumns(actions: DictDataColumnActions): DataTableColumns<Entity.DictData> {
|
||||
return [
|
||||
export const dictDataSearchColumns: ProSearchFormColumns<Entity.DictData> = [
|
||||
{
|
||||
title: '字典名称',
|
||||
key: 'dictLabel',
|
||||
title: '数据名称',
|
||||
path: 'name',
|
||||
},
|
||||
{
|
||||
title: '字典码',
|
||||
key: 'dictType',
|
||||
},
|
||||
{
|
||||
title: '字典值',
|
||||
key: 'dictValue',
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
key: 'dictSort',
|
||||
align: 'center',
|
||||
width: '80px',
|
||||
title: '数据键值',
|
||||
path: 'value',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
render: (row) => {
|
||||
return (
|
||||
<span>{row.status === 1 ? '正常' : '停用'}</span>
|
||||
)
|
||||
path: 'status',
|
||||
field: 'select',
|
||||
fieldProps: {
|
||||
options: [
|
||||
{ label: '正常', value: 1 },
|
||||
{ label: '停用', value: 0 },
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export function createDictDataColumns(actions: DictDataColumnActions): DataTableColumns<Entity.DictData> {
|
||||
return [
|
||||
|
||||
{
|
||||
key: 'name',
|
||||
title: '数据名称',
|
||||
align: 'left',
|
||||
minWidth: 120,
|
||||
ellipsis: {
|
||||
tooltip: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
key: 'value',
|
||||
title: '数据键值',
|
||||
align: 'center',
|
||||
width: '15em',
|
||||
render: (row) => {
|
||||
return (
|
||||
<NFlex justify="center">
|
||||
width: 120,
|
||||
render: row => renderProCopyableText(row.value),
|
||||
},
|
||||
{
|
||||
key: 'dictType',
|
||||
title: '字典类型',
|
||||
align: 'center',
|
||||
render: row => renderProTags(row.dictType),
|
||||
},
|
||||
{
|
||||
key: 'sort',
|
||||
title: '排序',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
key: 'remark',
|
||||
title: '备注',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
title: '操作',
|
||||
align: 'center',
|
||||
width: 150,
|
||||
render: (row: Entity.DictData) => (
|
||||
<NSpace justify="center">
|
||||
<NButton
|
||||
size="small"
|
||||
type="primary"
|
||||
text
|
||||
onClick={() => actions.onEdit(row)}
|
||||
>
|
||||
编辑
|
||||
</NButton>
|
||||
<NPopconfirm onPositiveClick={() => actions.onDelete(row.id!)}>
|
||||
{{
|
||||
default: () => (
|
||||
<span>
|
||||
确认删除字典数据
|
||||
<b>{row.dictLabel}</b>
|
||||
{' '}
|
||||
?
|
||||
</span>
|
||||
<NPopconfirm
|
||||
onPositiveClick={() => actions.onDelete(row.id!)}
|
||||
v-slots={{
|
||||
trigger: () => (
|
||||
<NButton type="error" text>
|
||||
删除
|
||||
</NButton>
|
||||
),
|
||||
trigger: () => <NButton size="small" type="error">删除</NButton>,
|
||||
default: () => `确定删除字典数据"${row.name}"吗?`,
|
||||
}}
|
||||
</NPopconfirm>
|
||||
</NFlex>
|
||||
)
|
||||
},
|
||||
/>
|
||||
</NSpace>
|
||||
),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
143
src/views/setting/dict/components/DictDataModal.vue
Normal file
143
src/views/setting/dict/components/DictDataModal.vue
Normal file
@ -0,0 +1,143 @@
|
||||
<script setup lang="ts">
|
||||
import { useBoolean } from '@/hooks'
|
||||
import { createDictData, getDictDataById, updateDictData } from '@/api'
|
||||
import { createProModalForm } from 'pro-naive-ui'
|
||||
|
||||
interface Props {
|
||||
modalName?: string
|
||||
}
|
||||
|
||||
const {
|
||||
modalName = '',
|
||||
} = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
success: []
|
||||
}>()
|
||||
|
||||
const { bool: submitLoading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
|
||||
|
||||
const modalForm = createProModalForm<Partial<Entity.DictData>>({
|
||||
omitEmptyString: false,
|
||||
initialValues: {
|
||||
sort: 0,
|
||||
status: 1,
|
||||
},
|
||||
onSubmit: submitModal,
|
||||
})
|
||||
|
||||
type ModalType = 'add' | 'edit'
|
||||
const modalType = shallowRef<ModalType>('add')
|
||||
const modalTitle = computed(() => {
|
||||
const titleMap: Record<ModalType, string> = {
|
||||
add: '添加',
|
||||
edit: '编辑',
|
||||
}
|
||||
return `${titleMap[modalType.value]}${modalName}`
|
||||
})
|
||||
|
||||
async function openModal(type: ModalType = 'add', data?: Partial<Entity.DictData>) {
|
||||
modalType.value = type
|
||||
modalForm.open()
|
||||
const handlers = {
|
||||
async add() {
|
||||
if (data?.dictType) {
|
||||
modalForm.values.value.dictType = data.dictType
|
||||
}
|
||||
},
|
||||
async edit() {
|
||||
if (!data)
|
||||
return
|
||||
|
||||
const { data: dictData } = await getDictDataById(data.id!)
|
||||
modalForm.values.value = dictData
|
||||
},
|
||||
}
|
||||
await handlers[type]()
|
||||
}
|
||||
|
||||
async function submitModal(filedValues: Partial<Entity.DictData>) {
|
||||
const handlers = {
|
||||
async add() {
|
||||
try {
|
||||
await createDictData(filedValues)
|
||||
window.$message.success('字典数据创建成功')
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
console.error('创建字典数据失败', error)
|
||||
return false
|
||||
}
|
||||
},
|
||||
async edit() {
|
||||
try {
|
||||
await updateDictData(modalForm.values.value.id!, filedValues)
|
||||
window.$message.success('字典数据更新成功')
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
console.error('更新字典数据失败', error)
|
||||
return false
|
||||
}
|
||||
},
|
||||
}
|
||||
startLoading()
|
||||
const success = await handlers[modalType.value]()
|
||||
endLoading()
|
||||
|
||||
if (success) {
|
||||
emit('success')
|
||||
modalForm.close()
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<pro-modal-form
|
||||
:title="modalTitle"
|
||||
:form="modalForm"
|
||||
:loading="submitLoading"
|
||||
width="700px"
|
||||
>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<pro-input
|
||||
required
|
||||
title="字典名称"
|
||||
path="name"
|
||||
placeholder="请输入字典名称"
|
||||
/>
|
||||
<pro-input
|
||||
required
|
||||
title="字典键值"
|
||||
path="value"
|
||||
placeholder="请输入字典键值"
|
||||
/>
|
||||
<pro-input
|
||||
title="字典类型"
|
||||
path="dictType"
|
||||
placeholder="字典类型"
|
||||
:readonly="true"
|
||||
/>
|
||||
<pro-digit
|
||||
title="排序"
|
||||
path="sort"
|
||||
:field-props="{ min: 0, max: 999 }"
|
||||
/>
|
||||
<pro-switch
|
||||
title="状态"
|
||||
path="status"
|
||||
:field-props="{ checkedValue: 1, uncheckedValue: 0 }"
|
||||
/>
|
||||
<pro-textarea
|
||||
class="col-span-2"
|
||||
title="备注"
|
||||
path="remark"
|
||||
placeholder="请输入备注信息"
|
||||
/>
|
||||
</div>
|
||||
</pro-modal-form>
|
||||
</template>
|
@ -1,202 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormRules } from 'naive-ui'
|
||||
import { useBoolean } from '@/hooks'
|
||||
import { createDictData, createDictType, updateDictData, updateDictType } from '@/api'
|
||||
|
||||
interface Props {
|
||||
modalName?: string
|
||||
dictCode?: string
|
||||
isRoot?: boolean
|
||||
}
|
||||
|
||||
const {
|
||||
modalName = '',
|
||||
dictCode,
|
||||
isRoot = false,
|
||||
} = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
open: []
|
||||
close: []
|
||||
success: []
|
||||
}>()
|
||||
|
||||
const { bool: modalVisible, setTrue: showModal, setFalse: hiddenModal } = useBoolean(false)
|
||||
|
||||
const { bool: submitLoading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
|
||||
|
||||
const formDefault: any = {
|
||||
label: '',
|
||||
code: '',
|
||||
}
|
||||
const formModel = ref<any>({ ...formDefault })
|
||||
|
||||
type ModalType = 'add' | 'view' | 'edit'
|
||||
const modalType = shallowRef<ModalType>('add')
|
||||
const modalTitle = computed(() => {
|
||||
const titleMap: Record<ModalType, string> = {
|
||||
add: '添加',
|
||||
view: '查看',
|
||||
edit: '编辑',
|
||||
}
|
||||
return `${titleMap[modalType.value]}${modalName}`
|
||||
})
|
||||
|
||||
async function openModal(type: ModalType = 'add', data?: any) {
|
||||
emit('open')
|
||||
modalType.value = type
|
||||
showModal()
|
||||
const handlers = {
|
||||
async add() {
|
||||
formModel.value = { ...formDefault }
|
||||
|
||||
formModel.value.isRoot = isRoot ? 1 : 0
|
||||
if (dictCode) {
|
||||
formModel.value.code = dictCode
|
||||
}
|
||||
},
|
||||
async view() {
|
||||
if (!data)
|
||||
return
|
||||
formModel.value = { ...data }
|
||||
},
|
||||
async edit() {
|
||||
if (!data)
|
||||
return
|
||||
formModel.value = { ...data }
|
||||
},
|
||||
}
|
||||
await handlers[type]()
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
hiddenModal()
|
||||
endLoading()
|
||||
emit('close')
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
})
|
||||
|
||||
const formRef = ref()
|
||||
async function submitModal() {
|
||||
const handlers = {
|
||||
async add() {
|
||||
try {
|
||||
if (isRoot) {
|
||||
// 创建字典类型
|
||||
await createDictType({
|
||||
dictType: formModel.value.label,
|
||||
dictName: formModel.value.code,
|
||||
})
|
||||
window.$message.success('字典类型创建成功')
|
||||
}
|
||||
else {
|
||||
// 创建字典数据
|
||||
await createDictData({
|
||||
dictType: formModel.value.label,
|
||||
dictValue: formModel.value.value,
|
||||
})
|
||||
window.$message.success('字典数据创建成功')
|
||||
}
|
||||
emit('success')
|
||||
return true
|
||||
}
|
||||
catch {
|
||||
return false
|
||||
}
|
||||
},
|
||||
async edit() {
|
||||
try {
|
||||
if (isRoot) {
|
||||
// 更新字典类型
|
||||
await updateDictType(formModel.value.id!, {
|
||||
dictType: formModel.value.label as any,
|
||||
dictName: formModel.value.code,
|
||||
})
|
||||
window.$message.success('字典类型更新成功')
|
||||
}
|
||||
else {
|
||||
// 更新字典数据
|
||||
await updateDictData(formModel.value.id!, {
|
||||
dictType: formModel.value.label,
|
||||
dictValue: formModel.value.value,
|
||||
})
|
||||
window.$message.success('字典数据更新成功')
|
||||
}
|
||||
emit('success')
|
||||
return true
|
||||
}
|
||||
catch {
|
||||
return false
|
||||
}
|
||||
},
|
||||
async view() {
|
||||
return true
|
||||
},
|
||||
}
|
||||
await formRef.value?.validate()
|
||||
startLoading()
|
||||
const success = await handlers[modalType.value]()
|
||||
endLoading()
|
||||
if (success) {
|
||||
closeModal()
|
||||
}
|
||||
}
|
||||
|
||||
const rules: FormRules = {
|
||||
label: {
|
||||
required: true,
|
||||
message: '请输入字典名称',
|
||||
trigger: ['input', 'blur'],
|
||||
},
|
||||
code: {
|
||||
required: true,
|
||||
message: '请输入字典码',
|
||||
trigger: ['input', 'blur'],
|
||||
},
|
||||
value: {
|
||||
required: true,
|
||||
message: '请输入字典值',
|
||||
type: 'number',
|
||||
trigger: ['input', 'blur'],
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-modal
|
||||
v-model:show="modalVisible"
|
||||
:mask-closable="false"
|
||||
preset="card"
|
||||
:title="modalTitle"
|
||||
class="w-700px"
|
||||
:segmented="{
|
||||
content: true,
|
||||
action: true,
|
||||
}"
|
||||
>
|
||||
<n-form ref="formRef" :rules="rules" label-placement="left" :model="formModel" :label-width="100" :disabled="modalType === 'view'">
|
||||
<n-form-item label="字典名称" path="label">
|
||||
<n-input v-model:value="formModel.label" />
|
||||
</n-form-item>
|
||||
<n-form-item label="字典码" path="code">
|
||||
<n-input v-model:value="formModel.code" :disabled="!isRoot" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="!isRoot" label="字典值" path="value">
|
||||
<n-input-number v-model:value="formModel.value" :min="0" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #action>
|
||||
<n-space justify="center">
|
||||
<n-button @click="closeModal">
|
||||
取消
|
||||
</n-button>
|
||||
<n-button type="primary" :loading="submitLoading" @click="submitModal">
|
||||
提交
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</template>
|
171
src/views/setting/dict/components/DictTypeList.vue
Normal file
171
src/views/setting/dict/components/DictTypeList.vue
Normal file
@ -0,0 +1,171 @@
|
||||
<script setup lang="tsx">
|
||||
import type { DataTableColumns } from 'naive-ui'
|
||||
import { NButton, NPopconfirm, NSpace } from 'naive-ui'
|
||||
import type { ProSearchFormColumns } from 'pro-naive-ui'
|
||||
import { createProSearchForm, renderProCopyableText, useNDataTable } from 'pro-naive-ui'
|
||||
import { deleteDictType, getDictTypeList } from '@/api'
|
||||
import DictTypeModal from './DictTypeModal.vue'
|
||||
|
||||
defineEmits(['select'])
|
||||
|
||||
// 字典类型相关
|
||||
const dictTypeModalRef = ref<InstanceType<typeof DictTypeModal>>()
|
||||
|
||||
// 搜索表单
|
||||
const dictTypeSearchForm = createProSearchForm<Partial<Entity.DictType>>({
|
||||
initialValues: {},
|
||||
})
|
||||
|
||||
// 搜索表单列配置
|
||||
const dictTypeSearchColumns: ProSearchFormColumns<Entity.DictType> = [
|
||||
{
|
||||
title: '字典名称',
|
||||
path: 'name',
|
||||
},
|
||||
{
|
||||
title: '字典类型',
|
||||
path: 'type',
|
||||
},
|
||||
]
|
||||
|
||||
// 使用 useNDataTable
|
||||
const {
|
||||
table: {
|
||||
tableProps: dictTypeTableProps,
|
||||
},
|
||||
search: {
|
||||
proSearchFormProps: dictTypeSearchProps,
|
||||
},
|
||||
refresh: refreshDictTypes,
|
||||
} = useNDataTable(getDictTypePage, {
|
||||
form: dictTypeSearchForm,
|
||||
})
|
||||
|
||||
// 获取字典类型分页数据
|
||||
async function getDictTypePage({ current, pageSize }: any, formData: Partial<Entity.DictType>) {
|
||||
try {
|
||||
const { data } = await getDictTypeList({
|
||||
...formData,
|
||||
pageNum: current || 1,
|
||||
pageSize: pageSize || 10,
|
||||
})
|
||||
return data
|
||||
}
|
||||
catch {
|
||||
return {
|
||||
list: [],
|
||||
total: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 删除字典类型
|
||||
async function deleteDictTypeData(id: number) {
|
||||
try {
|
||||
await deleteDictType(id)
|
||||
window.$message.success('字典类型删除成功')
|
||||
refreshDictTypes()
|
||||
}
|
||||
catch {
|
||||
window.$message.error('字典类型删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 表格列配置
|
||||
const columns: DataTableColumns<Entity.DictType> = [
|
||||
{
|
||||
key: 'name',
|
||||
title: '字典名称',
|
||||
ellipsis: {
|
||||
tooltip: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
title: '字典类型',
|
||||
render: row => renderProCopyableText(row.type),
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
title: '操作',
|
||||
align: 'center',
|
||||
render: (row: Entity.DictType) => (
|
||||
<NSpace size="small" justify="center">
|
||||
<NButton
|
||||
text
|
||||
type="primary"
|
||||
onClick={(e: Event) => {
|
||||
e.stopPropagation()
|
||||
dictTypeModalRef.value?.openModal('edit', row)
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
</NButton>
|
||||
<NPopconfirm
|
||||
onPositiveClick={() => deleteDictTypeData(row.id!)}
|
||||
v-slots={{
|
||||
trigger: () => (
|
||||
<NButton
|
||||
text
|
||||
type="error"
|
||||
onClick={(e: Event) => e.stopPropagation()}
|
||||
>
|
||||
删除
|
||||
</NButton>
|
||||
),
|
||||
default: () => `确定删除字典类型"${row.name}"吗?`,
|
||||
}}
|
||||
/>
|
||||
</NSpace>
|
||||
),
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpace vertical>
|
||||
<!-- 搜索表单 -->
|
||||
<n-card>
|
||||
<pro-search-form
|
||||
:form="dictTypeSearchForm"
|
||||
:columns="dictTypeSearchColumns"
|
||||
:collapse-button-props="false"
|
||||
v-bind="dictTypeSearchProps"
|
||||
:cols="3"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<pro-data-table
|
||||
:columns="columns"
|
||||
v-bind="dictTypeTableProps"
|
||||
:row-props="(row:Entity.DictType) => {
|
||||
return {
|
||||
onClick: () => $emit('select', row),
|
||||
style: {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
}
|
||||
}"
|
||||
>
|
||||
<template #title>
|
||||
<NButton
|
||||
type="primary"
|
||||
@click="dictTypeModalRef?.openModal('add')"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-park-outline-plus />
|
||||
</template>
|
||||
新增字典类型
|
||||
</NButton>
|
||||
</template>
|
||||
</pro-data-table>
|
||||
|
||||
<!-- 字典类型弹窗 -->
|
||||
<DictTypeModal
|
||||
ref="dictTypeModalRef"
|
||||
modal-name="字典类型"
|
||||
@success="refreshDictTypes"
|
||||
/>
|
||||
</NSpace>
|
||||
</template>
|
133
src/views/setting/dict/components/DictTypeModal.vue
Normal file
133
src/views/setting/dict/components/DictTypeModal.vue
Normal file
@ -0,0 +1,133 @@
|
||||
<script setup lang="ts">
|
||||
import { useBoolean } from '@/hooks'
|
||||
import { createDictType, getDictTypeById, updateDictType } from '@/api'
|
||||
import { createProModalForm } from 'pro-naive-ui'
|
||||
|
||||
interface Props {
|
||||
modalName?: string
|
||||
}
|
||||
|
||||
const {
|
||||
modalName = '',
|
||||
} = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
success: []
|
||||
}>()
|
||||
|
||||
const { bool: submitLoading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
|
||||
|
||||
const modalForm = createProModalForm<Partial<Entity.DictType>>({
|
||||
omitEmptyString: false,
|
||||
initialValues: {
|
||||
name: '',
|
||||
type: '',
|
||||
remark: '',
|
||||
status: 1,
|
||||
},
|
||||
onSubmit: submitModal,
|
||||
})
|
||||
|
||||
type ModalType = 'add' | 'edit'
|
||||
const modalType = shallowRef<ModalType>('add')
|
||||
const modalTitle = computed(() => {
|
||||
const titleMap: Record<ModalType, string> = {
|
||||
add: '添加',
|
||||
edit: '编辑',
|
||||
}
|
||||
return `${titleMap[modalType.value]}${modalName}`
|
||||
})
|
||||
|
||||
async function openModal(type: ModalType = 'add', data?: Partial<Entity.DictType>) {
|
||||
modalType.value = type
|
||||
modalForm.open()
|
||||
const handlers = {
|
||||
async add() {
|
||||
// 使用默认值
|
||||
},
|
||||
async edit() {
|
||||
if (!data)
|
||||
return
|
||||
|
||||
const { data: dictType } = await getDictTypeById(data.id!)
|
||||
modalForm.values.value = dictType
|
||||
},
|
||||
}
|
||||
await handlers[type]()
|
||||
}
|
||||
|
||||
async function submitModal(filedValues: Partial<Entity.DictType>) {
|
||||
const handlers = {
|
||||
async add() {
|
||||
try {
|
||||
await createDictType(filedValues)
|
||||
window.$message.success('字典类型创建成功')
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
console.error('创建字典类型失败', error)
|
||||
return false
|
||||
}
|
||||
},
|
||||
async edit() {
|
||||
try {
|
||||
await updateDictType(modalForm.values.value.id!, filedValues)
|
||||
window.$message.success('字典类型更新成功')
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
console.error('更新字典类型失败', error)
|
||||
return false
|
||||
}
|
||||
},
|
||||
}
|
||||
startLoading()
|
||||
const success = await handlers[modalType.value]()
|
||||
endLoading()
|
||||
|
||||
if (success) {
|
||||
emit('success')
|
||||
modalForm.close()
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<pro-modal-form
|
||||
:title="modalTitle"
|
||||
:form="modalForm"
|
||||
:loading="submitLoading"
|
||||
width="600px"
|
||||
>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<pro-input
|
||||
required
|
||||
title="字典名称"
|
||||
path="name"
|
||||
placeholder="请输入字典名称"
|
||||
/>
|
||||
<pro-input
|
||||
required
|
||||
title="字典类型"
|
||||
path="type"
|
||||
placeholder="请输入字典类型"
|
||||
:readonly="modalType === 'edit'"
|
||||
/>
|
||||
<pro-textarea
|
||||
class="col-span-2"
|
||||
title="备注"
|
||||
path="remark"
|
||||
placeholder="请输入备注信息"
|
||||
/>
|
||||
<pro-switch
|
||||
title="状态"
|
||||
path="status"
|
||||
:field-props="{ checkedValue: 1, uncheckedValue: 0 }"
|
||||
/>
|
||||
</div>
|
||||
</pro-modal-form>
|
||||
</template>
|
@ -1,144 +1,124 @@
|
||||
<script setup lang="tsx">
|
||||
import { useBoolean } from '@/hooks'
|
||||
import { deleteDictData, deleteDictType, getDictDataByType, getDictTypeList } from '@/api'
|
||||
import { createDictDataColumns, createDictTypeColumns } from './columns'
|
||||
import DictModal from './components/DictModal.vue'
|
||||
<script setup lang="ts">
|
||||
import { createProSearchForm, useNDataTable } from 'pro-naive-ui'
|
||||
import { deleteDictData, getDictDataList } from '@/api'
|
||||
import { createDictDataColumns, dictDataSearchColumns } from './columns'
|
||||
import DictTypeList from './components/DictTypeList.vue'
|
||||
import DictDataModal from './components/DictDataModal.vue'
|
||||
|
||||
const { bool: dictLoading, setTrue: startDictLoading, setFalse: endDictLoading } = useBoolean(false)
|
||||
const { bool: contentLoading, setTrue: startContentLoading, setFalse: endContentLoading } = useBoolean(false)
|
||||
const dictTypeListRef = ref<InstanceType<typeof DictTypeList>>()
|
||||
|
||||
const dictRef = ref<InstanceType<typeof DictModal>>()
|
||||
const dictContentRef = ref<InstanceType<typeof DictModal>>()
|
||||
// 字典数据相关
|
||||
const dictDataSearchForm = createProSearchForm<Partial<Entity.DictData>>({
|
||||
initialValues: {},
|
||||
})
|
||||
const { values } = dictDataSearchForm
|
||||
|
||||
onMounted(() => {
|
||||
getDictList()
|
||||
const {
|
||||
table: {
|
||||
tableProps: dictDataTableProps,
|
||||
},
|
||||
search: {
|
||||
proSearchFormProps: dictDataSearchProps,
|
||||
},
|
||||
refresh: refreshDictData,
|
||||
} = useNDataTable(getDictDataPage, {
|
||||
form: dictDataSearchForm,
|
||||
})
|
||||
|
||||
const dictData = ref<Entity.DictType[]>([])
|
||||
const dictContentData = ref<Entity.DictData[]>([])
|
||||
const dictDataModalRef = ref<InstanceType<typeof DictDataModal>>()
|
||||
|
||||
async function getDictList() {
|
||||
startDictLoading()
|
||||
try {
|
||||
const { data } = await getDictTypeList()
|
||||
dictData.value = data.list
|
||||
}
|
||||
catch (error) {
|
||||
console.error('获取字典类型列表失败', error)
|
||||
}
|
||||
finally {
|
||||
endDictLoading()
|
||||
}
|
||||
// 选择字典类型
|
||||
const currentDictType = ref<Entity.DictType | null>(null)
|
||||
function handleDictTypeSelect(dictType: Entity.DictType) {
|
||||
currentDictType.value = dictType
|
||||
refreshDictData()
|
||||
}
|
||||
|
||||
const lastDictCode = ref('')
|
||||
async function getDictContent(code: string) {
|
||||
startContentLoading()
|
||||
// 删除字典数据
|
||||
async function deleteDictDataItem(id: number) {
|
||||
try {
|
||||
const { data } = await getDictDataByType(code)
|
||||
dictContentData.value = data
|
||||
lastDictCode.value = code
|
||||
}
|
||||
catch (error) {
|
||||
console.error('获取字典数据失败', error)
|
||||
}
|
||||
finally {
|
||||
endContentLoading()
|
||||
}
|
||||
}
|
||||
|
||||
// 字典类型columns配置
|
||||
const dictColumns = createDictTypeColumns({
|
||||
onView: getDictContent,
|
||||
onEdit: row => dictRef.value!.openModal('edit', row),
|
||||
onDelete: id => deleteDict(id, true),
|
||||
})
|
||||
|
||||
// 字典数据columns配置
|
||||
const contentColumns = createDictDataColumns({
|
||||
onEdit: row => dictContentRef.value!.openModal('edit', row),
|
||||
onDelete: id => deleteDict(id, false),
|
||||
})
|
||||
|
||||
async function deleteDict(id: number, isType: boolean = false) {
|
||||
try {
|
||||
if (isType) {
|
||||
await deleteDictType(id)
|
||||
window.$message.success('删除字典类型成功')
|
||||
getDictList() // 重新加载字典类型列表
|
||||
}
|
||||
else {
|
||||
await deleteDictData(id)
|
||||
window.$message.success('删除字典数据成功')
|
||||
if (lastDictCode.value) {
|
||||
getDictContent(lastDictCode.value) // 重新加载字典数据
|
||||
window.$message.success('字典数据删除成功')
|
||||
refreshDictData()
|
||||
}
|
||||
catch {
|
||||
window.$message.error('字典数据删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 字典数据表格列配置
|
||||
const dictDataColumns = createDictDataColumns({
|
||||
onEdit: (row: Entity.DictData) => dictDataModalRef.value?.openModal('edit', row),
|
||||
onDelete: deleteDictDataItem,
|
||||
})
|
||||
|
||||
// 获取字典数据分页
|
||||
async function getDictDataPage({ current, pageSize }: any, formData: Partial<Entity.DictData>) {
|
||||
if (!currentDictType.value) {
|
||||
return { list: [], total: 0 }
|
||||
}
|
||||
|
||||
try {
|
||||
const { data } = await getDictDataList({
|
||||
...formData,
|
||||
dictType: currentDictType.value.type,
|
||||
pageNum: current || 1,
|
||||
pageSize,
|
||||
})
|
||||
return data
|
||||
}
|
||||
catch {
|
||||
return {
|
||||
list: [],
|
||||
total: 0,
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`删除${isType ? '字典类型' : '字典数据'}失败`, error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NFlex>
|
||||
<div class="basis-2/5">
|
||||
<n-card>
|
||||
<template #header>
|
||||
<NButton type="primary" @click="dictRef!.openModal('add')">
|
||||
<template #icon>
|
||||
<icon-park-outline-add-one />
|
||||
</template>
|
||||
新建
|
||||
</NButton>
|
||||
</template>
|
||||
<template #header-extra>
|
||||
<NFlex>
|
||||
<NButton type="primary" secondary @click="getDictList">
|
||||
<template #icon>
|
||||
<icon-park-outline-refresh />
|
||||
</template>
|
||||
刷新
|
||||
</NButton>
|
||||
</NFlex>
|
||||
</template>
|
||||
<n-data-table
|
||||
:columns="dictColumns" :data="dictData" :loading="dictLoading" :pagination="false"
|
||||
:bordered="false"
|
||||
<div class="flex h-full gap-2">
|
||||
<!-- 左侧字典类型列表 -->
|
||||
<div class="w-1/3">
|
||||
<DictTypeList
|
||||
ref="dictTypeListRef"
|
||||
@select="handleDictTypeSelect"
|
||||
/>
|
||||
</n-card>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<n-card>
|
||||
<template #header>
|
||||
<NButton type="primary" :disabled="!lastDictCode" @click="dictContentRef!.openModal('add')">
|
||||
<template #icon>
|
||||
<icon-park-outline-add-one />
|
||||
</template>
|
||||
新建
|
||||
</NButton>
|
||||
</template>
|
||||
<template #header-extra>
|
||||
<NFlex>
|
||||
<NButton type="primary" :disabled="!lastDictCode" secondary @click="getDictContent(lastDictCode)">
|
||||
<template #icon>
|
||||
<icon-park-outline-refresh />
|
||||
</template>
|
||||
刷新
|
||||
</NButton>
|
||||
</NFlex>
|
||||
</template>
|
||||
<n-data-table
|
||||
:columns="contentColumns" :data="dictContentData" :loading="contentLoading" :pagination="false"
|
||||
:bordered="false"
|
||||
/>
|
||||
</n-card>
|
||||
</div>
|
||||
|
||||
<DictModal ref="dictRef" modal-name="字典项" is-root @success="getDictList" />
|
||||
<DictModal ref="dictContentRef" modal-name="字典值" :dict-code="lastDictCode" @success="() => getDictContent(lastDictCode)" />
|
||||
</NFlex>
|
||||
<!-- 右侧字典数据表格 -->
|
||||
<n-space class="flex-1" vertical>
|
||||
<n-card>
|
||||
<pro-search-form
|
||||
:form="dictDataSearchForm"
|
||||
:columns="dictDataSearchColumns"
|
||||
v-bind="dictDataSearchProps"
|
||||
:collapse-button-props="false"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<pro-data-table
|
||||
:columns="dictDataColumns"
|
||||
v-bind="dictDataTableProps"
|
||||
>
|
||||
<template #title>
|
||||
<n-button
|
||||
v-if="values.dictType"
|
||||
type="primary"
|
||||
@click="dictDataModalRef?.openModal('add', { dictType: values.dictType })"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-park-outline-plus />
|
||||
</template>
|
||||
新增字典数据
|
||||
</n-button>
|
||||
</template>
|
||||
</pro-data-table>
|
||||
</n-space>
|
||||
<!-- 字典数据弹窗 -->
|
||||
<DictDataModal
|
||||
ref="dictDataModalRef"
|
||||
@success="refreshDictData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
@ -88,7 +88,7 @@ export function createMenuColumns(actions: MenuColumnActions): DataTableColumns<
|
||||
type="primary"
|
||||
onClick={() => onAdd(row)}
|
||||
>
|
||||
新建
|
||||
新增
|
||||
</NButton>
|
||||
)}
|
||||
<NButton
|
||||
|
@ -71,7 +71,7 @@ async function openModal(type: ModalType = 'add', data?: Partial<Entity.Menu>) {
|
||||
modalForm.open()
|
||||
const handlers = {
|
||||
async add() {
|
||||
// 如果新建传入了menuId,设置为父级菜单
|
||||
// 如果新增传入了menuId,设置为父级菜单
|
||||
if (data?.id) {
|
||||
modalForm.values.value.parentId = data.id
|
||||
modalForm.values.value.path = `${data.path}/`
|
||||
@ -93,27 +93,34 @@ async function submitModal(filedValues: Partial<Entity.Menu>) {
|
||||
try {
|
||||
await createMenu(filedValues)
|
||||
window.$message.success('菜单创建成功')
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
console.error('创建菜单失败', error)
|
||||
return false
|
||||
}
|
||||
},
|
||||
async edit() {
|
||||
try {
|
||||
await updateMenu(modalForm.values.value.id!, filedValues)
|
||||
window.$message.success('菜单更新成功')
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
console.error('更新菜单失败', error)
|
||||
return false
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
startLoading()
|
||||
await handlers[modalType.value]()
|
||||
const success = await handlers[modalType.value]()
|
||||
endLoading()
|
||||
|
||||
if (success) {
|
||||
emit('success')
|
||||
modalForm.close()
|
||||
endLoading()
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
@ -127,6 +134,7 @@ defineExpose({
|
||||
:form="modalForm"
|
||||
:loading="submitLoading"
|
||||
width="700px"
|
||||
label-width="120px"
|
||||
>
|
||||
<pro-field
|
||||
path="menuType"
|
||||
|
@ -79,9 +79,9 @@ onMounted(() => {
|
||||
<template #title>
|
||||
<n-button type="primary" @click="menuModalRef.openModal('add')">
|
||||
<template #icon>
|
||||
<icon-park-outline-add-one />
|
||||
<icon-park-outline-plus />
|
||||
</template>
|
||||
新建
|
||||
新增
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
|
@ -9,12 +9,12 @@ export const searchColumns: ProSearchFormColumns<Entity.Role> = [
|
||||
path: 'roleName',
|
||||
},
|
||||
{
|
||||
title: '角色权限',
|
||||
title: '权限标识',
|
||||
path: 'roleKey',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
path: 'roleStatus',
|
||||
path: 'status',
|
||||
field: 'select',
|
||||
fieldProps: {
|
||||
options: [
|
||||
@ -35,24 +35,18 @@ export const searchColumns: ProSearchFormColumns<Entity.Role> = [
|
||||
interface RoleColumnActions {
|
||||
onEdit: (row: Entity.Role) => void
|
||||
onDelete: (id: number) => void
|
||||
onStatusChange: (value: 0 | 1, id: number) => void
|
||||
onStatusChange: (id: number, value: 0 | 1) => void
|
||||
}
|
||||
|
||||
export function createRoleColumns(actions: RoleColumnActions): DataTableColumns<Entity.Role> {
|
||||
return [
|
||||
{
|
||||
title: '角色ID',
|
||||
align: 'center',
|
||||
key: 'roleId',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '角色名称',
|
||||
align: 'center',
|
||||
key: 'roleName',
|
||||
},
|
||||
{
|
||||
title: '角色权限',
|
||||
title: '权限标识',
|
||||
align: 'center',
|
||||
key: 'roleKey',
|
||||
render: row => renderProCopyableText(row.roleKey),
|
||||
@ -63,25 +57,18 @@ export function createRoleColumns(actions: RoleColumnActions): DataTableColumns<
|
||||
key: 'remark',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
align: 'center',
|
||||
key: 'sort',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
align: 'center',
|
||||
key: 'roleStatus',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
render: (row) => {
|
||||
return (
|
||||
<NSwitch
|
||||
value={row.roleStatus || 1}
|
||||
checked-value={1}
|
||||
unchecked-value={0}
|
||||
onUpdateValue={(value: 0 | 1) =>
|
||||
actions.onStatusChange(value, row.roleId)}
|
||||
value={row.status}
|
||||
checked-value={0}
|
||||
unchecked-value={1}
|
||||
onUpdateValue={(value: 0 | 1) => actions.onStatusChange(row.id, value)}
|
||||
>
|
||||
{{ checked: () => '启用', unchecked: () => '禁用' }}
|
||||
</NSwitch>
|
||||
@ -106,19 +93,19 @@ export function createRoleColumns(actions: RoleColumnActions): DataTableColumns<
|
||||
return (
|
||||
<NSpace justify="center">
|
||||
<NButton
|
||||
size="small"
|
||||
text
|
||||
type="primary"
|
||||
onClick={() => actions.onEdit(row)}
|
||||
>
|
||||
编辑
|
||||
</NButton>
|
||||
<NPopconfirm
|
||||
onPositiveClick={() => actions.onDelete(row.roleId)}
|
||||
onPositiveClick={() => actions.onDelete(row.id)}
|
||||
>
|
||||
{{
|
||||
default: () => '确认删除该角色?',
|
||||
trigger: () => (
|
||||
<NButton size="small" type="error">
|
||||
<NButton text type="error">
|
||||
删除
|
||||
</NButton>
|
||||
),
|
||||
|
@ -20,11 +20,7 @@ const { bool: submitLoading, setTrue: startLoading, setFalse: endLoading } = use
|
||||
const modalForm = createProModalForm<Partial<Entity.Role>>({
|
||||
omitEmptyString: false,
|
||||
initialValues: {
|
||||
roleName: '',
|
||||
roleKey: '',
|
||||
remark: '',
|
||||
sort: 0,
|
||||
roleStatus: 1,
|
||||
status: 0,
|
||||
},
|
||||
onSubmit: submitModal,
|
||||
})
|
||||
@ -51,7 +47,7 @@ async function openModal(type: ModalType = 'add', data?: Partial<Entity.Role>) {
|
||||
if (!data)
|
||||
return
|
||||
|
||||
const { data: role } = await getRoleById(data.roleId!)
|
||||
const { data: role } = await getRoleById(data.id!)
|
||||
modalForm.values.value = role
|
||||
},
|
||||
}
|
||||
@ -64,28 +60,33 @@ async function submitModal(filedValues: Partial<Entity.Role>) {
|
||||
try {
|
||||
await createRole(filedValues)
|
||||
window.$message.success('角色创建成功')
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
console.error('创建角色失败', error)
|
||||
window.$message.error('创建角色失败')
|
||||
return false
|
||||
}
|
||||
},
|
||||
async edit() {
|
||||
try {
|
||||
await updateRole(modalForm.values.value.roleId!, filedValues)
|
||||
await updateRole(modalForm.values.value.id!, filedValues)
|
||||
window.$message.success('角色更新成功')
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
console.error('更新角色失败', error)
|
||||
window.$message.error('更新角色失败')
|
||||
return false
|
||||
}
|
||||
},
|
||||
}
|
||||
startLoading()
|
||||
await handlers[modalType.value]()
|
||||
const success = await handlers[modalType.value]()
|
||||
endLoading()
|
||||
|
||||
if (success) {
|
||||
emit('success')
|
||||
modalForm.close()
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
@ -105,29 +106,21 @@ defineExpose({
|
||||
required
|
||||
title="角色名称"
|
||||
path="roleName"
|
||||
placeholder="请输入角色名称"
|
||||
/>
|
||||
<pro-input
|
||||
required
|
||||
title="角色权限"
|
||||
title="权限标识"
|
||||
path="roleKey"
|
||||
placeholder="请输入角色权限"
|
||||
/>
|
||||
<pro-switch
|
||||
title="状态"
|
||||
path="status"
|
||||
:field-props="{ checkedValue: 0, uncheckedValue: 1 }"
|
||||
/>
|
||||
<pro-textarea
|
||||
class="col-span-2"
|
||||
title="备注"
|
||||
path="remark"
|
||||
placeholder="请输入备注信息"
|
||||
/>
|
||||
<pro-digit
|
||||
title="排序"
|
||||
path="sort"
|
||||
:field-props="{ min: 0, max: 999 }"
|
||||
/>
|
||||
<pro-switch
|
||||
title="状态"
|
||||
path="roleStatus"
|
||||
:field-props="{ checkedValue: 1, uncheckedValue: 0 }"
|
||||
/>
|
||||
</div>
|
||||
</pro-modal-form>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup lang="tsx">
|
||||
import { createProSearchForm, useNDataTable } from 'pro-naive-ui'
|
||||
import { deleteRole, getRoleList } from '@/api'
|
||||
import { deleteRole, getRoleList, updateRole } from '@/api'
|
||||
import { createRoleColumns, searchColumns } from './columns'
|
||||
import RoleModal from './components/RoleModal.vue'
|
||||
|
||||
@ -34,10 +34,21 @@ async function deleteRoleData(id: number) {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateRoleStatus(id: number, value: 0 | 1) {
|
||||
try {
|
||||
await updateRole(id, { status: value })
|
||||
window.$message.success('角色状态更新成功')
|
||||
refresh() // 重新加载列表
|
||||
}
|
||||
catch {
|
||||
window.$message.error('角色状态更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
const tablecolumns = createRoleColumns({
|
||||
onEdit: (row: Entity.Role) => modalRef.value?.openModal('edit', row),
|
||||
onDelete: deleteRoleData,
|
||||
onStatusChange: () => {},
|
||||
onStatusChange: updateRoleStatus,
|
||||
})
|
||||
|
||||
async function getRolePage({ current, pageSize }: any, formData: Entity.Role[]) {
|
||||
@ -47,10 +58,7 @@ async function getRolePage({ current, pageSize }: any, formData: Entity.Role[])
|
||||
pageNum: current,
|
||||
pageSize,
|
||||
})
|
||||
return {
|
||||
list: data.list,
|
||||
total: data.total,
|
||||
}
|
||||
return data
|
||||
}
|
||||
catch {
|
||||
return {
|
||||
@ -81,7 +89,7 @@ async function getRolePage({ current, pageSize }: any, formData: Entity.Role[])
|
||||
<template #icon>
|
||||
<icon-park-outline-plus />
|
||||
</template>
|
||||
新建角色
|
||||
新增角色
|
||||
</n-button>
|
||||
</template>
|
||||
</pro-data-table>
|
||||
|
@ -14,17 +14,17 @@ export const searchColumns: ProSearchFormColumns<Entity.User> = [
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
path: 'userStatus',
|
||||
path: 'status',
|
||||
field: 'select',
|
||||
fieldProps: {
|
||||
options: [
|
||||
{
|
||||
label: '启用',
|
||||
value: 1,
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '禁用',
|
||||
value: 0,
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -36,7 +36,7 @@ export const searchColumns: ProSearchFormColumns<Entity.User> = [
|
||||
interface UserColumnActions {
|
||||
onEdit: (row: Entity.User) => void
|
||||
onDelete: (id: number) => void
|
||||
onStatusChange: (value: 0 | 1, id: number) => void
|
||||
onStatusChange: (id: number, value: 0 | 1) => void
|
||||
}
|
||||
|
||||
export function createUserColumns(actions: UserColumnActions): DataTableColumns<Entity.User> {
|
||||
@ -83,16 +83,15 @@ export function createUserColumns(actions: UserColumnActions): DataTableColumns<
|
||||
{
|
||||
title: '状态',
|
||||
align: 'center',
|
||||
key: 'userStatus',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
render: (row) => {
|
||||
return (
|
||||
<NSwitch
|
||||
value={row.userStatus}
|
||||
value={row.status}
|
||||
checked-value={0}
|
||||
unchecked-value={1}
|
||||
onUpdateValue={(value: 0 | 1) =>
|
||||
actions.onStatusChange(value, row.userId)}
|
||||
onUpdateValue={(value: 0 | 1) => actions.onStatusChange(row.id, value)}
|
||||
>
|
||||
{{ checked: () => '启用', unchecked: () => '禁用' }}
|
||||
</NSwitch>
|
||||
@ -122,7 +121,7 @@ export function createUserColumns(actions: UserColumnActions): DataTableColumns<
|
||||
>
|
||||
编辑
|
||||
</NButton>
|
||||
<NPopconfirm onPositiveClick={() => actions.onDelete(row.userId)}>
|
||||
<NPopconfirm onPositiveClick={() => actions.onDelete(row.id)}>
|
||||
{{
|
||||
default: () => '确认删除',
|
||||
trigger: () => <NButton text type="error">删除</NButton>,
|
||||
|
@ -2,6 +2,7 @@
|
||||
import { useBoolean } from '@/hooks'
|
||||
import { createUser, getRoleOptions, getUserById, updateUser } from '@/api'
|
||||
import { createProModalForm } from 'pro-naive-ui'
|
||||
import { Regex } from '@/constants'
|
||||
|
||||
interface Props {
|
||||
modalName?: string
|
||||
@ -17,12 +18,11 @@ const emit = defineEmits<{
|
||||
|
||||
const { bool: submitLoading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
|
||||
|
||||
const modalForm = createProModalForm<Partial<Entity.User>>({
|
||||
const modalForm = createProModalForm<Partial<Entity.User> & { roleIds?: number[] }>({
|
||||
omitEmptyString: false,
|
||||
initialValues: {
|
||||
gender: 'unknown',
|
||||
userStatus: 1,
|
||||
roles: [],
|
||||
status: 0,
|
||||
},
|
||||
onSubmit: submitModal,
|
||||
})
|
||||
@ -51,8 +51,9 @@ async function openModal(type: ModalType = 'add', data?: Partial<Entity.User>) {
|
||||
if (!data)
|
||||
return
|
||||
|
||||
const { data: user } = await getUserById(data.userId!)
|
||||
const { data: user } = await getUserById(data.id!)
|
||||
modalForm.values.value = user
|
||||
modalForm.values.value.roleIds = user.roles.map(role => role.id)
|
||||
},
|
||||
}
|
||||
await handlers[type]()
|
||||
@ -64,26 +65,33 @@ async function submitModal(filedValues: Partial<Entity.User>) {
|
||||
try {
|
||||
await createUser(filedValues)
|
||||
window.$message.success('用户创建成功')
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
console.error('创建用户失败', error)
|
||||
return false
|
||||
}
|
||||
},
|
||||
async edit() {
|
||||
try {
|
||||
await updateUser(modalForm.values.value.userId!, filedValues)
|
||||
await updateUser(modalForm.values.value.id!, filedValues)
|
||||
window.$message.success('用户更新成功')
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
console.error('更新用户失败', error)
|
||||
return false
|
||||
}
|
||||
},
|
||||
}
|
||||
startLoading()
|
||||
await handlers[modalType.value]()
|
||||
const success = await handlers[modalType.value]()
|
||||
endLoading()
|
||||
|
||||
if (success) {
|
||||
emit('success')
|
||||
modalForm.close()
|
||||
}
|
||||
}
|
||||
defineExpose({
|
||||
openModal,
|
||||
@ -104,6 +112,12 @@ defineExpose({
|
||||
path="username"
|
||||
:readonly="modalType === 'edit'"
|
||||
/>
|
||||
<pro-input
|
||||
v-if="modalType === 'add'"
|
||||
required
|
||||
title="密码"
|
||||
path="password"
|
||||
/>
|
||||
<pro-input
|
||||
title="昵称"
|
||||
path="nickName"
|
||||
@ -112,7 +126,6 @@ defineExpose({
|
||||
title="性别"
|
||||
path="gender"
|
||||
:field-props="{
|
||||
type: 'button',
|
||||
options: [
|
||||
{ label: '男', value: 'male' },
|
||||
{ label: '女', value: 'female' },
|
||||
@ -127,28 +140,41 @@ defineExpose({
|
||||
title="邮箱"
|
||||
path="email"
|
||||
placeholder="example@domain.com"
|
||||
:rule="[
|
||||
{
|
||||
pattern: new RegExp(Regex.Email),
|
||||
message: '请输入正确的邮箱格式',
|
||||
trigger: 'blur',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<pro-input
|
||||
title="手机号"
|
||||
path="phone"
|
||||
placeholder="11位手机号"
|
||||
:rule="[
|
||||
{
|
||||
pattern: new RegExp(Regex.Phone),
|
||||
message: '请输入正确的手机号格式',
|
||||
trigger: 'blur',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<pro-select
|
||||
title="角色"
|
||||
path="roleIds"
|
||||
:field-props="{ multiple: true, options: roleOptions }"
|
||||
/>
|
||||
<pro-switch
|
||||
title="用户状态"
|
||||
path="status"
|
||||
:field-props="{ checkedValue: 0, uncheckedValue: 1 }"
|
||||
/>
|
||||
<pro-textarea
|
||||
class="col-span-2"
|
||||
title="备注"
|
||||
path="remark"
|
||||
/>
|
||||
<pro-switch
|
||||
title="用户状态"
|
||||
path="userStatus"
|
||||
:field-props="{ checkedValue: 1, uncheckedValue: 0 }"
|
||||
/>
|
||||
<pro-select
|
||||
class="col-span-2"
|
||||
title="角色"
|
||||
path="roles"
|
||||
:field-props="{ multiple: true, options: roleOptions }"
|
||||
/>
|
||||
</div>
|
||||
</pro-modal-form>
|
||||
</template>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup lang="tsx">
|
||||
import { createProSearchForm, useNDataTable } from 'pro-naive-ui'
|
||||
import { deleteUser, getUserList } from '@/api'
|
||||
import { deleteUser, getUserList, updateUser } from '@/api'
|
||||
import { createUserColumns, searchColumns } from './columns'
|
||||
import UserModal from './components/UserModal.vue'
|
||||
|
||||
@ -33,10 +33,21 @@ async function delteteUser(id: number) {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateUserStatus(id: number, value: 0 | 1) {
|
||||
try {
|
||||
await updateUser(id, { status: value })
|
||||
window.$message.success('用户状态更新成功')
|
||||
refresh() // 重新加载列表
|
||||
}
|
||||
catch {
|
||||
window.$message.error('角色状态更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
const tablecolumns = createUserColumns({
|
||||
onEdit: row => modalRef.value?.openModal('edit', row),
|
||||
onDelete: delteteUser,
|
||||
onStatusChange: () => {},
|
||||
onStatusChange: updateUserStatus,
|
||||
})
|
||||
|
||||
async function getUserPage({ current, pageSize }: any, formData: Entity.User[]) {
|
||||
@ -46,10 +57,7 @@ async function getUserPage({ current, pageSize }: any, formData: Entity.User[])
|
||||
pageNum: current,
|
||||
pageSize,
|
||||
})
|
||||
return {
|
||||
list: data.list,
|
||||
total: data.total,
|
||||
}
|
||||
return data
|
||||
}
|
||||
catch {
|
||||
return {
|
||||
@ -116,7 +124,7 @@ const treeData = ref([
|
||||
<template #icon>
|
||||
<icon-park-outline-plus />
|
||||
</template>
|
||||
新建用户
|
||||
新增用户
|
||||
</n-button>
|
||||
</template>
|
||||
</pro-data-table>
|
||||
|
Loading…
x
Reference in New Issue
Block a user