Skip to content
Toggle navigation
Projects
Groups
Snippets
Help
Toggle navigation
This project
Loading...
Sign in
李宁
/
Activity
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit 969889c6
authored
Nov 24, 2025
by
李宁
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
1
1 parent
dd3bdb7a
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
28 additions
and
254 deletions
channelBusiManage/src/assets/js/api/interface/account/index.js
channelBusiManage/src/components/DesktopMain.vue
channelBusiManage/src/components/OrganizationTree.vue
channelBusiManage/src/components/RoleManagement.vue
channelBusiManage/src/components/UserManagement.vue
channelBusiManage/src/assets/js/api/interface/account/index.js
View file @
969889c
...
...
@@ -5,37 +5,28 @@ import request from '../../request'
*/
export
function
queryAccountList
(
data
)
{
return
request
({
url
:
'/c
ompass/api/system/account/page
'
,
url
:
'/c
rm/getUserPageList
'
,
data
,
})
}
/**
* 查询角色列表
*/
export
function
queryRoleList
(
data
)
{
return
request
({
url
:
'/compass/api/common/enums/account-roles'
,
method
:
'GET'
})
}
/**
* 删除账号
* 添加/更新账号
*/
export
function
dele
teRole
(
data
)
{
export
function
addAndUpda
teRole
(
data
)
{
return
request
({
url
:
'/c
ompass/api/system/account/delete'
,
url
:
'/c
rm'
+
(
data
.
id
?
'/updateUser'
:
'/createUser'
)
,
data
,
})
}
/**
*
添加/更新账号
*
获取指定区域下的所有区域
*/
export
function
addAndUpdateRole
(
data
)
{
export
function
queryAreaData
(
data
)
{
return
request
({
url
:
'/c
ompass/api/system/account'
+
(
data
.
id
?
'/update'
:
'/create'
)
,
data
,
url
:
'/c
rm/getArea?areaId='
+
data
.
areaId
,
method
:
'GET'
})
}
\ No newline at end of file
channelBusiManage/src/components/DesktopMain.vue
View file @
969889c
...
...
@@ -15,7 +15,6 @@
<span
class=
"text-[16px]"
>
享零工云平台
</span>
</h1>
</div>
<!-- 导航菜单 -->
<nav
class=
"flex-1 py-6 px-3 overflow-hidden"
>
<div
class=
"space-y-6"
>
...
...
@@ -62,7 +61,6 @@
</button>
</
template
>
</div>
<!-- 系统管理菜单 -->
<div
class=
"space-y-1"
v-if=
"systemMenuItems.length>0"
>
<div
v-if=
"!isSidebarCollapsed"
class=
"px-4 mb-2"
>
...
...
@@ -110,7 +108,6 @@
</div>
</div>
</nav>
<!-- 收缩按钮 -->
<div
class=
"p-4 border-t border-neutral-800"
>
<button
...
...
@@ -132,7 +129,6 @@
</button>
</div>
</div>
<!-- 右侧主内容区 -->
<div
class=
"flex-1 flex flex-col overflow-hidden"
>
<!-- 顶部Header -->
...
...
@@ -188,7 +184,6 @@
</div>
</
template
>
</div>
<!-- 用户下拉菜单 -->
<el-dropdown
trigger=
"click"
@
command=
"handleCommand"
>
<el-button
...
...
@@ -216,7 +211,6 @@
</
template
>
</el-dropdown>
</header>
<!-- 页面内容区 -->
<main
class=
"flex-1 overflow-auto p-6 bg-[#F0F2F5]"
>
<!-- 订单监控页面 -->
...
...
@@ -276,7 +270,6 @@
</div>
</div>
</template>
<
script
setup
lang=
"ts"
>
import
{
ref
,
computed
}
from
'vue'
import
{
...
...
@@ -290,10 +283,8 @@ import {
ChevronDown
}
from
'lucide-vue-next'
import
{
ElMessage
,
ElMessageBox
}
from
'element-plus'
// 导入图片资源
import
platformLogoImg
from
'@/assets/8a3322d5ba8c2ae3592af24d73566a63828a3a27.png'
// 导入子组件
import
OrderMonitoring
from
'./OrderMonitoring.vue'
import
OrderDetail
from
'./OrderDetail.vue'
...
...
@@ -301,9 +292,7 @@ import BusinessRulesManagement from './BusinessRulesManagement.vue'
import
RoleManagement
,
{
type
Permission
}
from
'./RoleManagement.vue'
import
UserManagement
,
{
type
User
as
UserType
}
from
'./UserManagement.vue'
import
RoleIcon
from
'./icons/RoleIcon.vue'
const
platformLogo
=
ref
(
platformLogoImg
)
// 本地Role类型定义,确保permissions字段是必需的
interface
Role
{
id
:
string
...
...
@@ -314,22 +303,17 @@ interface Role {
status
:
'启用'
|
'禁用'
createTime
?:
string
}
interface
DesktopMainProps
{
currentUser
:
{
username
:
string
;
role
:
'admin'
|
'viewer'
}
|
null
;
}
const
props
=
defineProps
<
DesktopMainProps
>
()
const
emit
=
defineEmits
([
'logout'
])
// 响应式数据
const
isSidebarCollapsed
=
ref
(
false
)
const
activeMenu
=
ref
<
string
>
(
'orders'
)
// 当前激活的菜单
const
activeView
=
ref
<
string
>
(
'orders'
)
// 当前显示的视图
const
selectedOrderId
=
ref
<
string
|
null
>
(
null
)
// 当前查看的订单ID
const
userInfo
=
ref
(
localStorage
.
getItem
(
'pcUserInfo'
)?
JSON
.
parse
(
localStorage
.
getItem
(
'pcUserInfo'
)):{})
// 菜单配置
// const businessMenuItems = [
// { id: 'orders', label: '登记订单管理', icon: LayoutDashboard },
...
...
@@ -353,10 +337,8 @@ const businessMenuItems = computed(() => {
return
barr
}
}
return
[]
})
// const systemMenuItems = [
// { id: 'roles', label: '角色管理', icon: RoleIcon },
// { id: 'users', label: '账号管理', icon: Users }
...
...
@@ -379,24 +361,20 @@ const systemMenuItems = computed(() => {
return
barr
}
}
return
[]
})
const
menuMap
:
Record
<
string
,
string
>
=
{
orders
:
'登记订单管理'
,
business
:
'业务酬金管理'
,
roles
:
'角色管理'
,
users
:
'账号管理'
,
}
const
currentMenuTitle
=
computed
(()
=>
{
if
(
activeView
.
value
===
'order-detail'
)
{
return
'订单详情'
}
return
menuMap
[
activeView
.
value
]
||
'首页'
})
// 业务规则数据
const
businessRules
=
ref
<
BusinessRule
[]
>
([
{
...
...
@@ -432,14 +410,11 @@ const businessRules = ref<BusinessRule[]>([
createTime
:
'2025-10-20 10:15'
}
])
// 模拟订单数据
// 使用空数组,让 OrderMonitoring 组件自动生成模拟数据
const
orders
=
ref
([])
// 存储从 OrderMonitoring 组件获取的订单数据
const
allOrders
=
ref
([]
as
any
[])
// 类型定义
interface
BusinessRule
{
id
:
string
...
...
@@ -449,8 +424,6 @@ interface BusinessRule {
status
:
"生效中"
|
"已停用"
createTime
:
string
}
interface
Organization
{
id
:
string
name
:
string
...
...
@@ -458,8 +431,6 @@ interface Organization {
parentId
?:
string
children
?:
Organization
[]
}
// 角色管理数据
const
roles
=
ref
<
Role
[]
>
([
{
...
...
@@ -509,21 +480,17 @@ const roles = ref<Role[]>([
createTime
:
'2025-10-20 10:00:00'
}
])
// 权限数据
const
permissions
=
ref
<
Permission
[]
>
(
JSON
.
parse
(
localStorage
.
getItem
(
'pcUserInfo'
)
||
'{}'
).
functions
||
[])
// 根据订单ID获取订单详情
const
getOrderById
=
(
orderId
:
string
)
=>
{
return
allOrders
.
value
.
find
((
order
:
any
)
=>
order
.
id
===
orderId
)
}
// 方法
const
handleMenuChange
=
(
menuId
:
string
)
=>
{
activeMenu
.
value
=
menuId
activeView
.
value
=
menuId
}
const
handleCommand
=
(
command
:
string
)
=>
{
if
(
command
===
'logout'
)
{
ElMessageBox
.
confirm
(
'确定要退出登录吗?'
,
'提示'
,
{
...
...
@@ -540,17 +507,14 @@ const handleCommand = (command: string) => {
ElMessage
.
info
(
`点击了
${
command
}
`
)
}
}
const
handleViewOrderDetail
=
(
order
:
any
)
=>
{
selectedOrderId
.
value
=
order
.
id
activeView
.
value
=
'order-detail'
}
const
handleBackToOrders
=
()
=>
{
selectedOrderId
.
value
=
null
activeView
.
value
=
'orders'
}
const
handleUpdateOrder
=
(
updates
:
any
)
=>
{
// 更新 allOrders 中的订单数据
const
orderIndex
=
allOrders
.
value
.
findIndex
(
order
=>
order
.
id
===
selectedOrderId
.
value
)
...
...
@@ -560,12 +524,10 @@ const handleUpdateOrder = (updates: any) => {
console
.
log
(
'更新订单:'
,
updates
)
ElMessage
.
success
(
'订单更新成功'
)
}
// 接收来自 OrderMonitoring 组件的订单数据
const
handleOrdersUpdate
=
(
ordersList
:
any
[])
=>
{
allOrders
.
value
=
ordersList
}
// 面包屑导航点击处理
const
handleBreadcrumbClick
=
(
level
:
string
)
=>
{
if
(
level
===
'level1'
)
{
...
...
@@ -579,7 +541,6 @@ const handleBreadcrumbClick = (level: string) => {
}
}
}
// 业务规则管理方法
const
handleAddBusinessRule
=
(
ruleData
:
Omit
<
BusinessRule
,
'id'
|
'createTime'
>
)
=>
{
const
newRule
=
{
...
...
@@ -597,7 +558,6 @@ const handleAddBusinessRule = (ruleData: Omit<BusinessRule, 'id' | 'createTime'>
ElMessage
.
success
(
'业务规则创建成功'
)
console
.
log
(
'新增业务规则:'
,
newRule
)
}
const
handleUpdateBusinessRule
=
(
ruleId
:
string
,
updates
:
Partial
<
BusinessRule
>
)
=>
{
const
ruleIndex
=
businessRules
.
value
.
findIndex
(
rule
=>
rule
.
id
===
ruleId
)
if
(
ruleIndex
!==
-
1
)
{
...
...
@@ -610,7 +570,6 @@ const handleUpdateBusinessRule = (ruleId: string, updates: Partial<BusinessRule>
console
.
log
(
'更新业务规则:'
,
ruleId
,
updates
)
}
}
const
handleToggleBusinessRuleStatus
=
(
ruleId
:
string
)
=>
{
const
ruleIndex
=
businessRules
.
value
.
findIndex
(
rule
=>
rule
.
id
===
ruleId
)
if
(
ruleIndex
!==
-
1
)
{
...
...
@@ -624,7 +583,6 @@ const handleToggleBusinessRuleStatus = (ruleId: string) => {
}
}
}
const
handleDeleteBusinessRule
=
(
ruleId
:
string
)
=>
{
const
ruleIndex
=
businessRules
.
value
.
findIndex
(
rule
=>
rule
.
id
===
ruleId
)
if
(
ruleIndex
!==
-
1
)
{
...
...
@@ -633,7 +591,6 @@ const handleDeleteBusinessRule = (ruleId: string) => {
console
.
log
(
'删除业务规则:'
,
ruleId
)
}
}
// 角色管理方法
const
handleAddRole
=
(
roleData
:
any
)
=>
{
const
newRole
:
Role
=
{
...
...
@@ -652,7 +609,6 @@ const handleAddRole = (roleData: any) => {
roles
.
value
.
push
(
newRole
)
console
.
log
(
'新增角色:'
,
newRole
)
}
const
handleUpdateRole
=
(
roleId
:
string
,
updates
:
any
)
=>
{
const
roleIndex
=
roles
.
value
.
findIndex
(
role
=>
role
.
id
===
roleId
)
if
(
roleIndex
!==
-
1
)
{
...
...
@@ -667,7 +623,6 @@ const handleUpdateRole = (roleId: string, updates: any) => {
}
}
}
// 组织架构数据
const
organizations
=
ref
<
Organization
[]
>
([
{
...
...
@@ -720,10 +675,8 @@ const organizations = ref<Organization[]>([
]
}
])
// 当前登录用户ID
const
currentUserId
=
ref
(
'user-001'
)
// 用户数据
const
users
=
ref
<
UserType
[]
>
([
{
...
...
@@ -775,7 +728,6 @@ const users = ref<UserType[]>([
creatorId
:
'user-001'
}
])
// 用户管理方法
const
handleAddUser
=
(
userData
:
Omit
<
UserType
,
'id'
|
'createTime'
|
'creatorId'
>
)
=>
{
const
newUser
=
{
...
...
@@ -794,7 +746,6 @@ const handleAddUser = (userData: Omit<UserType, 'id' | 'createTime' | 'creatorId
users
.
value
.
push
(
newUser
)
console
.
log
(
'新增用户:'
,
newUser
)
}
const
handleUpdateUser
=
(
userId
:
string
,
updates
:
Partial
<
UserType
>
)
=>
{
const
userIndex
=
users
.
value
.
findIndex
(
user
=>
user
.
id
===
userId
)
if
(
userIndex
!==
-
1
)
{
...
...
@@ -806,7 +757,6 @@ const handleUpdateUser = (userId: string, updates: Partial<UserType>) => {
console
.
log
(
'更新用户:'
,
userId
,
updates
)
}
}
const
handleDeleteUser
=
(
userId
:
string
)
=>
{
const
userIndex
=
users
.
value
.
findIndex
(
user
=>
user
.
id
===
userId
)
if
(
userIndex
!==
-
1
)
{
...
...
@@ -815,18 +765,15 @@ const handleDeleteUser = (userId: string) => {
}
}
</
script
>
<
style
scoped
>
/* 隐藏滚动条但保持滚动功能 */
.hide-scrollbar
{
scrollbar-width
:
none
;
/* Firefox */
-ms-overflow-style
:
none
;
/* IE and Edge */
}
.hide-scrollbar
::-webkit-scrollbar
{
display
:
none
;
/* Chrome, Safari, and Opera */
}
/* 移除右上角用户信息按钮的悬浮背景效果 */
:deep
(
header
.el-button.is-text
:hover
),
:deep
(
header
.el-button.is-text
:focus
),
...
...
channelBusiManage/src/components/OrganizationTree.vue
View file @
969889c
...
...
@@ -48,7 +48,6 @@
class=
"inline-block w-2 h-2 rounded-sm bg-white"
/>
</div>
<!-- 组织图标 -->
<Building2
:class=
"[
...
...
@@ -110,11 +109,9 @@
</div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
ref
,
computed
}
from
'vue'
import
{
Building2
,
ChevronDown
,
ChevronRight
}
from
'lucide-vue-next'
interface
Organization
{
id
:
string
name
:
string
...
...
@@ -122,13 +119,11 @@ interface Organization {
parentId
?:
string
children
?:
Organization
[]
}
interface
Role
{
id
:
string
name
:
string
level
:
'地市级'
|
'区县级'
|
'一线人员'
}
interface
Props
{
organizations
:
Organization
[]
selectedId
?:
string
...
...
@@ -137,7 +132,6 @@ interface Props {
expandedIds
?:
Set
<
string
>
accountType
?:
'地市级'
|
'区县级'
|
'一线人员'
}
const
props
=
withDefaults
(
defineProps
<
Props
>
(),
{
selectedId
:
''
,
roleId
:
''
,
...
...
@@ -145,12 +139,10 @@ const props = withDefaults(defineProps<Props>(), {
expandedIds
:
()
=>
new
Set
(),
accountType
:
undefined
})
const
emit
=
defineEmits
<
{
select
:
[
orgId
:
string
]
'toggle-expand'
:
[
orgId
:
string
]
}
>
()
// 如果没有传入 expandedIds,使用本地状态
const
localExpandedIds
=
ref
(
new
Set
<
string
>
())
const
expandedIds
=
computed
(()
=>
...
...
@@ -158,12 +150,10 @@ const expandedIds = computed(() =>
?
props
.
expandedIds
:
localExpandedIds
.
value
)
// 工具函数
const
hasChildren
=
(
org
:
Organization
):
boolean
=>
{
return
!!
(
org
.
children
&&
org
.
children
.
length
>
0
)
}
const
isSelectable
=
(
org
:
Organization
):
boolean
=>
{
console
.
log
(
'检查组织可选性:'
,
org
.
name
,
org
.
type
,
'账号类型:'
,
props
.
accountType
)
...
...
@@ -196,7 +186,6 @@ const isSelectable = (org: Organization): boolean => {
// 如果没有账号类型,可以选择所有组织
return
true
}
// 检查组织是否属于区县下
const
isUnderCounty
=
(
org
:
Organization
):
boolean
=>
{
console
.
log
(
'OrganizationTree.vue:202 检查组织是否属于区县下:'
,
org
.
name
,
org
.
type
,
org
.
id
)
...
...
@@ -239,7 +228,6 @@ const isUnderCounty = (org: Organization): boolean => {
console
.
log
(
'OrganizationTree.vue:241 未找到区县父组织'
)
return
false
}
const
getOrgTypeLabel
=
(
type
:
Organization
[
'type'
])
=>
{
switch
(
type
)
{
case
'地市'
:
...
...
@@ -252,21 +240,18 @@ const getOrgTypeLabel = (type: Organization['type']) => {
return
type
}
}
const
getRoleLevel
=
(
roleId
:
string
):
string
=>
{
if
(
!
props
.
roles
||
props
.
roles
.
length
===
0
)
return
'区县级'
const
role
=
props
.
roles
.
find
(
r
=>
r
.
id
===
roleId
)
return
role
?
role
.
level
:
'区县级'
}
// 事件处理
const
handleSelect
=
(
org
:
Organization
)
=>
{
if
(
isSelectable
(
org
))
{
emit
(
'select'
,
org
.
id
)
}
}
const
toggleExpand
=
(
orgId
:
string
)
=>
{
if
(
props
.
expandedIds
&&
props
.
expandedIds
.
size
>
0
)
{
// 使用父组件的展开状态
...
...
@@ -280,11 +265,9 @@ const toggleExpand = (orgId: string) => {
}
}
}
const
handleToggleExpand
=
(
orgId
:
string
)
=>
{
emit
(
'toggle-expand'
,
orgId
)
}
// 初始化展开顶级组织
if
(
props
.
expandedIds
&&
props
.
expandedIds
.
size
===
0
)
{
props
.
organizations
.
forEach
(
org
=>
{
...
...
@@ -294,128 +277,98 @@ if (props.expandedIds && props.expandedIds.size === 0) {
})
}
</
script
>
<
style
scoped
>
.space-y-1
>
*
+
*
{
margin-top
:
0.25rem
;
}
.flex
{
display
:
flex
;
}
.items-center
{
align-items
:
center
;
}
.gap-2
{
gap
:
0.5rem
;
}
.p-1
{
padding
:
0.25rem
;
}
.p-2
{
padding
:
0.5rem
;
}
.rounded
{
border-radius
:
0.25rem
;
}
.cursor-pointer
{
cursor
:
pointer
;
}
.cursor-not-allowed
{
cursor
:
not-allowed
;
}
.transition-colors
{
transition-property
:
color
,
background-color
,
border-color
,
text-decoration-color
,
fill
,
stroke
;
transition-timing-function
:
cubic-bezier
(
0.4
,
0
,
0.2
,
1
);
transition-duration
:
150ms
;
}
.opacity-40
{
opacity
:
0.4
;
}
.bg-blue-50
{
background-color
:
#eff6ff
;
}
.border
{
border-width
:
1px
;
}
.border-blue-500
{
border-color
:
#3b82f6
;
}
.hover
\
:bg-neutral-50:hover
{
background-color
:
#f9fafb
;
}
.hover
\
:bg-neutral-200:hover
{
background-color
:
#e5e7eb
;
}
.h-4
{
height
:
1rem
;
}
.w-4
{
width
:
1rem
;
}
.w-6
{
width
:
1.5rem
;
}
.text-neutral-300
{
color
:
#d1d5db
;
}
.text-neutral-400
{
color
:
#9ca3af
;
}
.text-neutral-500
{
color
:
#6b7280
;
}
.text-neutral-600
{
color
:
#4b5563
;
}
.text-neutral-900
{
color
:
#111827
;
}
.text-blue-600
{
color
:
#2563eb
;
}
.text-xs
{
font-size
:
0.75rem
;
line-height
:
1rem
;
}
.flex-1
{
flex
:
1
1
0%
;
}
.ml-4
{
margin-left
:
1rem
;
}
.border-l-2
{
border-left-width
:
2px
;
}
.border-neutral-200
{
border-color
:
#e5e7eb
;
}
</
style
>
</
style
>
\ No newline at end of file
channelBusiManage/src/components/RoleManagement.vue
View file @
969889c
...
...
@@ -261,19 +261,30 @@ const topLevelPermissions = computed(() =>
const
filteredRoles
=
ref
([])
const
getAllIds
=
(
tree
)
=>
{
const
ids
=
[]
// 结果池
const
stack
=
[...
tree
]
// 根节点入栈
while
(
stack
.
length
)
{
const
node
=
stack
.
pop
()
if
(
node
.
id
!==
undefined
)
ids
.
push
(
node
.
id
)
// 收集当前节点
if
(
node
.
children
?.
length
)
{
// 子节点全部入栈(顺序无所谓就 push,要顺序就 unshift)
stack
.
push
(...
node
.
children
)
}
}
return
ids
}
// 获取角色的权限ID列表,兼容不同的字段名
const
getRolePermissionIds
=
async
(
role
:
Role
)
=>
{
try
{
const
response
=
await
$api
.
queryRolePermission
({
id
:
role
.
id
})
return
[
1
,
2
,
3
,
4
]
if
(
response
.
c
===
0
&&
response
.
d
)
{
// 假设返回的数据结构是 { functionIds: ['id1', 'id2', ...] }
return
response
.
d
.
functionIds
||
response
.
d
.
permissionIds
||
[]
return
getAllIds
(
response
.
d
.
list
)
}
else
{
console
.
warn
(
'获取角色权限失败:'
,
response
.
msg
)
return
role
.
permissionIds
||
role
.
permissions
||
[]
...
...
@@ -340,17 +351,8 @@ const handleSave = async () => {
return
}
const
roleData
=
{
name
:
roleName
.
value
.
trim
(),
description
:
roleDescription
.
value
.
trim
(),
permissionIds
:
selectedPermissions
.
value
,
status
:
roleStatus
.
value
}
console
.
log
(
roleData
)
const
response
=
await
$api
.
createOrUpdateRole
({
roleId
:
''
,
roleId
:
editingRole
.
value
?
editingRole
.
value
.
id
:
''
,
roleName
:
roleName
.
value
.
trim
(),
remark
:
roleDescription
.
value
.
trim
(),
status
:
roleStatus
.
value
,
...
...
channelBusiManage/src/components/UserManagement.vue
View file @
969889c
...
...
@@ -15,7 +15,6 @@
新增用户
</el-button>
</div>
<!-- 搜索和筛选 -->
<el-card>
<div
class=
"grid grid-cols-5 gap-4 px-6 py-6"
>
...
...
@@ -28,7 +27,6 @@
clearable
/>
</div>
<el-select
v-model=
"roleFilter"
placeholder=
"角色"
...
...
@@ -43,7 +41,6 @@
:value=
"role.id"
/>
</el-select>
<el-select
v-model=
"accountTypeFilter"
placeholder=
"账号类型"
...
...
@@ -55,7 +52,6 @@
<el-option
label=
"区县级"
value=
"区县级"
/>
<el-option
label=
"一线人员"
value=
"一线人员"
/>
</el-select>
<el-select
v-model=
"statusFilter"
placeholder=
"状态"
...
...
@@ -66,7 +62,6 @@
<el-option
label=
"正常"
value=
"正常"
/>
<el-option
label=
"禁用"
value=
"禁用"
/>
</el-select>
<el-select
v-model=
"organizationFilter"
placeholder=
"所属组织"
...
...
@@ -83,7 +78,6 @@
</el-select>
</div>
</el-card>
<!-- 用户列表 -->
<el-card
class=
"p-5"
>
...
...
@@ -182,7 +176,6 @@
</el-table>
</div>
</el-card>
<!-- 新增/编辑用户对话框 -->
<el-dialog
v-model=
"isDialogOpen"
...
...
@@ -205,23 +198,18 @@
:disabled=
"!!editingUser"
/>
</el-form-item>
<el-form-item
label=
"用户姓名"
required
>
<el-input
v-model=
"formData.realName"
placeholder=
"真实姓名"
/>
</el-form-item>
<el-form-item
label=
"手机号"
required
>
<el-input
v-model=
"formData.phone"
placeholder=
"11位手机号"
/>
</el-form-item>
<el-form-item
label=
"账号类型"
required
>
<el-select
v-model=
"formData.accountType"
...
...
@@ -234,7 +222,6 @@
<el-option
label=
"一线人员"
value=
"一线人员"
/>
</el-select>
</el-form-item>
<el-form-item
label=
"角色"
required
class=
"col-span-2"
>
<el-select
v-model=
"formData.roleId"
...
...
@@ -254,7 +241,6 @@
</div>
</el-form-item>
</div>
<div
class=
"flex items-center justify-between p-4 bg-neutral-50 rounded border border-neutral-200"
>
<div>
<div
class=
"text-neutral-900 font-medium"
>
用户状态
</div>
...
...
@@ -265,7 +251,6 @@
/>
</div>
</div>
<!-- 所属组织(当前登录用户不可修改) -->
<div
v-if=
"!editingUser || !isCurrentUser(editingUser)"
class=
"space-y-4"
>
<div
class=
"flex items-center justify-between pb-2 border-b border-neutral-200"
>
...
...
@@ -296,7 +281,6 @@
提示:点击组织名称选择,点击箭头展开/收起子组织。灰色不可选的组织表示不符合当前角色的层级要求。
</p>
</div>
<!-- 当前登录用户的组织信息(只读显示) -->
<div
v-else-if=
"editingUser && isCurrentUser(editingUser)"
class=
"space-y-4"
>
<h3
class=
"text-neutral-900 pb-2 border-b border-neutral-200"
>
所属组织
</h3>
...
...
@@ -320,7 +304,6 @@
</div>
</div>
</div>
<
template
#
footer
>
<el-button
@
click=
"isDialogOpen = false"
>
取消
</el-button>
<el-button
type=
"primary"
@
click=
"handleSave"
>
确定
</el-button>
...
...
@@ -328,19 +311,16 @@
</el-dialog>
</div>
</template>
<
script
setup
lang=
"ts"
>
import
{
ref
,
computed
,
watch
}
from
'vue'
import
{
ElMessage
,
ElMessageBox
}
from
'element-plus'
import
{
Plus
,
Building2
,
Search
}
from
'lucide-vue-next'
import
OrganizationTree
from
'./OrganizationTree.vue'
// 类型定义
export
interface
User
{
id
:
string
username
:
string
realName
:
string
organizationId
:
string
roleId
:
string
accountType
?:
'地市级'
|
'区县级'
|
'一线人员'
...
...
@@ -350,7 +330,6 @@ export interface User {
createTime
?:
string
creatorId
?:
string
}
interface
Role
{
id
:
string
name
:
string
...
...
@@ -359,7 +338,6 @@ interface Role {
status
:
'启用'
|
'禁用'
permissions
:
string
[]
}
interface
Organization
{
id
:
string
name
:
string
...
...
@@ -367,7 +345,6 @@ interface Organization {
parentId
?:
string
children
?:
Organization
[]
}
// Props
interface
UserManagementProps
{
users
:
User
[]
...
...
@@ -375,13 +352,11 @@ interface UserManagementProps {
organizations
:
Organization
[]
currentUserId
:
string
}
const
props
=
defineProps
<
UserManagementProps
>
()
const
emit
=
defineEmits
<
{
addUser
:
[
user
:
Omit
<
User
,
'id'
|
'createTime'
|
'creatorId'
>
]
updateUser
:
[
id
:
string
,
updates
:
Partial
<
User
>
]
}
>
()
// 响应式数据
const
isDialogOpen
=
ref
(
false
)
const
editingUser
=
ref
<
User
|
null
>
(
null
)
...
...
@@ -391,30 +366,24 @@ const accountTypeFilter = ref<'all' | '地市级' | '区县级' | '一线人员'
const
statusFilter
=
ref
<
'all'
|
'正常'
|
'禁用'
>
(
'all'
)
const
organizationFilter
=
ref
<
string
>
(
'all'
)
const
organizationExpandedIds
=
ref
(
new
Set
<
string
>
())
// 表单数据
const
formData
=
ref
({
username
:
''
,
realName
:
''
,
phone
:
''
,
accountType
:
'一线人员'
as
'地市级'
|
'区县级'
|
'一线人员'
,
organizationId
:
''
,
roleId
:
''
,
isActive
:
true
})
// 计算属性
const
availableRoles
=
computed
(()
=>
props
.
roles
.
filter
(
r
=>
r
.
status
===
'启用'
&&
r
.
name
!==
'地市主管理员'
)
)
const
topLevelOrganizations
=
computed
(()
=>
props
.
organizations
.
filter
(
o
=>
!
o
.
parentId
)
)
const
totalUsers
=
computed
(()
=>
props
.
users
.
length
-
1
)
// 排除当前登录用户
const
hasFilters
=
computed
(()
=>
generalSearch
.
value
!==
''
||
roleFilter
.
value
!==
'all'
||
...
...
@@ -422,7 +391,6 @@ const hasFilters = computed(() =>
statusFilter
.
value
!==
'all'
||
organizationFilter
.
value
!==
'all'
)
const
filteredUsers
=
computed
(()
=>
{
return
props
.
users
.
filter
(
user
=>
{
// 过滤掉当前登录用户
...
...
@@ -441,11 +409,9 @@ const filteredUsers = computed(() => {
const
matchAccountType
=
accountTypeFilter
.
value
===
'all'
||
user
.
accountType
===
accountTypeFilter
.
value
const
matchStatus
=
statusFilter
.
value
===
'all'
||
user
.
status
===
statusFilter
.
value
const
matchOrganization
=
organizationFilter
.
value
===
'all'
||
user
.
organizationId
===
organizationFilter
.
value
return
matchGeneral
&&
matchRole
&&
matchAccountType
&&
matchStatus
&&
matchOrganization
})
})
const
roleHint
=
computed
(()
=>
{
if
(
!
formData
.
value
.
roleId
)
return
''
...
...
@@ -454,7 +420,6 @@ const roleHint = computed(() => {
return
`
${
role
.
name
}
`
})
// 工具函数
const
getOrganizationById
=
(
id
:
string
):
Organization
|
undefined
=>
{
const
find
=
(
orgs
:
Organization
[]):
Organization
|
undefined
=>
{
...
...
@@ -469,11 +434,9 @@ const getOrganizationById = (id: string): Organization | undefined => {
}
return
find
(
props
.
organizations
)
}
const
getRoleById
=
(
id
:
string
):
Role
|
undefined
=>
{
return
props
.
roles
.
find
(
r
=>
r
.
id
===
id
)
}
const
getAccountTypeTagType
=
(
accountType
:
string
)
=>
{
switch
(
accountType
)
{
case
'地市级'
:
return
'warning'
...
...
@@ -482,25 +445,21 @@ const getAccountTypeTagType = (accountType: string) => {
default
:
return
'info'
}
}
const
isCurrentUser
=
(
user
:
User
):
boolean
=>
{
return
user
.
id
===
props
.
currentUserId
}
const
getRowStyle
=
({
row
}:
{
row
:
User
})
=>
{
if
(
isCurrentUser
(
row
))
{
return
{
backgroundColor
:
'#f0f9ff'
,
borderLeft
:
'4px solid #1677ff'
}
}
return
{}
}
// 事件处理
const
handleOpenAddDialog
=
()
=>
{
editingUser
.
value
=
null
formData
.
value
=
{
username
:
''
,
realName
:
''
,
phone
:
''
,
accountType
:
'一线人员'
as
'地市级'
|
'区县级'
|
'一线人员'
,
organizationId
:
''
,
...
...
@@ -509,13 +468,11 @@ const handleOpenAddDialog = () => {
}
isDialogOpen
.
value
=
true
}
const
handleOpenEditDialog
=
(
user
:
User
)
=>
{
editingUser
.
value
=
user
formData
.
value
=
{
username
:
user
.
username
,
realName
:
user
.
realName
,
phone
:
user
.
phone
||
''
,
accountType
:
user
.
accountType
||
'一线人员'
,
organizationId
:
user
.
organizationId
,
...
...
@@ -524,7 +481,6 @@ const handleOpenEditDialog = (user: User) => {
}
isDialogOpen
.
value
=
true
}
const
handleAccountTypeChange
=
()
=>
{
// 当账号类型变更时,清空组织选择
formData
.
value
.
organizationId
=
''
...
...
@@ -539,15 +495,12 @@ const handleAccountTypeChange = () => {
ElMessage
.
info
(
'账号类型为一线人员,所属组织只能选择客户经理团队'
)
}
}
const
handleRoleChange
=
()
=>
{
// 角色变更时不进行层级检查
}
const
handleOrganizationSelect
=
(
orgId
:
string
)
=>
{
formData
.
value
.
organizationId
=
orgId
}
const
handleToggleExpand
=
(
orgId
:
string
)
=>
{
if
(
organizationExpandedIds
.
value
.
has
(
orgId
))
{
organizationExpandedIds
.
value
.
delete
(
orgId
)
...
...
@@ -555,7 +508,6 @@ const handleToggleExpand = (orgId: string) => {
organizationExpandedIds
.
value
.
add
(
orgId
)
}
}
const
isOrganizationSelectable
=
(
orgType
:
string
,
roleId
:
string
):
boolean
=>
{
// 只根据账号类型进行组织层级限制,不根据角色层级限制
const
accountType
=
formData
.
value
.
accountType
...
...
@@ -575,33 +527,26 @@ const isOrganizationSelectable = (orgType: string, roleId: string): boolean => {
return
true
}
}
const
handleSave
=
()
=>
{
// 表单验证
if
(
!
formData
.
value
.
username
.
trim
())
{
ElMessage
.
error
(
'请输入工号'
)
return
}
if
(
!
formData
.
value
.
realName
.
trim
())
{
ElMessage
.
error
(
'请输入用户姓名'
)
return
}
if
(
!
formData
.
value
.
phone
.
trim
())
{
ElMessage
.
error
(
'请输入手机号'
)
return
}
// 验证手机号格式
const
phoneRegex
=
/^1
[
3-9
]\d{9}
$/
if
(
!
phoneRegex
.
test
(
formData
.
value
.
phone
.
trim
()))
{
ElMessage
.
error
(
'请输入正确的11位手机号'
)
return
}
// 当前登录用户不需要验证组织(不可修改)
if
(
!
editingUser
.
value
||
!
isCurrentUser
(
editingUser
.
value
))
{
if
(
!
formData
.
value
.
organizationId
)
{
...
...
@@ -609,17 +554,14 @@ const handleSave = () => {
return
}
}
if
(
!
formData
.
value
.
roleId
)
{
ElMessage
.
error
(
'请选择角色'
)
return
}
if
(
!
formData
.
value
.
accountType
)
{
ElMessage
.
error
(
'请选择账号类型'
)
return
}
// 检查用户名是否重复
const
existingUser
=
props
.
users
.
find
(
u
=>
u
.
username
===
formData
.
value
.
username
.
trim
()
&&
u
.
id
!==
editingUser
.
value
?.
id
...
...
@@ -628,7 +570,6 @@ const handleSave = () => {
ElMessage
.
error
(
'该工号已存在'
)
return
}
const
userData
:
any
=
{
username
:
formData
.
value
.
username
.
trim
(),
realName
:
formData
.
value
.
realName
.
trim
(),
...
...
@@ -638,14 +579,10 @@ const handleSave = () => {
phone
:
formData
.
value
.
phone
.
trim
()
||
undefined
,
creatorId
:
props
.
currentUserId
}
// 当前登录用户不可修改自己的所属组织
if
(
!
editingUser
.
value
||
!
isCurrentUser
(
editingUser
.
value
))
{
userData
.
organizationId
=
formData
.
value
.
organizationId
}
if
(
editingUser
.
value
)
{
emit
(
'updateUser'
,
editingUser
.
value
.
id
,
userData
)
ElMessage
.
success
(
'用户更新成功'
)
...
...
@@ -653,10 +590,8 @@ const handleSave = () => {
emit
(
'addUser'
,
userData
)
ElMessage
.
success
(
'用户创建成功'
)
}
isDialogOpen
.
value
=
false
}
// 组织下拉选项(扁平化带缩进)
const
organizationOptions
=
computed
(()
=>
{
const
result
:
{
id
:
string
;
label
:
string
}[]
=
[]
...
...
@@ -670,9 +605,7 @@ const organizationOptions = computed(() => {
traverse
(
props
.
organizations
||
[])
return
result
})
</
script
>
<
style
scoped
>
/* 让表单项标签和输入框呈上下结构 */
:deep
(
.el-form-item
)
{
...
...
@@ -680,197 +613,150 @@ const organizationOptions = computed(() => {
flex-direction
:
column
;
align-items
:
stretch
;
}
:deep
(
.el-form-item__label
)
{
margin-bottom
:
0
;
text-align
:
left
!important
;
padding
:
0
!important
;
}
:deep
(
.el-form-item__content
)
{
margin-left
:
0
!important
;
}
.space-y-6
>
*
+
*
{
margin-top
:
1.5rem
;
}
.space-y-4
>
*
+
*
{
margin-top
:
1rem
;
}
.grid
{
display
:
grid
;
}
.grid-cols-2
{
grid-template-columns
:
repeat
(
2
,
minmax
(
0
,
1
fr
));
}
.col-span-2
{
grid-column
:
span
2
/
span
2
;
}
.grid-cols-4
{
grid-template-columns
:
repeat
(
4
,
minmax
(
0
,
1
fr
));
}
.grid-cols-5
{
grid-template-columns
:
repeat
(
5
,
minmax
(
0
,
1
fr
));
}
.gap-4
{
gap
:
1rem
;
}
.h-10
{
height
:
2.5rem
;
}
.w-full
{
width
:
100%
;
}
.text-xs
{
font-size
:
0.75rem
;
line-height
:
1rem
;
}
.text-sm
{
font-size
:
0.875rem
;
line-height
:
1.25rem
;
}
.font-semibold
{
font-weight
:
600
;
}
.font-bold
{
font-weight
:
700
;
}
.text-neutral-500
{
color
:
#6b7280
;
}
.text-neutral-700
{
color
:
#374151
;
}
.text-neutral-900
{
color
:
#111827
;
}
.text-blue-600
{
color
:
#2563eb
;
}
.text-red-500
{
color
:
#ef4444
;
}
.border-neutral-200
{
border-color
:
#e5e7eb
;
}
.border-neutral-300
{
border-color
:
#d1d5db
;
}
.bg-neutral-50
{
background-color
:
#f9fafb
;
}
.rounded
{
border-radius
:
0.25rem
;
}
.rounded-lg
{
border-radius
:
0.5rem
;
}
.border
{
border-width
:
1px
;
}
.border-b
{
border-bottom-width
:
1px
;
}
.p-4
{
padding
:
1rem
;
}
.p-5
{
padding
:
1.25rem
;
}
.p-6
{
padding
:
1.5rem
;
}
.px-4
{
padding-left
:
1rem
;
padding-right
:
1rem
;
}
.py-3
{
padding-top
:
0.75rem
;
padding-bottom
:
0.75rem
;
}
.pb-2
{
padding-bottom
:
0.5rem
;
}
.mt-1
{
margin-top
:
0.25rem
;
}
.mb-4
{
margin-bottom
:
1rem
;
}
.ml-2
{
margin-left
:
0.5rem
;
}
.mr-1
{
margin-right
:
0.25rem
;
}
.flex
{
display
:
flex
;
}
.items-center
{
align-items
:
center
;
}
.justify-between
{
justify-content
:
space-between
;
}
.gap-2
{
gap
:
0.5rem
;
}
.gap-3
{
gap
:
0.75rem
;
}
.overflow-hidden
{
overflow
:
hidden
;
}
.overflow-y-auto
{
overflow-y
:
auto
;
}
.max-h-
\
[
400px
\
]
{
max-height
:
400px
;
}
/* 用户管理对话框样式 */
:deep
(
.user-management-dialog
)
{
max-height
:
90vh
;
...
...
@@ -879,30 +765,25 @@ const organizationOptions = computed(() => {
display
:
flex
;
flex-direction
:
column
;
}
:deep
(
.user-management-dialog
.el-dialog__body
)
{
flex
:
1
;
overflow-y
:
auto
;
max-height
:
calc
(
90vh
-
120px
);
/* 减去头部和底部的空间 */
padding
:
24px
!important
;
}
:deep
(
.user-management-dialog
.el-dialog__header
)
{
flex-shrink
:
0
;
padding
:
24px
24px
0
24px
!important
;
}
:deep
(
.user-management-dialog
.el-dialog__footer
)
{
flex-shrink
:
0
;
padding
:
0
24px
24px
24px
!important
;
}
/* 搜索输入框样式 - 与订单管理保持一致 */
:deep
(
.search-input
.el-input__wrapper
)
{
padding-left
:
2.5rem
!important
;
}
:deep
(
.search-input
.el-input__inner
)
{
padding-left
:
0
!important
;
}
</
style
>
</
style
>
\ No newline at end of file
Write
Preview
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment