fix: auth.ts API路径添加/api前缀
- /auth/login -> /api/auth/login - /auth/logout -> /api/auth/logout - /auth/me -> /api/auth/me - /auth/refresh -> /api/auth/refresh
This commit is contained in:
parent
ea1eabafb0
commit
f111f4a8d5
|
|
@ -2,17 +2,17 @@ import request from '@/utils/request'
|
||||||
import type { LoginRequest, LoginResponse } from '@/types'
|
import type { LoginRequest, LoginResponse } from '@/types'
|
||||||
|
|
||||||
export const login = (data: LoginRequest) => {
|
export const login = (data: LoginRequest) => {
|
||||||
return request.post<LoginResponse>('/auth/login', data)
|
return request.post<LoginResponse>('/api/auth/login', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const logout = () => {
|
export const logout = () => {
|
||||||
return request.post('/auth/logout')
|
return request.post('/api/auth/logout')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getCurrentUser = () => {
|
export const getCurrentUser = () => {
|
||||||
return request.get('/auth/me')
|
return request.get('/api/auth/me')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const refreshToken = () => {
|
export const refreshToken = () => {
|
||||||
return request.post('/auth/refresh')
|
return request.post('/api/auth/refresh')
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import zhCN from 'ant-design-vue/es/locale/zh_CN'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
current?: number
|
current?: number
|
||||||
|
|
@ -34,6 +35,7 @@ const handleChange = (page: number, size: number) => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="table-pagination">
|
<div class="table-pagination">
|
||||||
|
<a-config-provider :locale="zhCN">
|
||||||
<a-pagination
|
<a-pagination
|
||||||
v-model:current="current"
|
v-model:current="current"
|
||||||
:total="total"
|
:total="total"
|
||||||
|
|
@ -44,6 +46,7 @@ const handleChange = (page: number, size: number) => {
|
||||||
:show-total="(total: number) => `共 ${total} 条`"
|
:show-total="(total: number) => `共 ${total} 条`"
|
||||||
@change="handleChange"
|
@change="handleChange"
|
||||||
/>
|
/>
|
||||||
|
</a-config-provider>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,15 @@ interface Props {
|
||||||
deleteDescription?: string
|
deleteDescription?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<Props>()
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
actions: () => [],
|
||||||
|
showEdit: true,
|
||||||
|
showDelete: true,
|
||||||
|
editText: '编辑',
|
||||||
|
deleteText: '删除',
|
||||||
|
deleteTitle: '确认删除',
|
||||||
|
deleteDescription: '删除后不可恢复,是否继续?'
|
||||||
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'edit'): void
|
(e: 'edit'): void
|
||||||
|
|
@ -87,6 +95,10 @@ const handleAction = (key: string) => {
|
||||||
.table-actions {
|
.table-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-actions :deep(.ant-btn) {
|
||||||
|
padding: 0 4px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,32 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, reactive } from 'vue'
|
import { ref, onMounted, reactive, computed } from 'vue'
|
||||||
import { Table, Tag, message } from 'ant-design-vue'
|
import { Button, Drawer, Form, Space, message } from 'ant-design-vue'
|
||||||
|
import { PlusOutlined, SearchOutlined } from '@ant-design/icons-vue'
|
||||||
import type { Permission } from '@/types'
|
import type { Permission } from '@/types'
|
||||||
import { getPermissions, createPermission, updatePermission, deletePermission } from '@/api/permission'
|
import { getPermissions, createPermission, updatePermission, deletePermission } from '@/api/permission'
|
||||||
|
import {
|
||||||
|
TableToolbar,
|
||||||
|
TableActions,
|
||||||
|
Pagination
|
||||||
|
} from '@/components'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// 表格列定义
|
||||||
const columns: any[] = [
|
const columns = [
|
||||||
{ title: '权限编码', dataIndex: 'code', key: 'code', width: 150 },
|
{ title: '权限编码', dataIndex: 'code', key: 'code', width: 140 },
|
||||||
{ title: '权限名称', dataIndex: 'name', key: 'name', width: 150 },
|
{ title: '权限名称', dataIndex: 'name', key: 'name', width: 120 },
|
||||||
{ title: '类型', dataIndex: 'type', key: 'type', width: 100 },
|
{ title: '类型', dataIndex: 'type', key: 'type', width: 80 },
|
||||||
{ title: '资源', dataIndex: 'resource', key: 'resource', width: 180 },
|
{ title: '资源', dataIndex: 'resource', key: 'resource', width: 160, ellipsis: true },
|
||||||
{ title: '方法', dataIndex: 'method', key: 'method', width: 80 },
|
{ title: '方法', dataIndex: 'method', key: 'method', width: 70 },
|
||||||
{ title: '描述', dataIndex: 'description', key: 'description', ellipsis: true },
|
{ title: '描述', dataIndex: 'description', key: 'description', ellipsis: true },
|
||||||
{ title: '操作', key: 'action', width: 150, fixed: 'right' }
|
{ title: '操作', key: 'action', width: 140, fixed: 'right' as const }
|
||||||
]
|
]
|
||||||
|
|
||||||
const permissions = ref<Permission[]>([])
|
const permissions = ref<Permission[]>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const modalVisible = ref(false)
|
const drawerVisible = ref(false)
|
||||||
const modalTitle = ref('新建权限')
|
const drawerTitle = ref('')
|
||||||
|
const formRef = ref()
|
||||||
|
const submitting = ref(false)
|
||||||
const editingId = ref<string | null>(null)
|
const editingId = ref<string | null>(null)
|
||||||
|
|
||||||
// 搜索相关
|
// 搜索相关
|
||||||
|
|
@ -28,9 +36,14 @@ const searchKeyword = ref('')
|
||||||
const pagination = reactive({
|
const pagination = reactive({
|
||||||
current: 1,
|
current: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
total: 0,
|
total: 0
|
||||||
showSizeChanger: true,
|
})
|
||||||
showTotal: (total: number) => `共 ${total} 条`
|
|
||||||
|
// 分页后的数据
|
||||||
|
const paginatedData = computed(() => {
|
||||||
|
const start = (pagination.current - 1) * pagination.pageSize
|
||||||
|
const end = start + pagination.pageSize
|
||||||
|
return permissions.value.slice(start, end)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
|
|
@ -43,12 +56,6 @@ const formState = reactive({
|
||||||
description: ''
|
description: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const formRules = {
|
|
||||||
code: [{ required: true, message: '请输入权限代码' }],
|
|
||||||
name: [{ required: true, message: '请输入权限名称' }],
|
|
||||||
type: [{ required: true, message: '请选择类型' }]
|
|
||||||
}
|
|
||||||
|
|
||||||
const typeOptions = [
|
const typeOptions = [
|
||||||
{ value: 'MENU', label: '菜单' },
|
{ value: 'MENU', label: '菜单' },
|
||||||
{ value: 'BUTTON', label: '按钮' },
|
{ value: 'BUTTON', label: '按钮' },
|
||||||
|
|
@ -103,13 +110,13 @@ const resetForm = () => {
|
||||||
editingId.value = null
|
editingId.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
const openCreateModal = () => {
|
const handleAdd = () => {
|
||||||
resetForm()
|
resetForm()
|
||||||
modalTitle.value = '新建权限'
|
drawerTitle.value = '新建权限'
|
||||||
modalVisible.value = true
|
drawerVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const openEditModal = (record: Permission) => {
|
const handleEdit = (record: Permission) => {
|
||||||
editingId.value = record.id
|
editingId.value = record.id
|
||||||
formState.code = record.code
|
formState.code = record.code
|
||||||
formState.name = record.name
|
formState.name = record.name
|
||||||
|
|
@ -117,24 +124,8 @@ const openEditModal = (record: Permission) => {
|
||||||
formState.resource = record.resource || ''
|
formState.resource = record.resource || ''
|
||||||
formState.method = record.method || 'GET'
|
formState.method = record.method || 'GET'
|
||||||
formState.description = record.description || ''
|
formState.description = record.description || ''
|
||||||
modalTitle.value = '编辑权限'
|
drawerTitle.value = '编辑权限'
|
||||||
modalVisible.value = true
|
drawerVisible.value = true
|
||||||
}
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
try {
|
|
||||||
if (editingId.value) {
|
|
||||||
await updatePermission(editingId.value, formState)
|
|
||||||
message.success('更新成功')
|
|
||||||
} else {
|
|
||||||
await createPermission(formState)
|
|
||||||
message.success('创建成功')
|
|
||||||
}
|
|
||||||
modalVisible.value = false
|
|
||||||
fetchPermissions()
|
|
||||||
} catch {
|
|
||||||
message.error(editingId.value ? '更新失败' : '创建失败')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDelete = async (id: string) => {
|
const handleDelete = async (id: string) => {
|
||||||
|
|
@ -147,9 +138,37 @@ const handleDelete = async (id: string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTableChange = (pag: any) => {
|
const handleSubmit = async () => {
|
||||||
pagination.current = pag.current
|
try {
|
||||||
pagination.pageSize = pag.pageSize
|
await formRef.value.validate()
|
||||||
|
submitting.value = true
|
||||||
|
|
||||||
|
if (editingId.value) {
|
||||||
|
await updatePermission(editingId.value, formState)
|
||||||
|
message.success('更新成功')
|
||||||
|
} else {
|
||||||
|
await createPermission(formState)
|
||||||
|
message.success('创建成功')
|
||||||
|
}
|
||||||
|
|
||||||
|
drawerVisible.value = false
|
||||||
|
fetchPermissions()
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.errorFields) return
|
||||||
|
message.error('操作失败')
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
drawerVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePageChange = (page: number, pageSize: number) => {
|
||||||
|
pagination.current = page
|
||||||
|
pagination.pageSize = pageSize
|
||||||
fetchPermissions()
|
fetchPermissions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,6 +177,12 @@ const handleSearch = () => {
|
||||||
fetchPermissions()
|
fetchPermissions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
searchKeyword.value = ''
|
||||||
|
pagination.current = 1
|
||||||
|
fetchPermissions()
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(fetchPermissions)
|
onMounted(fetchPermissions)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -166,123 +191,102 @@ onMounted(fetchPermissions)
|
||||||
<!-- 页面标题 -->
|
<!-- 页面标题 -->
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h2 class="page-title">权限管理</h2>
|
<h2 class="page-title">权限管理</h2>
|
||||||
|
<div class="page-header-actions">
|
||||||
|
<Button type="primary" @click="handleAdd">
|
||||||
|
<PlusOutlined /> 新建权限
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 表格 -->
|
<!-- 筛选区 -->
|
||||||
<div class="table-card">
|
<div class="filter-bar">
|
||||||
<div class="table-toolbar">
|
<a-space>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="searchKeyword"
|
v-model:value="searchKeyword"
|
||||||
placeholder="搜索权限编码或名称"
|
placeholder="搜索权限编码或名称"
|
||||||
style="width: 240px"
|
style="width: 240px"
|
||||||
|
allow-clear
|
||||||
@press-enter="handleSearch"
|
@press-enter="handleSearch"
|
||||||
>
|
/>
|
||||||
<template #prefix>
|
<a-button type="primary" @click="handleSearch">查询</a-button>
|
||||||
<SearchOutlined />
|
<a-button @click="handleReset">重置</a-button>
|
||||||
</template>
|
</a-space>
|
||||||
</a-input>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Table
|
<!-- 表格 -->
|
||||||
|
<div class="table-card">
|
||||||
|
<TableToolbar @refresh="fetchPermissions" />
|
||||||
|
|
||||||
|
<a-table
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="permissions"
|
:data-source="paginatedData"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:row-key="(record: Permission) => record.id"
|
:row-key="(record: Permission) => record.id"
|
||||||
:pagination="pagination"
|
:pagination="false"
|
||||||
:scroll="{ x: 1000 }"
|
|
||||||
@change="handleTableChange"
|
|
||||||
>
|
>
|
||||||
<template #title>
|
|
||||||
<div class="table-title">
|
|
||||||
<span>权限列表</span>
|
|
||||||
<a-button type="primary" @click="openCreateModal">
|
|
||||||
<template #icon><PlusOutlined /></template>
|
|
||||||
新建
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'type'">
|
<template v-if="column.key === 'type'">
|
||||||
<Tag :color="getTypeColor(record.type)">
|
<a-tag :color="getTypeColor(record.type)">
|
||||||
{{ getTypeLabel(record.type) }}
|
{{ getTypeLabel(record.type) }}
|
||||||
</Tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'action'">
|
<template v-else-if="column.key === 'action'">
|
||||||
<a-space>
|
<TableActions @edit="handleEdit(record)" @delete="handleDelete(record.id)" />
|
||||||
<a-button type="link" size="small" @click="openEditModal(record as any)">
|
|
||||||
编辑
|
|
||||||
</a-button>
|
|
||||||
<a-popconfirm
|
|
||||||
title="确定删除该权限吗?"
|
|
||||||
ok-text="确定"
|
|
||||||
cancel-text="取消"
|
|
||||||
@confirm="handleDelete(record.id)"
|
|
||||||
>
|
|
||||||
<a-button type="link" size="small" danger>
|
|
||||||
删除
|
|
||||||
</a-button>
|
|
||||||
</a-popconfirm>
|
|
||||||
</a-space>
|
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</Table>
|
</a-table>
|
||||||
|
|
||||||
|
<Pagination
|
||||||
|
v-model:current="pagination.current"
|
||||||
|
v-model:pageSize="pagination.pageSize"
|
||||||
|
:total="pagination.total"
|
||||||
|
@change="handlePageChange"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 新建/编辑模态框 -->
|
<!-- 抽屉 -->
|
||||||
<a-modal
|
<Drawer
|
||||||
v-model:open="modalVisible"
|
v-model:open="drawerVisible"
|
||||||
:title="modalTitle"
|
:title="drawerTitle"
|
||||||
:width="500"
|
width="480px"
|
||||||
@ok="handleSubmit"
|
:footer-style="{ textAlign: 'right' }"
|
||||||
@cancel="modalVisible = false"
|
@close="handleClose"
|
||||||
>
|
>
|
||||||
<a-form
|
<Form
|
||||||
|
ref="formRef"
|
||||||
:model="formState"
|
:model="formState"
|
||||||
:rules="formRules"
|
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
class="permission-form"
|
:rules="{
|
||||||
|
code: [{ required: true, message: '请输入权限代码' }],
|
||||||
|
name: [{ required: true, message: '请输入权限名称' }],
|
||||||
|
type: [{ required: true, message: '请选择类型' }]
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<a-form-item label="权限代码" name="code">
|
<Form.Item label="权限代码" name="code">
|
||||||
<a-input v-model:value="formState.code" placeholder="如: system:user:view" />
|
<a-input v-model:value="formState.code" placeholder="如: system:user:view" />
|
||||||
</a-form-item>
|
</Form.Item>
|
||||||
|
<Form.Item label="权限名称" name="name">
|
||||||
<a-form-item label="权限名称" name="name">
|
|
||||||
<a-input v-model:value="formState.name" placeholder="如: 查看用户" />
|
<a-input v-model:value="formState.name" placeholder="如: 查看用户" />
|
||||||
</a-form-item>
|
</Form.Item>
|
||||||
|
<Form.Item label="类型" name="type">
|
||||||
<a-form-item label="类型" name="type">
|
|
||||||
<a-select v-model:value="formState.type" :options="typeOptions" />
|
<a-select v-model:value="formState.type" :options="typeOptions" />
|
||||||
</a-form-item>
|
</Form.Item>
|
||||||
|
<Form.Item label="资源路径" name="resource">
|
||||||
<a-form-item label="资源路径" name="resource">
|
|
||||||
<a-input v-model:value="formState.resource" placeholder="如: /api/users" />
|
<a-input v-model:value="formState.resource" placeholder="如: /api/users" />
|
||||||
</a-form-item>
|
</Form.Item>
|
||||||
|
<Form.Item label="请求方法" name="method">
|
||||||
<a-form-item label="请求方法" name="method">
|
|
||||||
<a-select v-model:value="formState.method" :options="methodOptions" />
|
<a-select v-model:value="formState.method" :options="methodOptions" />
|
||||||
</a-form-item>
|
</Form.Item>
|
||||||
|
<Form.Item label="描述" name="description">
|
||||||
<a-form-item label="描述" name="description">
|
|
||||||
<a-textarea v-model:value="formState.description" :rows="3" placeholder="权限描述" />
|
<a-textarea v-model:value="formState.description" :rows="3" placeholder="权限描述" />
|
||||||
</a-form-item>
|
</Form.Item>
|
||||||
</a-form>
|
</Form>
|
||||||
</a-modal>
|
<template #footer>
|
||||||
|
<Space>
|
||||||
|
<Button @click="handleClose">取消</Button>
|
||||||
|
<Button type="primary" :loading="submitting" @click="handleSubmit">确定</Button>
|
||||||
|
</Space>
|
||||||
|
</template>
|
||||||
|
</Drawer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.table-toolbar {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-title {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.permission-form {
|
|
||||||
padding-top: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,24 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, reactive, computed } from 'vue'
|
||||||
import { Table, Button, Drawer, Input, Select, Form, Space, Popconfirm, message } from 'ant-design-vue'
|
import { Button, Drawer, Input, Select, Form, Space, message } from 'ant-design-vue'
|
||||||
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
import { PlusOutlined } from '@ant-design/icons-vue'
|
||||||
import { getRoles, createRole, updateRole, deleteRole } from '@/api/role'
|
import { getRoles, createRole, updateRole, deleteRole } from '@/api/role'
|
||||||
import type { Role } from '@/types'
|
import type { Role } from '@/types'
|
||||||
|
import {
|
||||||
|
TableToolbar,
|
||||||
|
TableActions,
|
||||||
|
Pagination,
|
||||||
|
StatusTag
|
||||||
|
} from '@/components'
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ title: '角色编码', dataIndex: 'code', key: 'code', width: 120 },
|
{ title: '角色编码', dataIndex: 'code', key: 'code', width: 100 },
|
||||||
{ title: '角色名称', dataIndex: 'name', key: 'name', width: 150 },
|
{ title: '角色名称', dataIndex: 'name', key: 'name', width: 100 },
|
||||||
{ title: '类型', dataIndex: 'type', key: 'type', width: 100 },
|
{ title: '类型', dataIndex: 'type', key: 'type', width: 90 },
|
||||||
{ title: '数据权限', dataIndex: 'dataScope', key: 'dataScope', width: 100 },
|
{ title: '数据权限', dataIndex: 'dataScope', key: 'dataScope', width: 100 },
|
||||||
{ title: '描述', dataIndex: 'description', key: 'description', ellipsis: true },
|
{ title: '描述', dataIndex: 'description', key: 'description', ellipsis: true },
|
||||||
{ title: '状态', dataIndex: 'status', key: 'status', width: 80 },
|
{ title: '状态', dataIndex: 'status', key: 'status', width: 70 },
|
||||||
{ title: '操作', key: 'action', width: 120, fixed: 'right' }
|
{ title: '操作', key: 'action', width: 140, fixed: 'right' as const }
|
||||||
]
|
]
|
||||||
|
|
||||||
const roles = ref<Role[]>([])
|
const roles = ref<Role[]>([])
|
||||||
|
|
@ -22,6 +28,24 @@ const drawerTitle = ref('')
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
|
|
||||||
|
// 筛选
|
||||||
|
const searchKeyword = ref('')
|
||||||
|
const searchStatus = ref('')
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
const pagination = reactive({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 分页后的数据
|
||||||
|
const paginatedData = computed(() => {
|
||||||
|
const start = (pagination.current - 1) * pagination.pageSize
|
||||||
|
const end = start + pagination.pageSize
|
||||||
|
return roles.value.slice(start, end)
|
||||||
|
})
|
||||||
|
|
||||||
const formState = ref({
|
const formState = ref({
|
||||||
id: '',
|
id: '',
|
||||||
code: '',
|
code: '',
|
||||||
|
|
@ -32,11 +56,6 @@ const formState = ref({
|
||||||
status: 'ACTIVE'
|
status: 'ACTIVE'
|
||||||
})
|
})
|
||||||
|
|
||||||
const statusOptions = [
|
|
||||||
{ value: 'ACTIVE', label: '正常', color: 'success' },
|
|
||||||
{ value: 'DISABLED', label: '禁用', color: 'error' }
|
|
||||||
]
|
|
||||||
|
|
||||||
const typeOptions = [
|
const typeOptions = [
|
||||||
{ value: 'SYSTEM', label: '系统角色' },
|
{ value: 'SYSTEM', label: '系统角色' },
|
||||||
{ value: 'PROJECT', label: '项目角色' },
|
{ value: 'PROJECT', label: '项目角色' },
|
||||||
|
|
@ -44,9 +63,9 @@ const typeOptions = [
|
||||||
]
|
]
|
||||||
|
|
||||||
const dataScopeOptions = [
|
const dataScopeOptions = [
|
||||||
{ value: 'ALL', label: '全部数据', color: 'red' },
|
{ value: 'ALL', label: '全部数据' },
|
||||||
{ value: 'PROJECT', label: '本项目数据', color: 'blue' },
|
{ value: 'PROJECT', label: '本项目数据' },
|
||||||
{ value: 'SELF', label: '本人数据', color: 'green' }
|
{ value: 'SELF', label: '本人数据' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const fetchRoles = async () => {
|
const fetchRoles = async () => {
|
||||||
|
|
@ -54,6 +73,7 @@ const fetchRoles = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getRoles()
|
const res = await getRoles()
|
||||||
roles.value = res.data.data || []
|
roles.value = res.data.data || []
|
||||||
|
pagination.total = roles.value.length
|
||||||
} catch {
|
} catch {
|
||||||
message.error('获取角色列表失败')
|
message.error('获取角色列表失败')
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -61,6 +81,24 @@ const fetchRoles = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
pagination.current = 1
|
||||||
|
fetchRoles()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
searchKeyword.value = ''
|
||||||
|
searchStatus.value = ''
|
||||||
|
pagination.current = 1
|
||||||
|
fetchRoles()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePageChange = (page: number, pageSize: number) => {
|
||||||
|
pagination.current = page
|
||||||
|
pagination.pageSize = pageSize
|
||||||
|
fetchRoles()
|
||||||
|
}
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
drawerTitle.value = '新增角色'
|
drawerTitle.value = '新增角色'
|
||||||
formState.value = {
|
formState.value = {
|
||||||
|
|
@ -126,49 +164,24 @@ const handleClose = () => {
|
||||||
drawerVisible.value = false
|
drawerVisible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStatusColor = (status: string) => {
|
|
||||||
const map: Record<string, string> = {
|
|
||||||
ACTIVE: 'success',
|
|
||||||
DISABLED: 'error'
|
|
||||||
}
|
|
||||||
return map[status] || 'default'
|
|
||||||
}
|
|
||||||
|
|
||||||
const getStatusLabel = (status: string) => {
|
|
||||||
const map: Record<string, string> = {
|
|
||||||
ACTIVE: '正常',
|
|
||||||
DISABLED: '禁用'
|
|
||||||
}
|
|
||||||
return map[status] || status
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTypeLabel = (type: string) => {
|
const getTypeLabel = (type: string) => {
|
||||||
const map: Record<string, string> = {
|
const map: Record<string, string> = {
|
||||||
SYSTEM: 'SYSTEM',
|
SYSTEM: '系统角色',
|
||||||
PROJECT: 'PROJECT',
|
PROJECT: '项目角色',
|
||||||
DEPARTMENT: 'DEPARTMENT'
|
DEPARTMENT: '部门级'
|
||||||
}
|
}
|
||||||
return map[type] || type
|
return map[type] || type
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDataScopeLabel = (dataScope: string) => {
|
const getDataScopeLabel = (dataScope: string) => {
|
||||||
const map: Record<string, string> = {
|
const map: Record<string, string> = {
|
||||||
ALL: 'ALL',
|
ALL: '全部数据',
|
||||||
PROJECT: 'PROJECT',
|
PROJECT: '本项目数据',
|
||||||
SELF: 'SELF'
|
SELF: '本人数据'
|
||||||
}
|
}
|
||||||
return map[dataScope] || dataScope
|
return map[dataScope] || dataScope
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDataScopeColor = (dataScope: string) => {
|
|
||||||
const map: Record<string, string> = {
|
|
||||||
ALL: 'red',
|
|
||||||
PROJECT: 'blue',
|
|
||||||
SELF: 'green'
|
|
||||||
}
|
|
||||||
return map[dataScope] || 'default'
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(fetchRoles)
|
onMounted(fetchRoles)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -184,49 +197,54 @@ onMounted(fetchRoles)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 筛选区 -->
|
||||||
|
<div class="filter-bar">
|
||||||
|
<a-space>
|
||||||
|
<a-input
|
||||||
|
v-model:value="searchKeyword"
|
||||||
|
placeholder="搜索角色编码或名称"
|
||||||
|
style="width: 240px"
|
||||||
|
allow-clear
|
||||||
|
@press-enter="handleSearch"
|
||||||
|
/>
|
||||||
|
<a-button type="primary" @click="handleSearch">查询</a-button>
|
||||||
|
<a-button @click="handleReset">重置</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 表格 -->
|
<!-- 表格 -->
|
||||||
<div class="table-card">
|
<div class="table-card">
|
||||||
<Table
|
<TableToolbar @refresh="fetchRoles" />
|
||||||
|
|
||||||
|
<a-table
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="roles"
|
:data-source="paginatedData"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:row-key="(record: Role) => record.id"
|
:row-key="(record: Role) => record.id"
|
||||||
:pagination="{ pageSize: 10, showSizeChanger: true, showTotal: (total: number) => `共 ${total} 条` }"
|
:pagination="false"
|
||||||
>
|
>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'type'">
|
<template v-if="column.key === 'type'">
|
||||||
<a-tag>{{ getTypeLabel(record.type) }}</a-tag>
|
<a-tag>{{ getTypeLabel(record.type) }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'dataScope'">
|
<template v-else-if="column.key === 'dataScope'">
|
||||||
<a-tag :color="getDataScopeColor(record.dataScope)">
|
<a-tag>{{ getDataScopeLabel(record.dataScope) }}</a-tag>
|
||||||
{{ getDataScopeLabel(record.dataScope) }}
|
|
||||||
</a-tag>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'status'">
|
<template v-else-if="column.key === 'status'">
|
||||||
<a-tag :color="getStatusColor(record.status)">
|
<StatusTag :status="record.status" />
|
||||||
{{ getStatusLabel(record.status) }}
|
|
||||||
</a-tag>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'action'">
|
<template v-else-if="column.key === 'action'">
|
||||||
<Space>
|
<TableActions @edit="handleEdit(record)" @delete="handleDelete(record.id)" />
|
||||||
<Button type="link" size="small" @click="handleEdit(record)">
|
|
||||||
<EditOutlined /> 编辑
|
|
||||||
</Button>
|
|
||||||
<Popconfirm
|
|
||||||
title="确认删除"
|
|
||||||
description="删除后不可恢复,是否继续?"
|
|
||||||
ok-text="确认"
|
|
||||||
cancel-text="取消"
|
|
||||||
@confirm="handleDelete(record.id)"
|
|
||||||
>
|
|
||||||
<Button type="link" danger size="small">
|
|
||||||
<DeleteOutlined /> 删除
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>
|
|
||||||
</Space>
|
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</Table>
|
</a-table>
|
||||||
|
|
||||||
|
<Pagination
|
||||||
|
v-model:current="pagination.current"
|
||||||
|
v-model:pageSize="pagination.pageSize"
|
||||||
|
:total="pagination.total"
|
||||||
|
@change="handlePageChange"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 抽屉 -->
|
<!-- 抽屉 -->
|
||||||
|
|
@ -262,7 +280,7 @@ onMounted(fetchRoles)
|
||||||
<Select v-model:value="formState.dataScope" :options="dataScopeOptions" placeholder="请选择数据权限" />
|
<Select v-model:value="formState.dataScope" :options="dataScopeOptions" placeholder="请选择数据权限" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="状态" name="status">
|
<Form.Item label="状态" name="status">
|
||||||
<Select v-model:value="formState.status" :options="statusOptions" />
|
<Select v-model:value="formState.status" :options="[{ value: 'ACTIVE', label: '正常' }, { value: 'DISABLED', label: '禁用' }]" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, reactive } from 'vue'
|
import { ref, onMounted, reactive, computed } from 'vue'
|
||||||
import { Button, Drawer, Form, Space, message } from 'ant-design-vue'
|
import { Button, Drawer, Form, Space, message } from 'ant-design-vue'
|
||||||
import { PlusOutlined } from '@ant-design/icons-vue'
|
import { PlusOutlined } from '@ant-design/icons-vue'
|
||||||
import { getUsers, createUser, updateUser, deleteUser } from '@/api/user'
|
import { getUsers, createUser, updateUser, deleteUser } from '@/api/user'
|
||||||
|
|
@ -19,12 +19,12 @@ import {
|
||||||
|
|
||||||
// 表格列定义
|
// 表格列定义
|
||||||
const columns = [
|
const columns = [
|
||||||
{ title: '用户名', dataIndex: 'username', key: 'username', width: 120 },
|
{ title: '用户名', dataIndex: 'username', key: 'username', width: 100 },
|
||||||
{ title: '姓名', dataIndex: 'realName', key: 'realName', width: 100 },
|
{ title: '姓名', dataIndex: 'realName', key: 'realName', width: 80 },
|
||||||
{ title: '手机', dataIndex: 'phone', key: 'phone', width: 120 },
|
{ title: '手机', dataIndex: 'phone', key: 'phone', width: 110 },
|
||||||
{ title: '邮箱', dataIndex: 'email', key: 'email', ellipsis: true },
|
{ title: '邮箱', dataIndex: 'email', key: 'email', width: 180, ellipsis: true },
|
||||||
{ title: '状态', dataIndex: 'status', key: 'status', width: 80 },
|
{ title: '状态', dataIndex: 'status', key: 'status', width: 70 },
|
||||||
{ title: '操作', key: 'action', width: 120, fixed: 'right' as const }
|
{ title: '操作', key: 'action', width: 140, fixed: 'right' as const }
|
||||||
]
|
]
|
||||||
|
|
||||||
// 数据
|
// 数据
|
||||||
|
|
@ -46,6 +46,13 @@ const pagination = reactive({
|
||||||
total: 0
|
total: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 分页后的数据
|
||||||
|
const paginatedData = computed(() => {
|
||||||
|
const start = (pagination.current - 1) * pagination.pageSize
|
||||||
|
const end = start + pagination.pageSize
|
||||||
|
return users.value.slice(start, end)
|
||||||
|
})
|
||||||
|
|
||||||
// 表单
|
// 表单
|
||||||
const formState = ref({
|
const formState = ref({
|
||||||
id: '',
|
id: '',
|
||||||
|
|
@ -172,16 +179,18 @@ onMounted(fetchUsers)
|
||||||
<template>
|
<template>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<!-- 页面标题 -->
|
<!-- 页面标题 -->
|
||||||
<PageHeader title="用户管理">
|
<div class="page-header">
|
||||||
<template #extra>
|
<h2 class="page-title">用户管理</h2>
|
||||||
|
<div class="page-header-actions">
|
||||||
<Button type="primary" @click="handleAdd">
|
<Button type="primary" @click="handleAdd">
|
||||||
<PlusOutlined /> 新增用户
|
<PlusOutlined /> 新增用户
|
||||||
</Button>
|
</Button>
|
||||||
</template>
|
</div>
|
||||||
</PageHeader>
|
</div>
|
||||||
|
|
||||||
<!-- 筛选区 -->
|
<!-- 筛选区 -->
|
||||||
<FilterBar @search="handleSearch" @reset="handleReset">
|
<div class="filter-bar">
|
||||||
|
<a-space>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="searchKeyword"
|
v-model:value="searchKeyword"
|
||||||
placeholder="搜索用户名/姓名/手机"
|
placeholder="搜索用户名/姓名/手机"
|
||||||
|
|
@ -190,15 +199,18 @@ onMounted(fetchUsers)
|
||||||
@pressEnter="handleSearch"
|
@pressEnter="handleSearch"
|
||||||
/>
|
/>
|
||||||
<StatusSelect v-model="searchStatus" placeholder="全部状态" />
|
<StatusSelect v-model="searchStatus" placeholder="全部状态" />
|
||||||
</FilterBar>
|
<a-button type="primary" @click="handleSearch">查询</a-button>
|
||||||
|
<a-button @click="handleReset">重置</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 表格 -->
|
<!-- 表格 -->
|
||||||
<TableCard>
|
<div class="table-card">
|
||||||
<TableToolbar @refresh="fetchUsers" />
|
<TableToolbar @refresh="fetchUsers" />
|
||||||
|
|
||||||
<a-table
|
<a-table
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="users"
|
:data-source="paginatedData"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:row-key="(record: User) => record.id"
|
:row-key="(record: User) => record.id"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
|
|
@ -219,7 +231,7 @@ onMounted(fetchUsers)
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
@change="handlePageChange"
|
@change="handlePageChange"
|
||||||
/>
|
/>
|
||||||
</TableCard>
|
</div>
|
||||||
|
|
||||||
<!-- 抽屉 -->
|
<!-- 抽屉 -->
|
||||||
<Drawer
|
<Drawer
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
const puppeteer = require('puppeteer');
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
console.log('启动浏览器...');
|
||||||
|
const browser = await puppeteer.launch({
|
||||||
|
headless: true,
|
||||||
|
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||||||
|
});
|
||||||
|
|
||||||
|
const page = await browser.newPage();
|
||||||
|
|
||||||
|
page.on('console', msg => console.log('Browser console:', msg.text()));
|
||||||
|
page.on('pageerror', error => console.log('Browser error:', error.message));
|
||||||
|
|
||||||
|
console.log('导航到登录页...');
|
||||||
|
await page.goto('http://127.0.0.1:5175/login', { waitUntil: 'networkidle0' });
|
||||||
|
|
||||||
|
await new Promise(r => setTimeout(r, 2000));
|
||||||
|
|
||||||
|
const title = await page.title();
|
||||||
|
console.log('页面标题:', title);
|
||||||
|
|
||||||
|
await page.screenshot({ path: '/tmp/login-page.png' });
|
||||||
|
console.log('已截图: /tmp/login-page.png');
|
||||||
|
|
||||||
|
const usernameInput = await page.$('input');
|
||||||
|
console.log('找到输入框:', usernameInput ? '是' : '否');
|
||||||
|
|
||||||
|
if (usernameInput) {
|
||||||
|
const inputs = await page.$$('input');
|
||||||
|
console.log('输入框数量:', inputs.length);
|
||||||
|
|
||||||
|
if (inputs.length >= 2) {
|
||||||
|
await inputs[0].type('admin');
|
||||||
|
await inputs[1].type('Admin@123');
|
||||||
|
|
||||||
|
await page.screenshot({ path: '/tmp/before-login.png' });
|
||||||
|
|
||||||
|
const buttons = await page.$$('button');
|
||||||
|
console.log('按钮数量:', buttons.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < buttons.length; i++) {
|
||||||
|
const text = await buttons[i].textContent();
|
||||||
|
console.log(`按钮 ${i}:`, text.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttons.length > 0) {
|
||||||
|
await buttons[0].click();
|
||||||
|
await new Promise(r => setTimeout(r, 3000));
|
||||||
|
|
||||||
|
const url = page.url();
|
||||||
|
console.log('登录后URL:', url);
|
||||||
|
|
||||||
|
await page.screenshot({ path: '/tmp/after-login.png' });
|
||||||
|
console.log('已截图: /tmp/after-login.png');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await browser.close();
|
||||||
|
console.log('测试完成');
|
||||||
|
})();
|
||||||
Loading…
Reference in New Issue