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:
chiguyong 2026-03-23 09:52:13 +08:00
parent ea1eabafb0
commit f111f4a8d5
7 changed files with 373 additions and 262 deletions

View File

@ -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')
} }

View File

@ -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,16 +35,18 @@ const handleChange = (page: number, size: number) => {
<template> <template>
<div class="table-pagination"> <div class="table-pagination">
<a-pagination <a-config-provider :locale="zhCN">
v-model:current="current" <a-pagination
:total="total" v-model:current="current"
:page-size="pageSize" :total="total"
:show-size-changer="true" :page-size="pageSize"
:show-quick-jumper="true" :show-size-changer="true"
:page-size-options="pageSizes.map(String)" :show-quick-jumper="true"
:show-total="(total: number) => `共 ${total} 条`" :page-size-options="pageSizes.map(String)"
@change="handleChange" :show-total="(total: number) => `共 ${total} 条`"
/> @change="handleChange"
/>
</a-config-provider>
</div> </div>
</template> </template>

View File

@ -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>

View File

@ -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>
<Table
:columns="columns"
:data-source="permissions"
:loading="loading"
:row-key="(record: Permission) => record.id"
:pagination="pagination"
: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 v-if="column.key === 'type'">
<Tag :color="getTypeColor(record.type)">
{{ getTypeLabel(record.type) }}
</Tag>
</template>
<template v-else-if="column.key === 'action'">
<a-space>
<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>
</Table>
</div> </div>
<!-- 新建/编辑模态框 --> <!-- 表格 -->
<a-modal <div class="table-card">
v-model:open="modalVisible" <TableToolbar @refresh="fetchPermissions" />
:title="modalTitle"
:width="500" <a-table
@ok="handleSubmit" :columns="columns"
@cancel="modalVisible = false" :data-source="paginatedData"
> :loading="loading"
<a-form :row-key="(record: Permission) => record.id"
:model="formState" :pagination="false"
:rules="formRules"
layout="vertical"
class="permission-form"
> >
<a-form-item label="权限代码" name="code"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'type'">
<a-tag :color="getTypeColor(record.type)">
{{ getTypeLabel(record.type) }}
</a-tag>
</template>
<template v-else-if="column.key === 'action'">
<TableActions @edit="handleEdit(record)" @delete="handleDelete(record.id)" />
</template>
</template>
</a-table>
<Pagination
v-model:current="pagination.current"
v-model:pageSize="pagination.pageSize"
:total="pagination.total"
@change="handlePageChange"
/>
</div>
<!-- 抽屉 -->
<Drawer
v-model:open="drawerVisible"
:title="drawerTitle"
width="480px"
:footer-style="{ textAlign: 'right' }"
@close="handleClose"
>
<Form
ref="formRef"
:model="formState"
layout="vertical"
:rules="{
code: [{ required: true, message: '请输入权限代码' }],
name: [{ required: true, message: '请输入权限名称' }],
type: [{ required: true, message: '请选择类型' }]
}"
>
<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>

View File

@ -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>

View File

@ -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,33 +179,38 @@ 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-input <a-space>
v-model:value="searchKeyword" <a-input
placeholder="搜索用户名/姓名/手机" v-model:value="searchKeyword"
style="width: 240px" placeholder="搜索用户名/姓名/手机"
allow-clear style="width: 240px"
@pressEnter="handleSearch" allow-clear
/> @pressEnter="handleSearch"
<StatusSelect v-model="searchStatus" placeholder="全部状态" /> />
</FilterBar> <StatusSelect v-model="searchStatus" placeholder="全部状态" />
<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

62
test-login.cjs Normal file
View File

@ -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('测试完成');
})();