Merge remote-tracking branch 'origin/main' into dev
This commit is contained in:
commit
46186ac453
@ -7,6 +7,7 @@ from .base import base_router
|
|||||||
from .menus import menus_router
|
from .menus import menus_router
|
||||||
from .roles import roles_router
|
from .roles import roles_router
|
||||||
from .users import users_router
|
from .users import users_router
|
||||||
|
from .depts import depts_router
|
||||||
|
|
||||||
v1_router = APIRouter()
|
v1_router = APIRouter()
|
||||||
|
|
||||||
@ -15,3 +16,4 @@ v1_router.include_router(users_router, prefix="/user", dependencies=[DependPermi
|
|||||||
v1_router.include_router(roles_router, prefix="/role", dependencies=[DependPermisson])
|
v1_router.include_router(roles_router, prefix="/role", dependencies=[DependPermisson])
|
||||||
v1_router.include_router(menus_router, prefix="/menu", dependencies=[DependPermisson])
|
v1_router.include_router(menus_router, prefix="/menu", dependencies=[DependPermisson])
|
||||||
v1_router.include_router(apis_router, prefix="/api", dependencies=[DependPermisson])
|
v1_router.include_router(apis_router, prefix="/api", dependencies=[DependPermisson])
|
||||||
|
v1_router.include_router(depts_router, prefix="/dept", dependencies=[DependPermisson])
|
||||||
|
|||||||
8
app/api/v1/depts/__init__.py
Normal file
8
app/api/v1/depts/__init__.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from .depts import router
|
||||||
|
|
||||||
|
depts_router = APIRouter()
|
||||||
|
depts_router.include_router(router, tags=["部门模块"])
|
||||||
|
|
||||||
|
__all__ = ["depts_router"]
|
||||||
49
app/api/v1/depts/depts.py
Normal file
49
app/api/v1/depts/depts.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
from fastapi import APIRouter, Query
|
||||||
|
|
||||||
|
from app.controllers.dept import dept_controller
|
||||||
|
from app.log import logger
|
||||||
|
from app.schemas import Success, SuccessExtra
|
||||||
|
from app.schemas.depts import *
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/list", summary="查看部门列表")
|
||||||
|
async def list_dept(
|
||||||
|
name: str = Query(None, description="部门名称"),
|
||||||
|
):
|
||||||
|
dept_tree = await dept_controller.get_dept_tree(name)
|
||||||
|
return Success(data=dept_tree)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/get", summary="查看部门")
|
||||||
|
async def get_dept(
|
||||||
|
id: int = Query(..., description="部门ID"),
|
||||||
|
):
|
||||||
|
dept_obj = await dept_controller.get(id=id)
|
||||||
|
data = await dept_obj.to_dict()
|
||||||
|
return Success(data=data)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/create", summary="创建部门")
|
||||||
|
async def create_dept(
|
||||||
|
dept_in: DeptCreate,
|
||||||
|
):
|
||||||
|
await dept_controller.create_dept(obj_in=dept_in)
|
||||||
|
return Success(msg="Created Successfully")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/update", summary="更新部门")
|
||||||
|
async def update_dept(
|
||||||
|
dept_in: DeptUpdate,
|
||||||
|
):
|
||||||
|
await dept_controller.update_dept(obj_in=dept_in)
|
||||||
|
return Success(msg="Update Successfully")
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/delete", summary="删除部门")
|
||||||
|
async def delete_dept(
|
||||||
|
dept_id: int = Query(..., description="部门ID"),
|
||||||
|
):
|
||||||
|
await dept_controller.delete_dept(dept_id=dept_id)
|
||||||
|
return Success(msg="Deleted Success")
|
||||||
@ -7,6 +7,7 @@ from tortoise.expressions import Q
|
|||||||
from app.controllers.user import UserController
|
from app.controllers.user import UserController
|
||||||
from app.schemas.base import Success, SuccessExtra
|
from app.schemas.base import Success, SuccessExtra
|
||||||
from app.schemas.users import *
|
from app.schemas.users import *
|
||||||
|
from app.controllers.dept import dept_controller
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ async def list_user(
|
|||||||
page_size: int = Query(10, description="每页数量"),
|
page_size: int = Query(10, description="每页数量"),
|
||||||
username: str = Query("", description="用户名称,用于搜索"),
|
username: str = Query("", description="用户名称,用于搜索"),
|
||||||
email: str = Query("", description="邮箱地址"),
|
email: str = Query("", description="邮箱地址"),
|
||||||
|
dept_id: int = Query(None, description="部门ID")
|
||||||
):
|
):
|
||||||
user_controller = UserController()
|
user_controller = UserController()
|
||||||
q = Q()
|
q = Q()
|
||||||
@ -26,8 +28,14 @@ async def list_user(
|
|||||||
q &= Q(username__contains=username)
|
q &= Q(username__contains=username)
|
||||||
if email:
|
if email:
|
||||||
q &= Q(email__contains=email)
|
q &= Q(email__contains=email)
|
||||||
|
if dept_id is not None:
|
||||||
|
q &= Q(dept_id=dept_id)
|
||||||
total, user_objs = await user_controller.list(page=page, page_size=page_size, search=q)
|
total, user_objs = await user_controller.list(page=page, page_size=page_size, search=q)
|
||||||
data = [await obj.to_dict(m2m=True, exclude_fields=["password"]) for obj in user_objs]
|
data = [await obj.to_dict(m2m=True, exclude_fields=["password"]) for obj in user_objs]
|
||||||
|
for item in data:
|
||||||
|
dept_id = item.pop("dept_id", None)
|
||||||
|
item["dept"] = await (await dept_controller.get(id=dept_id)).to_dict() if dept_id else {}
|
||||||
|
|
||||||
return SuccessExtra(data=data, total=total, page=page, page_size=page_size)
|
return SuccessExtra(data=data, total=total, page=page, page_size=page_size)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
95
app/controllers/dept.py
Normal file
95
app/controllers/dept.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
from app.core.crud import CRUDBase
|
||||||
|
from app.models.admin import Dept, DeptClosure
|
||||||
|
from app.schemas.depts import DeptCreate, DeptUpdate
|
||||||
|
from tortoise.transactions import atomic
|
||||||
|
from tortoise.expressions import Q
|
||||||
|
|
||||||
|
|
||||||
|
class DeptController(CRUDBase[Dept, DeptCreate, DeptUpdate]):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(model=Dept)
|
||||||
|
|
||||||
|
async def get_dept_tree(self, name):
|
||||||
|
q = Q()
|
||||||
|
# 获取所有未被软删除的部门
|
||||||
|
q &= Q(is_deleted=False)
|
||||||
|
if name:
|
||||||
|
q &= Q(name__contains=name)
|
||||||
|
all_depts = await self.model.filter(q).order_by('order')
|
||||||
|
# 辅助函数,用于递归构建部门树
|
||||||
|
def build_tree(parent_id):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'id': dept.id,
|
||||||
|
'name': dept.name,
|
||||||
|
'desc': dept.desc,
|
||||||
|
'order': dept.order,
|
||||||
|
'parent_id': dept.parent_id,
|
||||||
|
'children': build_tree(dept.id) # 递归构建子部门
|
||||||
|
}
|
||||||
|
for dept in all_depts if dept.parent_id == parent_id
|
||||||
|
]
|
||||||
|
|
||||||
|
# 从顶级部门(parent_id=0)开始构建部门树
|
||||||
|
dept_tree = build_tree(0)
|
||||||
|
return dept_tree
|
||||||
|
|
||||||
|
async def get_dept_info(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def update_dept_closure(self, obj: Dept):
|
||||||
|
parent_depts = await DeptClosure.filter(descendant=obj.parent_id)
|
||||||
|
for i in parent_depts:
|
||||||
|
print(i.ancestor, i.descendant)
|
||||||
|
dept_closure_objs: list[DeptClosure] = []
|
||||||
|
# 插入父级关系
|
||||||
|
for item in parent_depts:
|
||||||
|
dept_closure_objs.append(
|
||||||
|
DeptClosure(
|
||||||
|
ancestor=item.ancestor,
|
||||||
|
descendant=obj.id,
|
||||||
|
level=item.level + 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# 插入自身x
|
||||||
|
dept_closure_objs.append(
|
||||||
|
DeptClosure(
|
||||||
|
ancestor=obj.id,
|
||||||
|
descendant=obj.id,
|
||||||
|
level=0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# 创建关系
|
||||||
|
await DeptClosure.bulk_create(dept_closure_objs)
|
||||||
|
|
||||||
|
@atomic()
|
||||||
|
async def create_dept(self, obj_in: DeptCreate):
|
||||||
|
# 创建
|
||||||
|
if obj_in.parent_id != 0:
|
||||||
|
await self.get(id=obj_in.parent_id)
|
||||||
|
new_obj = await self.create(obj_in=obj_in)
|
||||||
|
await self.update_dept_closure(new_obj)
|
||||||
|
|
||||||
|
|
||||||
|
@atomic()
|
||||||
|
async def update_dept(self, obj_in: DeptUpdate):
|
||||||
|
dept_obj = await self.get(id=obj_in.id)
|
||||||
|
# 更新部门关系
|
||||||
|
if dept_obj.parent_id != obj_in.parent_id:
|
||||||
|
await DeptClosure.filter(ancestor=dept_obj.id).delete()
|
||||||
|
await DeptClosure.filter(descendant=dept_obj.id).delete()
|
||||||
|
await self.update_dept_closure(dept_obj)
|
||||||
|
# 更新部门信息
|
||||||
|
dept_obj.update_from_dict(obj_in.model_dump(exclude_unset=True))
|
||||||
|
await dept_obj.save()
|
||||||
|
|
||||||
|
@atomic()
|
||||||
|
async def delete_dept(self, dept_id: int):
|
||||||
|
# 删除部门
|
||||||
|
obj = await self.get(id=dept_id)
|
||||||
|
obj.is_deleted = True
|
||||||
|
await obj.save()
|
||||||
|
# 删除关系
|
||||||
|
await DeptClosure.filter(descendant=dept_id).delete()
|
||||||
|
|
||||||
|
dept_controller = DeptController()
|
||||||
@ -16,6 +16,7 @@ class User(BaseModel, TimestampMixin):
|
|||||||
is_superuser = fields.BooleanField(default=False, description="是否为超级管理员")
|
is_superuser = fields.BooleanField(default=False, description="是否为超级管理员")
|
||||||
last_login = fields.DatetimeField(null=True, description="最后登录时间")
|
last_login = fields.DatetimeField(null=True, description="最后登录时间")
|
||||||
roles = fields.ManyToManyField("models.Role", related_name="user_roles")
|
roles = fields.ManyToManyField("models.Role", related_name="user_roles")
|
||||||
|
dept_id = fields.IntField(null=True, description="部门ID")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
table = "user"
|
table = "user"
|
||||||
@ -65,10 +66,16 @@ class Menu(BaseModel, TimestampMixin):
|
|||||||
|
|
||||||
class Dept(BaseModel, TimestampMixin):
|
class Dept(BaseModel, TimestampMixin):
|
||||||
name = fields.CharField(max_length=20, unique=True, description="部门名称")
|
name = fields.CharField(max_length=20, unique=True, description="部门名称")
|
||||||
desc = fields.CharField(max_length=500, null=True, blank=True, description="菜单描述")
|
desc = fields.CharField(max_length=500, null=True, blank=True, description="备注")
|
||||||
is_deleted = fields.BooleanField(default=False, description="软删除标记")
|
is_deleted = fields.BooleanField(default=False, description="软删除标记")
|
||||||
order = fields.IntField(default=0, description="排序")
|
order = fields.IntField(default=0, description="排序")
|
||||||
parent_id = fields.IntField(default=0, max_length=10, description="父部门ID")
|
parent_id = fields.IntField(default=0, max_length=10, description="父部门ID")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
table = "dept"
|
table = "dept"
|
||||||
|
|
||||||
|
|
||||||
|
class DeptClosure(BaseModel, TimestampMixin):
|
||||||
|
ancestor = fields.IntField(description="父代")
|
||||||
|
descendant = fields.IntField(description="子代")
|
||||||
|
level = fields.IntField(default=0, description="深度")
|
||||||
|
|||||||
19
app/schemas/depts.py
Normal file
19
app/schemas/depts.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class BaseDept(BaseModel):
|
||||||
|
name: str = Field(..., description="部门名称", example="研发中心")
|
||||||
|
desc: str = Field("", description="备注", example="研发中心")
|
||||||
|
order: int = Field(0, description="排序")
|
||||||
|
parent_id: int = Field(0, description="父部门ID")
|
||||||
|
|
||||||
|
|
||||||
|
class DeptCreate(BaseDept):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class DeptUpdate(BaseDept):
|
||||||
|
id: int
|
||||||
|
|
||||||
|
def update_dict(self):
|
||||||
|
return self.model_dump(exclude_unset=True, exclude={"id"})
|
||||||
@ -23,6 +23,7 @@ class UserCreate(BaseModel):
|
|||||||
is_active: Optional[bool] = True
|
is_active: Optional[bool] = True
|
||||||
is_superuser: Optional[bool] = False
|
is_superuser: Optional[bool] = False
|
||||||
roles: Optional[List[int]] = []
|
roles: Optional[List[int]] = []
|
||||||
|
dept_id: Optional[int] = Field(0, description="部门ID")
|
||||||
|
|
||||||
def create_dict(self):
|
def create_dict(self):
|
||||||
return self.model_dump(exclude_unset=True, exclude={"roles"})
|
return self.model_dump(exclude_unset=True, exclude={"roles"})
|
||||||
@ -35,6 +36,7 @@ class UserUpdate(BaseModel):
|
|||||||
is_active: Optional[bool] = True
|
is_active: Optional[bool] = True
|
||||||
is_superuser: Optional[bool] = False
|
is_superuser: Optional[bool] = False
|
||||||
roles: Optional[List[int]] = []
|
roles: Optional[List[int]] = []
|
||||||
|
dept_id: Optional[int] = 0
|
||||||
|
|
||||||
|
|
||||||
class UpdatePassword(BaseModel):
|
class UpdatePassword(BaseModel):
|
||||||
|
|||||||
5483
web/pnpm-lock.yaml
generated
5483
web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -31,4 +31,9 @@ export default {
|
|||||||
updateApi: (data = {}) => request.post('/api/update', data),
|
updateApi: (data = {}) => request.post('/api/update', data),
|
||||||
deleteApi: (params = {}) => request.delete('/api/delete', { params }),
|
deleteApi: (params = {}) => request.delete('/api/delete', { params }),
|
||||||
refreshApi: (data = {}) => request.post('/api/refresh', data),
|
refreshApi: (data = {}) => request.post('/api/refresh', data),
|
||||||
|
// depts
|
||||||
|
getDepts: (params = {}) => request.get('/dept/list', { params }),
|
||||||
|
createDept: (data = {}) => request.post('/dept/create', data),
|
||||||
|
updateDept: (data = {}) => request.post('/dept/update', data),
|
||||||
|
deleteDept: (params = {}) => request.delete('/dept/delete', { params }),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -128,5 +128,6 @@ function onChecked(rowKeys) {
|
|||||||
defineExpose({
|
defineExpose({
|
||||||
handleSearch,
|
handleSearch,
|
||||||
handleReset,
|
handleReset,
|
||||||
|
tableData,
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
216
web/src/views/system/dept/index.vue
Normal file
216
web/src/views/system/dept/index.vue
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
<script setup>
|
||||||
|
import { h, onMounted, ref, resolveDirective, withDirectives } from 'vue'
|
||||||
|
import { NButton, NForm, NFormItem, NInput, NInputNumber, NPopconfirm, NTreeSelect } from 'naive-ui'
|
||||||
|
|
||||||
|
import CommonPage from '@/components/page/CommonPage.vue'
|
||||||
|
import QueryBarItem from '@/components/query-bar/QueryBarItem.vue'
|
||||||
|
import CrudModal from '@/components/table/CrudModal.vue'
|
||||||
|
import CrudTable from '@/components/table/CrudTable.vue'
|
||||||
|
import TheIcon from '@/components/icon/TheIcon.vue'
|
||||||
|
|
||||||
|
import { renderIcon } from '@/utils'
|
||||||
|
import { useCRUD } from '@/composables'
|
||||||
|
// import { loginTypeMap, loginTypeOptions } from '@/constant/data'
|
||||||
|
import api from '@/api'
|
||||||
|
|
||||||
|
defineOptions({ name: '部门管理' })
|
||||||
|
|
||||||
|
const $table = ref(null)
|
||||||
|
const queryItems = ref({})
|
||||||
|
const vPermission = resolveDirective('permission')
|
||||||
|
|
||||||
|
const {
|
||||||
|
modalVisible,
|
||||||
|
modalTitle,
|
||||||
|
modalLoading,
|
||||||
|
handleSave,
|
||||||
|
modalForm,
|
||||||
|
modalFormRef,
|
||||||
|
handleEdit,
|
||||||
|
handleDelete,
|
||||||
|
handleAdd,
|
||||||
|
} = useCRUD({
|
||||||
|
name: 'API',
|
||||||
|
initForm: { order: 0 },
|
||||||
|
doCreate: api.createDept,
|
||||||
|
doUpdate: api.updateDept,
|
||||||
|
doDelete: api.deleteDept,
|
||||||
|
refresh: () => $table.value?.handleSearch(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const deptOption = ref([])
|
||||||
|
const isDisabled = ref(false)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
$table.value?.handleSearch()
|
||||||
|
api.getDepts().then((res) => (deptOption.value = res.data))
|
||||||
|
})
|
||||||
|
|
||||||
|
const deptRules = {
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入部门名称',
|
||||||
|
trigger: ['input', 'blur', 'change'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addDepts() {
|
||||||
|
isDisabled.value = false
|
||||||
|
handleAdd()
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '部门名称',
|
||||||
|
key: 'name',
|
||||||
|
width: 'auto',
|
||||||
|
align: 'center',
|
||||||
|
ellipsis: { tooltip: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '备注',
|
||||||
|
key: 'desc',
|
||||||
|
align: 'center',
|
||||||
|
width: 'auto',
|
||||||
|
ellipsis: { tooltip: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 'auto',
|
||||||
|
align: 'center',
|
||||||
|
fixed: 'right',
|
||||||
|
render(row) {
|
||||||
|
return [
|
||||||
|
withDirectives(
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
type: 'primary',
|
||||||
|
style: 'margin-right: 8px;',
|
||||||
|
onClick: () => {
|
||||||
|
console.log('row', row.parent_id)
|
||||||
|
if (row.parent_id === 0) {
|
||||||
|
isDisabled.value = true
|
||||||
|
} else {
|
||||||
|
isDisabled.value = false
|
||||||
|
}
|
||||||
|
handleEdit(row)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => '编辑',
|
||||||
|
icon: renderIcon('material-symbols:edit', { size: 16 }),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
[[vPermission, 'post/api/v1/dept/update']]
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
NPopconfirm,
|
||||||
|
{
|
||||||
|
onPositiveClick: () => handleDelete({ dept_id: row.id }, false),
|
||||||
|
onNegativeClick: () => {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
trigger: () =>
|
||||||
|
withDirectives(
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
type: 'error',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => '删除',
|
||||||
|
icon: renderIcon('material-symbols:delete-outline', { size: 16 }),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
[[vPermission, 'delete/api/v1/dept/delete']]
|
||||||
|
),
|
||||||
|
default: () => h('div', {}, '确定删除该部门吗?'),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- 业务页面 -->
|
||||||
|
<CommonPage show-footer title="部门列表">
|
||||||
|
<template #action>
|
||||||
|
<div>
|
||||||
|
<NButton
|
||||||
|
v-permission="'post/api/v1/dept/create'"
|
||||||
|
class="float-right mr-15"
|
||||||
|
type="primary"
|
||||||
|
@click="addDepts"
|
||||||
|
>
|
||||||
|
<TheIcon icon="material-symbols:add" :size="18" class="mr-5" />新建部门
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- 表格 -->
|
||||||
|
<CrudTable
|
||||||
|
ref="$table"
|
||||||
|
v-model:query-items="queryItems"
|
||||||
|
:columns="columns"
|
||||||
|
:get-data="api.getDepts"
|
||||||
|
>
|
||||||
|
<template #queryBar>
|
||||||
|
<QueryBarItem label="部门名称" :label-width="80">
|
||||||
|
<NInput
|
||||||
|
v-model:value="queryItems.name"
|
||||||
|
clearable
|
||||||
|
type="text"
|
||||||
|
placeholder="请输入部门名称"
|
||||||
|
@keypress.enter="$table?.handleSearch()"
|
||||||
|
/>
|
||||||
|
</QueryBarItem>
|
||||||
|
</template>
|
||||||
|
</CrudTable>
|
||||||
|
|
||||||
|
<!-- 新增/编辑 弹窗 -->
|
||||||
|
<CrudModal
|
||||||
|
v-model:visible="modalVisible"
|
||||||
|
:title="modalTitle"
|
||||||
|
:loading="modalLoading"
|
||||||
|
@save="handleSave"
|
||||||
|
>
|
||||||
|
<NForm
|
||||||
|
ref="modalFormRef"
|
||||||
|
label-placement="left"
|
||||||
|
label-align="left"
|
||||||
|
:label-width="80"
|
||||||
|
:model="modalForm"
|
||||||
|
:rules="deptRules"
|
||||||
|
>
|
||||||
|
<NFormItem label="父级部门" path="parent_id">
|
||||||
|
<NTreeSelect
|
||||||
|
v-model:value="modalForm.parent_id"
|
||||||
|
:options="deptOption"
|
||||||
|
key-field="id"
|
||||||
|
label-field="name"
|
||||||
|
placeholder="请选择父级部门"
|
||||||
|
clearable
|
||||||
|
default-expand-all
|
||||||
|
:disabled="isDisabled"
|
||||||
|
></NTreeSelect>
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem label="部门名称" path="name">
|
||||||
|
<NInput v-model:value="modalForm.name" clearable placeholder="请输入部门名称" />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem label="备注" path="desc">
|
||||||
|
<NInput v-model:value="modalForm.desc" type="textarea" clearable />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem label="排序" path="order">
|
||||||
|
<NInputNumber v-model:value="modalForm.order" min="0"></NInputNumber>
|
||||||
|
</NFormItem>
|
||||||
|
</NForm>
|
||||||
|
</CrudModal>
|
||||||
|
</CommonPage>
|
||||||
|
</template>
|
||||||
@ -25,7 +25,6 @@ import { formatDate, renderIcon } from '@/utils'
|
|||||||
import { useCRUD } from '@/composables'
|
import { useCRUD } from '@/composables'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import TheIcon from '@/components/icon/TheIcon.vue'
|
import TheIcon from '@/components/icon/TheIcon.vue'
|
||||||
import { uniq } from 'lodash-es'
|
|
||||||
|
|
||||||
defineOptions({ name: '角色管理' })
|
defineOptions({ name: '角色管理' })
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,10 @@ import {
|
|||||||
NSwitch,
|
NSwitch,
|
||||||
NTag,
|
NTag,
|
||||||
NPopconfirm,
|
NPopconfirm,
|
||||||
|
NLayout,
|
||||||
|
NLayoutSider,
|
||||||
|
NLayoutContent,
|
||||||
|
NTreeSelect,
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
|
|
||||||
import CommonPage from '@/components/page/CommonPage.vue'
|
import CommonPage from '@/components/page/CommonPage.vue'
|
||||||
@ -53,10 +57,12 @@ const {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const roleOption = ref([])
|
const roleOption = ref([])
|
||||||
|
const deptOption = ref([])
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
$table.value?.handleSearch()
|
$table.value?.handleSearch()
|
||||||
api.getRoleList({ page: 1, page_size: 9999 }).then((res) => (roleOption.value = res.data))
|
api.getRoleList({ page: 1, page_size: 9999 }).then((res) => (roleOption.value = res.data))
|
||||||
|
api.getDepts().then((res) => (deptOption.value = res.data))
|
||||||
})
|
})
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
@ -104,6 +110,13 @@ const columns = [
|
|||||||
return h('span', group)
|
return h('span', group)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '部门',
|
||||||
|
key: 'dept.name',
|
||||||
|
align: 'center',
|
||||||
|
width: 40,
|
||||||
|
ellipsis: { tooltip: true },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '超级用户',
|
title: '超级用户',
|
||||||
key: 'is_superuser',
|
key: 'is_superuser',
|
||||||
@ -169,6 +182,7 @@ const columns = [
|
|||||||
onClick: () => {
|
onClick: () => {
|
||||||
// roles => role_ids
|
// roles => role_ids
|
||||||
handleEdit(row)
|
handleEdit(row)
|
||||||
|
modalForm.value.dept_id = row.dept?.id
|
||||||
modalForm.value.roles = row.roles.map((e) => (e = e.id))
|
modalForm.value.roles = row.roles.map((e) => (e = e.id))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -225,6 +239,7 @@ async function handleUpdateDisable(row) {
|
|||||||
role_ids.push(e.id)
|
role_ids.push(e.id)
|
||||||
})
|
})
|
||||||
row.roles = role_ids
|
row.roles = role_ids
|
||||||
|
row.dept_id = row.dept?.id
|
||||||
try {
|
try {
|
||||||
await api.updateUser(row)
|
await api.updateUser(row)
|
||||||
$message?.success(row.is_active ? '已取消禁用该用户' : '已禁用该用户')
|
$message?.success(row.is_active ? '已取消禁用该用户' : '已禁用该用户')
|
||||||
@ -237,6 +252,16 @@ async function handleUpdateDisable(row) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nodeProps = ({ option }) => {
|
||||||
|
return {
|
||||||
|
onClick() {
|
||||||
|
api.getUserList({ dept_id: option.id }).then((res) => {
|
||||||
|
$table.value.tableData = res.data
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const validateAddUser = {
|
const validateAddUser = {
|
||||||
username: [
|
username: [
|
||||||
{
|
{
|
||||||
@ -299,110 +324,138 @@ const validateAddUser = {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- 业务页面 -->
|
<NLayout has-sider wh-full>
|
||||||
<CommonPage show-footer title="用户列表">
|
<NLayoutSider bordered content-style="padding: 24px;">
|
||||||
<template #action>
|
<h1>部门管理</h1>
|
||||||
<NButton v-permission="'post/api/v1/user/create'" type="primary" @click="handleAdd">
|
<br />
|
||||||
<TheIcon icon="material-symbols:add" :size="18" class="mr-5" />新建用户
|
<NTree
|
||||||
</NButton>
|
block-line
|
||||||
</template>
|
:data="deptOption"
|
||||||
<!-- 表格 -->
|
key-field="id"
|
||||||
<CrudTable
|
label-field="name"
|
||||||
ref="$table"
|
default-expand-all
|
||||||
v-model:query-items="queryItems"
|
:node-props="nodeProps"
|
||||||
:columns="columns"
|
|
||||||
:get-data="api.getUserList"
|
|
||||||
>
|
|
||||||
<template #queryBar>
|
|
||||||
<QueryBarItem label="名称" :label-width="40">
|
|
||||||
<NInput
|
|
||||||
v-model:value="queryItems.username"
|
|
||||||
clearable
|
|
||||||
type="text"
|
|
||||||
placeholder="请输入用户名称"
|
|
||||||
@keypress.enter="$table?.handleSearch()"
|
|
||||||
/>
|
|
||||||
</QueryBarItem>
|
|
||||||
<QueryBarItem label="邮箱" :label-width="40">
|
|
||||||
<NInput
|
|
||||||
v-model:value="queryItems.email"
|
|
||||||
clearable
|
|
||||||
type="text"
|
|
||||||
placeholder="请输入邮箱"
|
|
||||||
@keypress.enter="$table?.handleSearch()"
|
|
||||||
/>
|
|
||||||
</QueryBarItem>
|
|
||||||
</template>
|
|
||||||
</CrudTable>
|
|
||||||
|
|
||||||
<!-- 新增/编辑 弹窗 -->
|
|
||||||
<CrudModal
|
|
||||||
v-model:visible="modalVisible"
|
|
||||||
:title="modalTitle"
|
|
||||||
:loading="modalLoading"
|
|
||||||
@save="handleSave"
|
|
||||||
>
|
|
||||||
<NForm
|
|
||||||
ref="modalFormRef"
|
|
||||||
label-placement="left"
|
|
||||||
label-align="left"
|
|
||||||
:label-width="80"
|
|
||||||
:model="modalForm"
|
|
||||||
:rules="validateAddUser"
|
|
||||||
>
|
>
|
||||||
<NFormItem label="用户名称" path="username">
|
</NTree>
|
||||||
<NInput v-model:value="modalForm.username" clearable placeholder="请输入用户名称" />
|
</NLayoutSider>
|
||||||
</NFormItem>
|
<NLayoutContent>
|
||||||
<NFormItem label="邮箱" path="email">
|
<CommonPage show-footer title="用户列表">
|
||||||
<NInput v-model:value="modalForm.email" clearable placeholder="请输入邮箱" />
|
<template #action>
|
||||||
</NFormItem>
|
<NButton v-permission="'post/api/v1/user/create'" type="primary" @click="handleAdd">
|
||||||
<NFormItem v-if="modalAction === 'add'" label="密码" path="password">
|
<TheIcon icon="material-symbols:add" :size="18" class="mr-5" />新建用户
|
||||||
<NInput
|
</NButton>
|
||||||
v-model:value="modalForm.password"
|
</template>
|
||||||
show-password-on="mousedown"
|
<!-- 表格 -->
|
||||||
type="password"
|
<CrudTable
|
||||||
clearable
|
ref="$table"
|
||||||
placeholder="请输入密码"
|
v-model:query-items="queryItems"
|
||||||
/>
|
:columns="columns"
|
||||||
</NFormItem>
|
:get-data="api.getUserList"
|
||||||
<NFormItem v-if="modalAction === 'add'" label="确认密码" path="confirmPassword">
|
>
|
||||||
<NInput
|
<template #queryBar>
|
||||||
v-model:value="modalForm.confirmPassword"
|
<QueryBarItem label="名称" :label-width="40">
|
||||||
show-password-on="mousedown"
|
<NInput
|
||||||
type="password"
|
v-model:value="queryItems.username"
|
||||||
clearable
|
clearable
|
||||||
placeholder="请确认密码"
|
type="text"
|
||||||
/>
|
placeholder="请输入用户名称"
|
||||||
</NFormItem>
|
@keypress.enter="$table?.handleSearch()"
|
||||||
<NFormItem label="角色" path="roles">
|
|
||||||
<NCheckboxGroup v-model:value="modalForm.roles">
|
|
||||||
<NSpace item-style="display: flex;">
|
|
||||||
<NCheckbox
|
|
||||||
v-for="item in roleOption"
|
|
||||||
:key="item.id"
|
|
||||||
:value="item.id"
|
|
||||||
:label="item.name"
|
|
||||||
/>
|
/>
|
||||||
</NSpace>
|
</QueryBarItem>
|
||||||
</NCheckboxGroup>
|
<QueryBarItem label="邮箱" :label-width="40">
|
||||||
</NFormItem>
|
<NInput
|
||||||
<NFormItem label="超级用户" path="is_superuser">
|
v-model:value="queryItems.email"
|
||||||
<NSwitch
|
clearable
|
||||||
v-model:value="modalForm.is_superuser"
|
type="text"
|
||||||
size="small"
|
placeholder="请输入邮箱"
|
||||||
:checked-value="true"
|
@keypress.enter="$table?.handleSearch()"
|
||||||
:unchecked-value="false"
|
/>
|
||||||
></NSwitch>
|
</QueryBarItem>
|
||||||
</NFormItem>
|
</template>
|
||||||
<NFormItem label="禁用" path="is_active">
|
</CrudTable>
|
||||||
<NSwitch
|
|
||||||
v-model:value="modalForm.is_active"
|
<!-- 新增/编辑 弹窗 -->
|
||||||
:checked-value="false"
|
<CrudModal
|
||||||
:unchecked-value="true"
|
v-model:visible="modalVisible"
|
||||||
:default-value="true"
|
:title="modalTitle"
|
||||||
/>
|
:loading="modalLoading"
|
||||||
</NFormItem>
|
@save="handleSave"
|
||||||
</NForm>
|
>
|
||||||
</CrudModal>
|
<NForm
|
||||||
</CommonPage>
|
ref="modalFormRef"
|
||||||
|
label-placement="left"
|
||||||
|
label-align="left"
|
||||||
|
:label-width="80"
|
||||||
|
:model="modalForm"
|
||||||
|
:rules="validateAddUser"
|
||||||
|
>
|
||||||
|
<NFormItem label="用户名称" path="username">
|
||||||
|
<NInput v-model:value="modalForm.username" clearable placeholder="请输入用户名称" />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem label="邮箱" path="email">
|
||||||
|
<NInput v-model:value="modalForm.email" clearable placeholder="请输入邮箱" />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem v-if="modalAction === 'add'" label="密码" path="password">
|
||||||
|
<NInput
|
||||||
|
v-model:value="modalForm.password"
|
||||||
|
show-password-on="mousedown"
|
||||||
|
type="password"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入密码"
|
||||||
|
/>
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem v-if="modalAction === 'add'" label="确认密码" path="confirmPassword">
|
||||||
|
<NInput
|
||||||
|
v-model:value="modalForm.confirmPassword"
|
||||||
|
show-password-on="mousedown"
|
||||||
|
type="password"
|
||||||
|
clearable
|
||||||
|
placeholder="请确认密码"
|
||||||
|
/>
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem label="角色" path="roles">
|
||||||
|
<NCheckboxGroup v-model:value="modalForm.roles">
|
||||||
|
<NSpace item-style="display: flex;">
|
||||||
|
<NCheckbox
|
||||||
|
v-for="item in roleOption"
|
||||||
|
:key="item.id"
|
||||||
|
:value="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
/>
|
||||||
|
</NSpace>
|
||||||
|
</NCheckboxGroup>
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem label="超级用户" path="is_superuser">
|
||||||
|
<NSwitch
|
||||||
|
v-model:value="modalForm.is_superuser"
|
||||||
|
size="small"
|
||||||
|
:checked-value="true"
|
||||||
|
:unchecked-value="false"
|
||||||
|
></NSwitch>
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem label="禁用" path="is_active">
|
||||||
|
<NSwitch
|
||||||
|
v-model:value="modalForm.is_active"
|
||||||
|
:checked-value="false"
|
||||||
|
:unchecked-value="true"
|
||||||
|
:default-value="true"
|
||||||
|
/>
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem label="部门" path="dept_id">
|
||||||
|
<NTreeSelect
|
||||||
|
v-model:value="modalForm.dept_id"
|
||||||
|
:options="deptOption"
|
||||||
|
key-field="id"
|
||||||
|
label-field="name"
|
||||||
|
placeholder="请选择部门"
|
||||||
|
clearable
|
||||||
|
default-expand-all
|
||||||
|
></NTreeSelect>
|
||||||
|
</NFormItem>
|
||||||
|
</NForm>
|
||||||
|
</CrudModal>
|
||||||
|
</CommonPage>
|
||||||
|
</NLayoutContent>
|
||||||
|
</NLayout>
|
||||||
|
<!-- 业务页面 -->
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user