Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
E
EMS
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
hejie
EMS
Commits
a3402cdf
提交
a3402cdf
authored
4月 24, 2025
作者:
hejie
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat: 用户管理
上级
49ff1dae
隐藏空白字符变更
内嵌
并排
正在显示
12 个修改的文件
包含
1360 行增加
和
21 行删除
+1360
-21
system.ts
src/api/system.ts
+12
-5
user.ts
src/api/user.ts
+7
-1
index.vue
src/views/systems/user/form/index.vue
+176
-0
role.vue
src/views/systems/user/form/role.vue
+53
-0
index.vue
src/views/systems/user/index.vue
+266
-15
expand.svg
src/views/systems/user/svg/expand.svg
+2
-0
unexpand.svg
src/views/systems/user/svg/unexpand.svg
+2
-0
tree.vue
src/views/systems/user/tree.vue
+211
-0
hook.tsx
src/views/systems/user/utils/hook.tsx
+549
-0
reset.css
src/views/systems/user/utils/reset.css
+5
-0
rule.ts
src/views/systems/user/utils/rule.ts
+39
-0
types.ts
src/views/systems/user/utils/types.ts
+38
-0
没有找到文件。
src/api/system.ts
浏览文件 @
a3402cdf
...
...
@@ -9,19 +9,22 @@ type ResultTable = {
success
:
boolean
;
data
?:
{
/** 列表数据 */
list
:
Array
<
any
>
;
list
?
:
Array
<
any
>
;
/** 总条目数 */
total
?:
number
;
/** 每页显示条目个数 */
pageSize
?:
number
;
/** 当前页数 */
currentPage
?:
number
;
records
?:
Array
<
any
>
;
};
};
/** 获取系统管理-用户管理列表 */
export
const
getUserList
=
(
data
?:
object
)
=>
{
return
http
.
request
<
ResultTable
>
(
"post"
,
"/user"
,
{
data
});
return
http
.
request
<
ResultTable
>
(
"post"
,
"/api/user/find-user-list-by-page"
,
{
data
});
};
/** 系统管理-用户管理-获取所有角色列表 */
...
...
@@ -53,9 +56,13 @@ export const addMenu = (data?: object) => {
/** 获取系统管理-部门管理列表 */
export
const
getDeptList
=
(
data
?:
object
)
=>
{
return
http
.
request
<
Result
>
(
"post"
,
"/api/depart/get-depart-list-by-page"
,
{
data
});
return
http
.
request
<
ResultTable
>
(
"post"
,
"/api/depart/get-depart-list-by-page"
,
{
data
}
);
};
/** 新增部门-部门管理列表 */
...
...
src/api/user.ts
浏览文件 @
a3402cdf
...
...
@@ -54,7 +54,8 @@ export type UserInfo = {
export
type
UserInfoResult
=
{
success
:
boolean
;
data
:
UserInfo
;
data
?:
UserInfo
;
code
?:
number
;
};
type
ResultTable
=
{
...
...
@@ -90,3 +91,8 @@ export const getMine = () => {
export
const
getMineLogs
=
(
data
?:
object
)
=>
{
return
http
.
request
<
ResultTable
>
(
"get"
,
"/mine-logs"
,
{
data
});
};
/** 系统管理-用户管理-新增用户 */
export
const
addUser
=
(
data
?:
object
)
=>
{
return
http
.
request
<
UserInfoResult
>
(
"post"
,
"/api/user/add-user"
,
{
data
});
};
src/views/systems/user/form/index.vue
0 → 100644
浏览文件 @
a3402cdf
<
script
setup
lang=
"ts"
>
import
{
ref
}
from
"vue"
;
import
ReCol
from
"@/components/ReCol"
;
import
{
formRules
}
from
"../utils/rule"
;
import
{
FormProps
}
from
"../utils/types"
;
import
{
usePublicHooks
}
from
"../../hooks"
;
const
props
=
withDefaults
(
defineProps
<
FormProps
>
(),
{
formInline
:
()
=>
({
title
:
"新增"
,
higherDeptOptions
:
[],
parentId
:
0
,
nickname
:
""
,
username
:
""
,
password
:
""
,
phone
:
""
,
email
:
""
,
sex
:
""
,
status
:
1
,
remark
:
""
})
});
const
sexOptions
=
[
{
value
:
0
,
label
:
"男"
},
{
value
:
1
,
label
:
"女"
}
];
const
ruleFormRef
=
ref
();
const
{
switchStyle
}
=
usePublicHooks
();
const
newFormInline
=
ref
(
props
.
formInline
);
function
getRef
()
{
return
ruleFormRef
.
value
;
}
defineExpose
({
getRef
});
</
script
>
<
template
>
<el-form
ref=
"ruleFormRef"
:model=
"newFormInline"
:rules=
"formRules"
label-width=
"82px"
>
<el-row
:gutter=
"30"
>
<re-col
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"用户昵称"
prop=
"nickname"
>
<el-input
v-model=
"newFormInline.nickname"
clearable
placeholder=
"请输入用户昵称"
/>
</el-form-item>
</re-col>
<re-col
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"用户名称"
prop=
"username"
>
<el-input
v-model=
"newFormInline.username"
clearable
placeholder=
"请输入用户名称"
/>
</el-form-item>
</re-col>
<re-col
v-if=
"newFormInline.title === '新增'"
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"用户密码"
prop=
"password"
>
<el-input
v-model=
"newFormInline.password"
clearable
placeholder=
"请输入用户密码"
/>
</el-form-item>
</re-col>
<re-col
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"手机号"
prop=
"phone"
>
<el-input
v-model=
"newFormInline.phone"
clearable
placeholder=
"请输入手机号"
/>
</el-form-item>
</re-col>
<re-col
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"邮箱"
prop=
"email"
>
<el-input
v-model=
"newFormInline.email"
clearable
placeholder=
"请输入邮箱"
/>
</el-form-item>
</re-col>
<re-col
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"用户性别"
>
<el-select
v-model=
"newFormInline.sex"
placeholder=
"请选择用户性别"
class=
"w-full"
clearable
>
<el-option
v-for=
"(item, index) in sexOptions"
:key=
"index"
:label=
"item.label"
:value=
"item.value"
/>
</el-select>
</el-form-item>
</re-col>
<re-col
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"归属部门"
>
<el-cascader
v-model=
"newFormInline.parentId"
class=
"w-full"
:options=
"newFormInline.higherDeptOptions"
:props=
"
{
value: 'id',
label: 'name',
emitPath: false,
checkStrictly: true
}"
clearable
filterable
placeholder="请选择归属部门"
>
<template
#
default=
"
{ node, data }">
<span>
{{
data
.
name
}}
</span>
<span
v-if=
"!node.isLeaf"
>
(
{{
data
.
children
.
length
}}
)
</span>
</
template
>
</el-cascader>
</el-form-item>
</re-col>
<re-col
v-if=
"newFormInline.title === '新增'"
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"用户状态"
>
<el-switch
v-model=
"newFormInline.status"
inline-prompt
:active-value=
"1"
:inactive-value=
"0"
active-text=
"启用"
inactive-text=
"停用"
:style=
"switchStyle"
/>
</el-form-item>
</re-col>
<re-col>
<el-form-item
label=
"备注"
>
<el-input
v-model=
"newFormInline.remark"
placeholder=
"请输入备注信息"
type=
"textarea"
/>
</el-form-item>
</re-col>
</el-row>
</el-form>
</template>
src/views/systems/user/form/role.vue
0 → 100644
浏览文件 @
a3402cdf
<
script
setup
lang=
"ts"
>
import
{
ref
}
from
"vue"
;
import
ReCol
from
"@/components/ReCol"
;
import
{
RoleFormProps
}
from
"../utils/types"
;
const
props
=
withDefaults
(
defineProps
<
RoleFormProps
>
(),
{
formInline
:
()
=>
({
username
:
""
,
nickname
:
""
,
roleOptions
:
[],
ids
:
[]
})
});
const
newFormInline
=
ref
(
props
.
formInline
);
</
script
>
<
template
>
<el-form
:model=
"newFormInline"
>
<el-row
:gutter=
"30"
>
<!--
<re-col>
<el-form-item
label=
"用户名称"
prop=
"username"
>
<el-input
disabled
v-model=
"newFormInline.username"
/>
</el-form-item>
</re-col>
-->
<re-col>
<el-form-item
label=
"用户昵称"
prop=
"nickname"
>
<el-input
v-model=
"newFormInline.nickname"
disabled
/>
</el-form-item>
</re-col>
<re-col>
<el-form-item
label=
"角色列表"
prop=
"ids"
>
<el-select
v-model=
"newFormInline.ids"
placeholder=
"请选择"
class=
"w-full"
clearable
multiple
>
<el-option
v-for=
"(item, index) in newFormInline.roleOptions"
:key=
"index"
:value=
"item.id"
:label=
"item.name"
>
{{
item
.
name
}}
</el-option>
</el-select>
</el-form-item>
</re-col>
</el-row>
</el-form>
</
template
>
src/views/systems/user/index.vue
浏览文件 @
a3402cdf
<
script
setup
lang=
"ts"
>
import
{
ref
}
from
"vue"
;
import
tree
from
"./tree.vue"
;
import
{
useUser
}
from
"./utils/hook"
;
import
{
PureTableBar
}
from
"@/components/RePureTableBar"
;
import
{
useRenderIcon
}
from
"@/components/ReIcon/src/hooks"
;
import
Upload
from
"~icons/ri/upload-line"
;
import
Role
from
"~icons/ri/admin-line"
;
import
Password
from
"~icons/ri/lock-password-line"
;
import
More
from
"~icons/ep/more-filled"
;
import
Delete
from
"~icons/ep/delete"
;
import
EditPen
from
"~icons/ep/edit-pen"
;
import
Refresh
from
"~icons/ep/refresh"
;
import
AddFill
from
"~icons/ri/add-circle-line"
;
defineOptions
({
name
:
"SystemUser"
});
const
treeRef
=
ref
();
const
formRef
=
ref
();
const
tableRef
=
ref
();
const
{
form
,
loading
,
columns
,
dataList
,
treeData
,
treeLoading
,
selectedNum
,
pagination
,
buttonClass
,
deviceDetection
,
onSearch
,
resetForm
,
onbatchDel
,
openDialog
,
onTreeSelect
,
handleUpdate
,
handleDelete
,
handleUpload
,
handleReset
,
handleRole
,
handleSizeChange
,
onSelectionCancel
,
handleCurrentChange
,
handleSelectionChange
}
=
useUser
(
tableRef
,
treeRef
);
</
script
>
<
template
>
<div
class=
"systems"
>
<h2>
Systems
</h2>
<slot
/>
<div
:class=
"['flex', 'justify-between', deviceDetection() && 'flex-wrap']"
>
<tree
ref=
"treeRef"
:class=
"['mr-2', deviceDetection() ? 'w-full' : 'min-w-[200px]']"
:treeData=
"treeData"
:treeLoading=
"treeLoading"
@
tree-select=
"onTreeSelect"
/>
<div
:class=
"[deviceDetection() ? ['w-full', 'mt-2'] : 'w-[calc(100%-200px)]']"
>
<el-form
ref=
"formRef"
:inline=
"true"
:model=
"form"
class=
"search-form bg-bg_color w-full pl-8 pt-[12px] overflow-auto"
>
<el-form-item
label=
"用户名称:"
prop=
"username"
>
<el-input
v-model=
"form.username"
placeholder=
"请输入用户名称"
clearable
class=
"w-[180px]!"
/>
</el-form-item>
<el-form-item
label=
"手机号码:"
prop=
"phone"
>
<el-input
v-model=
"form.phone"
placeholder=
"请输入手机号码"
clearable
class=
"w-[180px]!"
/>
</el-form-item>
<el-form-item
label=
"状态:"
prop=
"status"
>
<el-select
v-model=
"form.status"
placeholder=
"请选择"
clearable
class=
"w-[180px]!"
>
<el-option
label=
"已开启"
value=
"1"
/>
<el-option
label=
"已关闭"
value=
"0"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button
type=
"primary"
:icon=
"useRenderIcon('ri/search-line')"
:loading=
"loading"
@
click=
"onSearch"
>
搜索
</el-button>
<el-button
:icon=
"useRenderIcon(Refresh)"
@
click=
"resetForm(formRef)"
>
重置
</el-button>
</el-form-item>
</el-form>
<PureTableBar
title=
"用户管理(仅演示,操作后不生效)"
:columns=
"columns"
@
refresh=
"onSearch"
>
<template
#
buttons
>
<el-button
type=
"primary"
:icon=
"useRenderIcon(AddFill)"
@
click=
"openDialog()"
>
新增用户
</el-button>
</
template
>
<
template
v-slot=
"{ size, dynamicColumns }"
>
<div
v-if=
"selectedNum > 0"
v-motion-fade
class=
"bg-[var(--el-fill-color-light)] w-full h-[46px] mb-2 pl-4 flex items-center"
>
<div
class=
"flex-auto"
>
<span
style=
"font-size: var(--el-font-size-base)"
class=
"text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]"
>
已选
{{
selectedNum
}}
项
</span>
<el-button
type=
"primary"
text
@
click=
"onSelectionCancel"
>
取消选择
</el-button>
</div>
<el-popconfirm
title=
"是否确认删除?"
@
confirm=
"onbatchDel"
>
<template
#
reference
>
<el-button
type=
"danger"
text
class=
"mr-1!"
>
批量删除
</el-button>
</
template
>
</el-popconfirm>
</div>
<pure-table
ref=
"tableRef"
row-key=
"id"
adaptive
:adaptiveConfig=
"{ offsetBottom: 108 }"
align-whole=
"center"
table-layout=
"auto"
:loading=
"loading"
:size=
"size"
:data=
"dataList"
:columns=
"dynamicColumns"
:pagination=
"{ ...pagination, size }"
:header-cell-style=
"{
background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)'
}"
@
selection-change=
"handleSelectionChange"
@
page-size-change=
"handleSizeChange"
@
page-current-change=
"handleCurrentChange"
>
<
template
#
operation=
"{ row }"
>
<el-button
class=
"reset-margin"
link
type=
"primary"
:size=
"size"
:icon=
"useRenderIcon(EditPen)"
@
click=
"openDialog('修改', row)"
>
修改
</el-button>
<el-popconfirm
:title=
"`是否确认删除用户编号为$
{row.id}的这条数据`"
@confirm="handleDelete(row)"
>
<template
#
reference
>
<el-button
class=
"reset-margin"
link
type=
"primary"
:size=
"size"
:icon=
"useRenderIcon(Delete)"
>
删除
</el-button>
</
template
>
</el-popconfirm>
<el-dropdown>
<el-button
class=
"ml-3! mt-[2px]!"
link
type=
"primary"
:size=
"size"
:icon=
"useRenderIcon(More)"
@
click=
"handleUpdate(row)"
/>
<
template
#
dropdown
>
<el-dropdown-menu>
<el-dropdown-item>
<el-button
:class=
"buttonClass"
link
type=
"primary"
:size=
"size"
:icon=
"useRenderIcon(Upload)"
@
click=
"handleUpload(row)"
>
上传头像
</el-button>
</el-dropdown-item>
<el-dropdown-item>
<el-button
:class=
"buttonClass"
link
type=
"primary"
:size=
"size"
:icon=
"useRenderIcon(Password)"
@
click=
"handleReset(row)"
>
重置密码
</el-button>
</el-dropdown-item>
<el-dropdown-item>
<el-button
:class=
"buttonClass"
link
type=
"primary"
:size=
"size"
:icon=
"useRenderIcon(Role)"
@
click=
"handleRole(row)"
>
分配角色
</el-button>
</el-dropdown-item>
</el-dropdown-menu>
</
template
>
</el-dropdown>
</template>
</pure-table>
</template>
</PureTableBar>
</div>
</div>
</template>
<
script
setup
lang=
"ts"
>
// 组件逻辑部分
import
{
ref
}
from
"vue"
;
<
style
lang=
"scss"
scoped
>
:deep
(
.el-dropdown-menu__item
i
)
{
margin
:
0
;
}
const
count
=
ref
(
0
);
:deep
(
.el-button
:focus-visible
)
{
outline
:
none
;
}
const
increment
=
()
=>
{
count
.
value
++
;
};
</
script
>
.main-content
{
margin
:
24px
24px
0
!
important
;
}
<
style
scoped
lang=
"scss"
>
.systems
{
padding
:
20
px
;
border
:
1px
solid
#ccc
;
.search-form
{
:deep
(
.el-form-item
)
{
margin-bottom
:
12
px
;
}
}
</
style
>
src/views/systems/user/svg/expand.svg
0 → 100644
浏览文件 @
a3402cdf
<svg
width=
"32"
height=
"32"
viewBox=
"0 0 24 24"
><path
fill=
"currentColor"
d=
"M22 4V2H2v2h9v14.17l-5.5-5.5-1.42 1.41L12 22l7.92-7.92-1.42-1.41-5.5 5.5V4z"
/></svg>
\ No newline at end of file
src/views/systems/user/svg/unexpand.svg
0 → 100644
浏览文件 @
a3402cdf
<svg
width=
"32"
height=
"32"
viewBox=
"0 0 24 24"
><path
fill=
"currentColor"
d=
"M4 2H2v20h2v-9h14.17l-5.5 5.5 1.41 1.42L22 12l-7.92-7.92-1.41 1.42 5.5 5.5H4z"
/></svg>
\ No newline at end of file
src/views/systems/user/tree.vue
0 → 100644
浏览文件 @
a3402cdf
<
script
setup
lang=
"ts"
>
import
{
useRenderIcon
}
from
"@/components/ReIcon/src/hooks"
;
import
{
ref
,
computed
,
watch
,
getCurrentInstance
}
from
"vue"
;
import
Dept
from
"~icons/ri/git-branch-line"
;
// import Reset from "~icons/ri/restart-line";
import
More2Fill
from
"~icons/ri/more-2-fill?width=18&height=18"
;
import
OfficeBuilding
from
"~icons/ep/office-building"
;
import
LocationCompany
from
"~icons/ep/add-location"
;
import
ExpandIcon
from
"./svg/expand.svg?component"
;
import
UnExpandIcon
from
"./svg/unexpand.svg?component"
;
interface
Tree
{
id
:
number
;
name
:
string
;
highlight
?:
boolean
;
children
?:
Tree
[];
}
defineProps
({
treeLoading
:
Boolean
,
treeData
:
Array
});
const
emit
=
defineEmits
([
"tree-select"
]);
const
treeRef
=
ref
();
const
isExpand
=
ref
(
true
);
const
searchValue
=
ref
(
""
);
const
highlightMap
=
ref
({});
const
{
proxy
}
=
getCurrentInstance
();
const
defaultProps
=
{
children
:
"children"
,
label
:
"name"
};
const
buttonClass
=
computed
(()
=>
{
return
[
"h-[20px]!"
,
"text-sm!"
,
"reset-margin"
,
"text-(--el-text-color-regular)!"
,
"dark:text-white!"
,
"dark:hover:text-primary!"
];
});
const
filterNode
=
(
value
:
string
,
data
:
Tree
)
=>
{
if
(
!
value
)
return
true
;
return
data
.
name
.
includes
(
value
);
};
function
nodeClick
(
value
)
{
const
nodeId
=
value
.
$treeNodeId
;
highlightMap
.
value
[
nodeId
]
=
highlightMap
.
value
[
nodeId
]?.
highlight
?
Object
.
assign
({
id
:
nodeId
},
highlightMap
.
value
[
nodeId
],
{
highlight
:
false
})
:
Object
.
assign
({
id
:
nodeId
},
highlightMap
.
value
[
nodeId
],
{
highlight
:
true
});
Object
.
values
(
highlightMap
.
value
).
forEach
((
v
:
Tree
)
=>
{
if
(
v
.
id
!==
nodeId
)
{
v
.
highlight
=
false
;
}
});
emit
(
"tree-select"
,
highlightMap
.
value
[
nodeId
]?.
highlight
?
Object
.
assign
({
...
value
,
selected
:
true
})
:
Object
.
assign
({
...
value
,
selected
:
false
})
);
}
function
toggleRowExpansionAll
(
status
)
{
isExpand
.
value
=
status
;
const
nodes
=
(
proxy
.
$refs
[
"treeRef"
]
as
any
).
store
.
_getAllNodes
();
for
(
let
i
=
0
;
i
<
nodes
.
length
;
i
++
)
{
nodes
[
i
].
expanded
=
status
;
}
}
/** 重置部门树状态(选中状态、搜索框值、树初始化) */
function
onTreeReset
()
{
highlightMap
.
value
=
{};
searchValue
.
value
=
""
;
toggleRowExpansionAll
(
true
);
}
watch
(
searchValue
,
val
=>
{
treeRef
.
value
!
.
filter
(
val
);
});
defineExpose
({
onTreeReset
});
</
script
>
<
template
>
<div
v-loading=
"treeLoading"
class=
"h-full bg-bg_color overflow-hidden relative"
:style=
"
{ minHeight: `calc(100vh - 141px)` }"
>
<div
class=
"flex items-center h-[34px]"
>
<el-input
v-model=
"searchValue"
class=
"ml-2"
size=
"small"
placeholder=
"请输入部门名称"
clearable
>
<template
#
suffix
>
<el-icon
class=
"el-input__icon"
>
<IconifyIconOffline
v-show=
"searchValue.length === 0"
icon=
"ri/search-line"
/>
</el-icon>
</
template
>
</el-input>
<el-dropdown
:hide-on-click=
"false"
>
<More2Fill
class=
"w-[28px] cursor-pointer outline-hidden"
/>
<
template
#
dropdown
>
<el-dropdown-menu>
<el-dropdown-item>
<el-button
:class=
"buttonClass"
link
type=
"primary"
:icon=
"useRenderIcon(isExpand ? ExpandIcon : UnExpandIcon)"
@
click=
"toggleRowExpansionAll(isExpand ? false : true)"
>
{{
isExpand
?
"折叠全部"
:
"展开全部"
}}
</el-button>
</el-dropdown-item>
<!--
<el-dropdown-item>
<el-button
:class=
"buttonClass"
link
type=
"primary"
:icon=
"useRenderIcon(Reset)"
@
click=
"onTreeReset"
>
重置状态
</el-button>
</el-dropdown-item>
-->
</el-dropdown-menu>
</
template
>
</el-dropdown>
</div>
<el-divider
/>
<el-scrollbar
height=
"calc(90vh - 88px)"
>
<el-tree
ref=
"treeRef"
:data=
"treeData"
node-key=
"id"
size=
"small"
:props=
"defaultProps"
default-expand-all
:expand-on-click-node=
"false"
:filter-node-method=
"filterNode"
@
node-click=
"nodeClick"
>
<
template
#
default=
"{ node, data }"
>
<div
:class=
"[
'rounded-sm',
'flex',
'items-center',
'select-none',
'hover:text-primary',
searchValue.trim().length > 0 &&
node.label.includes(searchValue) &&
'text-red-500',
highlightMap[node.id]?.highlight ? 'dark:text-primary' : ''
]"
:style=
"
{
color: highlightMap[node.id]?.highlight
? 'var(--el-color-primary)'
: '',
background: highlightMap[node.id]?.highlight
? 'var(--el-color-primary-light-7)'
: 'transparent'
}"
>
<IconifyIconOffline
:icon=
"
data.type === 1
? OfficeBuilding
: data.type === 2
? LocationCompany
: Dept
"
/>
<span
class=
"w-[120px]! truncate!"
:title=
"node.label"
>
{{
node
.
label
}}
</span>
</div>
</
template
>
</el-tree>
</el-scrollbar>
</div>
</template>
<
style
lang=
"scss"
scoped
>
:deep
(
.el-divider
)
{
margin
:
0
;
}
:deep
(
.el-tree
)
{
--el-tree-node-hover-bg-color
:
transparent
;
}
</
style
>
src/views/systems/user/utils/hook.tsx
0 → 100644
浏览文件 @
a3402cdf
import
"./reset.css"
;
// import dayjs from "dayjs";
import
roleForm
from
"../form/role.vue"
;
import
editForm
from
"../form/index.vue"
;
import
{
zxcvbn
}
from
"@zxcvbn-ts/core"
;
import
{
handleTree
}
from
"@/utils/tree"
;
import
{
message
}
from
"@/utils/message"
;
import
userAvatar
from
"@/assets/user.jpg"
;
import
{
usePublicHooks
}
from
"../../hooks"
;
import
{
addDialog
}
from
"@/components/ReDialog"
;
import
type
{
PaginationProps
}
from
"@pureadmin/table"
;
import
ReCropperPreview
from
"@/components/ReCropperPreview"
;
import
type
{
FormItemProps
,
RoleFormItemProps
}
from
"../utils/types"
;
import
{
getKeyList
,
isAllEmpty
,
// hideTextAtIndex,
deviceDetection
}
from
"@pureadmin/utils"
;
import
{
getRoleIds
,
getDeptList
,
getUserList
,
getAllRoleList
}
from
"@/api/system"
;
import
{
addUser
}
from
"@/api/user"
;
import
{
ElForm
,
ElInput
,
ElFormItem
,
ElProgress
,
ElMessageBox
}
from
"element-plus"
;
import
{
type
Ref
,
h
,
ref
,
toRaw
,
watch
,
computed
,
reactive
,
onMounted
}
from
"vue"
;
export
function
useUser
(
tableRef
:
Ref
,
treeRef
:
Ref
)
{
const
form
=
reactive
({
// 左侧部门树的id
deptId
:
""
,
username
:
""
,
phone
:
""
,
status
:
""
,
pageNum
:
1
,
pageSize
:
10
});
const
formRef
=
ref
();
const
ruleFormRef
=
ref
();
const
dataList
=
ref
([]);
const
loading
=
ref
(
true
);
// 上传头像信息
const
avatarInfo
=
ref
();
const
switchLoadMap
=
ref
({});
const
{
switchStyle
}
=
usePublicHooks
();
const
higherDeptOptions
=
ref
();
const
treeData
=
ref
([]);
const
treeLoading
=
ref
(
true
);
const
selectedNum
=
ref
(
0
);
const
pagination
=
reactive
<
PaginationProps
>
({
total
:
0
,
pageSize
:
10
,
currentPage
:
1
,
background
:
true
});
const
columns
:
TableColumnList
=
[
{
label
:
"勾选列"
,
// 如果需要表格多选,此处label必须设置
type
:
"selection"
,
fixed
:
"left"
,
reserveSelection
:
true
// 数据刷新后保留选项
},
{
label
:
"用户编号"
,
prop
:
"id"
,
width
:
90
},
{
label
:
"用户头像"
,
prop
:
"avatar"
,
cellRenderer
:
({
row
})
=>
(
<
el
-
image
fit=
"cover"
preview
-
teleported=
{
true
}
src=
{
row
.
avatar
||
userAvatar
}
preview
-
src
-
list=
{
Array
.
of
(
row
.
avatar
||
userAvatar
)
}
class=
"w-[24px] h-[24px] rounded-full align-middle"
/>
),
width
:
90
},
{
label
:
"用户名称"
,
prop
:
"username"
,
minWidth
:
130
},
{
label
:
"用户昵称"
,
prop
:
"nickname"
,
minWidth
:
130
},
// {
// label: "性别",
// prop: "gender",
// minWidth: 90,
// cellRenderer: ({ row, props }) => (
// <el-tag
// size={props.size}
// type={row.gender === 1 ? "danger" : null}
// effect="plain"
// >
// {row.gender === 1 ? "女" : "男"}
// </el-tag>
// )
// },
// {
// label: "部门",
// prop: "dept.name",
// minWidth: 90
// },
{
label
:
"手机号码"
,
prop
:
"mobile"
,
minWidth
:
90
// formatter: ({ phone }) => hideTextAtIndex(phone, { start: 3, end: 6 })
},
{
label
:
"状态"
,
prop
:
"status"
,
minWidth
:
90
,
cellRenderer
:
scope
=>
(
<
el
-
switch
size=
{
scope
.
props
.
size
===
"small"
?
"small"
:
"default"
}
loading=
{
switchLoadMap
.
value
[
scope
.
index
]?.
loading
}
v
-
model=
{
scope
.
row
.
status
}
active
-
value=
{
1
}
inactive
-
value=
{
0
}
active
-
text=
"已启用"
inactive
-
text=
"已停用"
inline
-
prompt
style=
{
switchStyle
.
value
}
onChange=
{
()
=>
onChange
(
scope
as
any
)
}
/>
)
},
// {
// label: "创建时间",
// minWidth: 90,
// prop: "createTime",
// formatter: ({ createTime }) =>
// dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
// },
{
label
:
"操作"
,
fixed
:
"right"
,
width
:
180
,
slot
:
"operation"
}
];
const
buttonClass
=
computed
(()
=>
{
return
[
"h-[20px]!"
,
"reset-margin"
,
"text-gray-500!"
,
"dark:text-white!"
,
"dark:hover:text-primary!"
];
});
// 重置的新密码
const
pwdForm
=
reactive
({
newPwd
:
""
});
const
pwdProgress
=
[
{
color
:
"#e74242"
,
text
:
"非常弱"
},
{
color
:
"#EFBD47"
,
text
:
"弱"
},
{
color
:
"#ffa500"
,
text
:
"一般"
},
{
color
:
"#1bbf1b"
,
text
:
"强"
},
{
color
:
"#008000"
,
text
:
"非常强"
}
];
// 当前密码强度(0-4)
const
curScore
=
ref
();
const
roleOptions
=
ref
([]);
function
onChange
({
row
,
index
})
{
ElMessageBox
.
confirm
(
`确认要<strong>
${
row
.
status
===
0
?
"停用"
:
"启用"
}
</strong><strong style='color:var(--el-color-primary)'>
${
row
.
username
}
</strong>用户吗?`
,
"系统提示"
,
{
confirmButtonText
:
"确定"
,
cancelButtonText
:
"取消"
,
type
:
"warning"
,
dangerouslyUseHTMLString
:
true
,
draggable
:
true
}
)
.
then
(()
=>
{
switchLoadMap
.
value
[
index
]
=
Object
.
assign
(
{},
switchLoadMap
.
value
[
index
],
{
loading
:
true
}
);
setTimeout
(()
=>
{
switchLoadMap
.
value
[
index
]
=
Object
.
assign
(
{},
switchLoadMap
.
value
[
index
],
{
loading
:
false
}
);
message
(
"已成功修改用户状态"
,
{
type
:
"success"
});
},
300
);
})
.
catch
(()
=>
{
row
.
status
===
0
?
(
row
.
status
=
1
)
:
(
row
.
status
=
0
);
});
}
function
handleUpdate
(
row
)
{
console
.
log
(
row
);
}
function
handleDelete
(
row
)
{
message
(
`您删除了用户编号为
${
row
.
id
}
的这条数据`
,
{
type
:
"success"
});
onSearch
();
}
function
handleSizeChange
(
val
:
number
)
{
console
.
log
(
`
${
val
}
items per page`
);
}
function
handleCurrentChange
(
val
:
number
)
{
console
.
log
(
`current page:
${
val
}
`
);
}
/** 当CheckBox选择项发生变化时会触发该事件 */
function
handleSelectionChange
(
val
)
{
selectedNum
.
value
=
val
.
length
;
// 重置表格高度
tableRef
.
value
.
setAdaptive
();
}
/** 取消选择 */
function
onSelectionCancel
()
{
selectedNum
.
value
=
0
;
// 用于多选表格,清空用户的选择
tableRef
.
value
.
getTableRef
().
clearSelection
();
}
/** 批量删除 */
function
onbatchDel
()
{
// 返回当前选中的行
const
curSelected
=
tableRef
.
value
.
getTableRef
().
getSelectionRows
();
// 接下来根据实际业务,通过选中行的某项数据,比如下面的id,调用接口进行批量删除
message
(
`已删除用户编号为
${
getKeyList
(
curSelected
,
"id"
)}
的数据`
,
{
type
:
"success"
});
tableRef
.
value
.
getTableRef
().
clearSelection
();
onSearch
();
}
async
function
onSearch
()
{
loading
.
value
=
true
;
const
{
data
}
=
await
getUserList
(
toRaw
(
form
));
dataList
.
value
=
data
.
records
;
pagination
.
total
=
data
.
total
;
pagination
.
pageSize
=
data
.
pageSize
;
pagination
.
currentPage
=
data
.
currentPage
;
setTimeout
(()
=>
{
loading
.
value
=
false
;
},
500
);
}
const
resetForm
=
formEl
=>
{
if
(
!
formEl
)
return
;
formEl
.
resetFields
();
form
.
deptId
=
""
;
treeRef
.
value
.
onTreeReset
();
onSearch
();
};
function
onTreeSelect
({
id
,
selected
})
{
form
.
deptId
=
selected
?
id
:
""
;
onSearch
();
}
function
formatHigherDeptOptions
(
treeList
)
{
// 根据返回数据的status字段值判断追加是否禁用disabled字段,返回处理后的树结构,用于上级部门级联选择器的展示(实际开发中也是如此,不可能前端需要的每个字段后端都会返回,这时需要前端自行根据后端返回的某些字段做逻辑处理)
if
(
!
treeList
||
!
treeList
.
length
)
return
;
const
newTreeList
=
[];
for
(
let
i
=
0
;
i
<
treeList
.
length
;
i
++
)
{
treeList
[
i
].
disabled
=
treeList
[
i
].
status
===
0
?
true
:
false
;
formatHigherDeptOptions
(
treeList
[
i
].
children
);
newTreeList
.
push
(
treeList
[
i
]);
}
return
newTreeList
;
}
function
openDialog
(
title
=
"新增"
,
row
?:
FormItemProps
)
{
addDialog
({
title
:
`
${
title
}
用户`
,
props
:
{
formInline
:
{
title
,
higherDeptOptions
:
formatHigherDeptOptions
(
higherDeptOptions
.
value
),
parentId
:
row
?.
dept
.
id
??
0
,
nickname
:
row
?.
nickname
??
""
,
username
:
row
?.
username
??
""
,
password
:
row
?.
password
??
""
,
phone
:
row
?.
phone
??
""
,
email
:
row
?.
email
??
""
,
gender
:
row
?.
gender
??
""
,
status
:
row
?.
status
??
1
,
remark
:
row
?.
remark
??
""
}
},
width
:
"46%"
,
draggable
:
true
,
fullscreen
:
deviceDetection
(),
fullscreenIcon
:
true
,
closeOnClickModal
:
false
,
contentRenderer
:
()
=>
h
(
editForm
,
{
ref
:
formRef
,
formInline
:
null
}),
beforeSure
:
(
done
,
{
options
})
=>
{
const
FormRef
=
formRef
.
value
.
getRef
();
const
curData
=
options
.
props
.
formInline
as
FormItemProps
;
function
chores
()
{
message
(
`您
${
title
}
了用户名称为
${
curData
.
username
}
的这条数据`
,
{
type
:
"success"
});
done
();
// 关闭弹框
onSearch
();
// 刷新表格数据
}
FormRef
.
validate
(
valid
=>
{
if
(
valid
)
{
console
.
log
(
"curData"
,
curData
);
// 表单规则校验通过
if
(
title
===
"新增"
)
{
// 实际开发先调用新增接口,再进行下面操作
// 新增用户
curData
.
name
=
curData
.
username
;
curData
.
deptId
=
curData
.
dept
||
""
;
addUser
(
curData
).
then
(
res
=>
{
if
(
res
.
code
===
200
)
{
message
(
"新增用户成功"
,
{
type
:
"success"
});
chores
();
}
else
{
message
(
"新增用户失败"
,
{
type
:
"error"
});
}
});
chores
();
}
else
{
// 实际开发先调用修改接口,再进行下面操作
chores
();
}
}
});
}
});
}
const
cropRef
=
ref
();
/** 上传头像 */
function
handleUpload
(
row
)
{
addDialog
({
title
:
"裁剪、上传头像"
,
width
:
"40%"
,
closeOnClickModal
:
false
,
fullscreen
:
deviceDetection
(),
contentRenderer
:
()
=>
h
(
ReCropperPreview
,
{
ref
:
cropRef
,
imgSrc
:
row
.
avatar
||
userAvatar
,
onCropper
:
info
=>
(
avatarInfo
.
value
=
info
)
}),
beforeSure
:
done
=>
{
console
.
log
(
"裁剪后的图片信息:"
,
avatarInfo
.
value
);
// 根据实际业务使用avatarInfo.value和row里的某些字段去调用上传头像接口即可
done
();
// 关闭弹框
onSearch
();
// 刷新表格数据
},
closeCallBack
:
()
=>
cropRef
.
value
.
hidePopover
()
});
}
watch
(
pwdForm
,
({
newPwd
})
=>
(
curScore
.
value
=
isAllEmpty
(
newPwd
)
?
-
1
:
zxcvbn
(
newPwd
).
score
)
);
/** 重置密码 */
function
handleReset
(
row
)
{
addDialog
({
title
:
`重置
${
row
.
username
}
用户的密码`
,
width
:
"30%"
,
draggable
:
true
,
closeOnClickModal
:
false
,
fullscreen
:
deviceDetection
(),
contentRenderer
:
()
=>
(
<>
<
ElForm
ref=
{
ruleFormRef
}
model=
{
pwdForm
}
>
<
ElFormItem
prop=
"newPwd"
rules=
{
[
{
required
:
true
,
message
:
"请输入新密码"
,
trigger
:
"blur"
}
]
}
>
<
ElInput
clearable
show
-
password
type=
"password"
v
-
model=
{
pwdForm
.
newPwd
}
placeholder=
"请输入新密码"
/>
</
ElFormItem
>
</
ElForm
>
<
div
class=
"my-4 flex"
>
{
pwdProgress
.
map
(({
color
,
text
},
idx
)
=>
(
<
div
class=
"w-[19vw]"
style=
{
{
marginLeft
:
idx
!==
0
?
"4px"
:
0
}
}
>
<
ElProgress
striped
striped
-
flow
duration=
{
curScore
.
value
===
idx
?
6
:
0
}
percentage=
{
curScore
.
value
>=
idx
?
100
:
0
}
color=
{
color
}
stroke
-
width=
{
10
}
show
-
text=
{
false
}
/>
<
p
class=
"text-center"
style=
{
{
color
:
curScore
.
value
===
idx
?
color
:
""
}
}
>
{
text
}
</
p
>
</
div
>
))
}
</
div
>
</>
),
closeCallBack
:
()
=>
(
pwdForm
.
newPwd
=
""
),
beforeSure
:
done
=>
{
ruleFormRef
.
value
.
validate
(
valid
=>
{
if
(
valid
)
{
// 表单规则校验通过
message
(
`已成功重置
${
row
.
username
}
用户的密码`
,
{
type
:
"success"
});
console
.
log
(
pwdForm
.
newPwd
);
// 根据实际业务使用pwdForm.newPwd和row里的某些字段去调用重置用户密码接口即可
done
();
// 关闭弹框
onSearch
();
// 刷新表格数据
}
});
}
});
}
/** 分配角色 */
async
function
handleRole
(
row
)
{
// 选中的角色列表
const
ids
=
(
await
getRoleIds
({
userId
:
row
.
id
})).
data
??
[];
addDialog
({
title
:
`分配
${
row
.
username
}
用户的角色`
,
props
:
{
formInline
:
{
username
:
row
?.
username
??
""
,
nickname
:
row
?.
nickname
??
""
,
roleOptions
:
roleOptions
.
value
??
[],
ids
}
},
width
:
"400px"
,
draggable
:
true
,
fullscreen
:
deviceDetection
(),
fullscreenIcon
:
true
,
closeOnClickModal
:
false
,
contentRenderer
:
()
=>
h
(
roleForm
),
beforeSure
:
(
done
,
{
options
})
=>
{
const
curData
=
options
.
props
.
formInline
as
RoleFormItemProps
;
console
.
log
(
"curIds"
,
curData
.
ids
);
// 根据实际业务使用curData.ids和row里的某些字段去调用修改角色接口即可
done
();
// 关闭弹框
}
});
}
onMounted
(
async
()
=>
{
treeLoading
.
value
=
true
;
onSearch
();
// 归属部门
const
{
data
}
=
await
getDeptList
({
pageNum
:
1
,
pageSize
:
200
});
higherDeptOptions
.
value
=
handleTree
(
data
.
records
);
treeData
.
value
=
handleTree
(
data
.
records
);
treeLoading
.
value
=
false
;
// 角色列表
roleOptions
.
value
=
(
await
getAllRoleList
()).
data
;
});
return
{
form
,
loading
,
columns
,
dataList
,
treeData
,
treeLoading
,
selectedNum
,
pagination
,
buttonClass
,
deviceDetection
,
onSearch
,
resetForm
,
onbatchDel
,
openDialog
,
onTreeSelect
,
handleUpdate
,
handleDelete
,
handleUpload
,
handleReset
,
handleRole
,
handleSizeChange
,
onSelectionCancel
,
handleCurrentChange
,
handleSelectionChange
};
}
src/views/systems/user/utils/reset.css
0 → 100644
浏览文件 @
a3402cdf
/** 局部重置 ElProgress 的部分样式 */
.el-progress-bar__outer
,
.el-progress-bar__inner
{
border-radius
:
0
;
}
src/views/systems/user/utils/rule.ts
0 → 100644
浏览文件 @
a3402cdf
import
{
reactive
}
from
"vue"
;
import
type
{
FormRules
}
from
"element-plus"
;
import
{
isPhone
,
isEmail
}
from
"@pureadmin/utils"
;
/** 自定义表单规则校验 */
export
const
formRules
=
reactive
(
<
FormRules
>
{
nickname
:
[{
required
:
true
,
message
:
"用户昵称为必填项"
,
trigger
:
"blur"
}],
username
:
[{
required
:
true
,
message
:
"用户名称为必填项"
,
trigger
:
"blur"
}],
password
:
[{
required
:
true
,
message
:
"用户密码为必填项"
,
trigger
:
"blur"
}],
phone
:
[
{
validator
:
(
rule
,
value
,
callback
)
=>
{
if
(
value
===
""
)
{
callback
();
}
else
if
(
!
isPhone
(
value
))
{
callback
(
new
Error
(
"请输入正确的手机号码格式"
));
}
else
{
callback
();
}
},
trigger
:
"blur"
// trigger: "click" // 如果想在点击确定按钮时触发这个校验,trigger 设置成 click 即可
}
],
email
:
[
{
validator
:
(
rule
,
value
,
callback
)
=>
{
if
(
value
===
""
)
{
callback
();
}
else
if
(
!
isEmail
(
value
))
{
callback
(
new
Error
(
"请输入正确的邮箱格式"
));
}
else
{
callback
();
}
},
trigger
:
"blur"
}
]
});
src/views/systems/user/utils/types.ts
0 → 100644
浏览文件 @
a3402cdf
interface
FormItemProps
{
id
?:
number
;
/** 用于判断是`新增`还是`修改` */
title
:
string
;
higherDeptOptions
:
Record
<
string
,
unknown
>
[];
parentId
:
number
;
nickname
:
string
;
username
:
string
;
password
:
string
;
mobile
:
string
|
number
;
email
:
string
;
gender
:
string
|
number
;
status
:
number
;
dept
?:
{
id
?:
number
;
name
?:
string
;
};
remark
:
string
;
name
?:
string
;
deptId
?:
number
;
}
interface
FormProps
{
formInline
:
FormItemProps
;
}
interface
RoleFormItemProps
{
username
:
string
;
nickname
:
string
;
/** 角色列表 */
roleOptions
:
any
[];
/** 选中的角色列表 */
ids
:
Record
<
number
,
unknown
>
[];
}
interface
RoleFormProps
{
formInline
:
RoleFormItemProps
;
}
export
type
{
FormItemProps
,
FormProps
,
RoleFormItemProps
,
RoleFormProps
};
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论