feat: 增加字典管理角色管理

This commit is contained in:
chansee97 2025-08-31 17:25:53 +08:00
parent 3124d0b923
commit 53282f9453
26 changed files with 792 additions and 583 deletions

View File

@ -45,7 +45,7 @@ const propOverrides = {
cols: 4,
},
ProModalForm: {
labelWidth: 120,
labelWidth: 100,
labelPlacement: 'left',
preset: 'card',
},

View File

@ -6,7 +6,7 @@ interface UserQueryParams {
pageSize?: number
username?: string
gender?: 'male' | 'female' | 'unknown'
userStatus?: number
status?: number
deptId?: number
}

View File

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

View File

@ -1,5 +0,0 @@
/** Gender */
export enum Gender {
male,
female,
}

View File

@ -1,2 +1 @@
export * from './Regex'
export * from './User'

View File

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

View File

@ -8,7 +8,7 @@ namespace Entity {
/**
* ID
*/
roleId: number
id: number
/**
* ID数组
*/
@ -28,7 +28,7 @@ namespace Entity {
/**
*
*/
roleStatus?: number
status?: number
/**
*
*/

View File

@ -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
/** 备注 */

View File

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

View File

@ -49,7 +49,7 @@ const rules = computed(() => {
})
const formValue = ref({
account: 'admin',
pwd: '12345',
pwd: '123456',
captcha: '',
})
const isRemember = ref(false)

View File

@ -185,7 +185,7 @@ function handleAddTable() {
<template #icon>
<icon-park-outline-add-one />
</template>
</NButton>
<NButton strong secondary>
<template #icon>

View File

@ -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>
),
},
]
}

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

View File

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

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

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

View File

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

View File

@ -88,7 +88,7 @@ export function createMenuColumns(actions: MenuColumnActions): DataTableColumns<
type="primary"
onClick={() => onAdd(row)}
>
</NButton>
)}
<NButton

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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