项目开始

  • 项目初始 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>

后台接口

参考资料