Ai编程从零开始全栈开发一个后台管理系统之用户登录、权限控制、用户管理-前端部分(十二)

云风网
云风笔记
云风知识库

一、创建前端部分

1、vite初始化项目

npm create vite@latest admin-frontend – --template vue-ts

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、安装必要的依赖
npm install vue-router pinia axios element-plus @element-plus/icons-vue

安装完成后package.json如下:

{
  "name": "admin-frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc -b && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@element-plus/icons-vue": "^2.3.1",
    "axios": "^1.7.7",
    "element-plus": "^2.8.7",
    "pinia": "^2.2.6",
    "vue": "^3.5.12",
    "vue-router": "^4.4.5"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.1.4",
    "typescript": "~5.6.2",
    "vite": "^5.4.10",
    "vue-tsc": "^2.1.8"
  }
}
3、创建必要的文件结构
├── src/
│   ├── assets/
│   ├── api/
│   ├── components/
│   ├── layouts/
│   ├── router/
│   ├── store/
│   ├── types/
│   ├── utils/
│   └── views/
4、在main.ts引入router 、pinia、elementPlus
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import { createPinia } from 'pinia'
import router from './router' 
const app = createApp(App)
const pinia = createPinia()
app.use(pinia) 
app.use(router) 
app.use(ElementPlus) 
app.mount('#app')
5、在vite.config.ts进行别名alias和base地址配置
import { defineConfig } from 'vite'
import path from 'path'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
  plugins: [
    vue()
  ],
  base: '/',
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    }
  }
})

这里如果采用的是ts的写法,配置完可能不会生效,解决方案可以参考
ts兼容别名处理

6、新建router/index.ts管理项目路由
import { createRouter, createWebHistory } from 'vue-router'
import Layout from '@/layouts/BasicLayout.vue'
const routes = [
  {
    path: '/login',
    component: () => import('@/views/Login.vue'),
    meta: { requiresAuth: false }
  },
  {
    path: '/',
    component: Layout,
    meta: { requiresAuth: true },
    children: [
      {
        path: '',
        redirect: '/dashboard'
      },
      {
        path: 'dashboard',
        component: () => import('@/views/Dashboard.vue')
      },
      {
        path: 'users',
        component: () => import('@/views/user/UserList.vue')
      }
    ]
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

router.beforeEach((to, from, next) => {
  const token = localStorage.getItem('token')
  if (to.meta.requiresAuth && !token) {
    next('/login')
  } else {
    next()
  }
})

export default router 
7、新建公共组件layouts/BasicLayout.vue
<template>
  <div class="basic-layout">
    <header class="header">
      <h1 class="logo">My Application</h1>
      <nav class="nav">
        <router-link
          to="/dashboard"
          class="nav-item"
          :class="{ active: isActive('/dashboard') }"
          @click="setActive('/dashboard')"
        >Dashboard</router-link>
        <router-link
          to="/users"
          class="nav-item"
          :class="{ active: isActive('/users') }"
          @click="setActive('/users')"
        >Users</router-link>
      </nav>
    </header>
    <main class="main">
      <router-view />
    </main>
    <footer class="footer">
      <p>&copy; 2024 My Application</p>
    </footer>
  </div>
</template>

<script setup lang="ts">
import { useRoute } from 'vue-router'

const activeMenu = ref('')

// 获取当前路由
const route = useRoute()

// 设置选中的菜单项
const setActive = (menu: string) => {
  activeMenu.value = menu
}

// 检查当前菜单项是否为选中状态
const isActive = (menu: string) => {
  return activeMenu.value === menu || route.path === menu
}

// 初始化选中的菜单项
setActive(route.path)
</script>

<style scoped>
.basic-layout {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.header {
  background-color: #282c34;
  color: white;
  padding: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.main {
  flex: 1; /* 使主内容区域占据剩余空间 */
  background-color: #f4f4f4; /* 浅色背景 */
  padding: 20px; /* 内边距 */
}

.footer {
  background-color: #282c34; /* 深色背景 */
  color: white; /* 白色文本 */
  text-align: center; /* 中心对齐 */
  padding: 10px; /* 内边距 */
}
.logo {
  font-size: 24px;
  font-weight: bold;
}

.nav {
  display: flex;
}

.nav-item {
  color: white;
  text-decoration: none;
  padding: 10px 15px;
  transition: background-color 0.3s;
}

.nav-item:hover {
  background-color: #3a3f47;
  border-radius: 5px;
}

.nav-item.active {
  background-color: #409eff; /* 选中状态的背景色 */
  border-radius: 5px; /* 圆角 */
}
</style>

这里可能会报错ref找不到相应的模块,这是可以采用自动导入插件unplugin-auto-import

可以参考 unplugin-auto-import自动导入

8、改写app.vue
<template>
  <div id="app">
    <router-view />
  </div>
</template>

<script setup lang="ts">

</script>

<style>
body {
  margin: 0;
  font-family: Arial, sans-serif;
  background-color: #f4f4f4;
}

#app {
  min-height: 100vh;
  width: 100%;
}
</style>
9、新建views/Login.vue
<template>
  <div class="login-container">
    <el-card class="login-card" shadow="hover">
      <h2 class="login-title">系统登录</h2>
      <el-form :model="loginForm" @submit.prevent="handleLogin">
        <el-form-item>
          <el-input
            v-model="loginForm.username"
            placeholder="用户名"
            :prefix-icon="User"
            class="input-field"
          />
        </el-form-item>
        <el-form-item>
          <el-input
            v-model="loginForm.password"
            type="password"
            placeholder="密码"
            :prefix-icon="Lock"
            class="input-field"
          />
        </el-form-item>
        <el-form-item class="button-container">
          <el-button type="primary" native-type="submit" class="login-button">登录</el-button>
        </el-form-item>
      </el-form>
    </el-card>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '../store/user'
import type { LoginForm } from '../types/user'
import { User, Lock } from '@element-plus/icons-vue'
const router = useRouter()
const userStore = useUserStore()

const loginForm = ref<LoginForm>({
  username: '',
  password: ''
})

const handleLogin = async () => {
  try {
    await userStore.login(loginForm.value)
    router.push('/')
  } catch (error) {
    console.error(error)
  }
}
</script>

<style scoped>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  height: 100%;
  overflow: hidden; /* Prevent scrollbars */
}

.login-container {
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #e9ecef; /* Light background color */
}

.login-card {
  width: 400px; /* Increased width for better aesthetics */
  padding: 20px; /* Padding for the card */
  border-radius: 10px; /* Rounded corners */
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); /* Soft shadow */
  background-color: #ffffff; /* White background for the card */
}

.login-title {
  text-align: center;
  margin-bottom: 20px; /* Space below the title */
  font-size: 24px; /* Larger font size */
  color: #333; /* Darker text color */
}

.input-field {
  margin-bottom: 15px; /* Space between input fields */
}

.button-container {
  display: flex; /* Use flexbox for centering */
  justify-content: center; /* Center the button horizontally */
}

.login-button {
  background-color: #409eff; /* Primary color */
  color: #fff; /* Text color */
  border: none; /* Remove border */
  border-radius: 5px; /* Rounded corners */
  padding: 10px 20px; /* Padding for the button */
  font-size: 16px; /* Button font size */
  transition: background-color 0.3s, transform 0.2s; /* Transition effects */
  width: 100%; /* Make button full width */
}

.login-button:hover {
  background-color: #66b1ff; /* Lighter color on hover */
  transform: translateY(-2px); /* Slight lift effect */
}

.login-button:active {
  background-color: #007bff; /* Darker color on click */
  transform: translateY(0); /* Reset lift effect */
}
</style> 
10、新建store/user.ts进行全局变量状态管理
import { defineStore } from 'pinia'
import { login } from '@/api/user'
import type { LoginForm, UserInfo } from '@/types/user'

export const useUserStore = defineStore('user', {
  state: () => ({
    token: localStorage.getItem('token') || '',
    userInfo: null as UserInfo | null
  }),
  
  actions: {
    async login(loginForm: LoginForm) {
      const { token, userInfo } = await login(loginForm)
      this.token = token
      this.userInfo = userInfo
      localStorage.setItem('token', token)
    },
    
    logout() {
      this.token = ''
      this.userInfo = null
      localStorage.removeItem('token')
    }
  }
}) 
11、新建api/user.ts定义接口请求方法
import axios from 'axios';
import type { LoginForm, UserInfo } from '@/types/user';

export async function login(loginForm: LoginForm): Promise<{ token: string; userInfo: UserInfo }> {
  try {
    const response = await axios.post('/api/login', loginForm);
    return response.data;
  } catch (error) {
    console.error('Login failed:', error);
    throw new Error('Login failed');
  }
} 
12、新建types/user.ts定义接口interface
export interface LoginForm {
  username: string
  password: string
}

export interface UserInfo {
  id: string
  username: string
} 
13、新建utils/request.ts定义请求配置以及拦截器
import axios from 'axios'
import { ElMessage } from 'element-plus'

const request = axios.create({
  baseURL: '/',
  timeout: 6000
})

request.interceptors.request.use(
  config => {
    const token = localStorage.getItem('token')
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  error => {
    return Promise.reject(error)
  }
)

request.interceptors.response.use(
  response => response.data,
  error => {
    ElMessage.error(error.response?.data?.message || '请求失败')
    return Promise.reject(error)
  }
)

export default request 
14、项目安装mock进行接口请求模拟数据处理
1、安装依赖
npm i mockjs vite-plugin-mock --save-dev
2、新建src/mock/index.ts定义模拟返回数据
// mock/index.js
export default [{
  type: 'get',
  url: '/api/login',
  response: () => {
    return {
      token: '1234567890',
      userInfo: {
        name: 'admin',
        avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif'
      }
    }
  }
}]
3、main.ts引入
import './mock/index.ts' 
4、vite.config.ts进行配置引入
import { defineConfig } from 'vite'
import path from 'path'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite';
import { viteMockServe } from 'vite-plugin-mock'
// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      imports:['vue','vue-router'],
      dts: 'src/auto-imports.d.ts'
    }),
    viteMockServe({
      mockPath: 'src/mock/',
      enable: true
    })
  ],
  base: '/',
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    },
    extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
  }
})

有可能引用不生效可以参考mock数据处理

15、项目首页新建view/Dashboard.vue
<template>
  <div class="dashboard">
    <h2>Dashboard</h2>
    <p>Welcome to the dashboard! Here you can find an overview of your application.</p>
    <div class="stats">
      <div class="stat-card">
        <h3>Total Users</h3>
        <p>{{ totalUsers }}</p>
      </div>
      <div class="stat-card">
        <h3>Total Posts</h3>
        <p>{{ totalPosts }}</p>
      </div>
      <div class="stat-card">
        <h3>Total Comments</h3>
        <p>{{ totalComments }}</p>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

// 定义响应式数据
const totalUsers = ref(100) // 示例数据
const totalPosts = ref(50)   // 示例数据
const totalComments = ref(200) // 示例数据
</script>

<style scoped>
.dashboard {
  padding: 1rem;
}

.stats {
  display: flex;
  justify-content: space-around;
  margin-top: 2rem;
}

.stat-card {
  border: 1px solid #ccc;
  border-radius: 8px;
  padding: 1rem;
  width: 30%;
  text-align: center;
}
</style>
15、项目用户管理新建view/user/UserList.vue
<template>
  <div class="user-list">
    <h2>User List</h2>
    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>Name</th>
          <th>Email</th>
          <th>Actions</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="user in users" :key="user.id">
          <td>{{ user.id }}</td>
          <td>{{ user.name }}</td>
          <td>{{ user.email }}</td>
          <td>
            <button @click="editUser(user.id)">Edit</button>
            <button @click="deleteUser(user.id)">Delete</button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

// 示例用户数据
const users = ref([
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' },
  { id: 3, name: 'Charlie', email: 'charlie@example.com' }
])

// 编辑用户的函数
const editUser = (id: number) => {
  console.log(`Edit user with ID: ${id}`)
  // 在这里添加编辑用户的逻辑
}

// 删除用户的函数
const deleteUser = (id: number) => {
  console.log(`Delete user with ID: ${id}`)
  // 在这里添加删除用户的逻辑
}
</script>

<style scoped>
.user-list {
  padding: 1rem;
}

table {
  width: 100%;
  border-collapse: collapse;
}

th, td {
  border: 1px solid #ccc;
  padding: 0.5rem;
  text-align: left;
}

button {
  margin-right: 0.5rem;
}
</style>

至此用户相关前端部分基础完成,运行项目可以看到效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
附加 package.json:

{
  "name": "admin-frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc -b && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@element-plus/icons-vue": "^2.3.1",
    "axios": "^1.7.7",
    "element-plus": "^2.8.7",
    "pinia": "^2.2.6",
    "vite-plugin-mock": "^3.0.2",
    "vue": "^3.5.12",
    "vue-router": "^4.4.5"
  },
  "devDependencies": {
    "@types/mockjs": "^1.0.10",
    "@types/node": "^22.9.0",
    "@vitejs/plugin-vue": "^5.1.4",
    "mockjs": "^1.1.0",
    "typescript": "~5.6.2",
    "unplugin-auto-import": "^0.18.4",
    "vite": "^5.4.10",
    "vue-tsc": "^2.1.8"
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/941356.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CVE-2024-34351 漏洞复现

CVE-2024-34351&#xff0c;由Next.js异步函数createRedirectRenderResult导致的SSRF。 影响版本&#xff1a;13.4.0< Next.js < 14.1.1 参考文章&#xff1a; Next.js Server-Side Request Forgery in Server Actions CVE-2024-34351 GitHub Advisory Database Gi…

怎么理解GKE Role-Based Access Control (RBAC) 和 Pod Security Policies (PSP)

怎么理解GKE Role-Based Access Control (RBAC) 和 Pod Security Policies (PSP) 理解 Google Kubernetes Engine (GKE) 中的角色基于访问控制&#xff08;RBAC&#xff09;和 Pod 安全策略&#xff08;PSP&#xff09;对于确保集群安全性至关重要。以下是对这两个概念的详细解…

什么是 DevOps 自动化?

DevOps 自动化是一种现代软件开发方法&#xff0c;它使用工具和流程来自动化任务并简化工作流程。它将开发人员、IT 运营和安全团队聚集在一起&#xff0c;帮助他们有效协作并交付可靠的软件。借助 DevOps 自动化&#xff0c;组织能够处理重复性任务、优化流程并更快地将应用程…

帝国CMS:如何去掉帝国CMS登录界面的认证码登录

如果在安装的时候&#xff0c;不小心选中了认证码选项&#xff0c;那么后面登录帝国后台都会要求输入认证码才能登录&#xff0c;如何去除这个设置呢&#xff0c;笔者以古诗词网 www.gushichi.com为例&#xff0c;为大家举例说明&#xff01; 去除步骤如下&#xff1a; 1.前往…

4.2V单节锂电池充电电路(TP4056)、USB与锂电池切换电路分享

一、充电原理图 1、连接说明 BAT_VCC和BAT_GND连接电池 VUSB和GND连接USB电源 2、芯片介绍 a、DW01 DW01芯片是一种电池管理保护芯片&#xff0c;主要用于锂离子电池的保护和管理。DW01芯片具有以下特点&#xff1a; 电池电压保护&#xff1a;DW01芯片可以监测和保护电池的…

ChatGPT生成接口文档实践案例(二)

不难发现&#xff0c;两个方案都出色地完成了接口文档的生成&#xff0c;但笔者更喜欢Response 2的表达&#xff0c;因为其描述更加全面。 还可以让ChatGPT生成符合OpenAPI 3.0规范的接口文档&#xff0c;以便于项目相关成员阅读&#xff0c;如图5-13所示。 为什么要生成OpenAP…

MFC用List Control 和Picture控件实现界面切换效果

添加List Control 和Picture控件 添加 3个子窗体 把子窗体边框设置为None, 样式设为Child 声明 CListCtrl m_listPageForm;void ShowForm(int nIndex);void CreatFormList();void CMFCApplication3Dlg::DoDataExchange(CDataExchange* pDX) {CDialogEx::DoDataExchange(pDX);DD…

JavaWeb Servlet的反射优化、Dispatcher优化、视图(重定向)优化、方法参数值获取优化

目录 1. 背景2. 实现2.1 pom.xml2.2 FruitController.java2.3 DispatcherServlet.java2.4 applicationContext.xml 3. 测试 1. 背景 前面我们做了Servlet的一个案例。但是存在很多问题&#xff0c;现在我们要做优化&#xff0c;优化的步骤如下&#xff1a; 每个Fruit请求都需…

frp内网穿透云服务器。云服务器映射多个家庭局域网内网端口。家庭Windows主机内网运行多个web程序

这篇文章最终要实现的效果是&#xff0c;把云服务器的公网IP绑定到自己本地局域网上的主机一样的效果。相当于局域网主机有了一个自己的公网IP地址。 FRP (Fast Reverse Proxy) 是一个用 Go 语言编写的高性能反向代理应用程序&#xff0c;主要用于内网穿透。它允许位于 NAT 或…

windos 安装docker

文章目录 安装1.下载安装包2.双击安装软件 验证修改国内镜像地址FAQDocker Engine stopped 小结 安装 1.下载安装包 到官网下载适配的安装包&#xff1a;https://www.docker.com/products/docker-desktop/ 2.双击安装软件 选择ok 等待安装依赖 安装完成以后会重启电脑&am…

【已解决】黑马点评项目Redis版本替换过程中误删数据库后前端显示出现的问题

为了实现基于Redis的Stream结构作为消息队列&#xff0c;实现异步秒杀下单的功能&#xff0c;换Redis版本 Redis版本太旧了&#xff0c;所以从3.2.1换成了5.0.14 此时犯了一个大忌&#xff0c;因为新的Redis打开后&#xff0c;没有缓存&#xff0c;不知道出了什么问题&#xf…

Odoo 免费开源 ERP:通过 JavaScript 创建对话框窗口的技术实践分享

作者 | 老杨 出品 | 上海开源智造软件有限公司&#xff08;OSCG&#xff09; 概述 在本文中&#xff0c;我们将深入研讨如何于 Odoo 18 中构建 JavaScript&#xff08;JS&#xff09;对话框或弹出窗口。对话框乃是展现重要讯息、确认用户操作以及警示用户留意警告或错误的行…

Tool之Excalidraw:Excalidraw(开源的虚拟手绘风格白板)的简介、安装和使用方法、艾米莉应用之详细攻略

Tool之Excalidraw&#xff1a;Excalidraw(开源的虚拟手绘风格白板)的简介、安装和使用方法、艾米莉应用之详细攻略 目录 Excalidraw 简介 1、Excalidraw 的主要特点&#xff1a; Excalidraw 安装和使用方法 1、Excalidraw的安装 T1、使用 npm 安装&#xff1a; T2、使用 …

探索CSDN博客数据:使用Python爬虫技术

探索CSDN博客数据&#xff1a;使用Python爬虫技术 在数字化的浪潮中&#xff0c;数据的获取与分析变得日益关键。CSDN作为中国领先的IT社区和服务平台&#xff0c;汇聚了海量的技术博客与文章&#xff0c;成为一座蕴藏丰富的数据宝库。本文将引领您穿梭于Python的requests和py…

【蓝桥杯选拔赛真题96】Scratch风车旋转 第十五届蓝桥杯scratch图形化编程 少儿编程创意编程选拔赛真题解析

目录 scratch风车旋转 一、题目要求 编程实现 二、案例分析 1、角色分析 2、背景分析 3、前期准备 三、解题思路 1、思路分析 2、详细过程 四、程序编写 五、考点分析 六、推荐资料 1、入门基础 2、蓝桥杯比赛 3、考级资料 4、视频课程 5、python资料 scratc…

LeetCode:222.完全二叉树节点的数量

跟着carl学算法&#xff0c;本系列博客仅做个人记录&#xff0c;建议大家都去看carl本人的博客&#xff0c;写的真的很好的&#xff01; 代码随想录 LeetCode&#xff1a;222.完全二叉树节点的数量 给你一棵 完全二叉树 的根节点 root &#xff0c;求出该树的节点个数。 完全二…

CNN和Transfomer介绍

文章目录 CNN和Transfomer介绍CNN和Transfomer的区别1. **基本概念**2. **数据处理方式**3. **模型结构差异**4. **应用场景区别** 自注意力机制1. **自注意力机制的概念**2. **自注意力机制的实现步骤**3. **自注意力机制的优势** Transformer结构组成1. **多头注意力层&#…

【数据结构练习题】栈与队列

栈与队列 选择题括号匹配逆波兰表达式求值出栈入栈次序匹配最小栈设计循环队列面试题1. 用队列实现栈。[OJ链接](https://leetcode.cn/problems/implement-stack-using-queues/solutions/)2. 用栈实现队列。[OJ链接](https://leetcode.cn/problems/implement-queue-using-stack…

python 定时任务管理封装

主逻辑代码 # -*- coding: utf-8 -*- # import apscheduler import pandas as pd from datetime import datetime # 导入调度器&#xff0c;此处使用BackgroundScheduler阻塞调度器 from apscheduler.schedulers.background import BackgroundScheduler # 导入触发器&#xf…

MaxKB基于大语言模型和 RAG的开源知识库问答系统的快速部署教程

1 部署要求 1.1 服务器配置 部署服务器要求&#xff1a; 操作系统&#xff1a;Ubuntu 22.04 / CentOS 7.6 64 位系统CPU/内存&#xff1a;4C/8GB 以上磁盘空间&#xff1a;100GB 1.2 端口要求 在线部署MaxKB需要开通的访问端口说明如下&#xff1a; 端口作用说明22SSH安装…