Files
akmon/pages/admins/roles/index.uvue
2026-01-20 08:04:15 +08:00

1190 lines
29 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="roles-page">
<admin-layout>
<view class="container">
<view class="page-header">
<text class="page-title">角色权限管理</text>
<view class="header-actions">
<button class="primary-btn" @click="showAddModal = true">添加角色</button>
</view>
</view>
<!-- 横向tab -->
<view class="swiper-tabs">
<scroll-view direction="horizontal" :show-scrollbar="false">
<view class="flex-row">
<text v-for="(tab, idx) in tabList" :key="tab.key" class="swiper-tabs-item"
:class="swiperIndex==idx ? 'swiper-tabs-item-active' : ''" @click="onTabClick(idx)">
{{tab.name}}
</text>
</view>
</scroll-view>
<view class="swiper-tabs-indicator" :style="indicatorStyle ?? {}"></view>
</view>
<!-- swiper内容-->
<swiper class="swiper-view" :current="swiperIndex" @change="onSwiperChange">
<swiper-item class="swiper-item">
<!-- 角色管理tabsupadb表格大屏小屏卡-->
<supadb ref="rolesdb" :collection="'ak_roles'" :field="'*'" :orderby="'level.desc'"
:page-size="10" :page-current="rolesPageCurrent" getcount="exact" loadtime="manual"
v-slot:default="{ data, current, loading, total, error }" @load="onRolesLoad">
<view v-if="loading" class="loading-container">加载中...</view>
<view v-else-if="error">{{ error }}</view>
<view v-else>
<!-- 大屏表格 -->
<view class="table-view">
<view class="table-header">
<text>角色名称</text>
<text>描述</text>
<text>级别</text>
<text>系统角色</text>
<text>操作</text>
</view>
<view class="table-content">
<view v-for="(role, idx) in (data as Array<UTSJSONObject>)"
:key="role.get('id')"
:class="['table-row', idx % 2 === 0 ? 'row-even' : 'row-odd']">
<text>{{ role.get('name') }}</text>
<text>{{ (role.get('description') ?? '') !== '' ? role.get('description') : '-' }}</text>
<text>{{ role.get('level') }}</text>
<text>{{ role.get('is_system') === true ? '是' : '否' }}</text>
<view class="action-buttons">
<button @click="viewRoleDetail(roleToRoleObj(role))">查看权限</button>
<button v-if="role.get('is_system') !== true"
@click="editRole(roleToRoleObj(role))">编辑</button>
<button v-if="role.get('is_system') !== true"
@click="confirmDeleteRole(roleToRoleObj(role))">删除</button>
</view>
</view>
</view>
</view>
<!-- 小屏卡片 -->
<view class="card-view">
<view v-for="(role, idx) in (data as Array<UTSJSONObject>)" :key="role.get('id')"
class="user-card">
<view class="card-row"><text class="card-label">角色名称:</text><text
class="card-value">{{ role.get('name') }}</text></view>
<view class="card-row"><text class="card-label">描述:</text><text
class="card-value">{{ (role.get('description') ?? '') !== '' ? role.get('description') : '-' }}</text>
</view>
<view class="card-row"><text class="card-label">级别:</text><text
class="card-value">{{ role.get('level') }}</text></view>
<view class="card-row"><text class="card-label">系统角色:</text><text
class="card-value">{{ role.get('is_system') === true ? '是' : '否' }}</text>
</view>
<view class="card-actions">
<button @click="viewRoleDetail(roleToRoleObj(role))">查看权限</button>
<button v-if="role.get('is_system') !== true"
@click="editRole(roleToRoleObj(role))">编辑</button>
<button v-if="role.get('is_system') !== true"
@click="confirmDeleteRole(roleToRoleObj(role))">删除</button>
</view>
</view>
</view>
<view class="pagination">
<button @click="rolesPrevPage()">上一页</button>
<text>{{ current }}/{{ total }}</text>
<button @click="rolesNextPage()">下一页</button>
</view>
</view>
</supadb>
</swiper-item>
<swiper-item class="swiper-item">
<!-- 权限管理tabsupadb表格大屏小屏卡片-->
<supadb ref="permissionsdb" :collection="'ak_permissions'" :field="'*'" :orderby="'id.desc'"
:page-size="10" :page-current="permissionsPageCurrent" getcount="exact" loadtime="manual"
v-slot:default="{ data, current, loading, total, error }" @load="onPermissionsLoad">
<view v-if="loading" class="loading-container">加载中...</view>
<view v-else-if="error">{{ error }}</view>
<view v-else>
<!-- 大屏表格 -->
<view class="table-view">
<view class="table-header">
<text>权限代码</text>
<text>名称</text>
<text>资源类型</text>
<text>操作</text>
<text>描述</text>
</view>
<scroll-view class="table-content" direction="vertical">
<view v-for="(perm, idx) in (data as Array<UTSJSONObject>)"
:key="perm.get('id')"
:class="['table-row', idx % 2 === 0 ? 'row-even' : 'row-odd']">
<text>{{ perm.get('code') }}</text>
<text>{{ perm.get('name') }}</text>
<text>{{ perm.get('resource_type') }}</text>
<text>{{ perm.get('action') }}</text>
<text>{{ (perm.get('description') ?? '') !== '' ? perm.get('description') : '-' }}</text>
</view>
</scroll-view>
</view>
<!-- 小屏卡片 -->
<scroll-view class="card-view" direction="vertical">
<view v-for="(perm, idx) in (data as Array<UTSJSONObject>)" :key="perm.get('id')"
class="user-card">
<view class="card-row"><text class="card-label">权限代码:</text><text
class="card-value">{{ perm.get('code') }}</text></view>
<view class="card-row"><text class="card-label">名称:</text><text
class="card-value">{{ perm.get('name') }}</text></view>
<view class="card-row"><text class="card-label">资源类型:</text><text
class="card-value">{{ perm.get('resource_type') }}</text></view>
<view class="card-row"><text class="card-label">操作:</text><text
class="card-value">{{ perm.get('action') }}</text></view>
<view class="card-row"><text class="card-label">描述:</text><text
class="card-value">{{ (perm.get('description') ?? '') !== '' ? perm.get('description') : '-' }}</text>
</view>
</view>
</scroll-view>
<view class="pagination">
<button @click="permissionsPrevPage()">上一页</button>
<text>{{ current }}/{{ total }}</text>
<button @click="permissionsNextPage()">下一页</button>
</view>
</view>
</supadb>
</swiper-item>
</swiper>
</view>
<!-- 添加/编辑角色模态框 -->
<view class="modal" v-if="showAddModal || showEditModal">
<view class="modal-content">
<view class="modal-header">
<text class="modal-title">{{ showEditModal ? '编辑角色' : '添加角色' }}</text>
<text class="close-btn" @click="closeModal">×</text>
</view>
<view class="modal-body">
<view class="form-group">
<text class="form-label">角色名称:</text>
<input type="text" :value="formData.name" @input="onInput('name', $event.detail.value)" placeholder="请输入角色名称" />
</view>
<view class="form-group">
<text class="form-label">描述:</text>
<textarea :value="formData.description" @input="onInput('description', $event.detail.value)" placeholder="请输入角色描述"></textarea>
</view>
<view class="form-group">
<text class="form-label">级别:</text>
<input type="number" :value="formData.level" @input="onInput('level', $event.detail.value as number)" placeholder="请输入角色级别(数字越大权限越高)" />
</view>
</view>
<view class="modal-footer">
<button class="cancel-btn" @click="closeModal">取消</button>
<button class="submit-btn" @click="submitRole">确定</button>
</view>
</view>
</view>
<!-- 角色详情权限分配 -->
<view class="modal large" v-if="showRoleDetailModal">
<view class="modal-content">
<view class="modal-header">
<text class="modal-title">角色权限管理: {{ selectedRole?.name }}</text>
<text class="close-btn" @click="showRoleDetailModal = false">×</text>
</view>
<view class="modal-body">
<view v-if="rolePermissionsLoading" class="loading">
<text>加载中...</text>
</view>
<view v-else>
<text class="section-title">已分配权限</text>
<view class="permissions-section">
<view v-if="rolePermissions.length === 0" class="empty-data">
<text>暂无权限</text>
</view>
<view v-else class="permission-tags">
<view v-for="(perm, index) in rolePermissions" :key="perm.id" class="perm-tag">
<text>{{ perm.name }}</text>
<text v-if="selectedRole?.is_system !== true" class="remove-icon" @click="removePermission(perm)">×</text>
</view>
</view>
</view>
<view v-if="selectedRole?.is_system !== true">
<text class="section-title">添加权限</text>
<view class="permission-filter">
<view class="filter-group">
<text>资源类型:</text>
<view class="picker-field" @click="showResourceTypePicker = true">
<text class="picker-text">{{ (addPermResourceType ?? '') !== '' ? addPermResourceType : '全部' }}</text>
<text class="picker-arrow">></text>
</view>
<view v-if="showResourceTypePicker" class="picker-modal">
<picker-view
class="picker-view"
:value="[tempResourceTypeIndex]"
:indicator-style="'height: 50px;'"
@change="onResourceTypePickerViewChange"
>
<picker-view-column style="width:750rpx;">
<view v-for="(type, idx) in resourceTypes" :key="type" class="picker-item">{{ type }}</view>
</picker-view-column>
</picker-view>
<view class="picker-actions">
<button @click="showResourceTypePicker = false">取消</button>
<button @click="confirmResourceTypePicker" class="picker-actions-button">确定</button>
</view>
</view>
</view>
</view>
<view class="permission-select-list">
<view v-for="(perm, index) in (availablePermissions as Permission[])" :key="perm.id" class="permission-select-item" @click="assignPermission(perm)">
<view class="perm-info">
<text class="perm-name">{{ perm.name }}</text>
<text class="perm-code">{{ perm.code }}</text>
</view>
<text class="add-icon">+</text>
</view>
</view>
</view>
</view>
</view>
<view class="modal-footer">
<button class="submit-btn" @click="showRoleDetailModal = false">关闭</button>
</view>
</view>
</view>
<!-- 删除确认框-->
<view class="modal" v-if="showDeleteModal">
<view class="modal-content">
<view class="modal-header">
<text class="modal-title">确认删除</text>
<text class="close-btn" @click="showDeleteModal = false">×</text>
</view>
<view class="modal-body">
<text>确定要删除角色“{{ deleteRole?.name }}”吗?此操作不可恢复,所有拥有该角色的用户将失去相应权限。</text>
</view>
<view class="modal-footer">
<button class="cancel-btn" @click="showDeleteModal = false">取消</button>
<button class="delete-btn" @click="deleteRoleConfirmed">删除</button>
</view>
</view>
</view>
</admin-layout>
</view>
</template>
<script lang="uts">
import {
ref,
reactive,
onMounted,
computed,
watch
} from 'vue'
import {
unref
} from 'vue'
import supa from '@/components/supadb/aksupainstance.uts'
// 定义角色对象类型
type Role = {
id: number
name: string
description: string
level: number
is_system: boolean
}
// 定义权限对象类型
type Permission = {
id: number
code: string
name: string
resource_type: string
action: string
description: string
}
// 定义角色-权限关联类型
type RolePermission = {
role_id: number
permission_id: number
}
export default {
setup() {
// 页面状态
const rolesdb = ref(null)
const permissionsdb = ref(null)
const rolesPageCurrent = ref(1)
const permissionsPageCurrent = ref(1)
const rolesTotal = ref(0)
const permissionsTotal = ref(0)
// 模态框状态
const showAddModal = ref(false)
const showEditModal = ref(false)
const showDeleteModal = ref(false)
const showRoleDetailModal = ref(false)
// 表单数据
const formData = reactive < Role > ({
id: -1,
name: '',
description: '',
level: 0,
is_system: false
})
// 当前操作的角色
const selectedRole = ref < Role | null > (null)
const deleteRole = ref < Role | null > (null)
// 权限管理
const rolePermissions = ref < Permission[] > ([])
const allPermissions = ref < Permission[] > ([])
const availablePermissions = ref < Permission[] > ([])
const rolePermissionsLoading = ref(false)
const addPermResourceType = ref < string | null > (null)
const resourceTypes = ref < string[] > ([])
// picker相关
const showResourceTypePicker = ref(false)
const tempResourceTypeIndex = ref(0)
// Tab切换相关
const swiperIndex = ref(0)
const tabList = [{
key: 'roles',
name: '角色管理'
}, {
key: 'permissions',
name: '权限列表'
}]
const indicatorStyle = computed(() => {
const tabWidth = 100 / tabList.length
const left = swiperIndex.value * tabWidth
return {
width: `${tabWidth}%`,
left: `${left}%`
}
})
const onTabClick = (index: number) => {
swiperIndex.value = index
}
const onSwiperChange = (e: any) => {
swiperIndex.value = e.detail.current
}
// 数据加载
onMounted(() => {
loadRoles()
loadPermissions()
loadAllPermissionsForAssignment()
})
const loadRoles = () => {
const rolesdbRef = unref(rolesdb)
if (rolesdbRef != null) {
(rolesdbRef as any).loadData()
}
}
const loadPermissions = () => {
const permissionsdbRef = unref(permissionsdb)
if (permissionsdbRef != null) {
(permissionsdbRef as any).loadData()
}
}
const onRolesLoad = (data: UTSJSONObject[], {
total
}: {
total: number
}) => {
rolesTotal.value = Math.ceil(total / 10)
}
const onPermissionsLoad = (data: UTSJSONObject[], {
total
}: {
total: number
}) => {
permissionsTotal.value = Math.ceil(total / 10)
}
// 分页
const rolesPrevPage = () => {
if (rolesPageCurrent.value > 1) {
rolesPageCurrent.value--
}
}
const rolesNextPage = () => {
if (rolesPageCurrent.value < rolesTotal.value) {
rolesPageCurrent.value++
}
}
const permissionsPrevPage = () => {
if (permissionsPageCurrent.value > 1) {
permissionsPageCurrent.value--
}
}
const permissionsNextPage = () => {
if (permissionsPageCurrent.value < permissionsTotal.value) {
permissionsPageCurrent.value++
}
}
// 表单操作
const onInput = (key: string, value: string | number) => {
if (key === 'level') {
(formData as any)[key] = parseInt(value as string, 10) || 0
} else {
(formData as any)[key] = value
}
}
const closeModal = () => {
showAddModal.value = false
showEditModal.value = false
resetFormData()
}
const resetFormData = () => {
formData.id = -1
formData.name = ''
formData.description = ''
formData.level = 0
formData.is_system = false
}
// 角色CRUD
const submitRole = async () => {
if (formData.name.trim() === '') {
uni.showToast({
title: '角色名称不能为空',
icon: 'none'
})
return
}
let result: any
if (showEditModal.value) {
// 编辑
const {
data,
error
} = await supabase
.from('ak_roles')
.update({
name: formData.name,
description: formData.description,
level: formData.level
})
.eq('id', formData.id)
result = {
data,
error
}
} else {
// 添加
const {
data,
error
} = await supabase
.from('ak_roles')
.insert([{
name: formData.name,
description: formData.description,
level: formData.level,
is_system: false
}])
result = {
data,
error
}
}
if (result.error) {
uni.showToast({
title: `操作失败: ${result.error.message}`,
icon: 'none'
})
} else {
uni.showToast({
title: '操作成功',
icon: 'success'
})
closeModal()
loadRoles()
}
}
const editRole = (role: Role) => {
formData.id = role.id
formData.name = role.name
formData.description = role.description
formData.level = role.level
showEditModal.value = true
}
const confirmDeleteRole = (role: Role) => {
deleteRole.value = role
showDeleteModal.value = true
}
const deleteRoleConfirmed = async () => {
if (!deleteRole.value) return
const {
error
} = await supabase
.from('ak_roles')
.delete()
.eq('id', deleteRole.value.id)
if (error) {
uni.showToast({
title: `删除失败: ${error.message}`,
icon: 'none'
})
} else {
uni.showToast({
title: '删除成功',
icon: 'success'
})
showDeleteModal.value = false
deleteRole.value = null
loadRoles()
}
}
// 权限详情与分配
const viewRoleDetail = async (role: Role) => {
selectedRole.value = role
showRoleDetailModal.value = true
rolePermissionsLoading.value = true
// 加载当前角色的权限
const { data, error } = await supabase
.from('ak_role_permissions')
.select('permission_id')
.eq('role_id', role.id)
if (error) {
console.error('获取角色权限失败:', error)
rolePermissions.value = []
} else {
const permissionIds = data.map(item => item.permission_id)
const { data: permsData, error: permsError } = await supabase
.from('ak_permissions')
.select('*')
.in('id', permissionIds)
if (permsError) {
rolePermissions.value = []
} else {
rolePermissions.value = permsData.map(p => ({
id: p.id,
code: p.code,
name: p.name,
resource_type: p.resource_type,
action: p.action,
description: p.description
}))
}
}
rolePermissionsLoading.value = false
filterAvailablePermissions()
}
const loadAllPermissionsForAssignment = async () => {
const { data, error } = await supabase
.from('ak_permissions')
.select('*')
.order('resource_type', { ascending: true })
.order('name', { ascending: true })
if (error) {
console.error('加载所有权限失败:', error)
} else {
allPermissions.value = data.map(p => ({
id: p.id,
code: p.code,
name: p.name,
resource_type: p.resource_type,
action: p.action,
description: p.description
}))
// 提取所有唯一的 resource_type
const types = new Set<string>()
types.add('全部') // 添加一个“全部”选项
allPermissions.value.forEach(p => types.add(p.resource_type))
resourceTypes.value = Array.from(types)
}
}
const filterAvailablePermissions = () => {
const assignedIds = new Set(rolePermissions.value.map(p => p.id))
let filtered = allPermissions.value.filter(p => !assignedIds.has(p.id))
if (addPermResourceType.value != null && addPermResourceType.value !== '全部') {
filtered = filtered.filter(p => p.resource_type === addPermResourceType.value)
}
availablePermissions.value = filtered
}
watch([() => rolePermissions.value, () => addPermResourceType.value], filterAvailablePermissions, { deep: true })
const assignPermission = async (permission: Permission) => {
if (!selectedRole.value) return
const { error } = await supabase
.from('ak_role_permissions')
.insert([{
role_id: selectedRole.value.id,
permission_id: permission.id
}])
if (error) {
uni.showToast({ title: `添加失败: ${error.message}`, icon: 'none' })
} else {
uni.showToast({ title: '添加成功', icon: 'success' })
// 重新加载权限
viewRoleDetail(selectedRole.value!)
}
}
const removePermission = async (permission: Permission) => {
if (!selectedRole.value) return
const { error } = await supabase
.from('ak_role_permissions')
.delete()
.eq('role_id', selectedRole.value.id)
.eq('permission_id', permission.id)
if (error) {
uni.showToast({ title: `移除失败: ${error.message}`, icon: 'none' })
} else {
uni.showToast({ title: '移除成功', icon: 'success' })
// 重新加载权限
viewRoleDetail(selectedRole.value!)
}
}
// Picker相关方法
const onResourceTypePickerViewChange = (e: any) => {
tempResourceTypeIndex.value = e.detail.value[0]
}
const confirmResourceTypePicker = () => {
addPermResourceType.value = resourceTypes.value[tempResourceTypeIndex.value]
showResourceTypePicker.value = false
}
// 工具函数
const roleToRoleObj = (role: UTSJSONObject): Role => {
return {
id: role.get('id') as number,
name: role.get('name') as string,
description: role.get('description') as string,
level: role.get('level') as number,
is_system: role.get('is_system') as boolean,
}
}
return {
// data
rolesdb,
permissionsdb,
rolesPageCurrent,
permissionsPageCurrent,
rolesTotal,
permissionsTotal,
showAddModal,
showEditModal,
showDeleteModal,
showRoleDetailModal,
formData,
selectedRole,
deleteRole,
rolePermissions,
availablePermissions,
rolePermissionsLoading,
addPermResourceType,
resourceTypes,
showResourceTypePicker,
tempResourceTypeIndex,
// computed
swiperIndex,
tabList,
indicatorStyle,
// methods
onTabClick,
onSwiperChange,
loadRoles,
loadPermissions,
onRolesLoad,
onPermissionsLoad,
rolesPrevPage,
rolesNextPage,
permissionsPrevPage,
permissionsNextPage,
onInput,
closeModal,
submitRole,
editRole,
confirmDeleteRole,
deleteRoleConfirmed,
viewRoleDetail,
assignPermission,
removePermission,
onResourceTypePickerViewChange,
confirmResourceTypePicker,
roleToRoleObj
}
}
}
</script>
<style>
/* 通用样式 */
.roles-page {
background-color: #f5f5f5;
min-height: 100vh;
}
.container {
padding: 20px;
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin: 16px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.page-title {
font-size: 24px;
font-weight: bold;
color: #333;
}
.header-actions .primary-btn {
background-color: #007aff;
color: white;
border: none;
padding: 10px 15px;
border-radius: 5px;
cursor: pointer;
}
.primary-btn:hover {
background-color: #0056b3;
}
/* Tab样式 */
.swiper-tabs {
position: relative;
background-color: #FFF;
border-bottom: 1px solid #ddd;
margin-bottom: 16px;
}
.swiper-tabs-item {
padding: 10px 20px;
font-size: 16px;
color: #555;
cursor: pointer;
text-align: center;
}
.swiper-tabs-item-active {
color: #007aff;
font-weight: bold;
}
.swiper-tabs-indicator {
position: absolute;
bottom: 0;
height: 3px;
background-color: #007aff;
transition: left 0.2s ease-in-out;
}
.swiper-view {
height: 600px; /* 根据内容调整 */
}
.loading-container {
text-align: center;
padding: 50px;
color: #888;
}
/* 分页 */
.pagination {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
}
.pagination button {
padding: 5px 10px;
margin: 0 10px;
background-color: #007aff;
color: white;
border: none;
border-radius: 4px;
}
.pagination text {
font-size: 14px;
color: #333;
}
/* 模态框样式 */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal.large .modal-content {
width: 80%;
max-width: 900px;
}
.modal-content {
background-color: white;
padding: 20px;
border-radius: 8px;
width: 90%;
max-width: 500px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
margin-bottom: 20px;
}
.modal-title {
font-size: 18px;
font-weight: bold;
}
.close-btn {
font-size: 24px;
cursor: pointer;
color: #888;
}
.modal-body .form-group {
margin-bottom: 15px;
}
.modal-body .form-label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
.modal-body input,
.modal-body textarea {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.modal-body textarea {
min-height: 80px;
resize: vertical;
}
.modal-footer {
display: flex;
justify-content: flex-end;
margin-top: 20px;
}
.modal-footer button {
padding: 10px 15px;
border-radius: 5px;
border: none;
cursor: pointer;
margin-left: 10px;
}
.cancel-btn {
background-color: #f0f0f0;
}
.submit-btn {
background-color: #007aff;
color: white;
}
.delete-btn {
background-color: #ff3b30;
color: white;
}
/* 权限详情 */
.section-title {
font-size: 16px;
font-weight: bold;
margin-top: 20px;
margin-bottom: 10px;
display: block;
}
.permissions-section .empty-data {
color: #888;
font-style: italic;
}
.permission-tags {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.perm-tag {
background-color: #eef5ff;
color: #007aff;
padding: 5px 10px;
border-radius: 15px;
display: flex;
align-items: center;
}
.perm-tag .remove-icon {
margin-left: 8px;
cursor: pointer;
font-weight: bold;
color: #ff3b30;
}
.permission-filter {
margin-bottom: 15px;
display: flex;
gap: 20px;
align-items: center;
}
.filter-group {
display: flex;
align-items: center;
gap: 10px;
}
.picker-field {
border: 1px solid #ccc;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
min-width: 150px;
}
.picker-text {
color: #333;
}
.picker-arrow {
color: #888;
}
.picker-modal {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: white;
z-index: 1100;
border-top: 1px solid #eee;
}
.picker-view {
width: 100%;
height: 200px;
}
.picker-item {
line-height: 50px;
text-align: center;
}
.picker-actions {
display: flex;
justify-content: space-between;
padding: 10px;
border-top: 1px solid #eee;
}
.picker-actions-button {
color: #007aff;
}
.permission-select-list {
max-height: 250px;
overflow-y: auto;
border: 1px solid #eee;
border-radius: 4px;
}
.permission-select-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #f5f5f5;
cursor: pointer;
}
.permission-select-item:hover {
background-color: #f9f9f9;
}
.perm-info .perm-name {
font-weight: 500;
}
.perm-info .perm-code {
font-size: 12px;
color: #888;
margin-left: 10px;
}
.add-icon {
font-size: 20px;
color: #007aff;
font-weight: bold;
}
/* 响应式布局 */
.table-view {
display: none;
}
.card-view {
display: block;
}
@media (min-width: 768px) {
.table-view {
display: block;
}
.card-view {
display: none;
}
.table-header,
.table-row {
display: grid;
grid-template-columns: 1fr 2fr 0.5fr 0.5fr 1.5fr;
gap: 10px;
padding: 10px;
align-items: center;
border-bottom: 1px solid #eee;
}
.table-header {
font-weight: bold;
background-color: #f9f9f9;
}
.table-content {
max-height: 450px; /* 设定一个最大高度 */
overflow-y: auto; /* 超出部分显示滚动条 */
}
.table-row.row-odd {
background-color: #f9f9f9;
}
.action-buttons {
display: flex;
gap: 5px;
}
.action-buttons button {
padding: 5px 8px;
font-size: 12px;
border: 1px solid #007aff;
background-color: transparent;
color: #007aff;
border-radius: 4px;
}
}
.user-card {
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 15px;
margin-bottom: 10px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.card-row {
display: flex;
padding: 5px 0;
}
.card-label {
width: 80px;
font-weight: bold;
color: #555;
}
.card-value {
flex: 1;
color: #333;
}
.card-actions {
margin-top: 10px;
display: flex;
gap: 10px;
justify-content: flex-end;
}
.card-actions button {
padding: 5px 10px;
font-size: 13px;
border: 1px solid #007aff;
background-color: transparent;
color: #007aff;
border-radius: 4px;
}
</style>