项目开始
- 项目初始
yarn create vite ts-antd-vite --template vue-ts
- 项目依赖
package.json
{
"name": "ts-antd-vite",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@ant-design/icons-vue": "^6.1.0",
"ant-design-vue": "3.3.0-beta.3",
"axios": "^1.2.0",
"vue": "^3.2.41",
"vue-router": "^4.0.13",
"vuex": "^4.0.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "^3.2.0",
"typescript": "^4.6.4",
"vite": "^3.2.3",
"vue-tsc": "^1.0.9"
}
}
项目准备
- 路由配置
- 请求拦截
- 登录界面
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import Login from '../pages/login/index.vue'
import AppLayout from '../components/AppLayout.vue'
import Home from '../pages/Home.vue'
// 路由配置
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/admin',
name: 'Admin',
component: AppLayout,
redirect: '/admin/index',
children: [
{
path: 'index',
name: 'AdminIndex',
component: () => import('../pages/admin/index.vue'),
},
{
path: 'home',
name: 'AdminHome',
component: () => import('../pages/Home.vue'),
},
],
},
{
path: '/login',
name: 'Login',
component: Login,
},
]
const router = createRouter({
history: createWebHashHistory(),
routes,
})
// 路由守卫
// 白名单
const whiteList = ['/login']
router.beforeEach((to, from, next) => {
// 获取token
const token = localStorage.getItem('token')
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
// 如果token存在,直接放行
if (token) {
next()
} else {
// 如果token不存在,判断是否是登录页面,如果是登录页面,直接放行
if (to.path === '/login') {
next()
} else {
// 如果不是登录页面,跳转到登录页面
next('/login')
}
}
}
})
export default router
<template>
<a-form
ref="loginForm"
:rules="rules"
:model="admin"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 14 }"
>
<a-form-item label="用户名" name="name">
<a-input v-model:value="admin.name" placeholder="Username" />
</a-form-item>
<a-form-item label="密码" name="password">
<a-input v-model:value="admin.password" placeholder="Password" />
</a-form-item>
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary" @click="doLogin">Login</a-button>
</a-form-item>
</a-form>
</template>
<script setup lang="ts">
import { message } from 'ant-design-vue'
import { ValidateErrorEntity } from 'ant-design-vue/lib/form/interface'
import { reactive, ref, toRaw } from 'vue'
import { useRouter } from 'vue-router'
import { login } from '../../api/login'
interface IAdmin {
name: string
password: string
}
const admin: IAdmin = reactive({
name: '',
password: '',
})
// 校验规则
const rules = {
name: [
{
required: true,
message: '请输入用户名',
trigger: 'blur',
},
],
password: [
{
required: true,
message: '请输入密码',
trigger: 'blur',
},
],
}
const loginForm = ref()
// 登录方法
const router = useRouter()
const doLogin = () => {
loginForm.value
.validate()
.then(() => {
console.log('values', admin, toRaw(admin))
login(admin.name, admin.password)
.then((res) => {
const { code, data, msg } = res.data
if (code === 200) {
localStorage.setItem('token', data)
message.success('登录成功')
router.push('/admin/index')
} else {
message.error(msg)
}
})
.catch((err) => {
console.log('err', err)
})
})
.catch((error: ValidateErrorEntity<IAdmin>) => {
console.log('error', error)
})
}
</script>
<style scoped></style>
import axios from 'axios'
const request = axios.create({
timeout: 5000,
})
// 请求拦截
request.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = token
}
return config
},
(error) => {
return Promise.reject(error)
}
)
export default request
import request from '../utils/request'
// 登录请求
const login = (name: string, password: string) => {
return request({
url: '/admin/login',
method: 'post',
data: {
name,
password,
},
})
}
export { login }
公共布局
<template>
<a-layout>
<a-layout-header class="header">
<div class="logo" />
<a-menu
v-model:selectedKeys="selectedKeys1"
theme="dark"
mode="horizontal"
:style="{ lineHeight: '64px' }"
>
<a-menu-item key="1">
<router-link to="/">首页</router-link>
</a-menu-item>
<a-menu-item key="2">nav 2</a-menu-item>
<a-menu-item key="3">nav 3</a-menu-item>
</a-menu>
</a-layout-header>
<a-layout>
<a-layout-sider width="200" style="background: #fff">
<a-menu
v-model:selectedKeys="selectedKeys2"
v-model:openKeys="openKeys"
mode="inline"
:style="{ height: '100%', borderRight: 0 }"
router
>
<a-sub-menu key="sub1">
<template #title>
<span>
<user-outlined />
管理员管理
</span>
</template>
<a-menu-item key="2">
<router-link to="/admin/home">首页</router-link>
</a-menu-item>
<a-menu-item key="1">
<router-link to="/admin/index">管理员列表</router-link>
</a-menu-item>
</a-sub-menu>
</a-menu>
</a-layout-sider>
<a-layout style="padding: 0 24px 24px">
<a-breadcrumb style="margin: 16px 0">
<a-breadcrumb-item>Home</a-breadcrumb-item>
<a-breadcrumb-item>List</a-breadcrumb-item>
<a-breadcrumb-item>App</a-breadcrumb-item>
</a-breadcrumb>
<a-layout-content
:style="{
background: '#fff',
padding: '24px',
margin: 0,
minHeight: '280px',
}"
>
<router-view></router-view>
</a-layout-content>
</a-layout>
</a-layout>
</a-layout>
</template>
<script lang="ts">
import {
UserOutlined,
LaptopOutlined,
NotificationOutlined,
} from '@ant-design/icons-vue'
import { defineComponent, ref } from 'vue'
export default defineComponent({
components: {
UserOutlined,
LaptopOutlined,
NotificationOutlined,
},
setup() {
return {
selectedKeys1: ref<string[]>(['2']),
selectedKeys2: ref<string[]>(['1']),
collapsed: ref<boolean>(false),
openKeys: ref<string[]>(['sub1']),
}
},
})
</script>
<style>
#components-layout-demo-top-side-2 .logo {
float: left;
width: 120px;
height: 31px;
margin: 16px 24px 16px 0;
background: rgba(255, 255, 255, 0.3);
}
.ant-row-rtl #components-layout-demo-top-side-2 .logo {
float: right;
margin: 16px 0 16px 24px;
}
.site-layout-background {
background: #fff;
}
</style>
管理员列表实现
- 创建管理员列表页面
- 管理员增删改查接口
<template>
<a-button type="primary" @click="showAddAdminModal">添加管理员</a-button>
<a-table
:loading="table.loading"
:dataSource="table.data"
rowKey="id"
:pagination="table.pagination"
@change="handleTableChange"
>
<a-table-column title="ID" dataIndex="id" key="id" />
<a-table-column title="用户名" dataIndex="name" key="name" />
<a-table-column title="手机号" dataIndex="mobile" key="mobile" />
<a-table-column title="邮箱" dataIndex="email" key="email" />
<a-table-column #default="{ text, record }">
<a-space>
<a-button type="primary" @click="showEditAdminModal(record)"
>管理</a-button
>
<DeleteAdminVue :admin="record" @deleteAdmin="deleteAdmin" />
</a-space>
</a-table-column>
</a-table>
<EditAdmin
:admin="table.admin"
:visible="table.isShowEditAdminModal"
@cancelEditAdmin="cancelEditAdmin"
/>
<AddAdmin
:visible="table.isShowAddAdminModal"
@cancelAddAdmin="cancelAddAdmin"
/>
</template>
<script setup lang="ts">
import DeleteAdminVue from './DeleteAdmin.vue'
import EditAdmin from './EditAdmin.vue'
import AddAdmin from './AddAdmin.vue'
import { onMounted, reactive } from 'vue'
import { getAdminList, deleteAdminById } from '../../api/admin'
export interface IAdmin {
id?: number
password?: string
name: string
email: string
mobile: string
}
interface IPagination {
current: number
pageSize: number
total: number
}
interface ITable {
loading: boolean
data: IAdmin[]
isShowEditAdminModal: boolean
isShowAddAdminModal: boolean
admin: IAdmin
pagination: IPagination
}
const table: ITable = reactive({
loading: false,
data: [],
isShowEditAdminModal: false,
isShowAddAdminModal: false,
admin: {} as IAdmin,
pagination: {
current: 1,
pageSize: 2,
total: 0,
},
})
// 获取管理员列表
const getAdminListPage = (page: number = 1) => {
table.loading = true
getAdminList(page, table.pagination.pageSize)
.then((res) => {
const { code, data } = res.data
console.log('data', data)
if (code === 200) {
table.data = data.data
table.pagination.current = data.currentPage
table.pagination.total = data.total
}
})
.catch((err) => {
console.log('err', err)
})
.finally(() => {
table.loading = false
})
}
onMounted(() => {
getAdminListPage()
})
// 分页、排序、筛选变化时触发
const handleTableChange = (pagination: IPagination) => {
getAdminListPage(pagination.current)
}
// 编辑管理员
const showEditAdminModal = (admin: IAdmin) => {
table.isShowEditAdminModal = true
table.admin = admin
}
// 取消编辑管理员
const cancelEditAdmin = (admin: IAdmin) => {
if (admin) {
table.data = table.data.map((item) => {
if (item.id === admin.id) {
return admin
}
return item
})
}
table.isShowEditAdminModal = false
}
// 添加管理员
const showAddAdminModal = () => {
table.isShowAddAdminModal = true
}
// 取消添加管理员
const cancelAddAdmin = (admin: IAdmin) => {
if (admin) {
table.data.unshift(admin)
}
table.isShowAddAdminModal = false
}
// 删除管理员
const deleteAdmin = (record: IAdmin) => {
table.data = table.data.filter((item) => item.id !== record.id)
}
</script>
<style scoped>
.wrapper-btn {
display: flex;
justify-content: space-evenly;
}
</style>
// 管理员列表
import request from '../utils/request'
export const getAdminList = (page: number = 1, limit: number = 15) => {
return request({
url: '/admin/adminList',
method: 'get',
params: {
page,
limit,
},
})
}
// 添加管理员
export const addAdmin = (data: any) => {
return request({
url: '/admin/addAdmin',
method: 'post',
data,
})
}
// 更新管理员
export const updateAdminById = (adminId: number, admin: any) => {
return request({
url: '/admin/editAdmin',
method: 'post',
data: {
adminId,
admin,
},
})
}
// 删除管理员
export const deleteAdminById = (adminId: number) => {
return request({
url: '/admin/deleteAdmin',
method: 'post',
data: {
adminId,
},
})
}
删除管理员
<template>
<a-popconfirm title="删除管理员?" @confirm="confirm">
<a-button type="primary" danger>删除</a-button>
</a-popconfirm>
</template>
<script setup lang="ts">
import { PropType } from 'vue'
import { IAdmin } from './index.vue'
import { deleteAdminById } from '../../api/admin'
import { message } from 'ant-design-vue'
// 父组件传递的管理员信息
const props = defineProps({
admin: {
type: Object as PropType<IAdmin>,
required: true,
},
})
// 传递给父组件的删除管理员事件
const emit = defineEmits(['deleteAdmin'])
const confirm = () => {
deleteAdminById(props.admin.id as number)
.then((res: any) => {
console.log('res', res)
const { code, data } = res.data
if (code === 200) {
message.success('删除成功')
emit('deleteAdmin', props.admin)
}
})
.catch((err: any) => {
message.error('删除失败')
})
}
</script>
<style scoped></style>
::::
编辑管理员
<template>
<a-modal
:visible="visible"
title="编辑管理员"
@cancel="cancel"
@ok="updateAdmin"
>
<a-form
ref="formRef"
:rules="rules"
:model="formState.admin"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 18 }"
>
<a-form-item label="用户名" name="name">
<a-input v-model:value="formState.admin.name" />
</a-form-item>
<a-form-item label="手机号" name="mobile">
<a-input v-model:value="formState.admin.mobile" />
</a-form-item>
<a-form-item label="邮箱" name="email">
<a-input v-model:value="formState.admin.email" />
</a-form-item>
</a-form>
</a-modal>
</template>
<script setup lang="ts">
import { onUpdated, PropType, reactive, ref } from 'vue'
import { IAdmin } from './index.vue'
import { updateAdminById } from '../../api/admin'
import { message } from 'ant-design-vue'
const props = defineProps({
admin: {
type: Object as PropType<IAdmin>,
required: true,
},
visible: {
type: Boolean,
required: true,
},
})
const formState = reactive<{ admin: IAdmin }>({
admin: {} as IAdmin,
})
const formRef = ref()
onUpdated(() => {
formState.admin = { ...props.admin }
})
const emit = defineEmits(['cancelEditAdmin'])
// 取消
const cancel = () => {
emit('cancelEditAdmin')
}
// 数据校验
const rules = {
name: [
{
type: 'string',
required: true,
message: '请输入用户名',
trigger: 'blur',
},
],
mobile: [
{
type: 'string',
required: true,
message: '请输入手机号',
trigger: 'blur',
},
],
email: [
{
type: 'string',
required: true,
message: '请输入邮箱',
trigger: 'blur',
},
],
}
// 更新管理员
const updateAdmin = () => {
console.log('formState', formState.admin)
updateAdminById(formState.admin.id as number, formState.admin)
.then((res: any) => {
console.log('res', res)
const { code, data } = res.data
if (code === 200) {
message.success('更新成功')
emit('cancelEditAdmin', formState.admin)
}
})
.catch((err: any) => {
message.error('更新失败')
})
}
</script>
<style scoped></style>
添加管理员
<template>
<a-modal
:visible="visible"
title="添加管理员"
@ok="handleOk"
@cancel="cancel"
>
<a-form
ref="AddformRef"
:model="formState.admin"
:rules="rules"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 18 }"
>
<a-form-item label="用户名" name="name">
<a-input v-model:value="formState.admin.name" />
</a-form-item>
<a-form-item label="密码" name="password">
<a-input v-model:value="formState.admin.password" />
</a-form-item>
<a-form-item label="手机号" name="mobile">
<a-input v-model:value="formState.admin.mobile" />
</a-form-item>
<a-form-item label="邮箱" name="email">
<a-input v-model:value="formState.admin.email" />
</a-form-item>
</a-form>
</a-modal>
</template>
<script setup lang="ts">
import { PropType } from 'vue'
import { IAdmin } from './index.vue'
import { ref, reactive } from 'vue'
import { addAdmin } from '../../api/admin'
import { message } from 'ant-design-vue'
const props = defineProps({
visible: {
type: Boolean,
required: true,
},
})
const emit = defineEmits(['cancelAddAdmin'])
const AddformRef = ref()
const formState = reactive<{ admin: IAdmin }>({
admin: {
name: '',
password: '',
mobile: '',
email: '',
} as IAdmin,
})
//校验规则
// 数据校验
const rules = {
name: [
{
type: 'string',
required: true,
message: '请输入用户名',
trigger: 'blur',
},
],
password: [
{
type: 'string',
required: true,
message: '请输入密码',
trigger: 'blur',
},
],
mobile: [
{
type: 'string',
required: true,
message: '请输入手机号',
trigger: 'blur',
},
],
email: [
{
type: 'string',
required: true,
message: '请输入邮箱',
trigger: 'blur',
},
],
}
const handleOk = () => {
AddformRef.value
.validate()
.then(() => {
addAdmin(formState.admin).then((res) => {
const { code, data } = res.data
if (code === 200) {
message.success('添加成功')
emit('cancelAddAdmin', formState.admin)
} else {
message.error(data.msg)
}
})
})
.catch((error: any) => {
console.log(error)
})
}
const cancel = () => {
emit('cancelAddAdmin')
}
</script>
<style scoped></style>