|
|
@@ -0,0 +1,546 @@
|
|
|
+<template>
|
|
|
+ <div class="page-container">
|
|
|
+ <!-- 搜索表单 -->
|
|
|
+ <div class="search-form">
|
|
|
+ <el-form :model="searchForm" inline data-id="search-form">
|
|
|
+ <el-form-item>
|
|
|
+ <el-input
|
|
|
+ v-model.trim="searchForm.roleName"
|
|
|
+ :placeholder="t('角色名称')"
|
|
|
+ clearable
|
|
|
+ @keyup.enter="handleSearch"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-input
|
|
|
+ v-model.trim="searchForm.roleCode"
|
|
|
+ :placeholder="t('角色编码')"
|
|
|
+ clearable
|
|
|
+ @keyup.enter="handleSearch"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-select v-model="searchForm.status" :placeholder="t('状态')" clearable>
|
|
|
+ <el-option :label="t('全部')" value="" />
|
|
|
+ <el-option :label="t('启用')" value="enabled" />
|
|
|
+ <el-option :label="t('禁用')" value="disabled" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" @click="handleSearch">
|
|
|
+ <Icon icon="mdi:magnify" width="16" height="16" style="margin-right: 4px" />
|
|
|
+ {{ t('查询') }}
|
|
|
+ </el-button>
|
|
|
+ <el-button type="info" @click="handleReset">
|
|
|
+ <Icon icon="mdi:refresh" width="16" height="16" style="margin-right: 4px" />
|
|
|
+ {{ t('重置') }}
|
|
|
+ </el-button>
|
|
|
+ <el-button type="primary" @click="handleAdd">
|
|
|
+ <Icon icon="mdi:plus" width="16" height="16" style="margin-right: 4px" />
|
|
|
+ {{ t('新增') }}
|
|
|
+ </el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 数据表格 -->
|
|
|
+ <div class="table-wrapper">
|
|
|
+ <el-table
|
|
|
+ ref="tableRef"
|
|
|
+ v-loading="loading"
|
|
|
+ :data="roleList"
|
|
|
+ stripe
|
|
|
+ size="default"
|
|
|
+ height="100%"
|
|
|
+ @sort-change="handleSortChange"
|
|
|
+ >
|
|
|
+ <el-table-column prop="id" :label="t('ID')" width="80" />
|
|
|
+ <el-table-column
|
|
|
+ prop="roleName"
|
|
|
+ :label="t('角色名称')"
|
|
|
+ min-width="120"
|
|
|
+ sortable="custom"
|
|
|
+ show-overflow-tooltip
|
|
|
+ />
|
|
|
+ <el-table-column prop="roleCode" :label="t('角色编码')" min-width="120" show-overflow-tooltip />
|
|
|
+ <el-table-column :label="t('用户数')" width="100" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag type="info" size="small">{{ row.userCount }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column :label="t('状态')" width="100" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="row.status === 'enabled' ? 'success' : 'danger'" size="small">
|
|
|
+ {{ row.status === 'enabled' ? t('启用') : t('禁用') }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="description" :label="t('描述')" min-width="200" show-overflow-tooltip />
|
|
|
+ <el-table-column :label="t('创建时间')" min-width="160">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ formatTime(row.createdAt) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column :label="t('操作')" width="150" align="center" fixed="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button type="primary" link @click="handleEdit(row)">
|
|
|
+ <Icon icon="mdi:note-edit-outline" width="20" height="20" />
|
|
|
+ </el-button>
|
|
|
+ <el-button type="primary" link @click="handlePermission(row)">
|
|
|
+ <Icon icon="mdi:shield-key" width="20" height="20" />
|
|
|
+ </el-button>
|
|
|
+ <el-button type="danger" link :disabled="row.roleCode === 'admin'" @click="handleDelete(row)">
|
|
|
+ <Icon icon="mdi:delete" width="20" height="20" />
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 分页 -->
|
|
|
+ <div class="pagination-container">
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="currentPage"
|
|
|
+ v-model:page-size="pageSize"
|
|
|
+ :page-sizes="[10, 15, 20, 50, 100]"
|
|
|
+ :total="total"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ background
|
|
|
+ @size-change="handleSizeChange"
|
|
|
+ @current-change="handleCurrentChange"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 角色编辑抽屉 -->
|
|
|
+ <el-drawer
|
|
|
+ v-model="drawerVisible"
|
|
|
+ :title="isEdit ? t('编辑角色') : t('新增角色')"
|
|
|
+ direction="rtl"
|
|
|
+ size="500px"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ destroy-on-close
|
|
|
+ >
|
|
|
+ <el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
|
|
+ <el-form-item :label="t('角色名称')" prop="roleName">
|
|
|
+ <el-input v-model="form.roleName" :placeholder="t('请输入角色名称')" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item :label="t('角色编码')" prop="roleCode">
|
|
|
+ <el-input v-model="form.roleCode" :disabled="isEdit" :placeholder="t('请输入角色编码')" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item :label="t('排序')" prop="sort">
|
|
|
+ <el-input-number v-model="form.sort" :min="0" :max="999" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item :label="t('状态')" prop="status">
|
|
|
+ <el-radio-group v-model="form.status">
|
|
|
+ <el-radio value="enabled">{{ t('启用') }}</el-radio>
|
|
|
+ <el-radio value="disabled">{{ t('禁用') }}</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item :label="t('描述')" prop="description">
|
|
|
+ <el-input v-model="form.description" type="textarea" :rows="3" :placeholder="t('请输入描述')" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <div class="drawer-footer">
|
|
|
+ <el-button @click="drawerVisible = false">{{ t('取消') }}</el-button>
|
|
|
+ <el-button type="primary" :loading="submitting" @click="handleSubmit">
|
|
|
+ {{ isEdit ? t('更新') : t('添加') }}
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-drawer>
|
|
|
+
|
|
|
+ <!-- 权限配置抽屉 -->
|
|
|
+ <el-drawer
|
|
|
+ v-model="permissionDrawerVisible"
|
|
|
+ :title="`${t('权限配置')} - ${currentRole?.roleName || ''}`"
|
|
|
+ direction="rtl"
|
|
|
+ size="500px"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ destroy-on-close
|
|
|
+ >
|
|
|
+ <el-tree
|
|
|
+ ref="treeRef"
|
|
|
+ :data="permissionTree"
|
|
|
+ show-checkbox
|
|
|
+ node-key="id"
|
|
|
+ :default-checked-keys="checkedPermissions"
|
|
|
+ :props="{ label: 'name', children: 'children' }"
|
|
|
+ />
|
|
|
+ <template #footer>
|
|
|
+ <div class="drawer-footer">
|
|
|
+ <el-button @click="permissionDrawerVisible = false">{{ t('取消') }}</el-button>
|
|
|
+ <el-button type="primary" :loading="permissionSubmitting" @click="handleSavePermission">
|
|
|
+ {{ t('保存') }}
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-drawer>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, reactive, onMounted, computed } from 'vue'
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+import { Icon } from '@iconify/vue'
|
|
|
+import type { FormInstance, FormRules } from 'element-plus'
|
|
|
+import { useI18n } from 'vue-i18n'
|
|
|
+import { formatTime } from '@/utils/dayjs'
|
|
|
+
|
|
|
+const { t } = useI18n({ useScope: 'global' })
|
|
|
+
|
|
|
+// Mock 数据
|
|
|
+interface Role {
|
|
|
+ id: number
|
|
|
+ roleName: string
|
|
|
+ roleCode: string
|
|
|
+ userCount: number
|
|
|
+ status: 'enabled' | 'disabled'
|
|
|
+ description: string
|
|
|
+ sort: number
|
|
|
+ createdAt: string
|
|
|
+}
|
|
|
+
|
|
|
+interface Permission {
|
|
|
+ id: number
|
|
|
+ name: string
|
|
|
+ children?: Permission[]
|
|
|
+}
|
|
|
+
|
|
|
+const mockRoles: Role[] = [
|
|
|
+ {
|
|
|
+ id: 1,
|
|
|
+ roleName: '管理员',
|
|
|
+ roleCode: 'admin',
|
|
|
+ userCount: 1,
|
|
|
+ status: 'enabled',
|
|
|
+ description: '系统管理员,拥有所有权限',
|
|
|
+ sort: 0,
|
|
|
+ createdAt: '2024-01-01T10:00:00Z'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 2,
|
|
|
+ roleName: '操作员',
|
|
|
+ roleCode: 'operator',
|
|
|
+ userCount: 2,
|
|
|
+ status: 'enabled',
|
|
|
+ description: '可以操作设备和查看数据',
|
|
|
+ sort: 1,
|
|
|
+ createdAt: '2024-01-15T14:30:00Z'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 3,
|
|
|
+ roleName: '查看者',
|
|
|
+ roleCode: 'viewer',
|
|
|
+ userCount: 3,
|
|
|
+ status: 'enabled',
|
|
|
+ description: '只能查看数据,无法操作',
|
|
|
+ sort: 2,
|
|
|
+ createdAt: '2024-02-01T09:00:00Z'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 4,
|
|
|
+ roleName: '测试角色',
|
|
|
+ roleCode: 'test',
|
|
|
+ userCount: 0,
|
|
|
+ status: 'disabled',
|
|
|
+ description: '测试用角色',
|
|
|
+ sort: 99,
|
|
|
+ createdAt: '2024-02-15T16:00:00Z'
|
|
|
+ }
|
|
|
+]
|
|
|
+
|
|
|
+const mockPermissions: Permission[] = [
|
|
|
+ {
|
|
|
+ id: 1,
|
|
|
+ name: '仪表盘',
|
|
|
+ children: [{ id: 11, name: '查看仪表盘' }]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 2,
|
|
|
+ name: 'LSS 管理',
|
|
|
+ children: [
|
|
|
+ { id: 21, name: '查看 LSS 列表' },
|
|
|
+ { id: 22, name: '编辑 LSS' },
|
|
|
+ { id: 23, name: '删除 LSS' }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 3,
|
|
|
+ name: '设备管理',
|
|
|
+ children: [
|
|
|
+ { id: 31, name: '查看设备列表' },
|
|
|
+ { id: 32, name: '添加设备' },
|
|
|
+ { id: 33, name: '编辑设备' },
|
|
|
+ { id: 34, name: '删除设备' }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 4,
|
|
|
+ name: '系统管理',
|
|
|
+ children: [
|
|
|
+ { id: 41, name: '用户管理' },
|
|
|
+ { id: 42, name: '角色管理' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+]
|
|
|
+
|
|
|
+const loading = ref(false)
|
|
|
+const roleList = ref<Role[]>([])
|
|
|
+const tableRef = ref()
|
|
|
+const treeRef = ref()
|
|
|
+
|
|
|
+// 搜索表单
|
|
|
+const searchForm = reactive({
|
|
|
+ roleName: '',
|
|
|
+ roleCode: '',
|
|
|
+ status: '' as '' | 'enabled' | 'disabled'
|
|
|
+})
|
|
|
+
|
|
|
+// 分页
|
|
|
+const currentPage = ref(1)
|
|
|
+const pageSize = ref(15)
|
|
|
+const total = ref(0)
|
|
|
+
|
|
|
+// 排序
|
|
|
+const sortState = reactive({
|
|
|
+ sortBy: '',
|
|
|
+ sortDir: '' as 'ASC' | 'DESC' | ''
|
|
|
+})
|
|
|
+
|
|
|
+// 抽屉
|
|
|
+const drawerVisible = ref(false)
|
|
|
+const isEdit = ref(false)
|
|
|
+const submitting = ref(false)
|
|
|
+const formRef = ref<FormInstance>()
|
|
|
+const currentRole = ref<Role | null>(null)
|
|
|
+
|
|
|
+// 权限配置
|
|
|
+const permissionDrawerVisible = ref(false)
|
|
|
+const permissionSubmitting = ref(false)
|
|
|
+const permissionTree = ref<Permission[]>(mockPermissions)
|
|
|
+const checkedPermissions = ref<number[]>([])
|
|
|
+
|
|
|
+// 表单
|
|
|
+const form = reactive({
|
|
|
+ roleName: '',
|
|
|
+ roleCode: '',
|
|
|
+ sort: 0,
|
|
|
+ status: 'enabled' as 'enabled' | 'disabled',
|
|
|
+ description: ''
|
|
|
+})
|
|
|
+
|
|
|
+// 表单验证规则
|
|
|
+const rules = computed<FormRules>(() => ({
|
|
|
+ roleName: [{ required: true, message: t('请输入角色名称'), trigger: 'blur' }],
|
|
|
+ roleCode: [{ required: true, message: t('请输入角色编码'), trigger: 'blur' }]
|
|
|
+}))
|
|
|
+
|
|
|
+// 获取列表
|
|
|
+async function getList() {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ await new Promise((resolve) => setTimeout(resolve, 300))
|
|
|
+
|
|
|
+ let filtered = [...mockRoles]
|
|
|
+
|
|
|
+ // 搜索过滤
|
|
|
+ if (searchForm.roleName) {
|
|
|
+ filtered = filtered.filter((r) => r.roleName.includes(searchForm.roleName))
|
|
|
+ }
|
|
|
+ if (searchForm.roleCode) {
|
|
|
+ filtered = filtered.filter((r) => r.roleCode.includes(searchForm.roleCode))
|
|
|
+ }
|
|
|
+ if (searchForm.status) {
|
|
|
+ filtered = filtered.filter((r) => r.status === searchForm.status)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 排序
|
|
|
+ if (sortState.sortBy) {
|
|
|
+ filtered.sort((a, b) => {
|
|
|
+ const aVal = a[sortState.sortBy as keyof Role] as string
|
|
|
+ const bVal = b[sortState.sortBy as keyof Role] as string
|
|
|
+ return sortState.sortDir === 'ASC' ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ total.value = filtered.length
|
|
|
+ const start = (currentPage.value - 1) * pageSize.value
|
|
|
+ roleList.value = filtered.slice(start, start + pageSize.value)
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function handleSearch() {
|
|
|
+ currentPage.value = 1
|
|
|
+ getList()
|
|
|
+}
|
|
|
+
|
|
|
+function handleReset() {
|
|
|
+ searchForm.roleName = ''
|
|
|
+ searchForm.roleCode = ''
|
|
|
+ searchForm.status = ''
|
|
|
+ currentPage.value = 1
|
|
|
+ getList()
|
|
|
+}
|
|
|
+
|
|
|
+function handleSortChange({ prop, order }: { prop: string; order: 'ascending' | 'descending' | null }) {
|
|
|
+ sortState.sortBy = prop || ''
|
|
|
+ sortState.sortDir = order === 'ascending' ? 'ASC' : order === 'descending' ? 'DESC' : ''
|
|
|
+ getList()
|
|
|
+}
|
|
|
+
|
|
|
+function handleSizeChange(val: number) {
|
|
|
+ pageSize.value = val
|
|
|
+ currentPage.value = 1
|
|
|
+ getList()
|
|
|
+}
|
|
|
+
|
|
|
+function handleCurrentChange(val: number) {
|
|
|
+ currentPage.value = val
|
|
|
+ getList()
|
|
|
+}
|
|
|
+
|
|
|
+function resetForm() {
|
|
|
+ form.roleName = ''
|
|
|
+ form.roleCode = ''
|
|
|
+ form.sort = 0
|
|
|
+ form.status = 'enabled'
|
|
|
+ form.description = ''
|
|
|
+ formRef.value?.clearValidate()
|
|
|
+}
|
|
|
+
|
|
|
+function handleAdd() {
|
|
|
+ isEdit.value = false
|
|
|
+ currentRole.value = null
|
|
|
+ resetForm()
|
|
|
+ drawerVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+function handleEdit(row: Role) {
|
|
|
+ isEdit.value = true
|
|
|
+ currentRole.value = row
|
|
|
+ form.roleName = row.roleName
|
|
|
+ form.roleCode = row.roleCode
|
|
|
+ form.sort = row.sort
|
|
|
+ form.status = row.status
|
|
|
+ form.description = row.description
|
|
|
+ drawerVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+async function handleSubmit() {
|
|
|
+ if (!formRef.value) return
|
|
|
+
|
|
|
+ await formRef.value.validate(async (valid) => {
|
|
|
+ if (!valid) return
|
|
|
+
|
|
|
+ submitting.value = true
|
|
|
+ try {
|
|
|
+ await new Promise((resolve) => setTimeout(resolve, 500))
|
|
|
+ ElMessage.success(isEdit.value ? t('更新成功') : t('添加成功'))
|
|
|
+ drawerVisible.value = false
|
|
|
+ getList()
|
|
|
+ } finally {
|
|
|
+ submitting.value = false
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+async function handleDelete(row: Role) {
|
|
|
+ if (row.roleCode === 'admin') {
|
|
|
+ ElMessage.warning(t('管理员角色不能删除'))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm(
|
|
|
+ `你确定要删除这个角色吗?<br/><br/>角色名称:${row.roleName}<br/>角色编码:${row.roleCode}`,
|
|
|
+ t('提示'),
|
|
|
+ {
|
|
|
+ type: 'warning',
|
|
|
+ dangerouslyUseHTMLString: true
|
|
|
+ }
|
|
|
+ )
|
|
|
+ await new Promise((resolve) => setTimeout(resolve, 300))
|
|
|
+ ElMessage.success(t('删除成功'))
|
|
|
+ getList()
|
|
|
+ } catch {
|
|
|
+ // 取消
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function handlePermission(row: Role) {
|
|
|
+ currentRole.value = row
|
|
|
+ // Mock 已选权限
|
|
|
+ if (row.roleCode === 'admin') {
|
|
|
+ checkedPermissions.value = [11, 21, 22, 23, 31, 32, 33, 34, 41, 42]
|
|
|
+ } else if (row.roleCode === 'operator') {
|
|
|
+ checkedPermissions.value = [11, 21, 22, 31, 32, 33]
|
|
|
+ } else if (row.roleCode === 'viewer') {
|
|
|
+ checkedPermissions.value = [11, 21, 31]
|
|
|
+ } else {
|
|
|
+ checkedPermissions.value = []
|
|
|
+ }
|
|
|
+ permissionDrawerVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+async function handleSavePermission() {
|
|
|
+ permissionSubmitting.value = true
|
|
|
+ try {
|
|
|
+ await new Promise((resolve) => setTimeout(resolve, 500))
|
|
|
+ ElMessage.success(t('权限配置保存成功'))
|
|
|
+ permissionDrawerVisible.value = false
|
|
|
+ } finally {
|
|
|
+ permissionSubmitting.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ getList()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.page-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.search-form {
|
|
|
+ flex-shrink: 0;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ padding: 16px 16px 4px 16px;
|
|
|
+ background: #f5f7fa;
|
|
|
+
|
|
|
+ :deep(.el-form-item) {
|
|
|
+ margin-bottom: 12px;
|
|
|
+ margin-right: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-input),
|
|
|
+ :deep(.el-select) {
|
|
|
+ width: 160px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.table-wrapper {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.pagination-container {
|
|
|
+ flex-shrink: 0;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ padding-top: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.drawer-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+</style>
|