first commit: 初始化1818-admin项目

This commit is contained in:
2026-02-13 17:47:58 +08:00
commit 67091b730d
59 changed files with 14102 additions and 0 deletions

280
src/views/system/admin.vue Normal file
View File

@@ -0,0 +1,280 @@
<template>
<div class="page-container">
<!-- 总览卡片 -->
<a-row :gutter="[16, 16]" class="stat-row">
<a-col :xs="12" :sm="6">
<div class="mini-stat-card">
<div class="stat-content">
<span class="stat-label">管理员总数</span>
<span class="stat-num">{{ stats.total }}</span>
</div>
<UserOutlined class="stat-bg-icon" />
</div>
</a-col>
<a-col :xs="12" :sm="6">
<div class="mini-stat-card">
<div class="stat-content">
<span class="stat-label">正常状态</span>
<span class="stat-num green">{{ stats.active }}</span>
</div>
<CheckCircleOutlined class="stat-bg-icon" />
</div>
</a-col>
<a-col :xs="12" :sm="6">
<div class="mini-stat-card">
<div class="stat-content">
<span class="stat-label">禁用状态</span>
<span class="stat-num red">{{ stats.disabled }}</span>
</div>
<StopOutlined class="stat-bg-icon" />
</div>
</a-col>
<a-col :xs="12" :sm="6">
<div class="mini-stat-card">
<div class="stat-content">
<span class="stat-label">角色数量</span>
<span class="stat-num blue">{{ allRoles.length }}</span>
</div>
<SafetyOutlined class="stat-bg-icon" />
</div>
</a-col>
</a-row>
<a-card :bordered="false">
<!-- 搜索栏 -->
<div class="filter-bar">
<div class="filter-left">
<span class="filter-result"> <b>{{ pagination.total }}</b> 条记录</span>
</div>
<div class="filter-right">
<a-input
v-model:value="searchForm.keyword"
placeholder="搜索用户名/姓名"
allow-clear
style="width: 180px"
@input="handleSearch"
>
<template #prefix><SearchOutlined /></template>
</a-input>
<a-select
v-model:value="searchForm.status"
placeholder="状态"
allow-clear
style="width: 100px"
@change="handleSearch"
>
<a-select-option :value="1">正常</a-select-option>
<a-select-option :value="0">禁用</a-select-option>
</a-select>
<a-button @click="handleReset">重置</a-button>
<a-button type="primary" v-permission="'system:admin:add'" @click="handleAdd">
<PlusOutlined /> 新增管理员
</a-button>
</div>
</div>
<a-table
:columns="columns"
:data-source="tableData"
:loading="loading"
:pagination="pagination"
row-key="id"
:scroll="{ x: 1000 }"
@change="handleTableChange"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'roles'">
<a-tag v-for="role in record.roles" :key="role.id" color="blue" size="small">
{{ role.name }}
</a-tag>
<span v-if="!record.roles?.length" style="color: #999">-</span>
</template>
<template v-if="column.key === 'status'">
<a-badge :status="record.status === 1 ? 'success' : 'error'" :text="record.status === 1 ? '正常' : '禁用'" />
</template>
<template v-if="column.key === 'action'">
<a-space :size="0" split>
<a-button type="link" size="small" v-permission="'system:admin:edit'" @click="handleEdit(record)">编辑</a-button>
<a-popconfirm title="确定删除该管理员吗?" @confirm="handleDelete(record)">
<a-button type="link" size="small" danger v-permission="'system:admin:delete'">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
</a-card>
<!-- 新增/编辑弹窗 -->
<a-modal v-model:open="modalVisible" :title="isEdit ? '编辑管理员' : '新增管理员'" @ok="handleSubmit" :confirm-loading="submitting" width="560px">
<a-form :model="formData" :label-col="{ span: 5 }" style="margin-top: 16px">
<a-form-item label="用户名" required>
<a-input v-model:value="formData.username" :disabled="isEdit" placeholder="请输入用户名" />
</a-form-item>
<a-form-item v-if="!isEdit" label="密码" required>
<a-input-password v-model:value="formData.password" placeholder="请输入密码" />
</a-form-item>
<a-form-item v-if="isEdit" label="修改密码">
<a-input-password v-model:value="formData.password" placeholder="留空则不修改密码" />
</a-form-item>
<a-form-item label="真实姓名">
<a-input v-model:value="formData.realName" placeholder="请输入真实姓名" />
</a-form-item>
<a-form-item label="手机号">
<a-input v-model:value="formData.phone" placeholder="请输入手机号" />
</a-form-item>
<a-form-item label="邮箱">
<a-input v-model:value="formData.email" placeholder="请输入邮箱" />
</a-form-item>
<a-form-item label="角色" required>
<a-select v-model:value="formData.roleIds" mode="multiple" placeholder="请选择角色">
<a-select-option v-for="role in allRoles" :key="role.id" :value="role.id">{{ role.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="状态">
<a-radio-group v-model:value="formData.status">
<a-radio :value="1">正常</a-radio>
<a-radio :value="0">禁用</a-radio>
</a-radio-group>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import { UserOutlined, CheckCircleOutlined, StopOutlined, SafetyOutlined, SearchOutlined, PlusOutlined } from '@ant-design/icons-vue'
import { getAdminList, createAdmin, updateAdmin, deleteAdmin, getAllRoles } from '@/api/system'
import { debounce } from 'lodash-es'
const loading = ref(false)
const tableData = ref([])
const allRoles = ref([])
const searchForm = reactive({ keyword: '', status: undefined })
const pagination = reactive({ current: 1, pageSize: 10, total: 0, showSizeChanger: true, showQuickJumper: true })
const stats = ref({ total: 0, active: 0, disabled: 0 })
const modalVisible = ref(false)
const submitting = ref(false)
const isEdit = ref(false)
const currentId = ref(null)
const formData = reactive({
username: '', password: '', realName: '', phone: '', email: '', roleIds: [], status: 1
})
const columns = [
{ title: 'ID', dataIndex: 'id', width: 70 },
{ title: '用户名', dataIndex: 'username', width: 120 },
{ title: '真实姓名', dataIndex: 'realName', width: 100 },
{ title: '邮箱', dataIndex: 'email', ellipsis: true },
{ title: '手机号', dataIndex: 'phone', width: 120 },
{ title: '角色', key: 'roles', width: 150 },
{ title: '状态', key: 'status', width: 80 },
{ title: '最后登录', dataIndex: 'lastLoginTime', width: 160 },
{ title: '操作', key: 'action', width: 180, fixed: 'right' }
]
const fetchRoles = async () => {
try {
const data = await getAllRoles()
allRoles.value = data || []
} catch (error) {}
}
const fetchData = async () => {
loading.value = true
try {
const data = await getAdminList({
...searchForm,
page: pagination.current,
pageSize: pagination.pageSize
})
tableData.value = data?.list || []
pagination.total = data?.total || 0
if (data?.stats) stats.value = data.stats
} catch (error) {}
finally { loading.value = false }
}
const handleSearch = debounce(() => {
pagination.current = 1
fetchData()
}, 300)
const handleReset = () => {
Object.assign(searchForm, { keyword: '', status: undefined })
pagination.current = 1
fetchData()
}
const handleTableChange = pag => {
pagination.current = pag.current
pagination.pageSize = pag.pageSize
fetchData()
}
const resetForm = () => {
Object.assign(formData, { username: '', password: '', realName: '', phone: '', email: '', roleIds: [], status: 1 })
}
const handleAdd = () => {
isEdit.value = false
currentId.value = null
resetForm()
modalVisible.value = true
}
const handleEdit = record => {
isEdit.value = true
currentId.value = record.id
Object.assign(formData, {
username: record.username,
password: '', // 编辑时密码留空
realName: record.realName,
phone: record.phone,
email: record.email,
roleIds: record.roles?.map(r => r.id) || [],
status: record.status
})
modalVisible.value = true
}
const handleSubmit = async () => {
if (!formData.username) { message.warning('请输入用户名'); return }
if (!isEdit.value && !formData.password) { message.warning('请输入密码'); return }
if (!formData.roleIds?.length) { message.warning('请选择角色'); return }
submitting.value = true
try {
if (isEdit.value) {
await updateAdmin(currentId.value, formData)
message.success('更新成功')
} else {
await createAdmin(formData)
message.success('创建成功')
}
modalVisible.value = false
fetchData()
} catch (error) {}
finally { submitting.value = false }
}
const handleDelete = async record => {
try {
await deleteAdmin(record.id)
message.success('删除成功')
fetchData()
} catch (error) {}
}
onMounted(() => {
fetchRoles()
fetchData()
})
</script>
<style lang="less" scoped>
@import '@/styles/page-common.less';
</style>