vue3 前端路由权限控制与字典数据缓存实践(附Demo)

目录

  • 前言
  • 1. 基本知识
  • 2. Demo
  • 3. 实战

前言

🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF在这里插入图片描述

从实战中出发:

1. 基本知识

Vue3 和 Java 通信时如何进行字典数据管理

需要了解字典数据的结构。通常,字典数据包含一个键和一个值,有时候还有一些额外的属性,比如颜色类型或 CSS 类,这些在前端展示时可能会用到

库来发送 HTTP 请求到后端,获取这些字典数据。

需要分析如何在前端存储和管理这些字典数据。Pinia 是一个推荐的 Vue
通过 Pinia,可以定义一个字典 store,用来存储所有获取到的字典数据。这样,所有组件都可以方便地访问这些数据,而不需要每次都通过 API 获取.另外,前端缓存也是一个重要的考虑因素。由于字典数据通常是静态的或变化不大,可以在前端缓存这些数据,减少对后端的请求次数,提高应用的响应速度。Vue 的hooks,比如 useCache

如何在路由和权限控制中使用这些字典数据

在用户登录后,前端需要获取字典数据以完成菜单加载和路由动态生成。这里需要与 Vue Router 结合,根据字典数据生成对应的路由配置,确保用户只能访问其权限范围内的功能。还需要处理字典数据的更新和缓存的失效。如果字典数据有更新,前端需要有一个机制来刷新缓存,确保用户使用的是最新的数据。这可能涉及到设置缓存的有效期,或者在有更新时手动清除缓存并重新获取数据

字典数据管理是一个重要的组成部分,字典数据通常包括各种下拉列表、状态标识等,用于在页面中展示和交互

特别是当使用 Vue3 与 Java 后端进行通信时,字典数据的获取、存储、管理和reload都成为关键点

  1. 字典数据通常包括以下几个要素:
  • 字典类型(dictType):表示字典的分类,如用户状态、订单状态等
  • 字典值(dictValue):具体的一个字典项,包含 value 和 label,有时还包括额外属性如颜色或 CSS 类
  • 其他属性:根据业务需求,可能还包括颜色类型、排序顺序等
interface DictDataVO {dictType: string;value: string;label: string;colorType?: string;cssClass?: string;
}

后端会提供一个获取字典数据的接口,如:GET /api/system/dict/simple

返回的数据格式如下:

[{"dictType": "user_status","value": "0","label": "正常"},{"dictType": "user_status","value": "1","label": "禁用"},// 其他字典项
]
  1. 使用 Pinia 进行字典数据的状态管理,Pinia 是 Vue3 推荐的状态管理库,适合管理全局状态,如字典数据
    具体定义的Store:
export const useDictStore = defineStore('dict', {state: () => ({dictMap: new Map<string, any>(),isSetDict: false}),getters: {getDictMap: (state) => state.dictMap,getIsSetDict: (state) => state.isSetDict},actions: {async setDictMap() {// 具体逻辑稍后分析},getDictByType(type: string) {return this.dictMap.get(type) || [];}}
});

后续只需要初始化对应的Store即可:

const dictStore = useDictStore();
dictStore.setDictMap();
  1. 前端缓存可以减少对后端的请求,提高响应速度。可以存储在 sessionStorage 或 localStorage 中

使用自定义缓存钩子:

import { CACHE_KEY } from '@/hooks/web/useCache';const { wsCache } = useCache('sessionStorage');// 存储字典数据
wsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 });// 获取字典数据
const cachedDict = wsCache.get(CACHE_KEY.DICT_CACHE);
  1. 字典数据的获取与初始化

在用户登录后,前端需要获取字典数据并存储到 Pinia 和缓存中

逻辑步骤:

检查缓存:首先尝试从缓存中获取字典数据,如果存在且未过期,直接使用
从后端获取:如果缓存不存在或过期,发送请求到后端获取最新数据
存储到 Pinia 和缓存:将获取到的字典数据存储到 Pinia 和 SessionStorage 中
动态生成路由(可选):根据字典数据动态加载菜单和路由,确保用户只能访问权限内的功能

主要的步骤如下:

async function initDictData() {const dictStore = useDictStore();if (dictStore.isSetDict) return;// 从缓存获取const cachedDict = wsCache.get(CACHE_KEY.DICT_CACHE);if (cachedDict) {dictStore.dictMap = cachedDict;dictStore.isSetDict = true;return;}// 从后端获取try {const response = await getSimpleDictDataList();const dictDataMap = new Map<string, any>();response.forEach((dictData: DictDataVO) => {if (!dictDataMap.has(dictData.dictType)) {dictDataMap.set(dictData.dictType, []);}dictDataMap.get(dictData.dictType)?.push({value: dictData.value,label: dictData.label,colorType: dictData.colorType,cssClass: dictData.cssClass});});// 存储到 Pinia 和缓存dictStore.dictMap = dictDataMap;dictStore.isSetDict = true;wsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 });} catch (error) {console.error('Failed to fetch dictionary data:', error);}
}
  1. 在组件中使用字典数据

在需要使用字典数据的组件中,可以轻松访问 Pinia 存储的字典数据

<template><div><label>用户状态:</label><select v-model="selectedStatus"><option v-for="(item, index) in dictStatus" :key="index" :value="item.value">{{ item.label }}</option></select></div>
</template><script setup lang="ts">
const dictStore = useDictStore();
const dictStatus = dictStore.getDictByType('user_status');
const selectedStatus = ref('0');
</script>
``6. 字典数据的更新与缓存失效
为确保字典数据的一致性,需要处理缓存的更新和失效```js
async function refreshDict() {const dictStore = useDictStore();// 清除缓存wsCache.delete(CACHE_KEY.DICT_CACHE);// 重新加载字典数据await dictStore.setDictMap();
}

2. Demo

以下主要是围绕第三章的实战中的Demo,提供的思路

前端与后端通信:使用 Vue3 和 Axion 发送 HTTP 请求到 Java 后端,获取字典数据
状态管理:使用 Pinia 管理字典数据,确保全局状态的唯一性和一致性
前端缓存:通过自定义缓存钩子,将字典数据存储在 sessionStorage 中,减少对后端的请求
组件化开发:创建可复用的字典选择组件,提升代码的可维护性和扩展性
动态数据加载:在应用初始化时加载字典数据,支持动态生成路由和菜单

项目结构:

src/
├── api/
│   └── dict.ts         # 后端 API 接口声明
├── hooks/
│   └── web/
│       └── useCache.ts # 缓存钩子
├── store/
│   └── modules/
│       └── dict.ts     # Pinia Store 定义
├── components/
│   └── DictSelect.vue  # 字典选择组件
└── main.ts             # 应用入口

定义Api接口:

// src/api/dict.ts
export interface DictDataVO {dictType: string;value: string;label: string;colorType?: string;cssClass?: string;
}export async function getSimpleDictDataList(): Promise<DictDataVO[]> {return await request({url: '/api/system/dict/simple',method: 'GET'});
}
  1. 创建字典状态管理 Store:
// src/store/modules/dict.ts
import { defineStore } from 'pinia';
import { CACHE_KEY, useCache } from '@/hooks/web/useCache';const { wsCache } = useCache('sessionStorage');
import { getSimpleDictDataList } from '@/api/dict';export interface DictState {dictMap: Map<string, any>;isSetDict: boolean;
}export const useDictStore = defineStore('dict', {state: () => ({dictMap: new Map<string, any>(),isSetDict: false}),getters: {getDictMap: state => state.dictMap,getIsSetDict: state => state.isSetDict},actions: {async setDictMap() {if (this.isSetDict) return;const cachedDict = wsCache.get(CACHE_KEY.DICT_CACHE);if (cachedDict) {this.dictMap = cachedDict;this.isSetDict = true;return;}try {const response = await getSimpleDictDataList();const dictDataMap = new Map<string, any>();response.forEach(dictData => {if (!dictDataMap.has(dictData.dictType)) {dictDataMap.set(dictData.dictType, []);}dictDataMap.get(dictData.dictType)?.push({value: dictData.value,label: dictData.label,colorType: dictData.colorType,cssClass: dictData.cssClass});});this.dictMap = dictDataMap;this.isSetDict = true;wsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 });} catch (error) {console.error('Failed to fetch dictionary data:', error);}},getDictByType(type: string) {return this.dictMap.get(type) || [];}}
});
  1. 创建字典选择组件:
<!-- src/components/DictSelect.vue -->
<template><div class="dict-select"><label>{{ label }}</label><select v-model="selectedValue" @change="handleChange"><option v-for="(item, index) in dictOptions" :key="index" :value="item.value">{{ item.label }}</option></select></div>
</template><script setup lang="ts">
import { useDictStore } from '@/store/modules/dict';interface DictSelectProps {dictType: string;label?: string;modelValue?: string;
}const props = defineProps<DictSelectProps>();
const emit = defineEmits(['update:modelValue']);const dictStore = useDictStore();
const dictOptions = dictStore.getDictByType(props.dictType);
const selectedValue = ref(props.modelValue);const handleChange = (e: Event) => {const value = (e.target as HTMLSelectElement).value;selectedValue.value = value;emit('update:modelValue', value);
};
</script><style scoped>
.dict-select {margin: 10px 0;
}select {padding: 5px;border: 1px solid #ddd;border-radius: 4px;
}
</style>
  1. 在主应用中初始化字典数据:
// src/main.ts
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import pinia from './store';
import { useDictStore } from './store/modules/dict';const app = createApp(App).use(router).use(pinia);router.isReady().then(() => {app.mount('#app');// 初始化字典数据const dictStore = useDictStore();dictStore.setDictMap();
});
  1. 使用字典选择组件:
<!-- src/App.vue -->
<template><div id="app"><h1>字典数据管理示例</h1><dict-selectdictType="user_status"label="用户状态"v-model:modelValue="selectedStatus"/></div>
</template><script setup lang="ts">
import DictSelect from './components/DictSelect.vue';const selectedStatus = ref('0');
</script>

3. 实战

以下实战来源:https://gitee.com/zhijiantianya/ruoyi-vue-pro

附上基本的代码注释讲解:

权限管理模块:

import router from './router'
import type { RouteRecordRaw } from 'vue-router'
import { isRelogin } from '@/config/axios/service'
import { getAccessToken } from '@/utils/auth'
import { useTitle } from '@/hooks/web/useTitle'
import { useNProgress } from '@/hooks/web/useNProgress'
import { usePageLoading } from '@/hooks/web/usePageLoading'
import { useDictStoreWithOut } from '@/store/modules/dict'
import { useUserStoreWithOut } from '@/store/modules/user'
import { usePermissionStoreWithOut } from '@/store/modules/permission'// 初始化进度条和页面加载状态
const { start, done } = useNProgress()
const { loadStart, loadDone } = usePageLoading()/*** 解析孔(URL)为路径和参数对象* @param url 输入的完整URL* @returns 包含 basePath 和 paramsObject的对象*/
const parseURL = (url: string | null | undefined
): { basePath: string; paramsObject: { [key: string]: string } } => {// 如果输入为 null 或 undefined,返回空字符串和空对象if (url == null) {return { basePath: '', paramsObject: {} }}// 找到问号的位置,分割基础路径和查询参数const questionMarkIndex = url.indexOf('?')let basePath = urlconst paramsObject: { [key: string]: string } = {}// 如果有查询参数,进行解析if (questionMarkIndex !== -1) {basePath = url.substring(0, questionMarkIndex)const queryString = url.substring(questionMarkIndex + 1)const searchParams = new URLSearchParams(queryString)searchParams.forEach((value, key) => {paramsObject[key] = value})}// 返回解析后的结果return { basePath, paramsObject }
}// 不需要重定向的白名单路径
const whiteList = ['/login','/social-login','/auth-redirect','/bind','/register','/oauthLogin/gitee'
]// 路由加载前的钩子函数
router.beforeEach(async (to, from, next) => {start() // 开始进度条loadStart() // 开始页面加载if (getAccessToken()) { // 检查是否有访问令牌if (to.path === '/login') {// 已经登录的情况下,重定向到主页next({ path: '/' })} else {// 获取字典、用户、权限仓库const dictStore = useDictStoreWithOut()const userStore = useUserStoreWithOut()const permissionStore = usePermissionStoreWithOut()// 检查字典是否加载完成if (!dictStore.getIsSetDict) {await dictStore.setDictMap() // 如果没有加载过,加载字典数据}// 检查用户信息是否加载完成if (!userStore.getIsSetUser) {isRelogin.show = true // 显示重新登录提示await userStore.setUserInfoAction() // 加载用户信息isRelogin.show = false// 加载权限路由await permissionStore.generateRoutes()permissionStore.getAddRouters.forEach((route) => {router.addRoute(route as unknown as RouteRecordRaw) // 动态添加路由})// 处理重定向路径const redirectPath = from.query.redirect || to.pathconst redirect = decodeURIComponent(redirectPath as string)const { basePath, paramsObject: query } = parseURL(redirect)// 根据情况决定是否替换当前路由const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect, query }next(nextData)} else {next() // 用户信息已加载,继续导航}}} else {// 没有登录,检查是否在白名单中if (whiteList.indexOf(to.path) !== -1) {next() // 白名单路径,允许访问} else {// 重定向到登录页,并携带当前路径作为 redirect 参数next(`/login?redirect=${to.fullPath}`)}
})// 路由加载完成后的钩子函数
router.afterEach((to) => {useTitle(to?.meta?.title as string) // 更新页面标题done() // 结束进度条loadDone() // 结束页面加载状态
})

字典管理模块:

import { defineStore } from 'pinia'
import { store } from '../index'
import { DictDataVO } from '@/api/system/dict/types'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache('sessionStorage')
import { getSimpleDictDataList } from '@/api/system/dict/dict.data'/*** 字典值的类型定义*/
export interface DictValueType {value: anylabel: stringcolorType?: stringcssClass?: string
}/*** 字典类型的类型定义*/
export interface DictTypeType {dictType: stringdictValue: DictValueType[]
}/*** 字典状态类型定义*/
export interface DictState {dictMap: Map<string, any>isSetDict: boolean
}/*** 定义字典 Pinia Store*/
export const useDictStore = defineStore('dict', {state: (): DictState => ({dictMap: new Map<string, any>(), // 字典映射表isSetDict: false // 表示字典是否已加载}),getters: {/*** 获取字典映射表,从缓存中读取*/getDictMap(): Recordable {const dictMap = wsCache.get(CACHE_KEY.DICT_CACHE)if (dictMap) {this.dictMap = dictMap}return this.dictMap},/*** 检查字典是否已加载*/getIsSetDict(): boolean {return this.isSetDict}},actions: {/*** 设置字典映射表,从缓存或API获取*/async setDictMap() {const dictMap = wsCache.get(CACHE_KEY.DICT_CACHE)if (dictMap) {this.dictMap = dictMapthis.isSetDict = true} else {const res = await getSimpleDictDataList()// 构建字典数据映射const dictDataMap = new Map<string, any>()res.forEach((dictData: DictDataVO) => {// 按照 dictType 分组if (!dictDataMap.has(dictData.dictType)) {dictDataMap.set(dictData.dictType, [])}// 添加字典值dictDataMap.get(dictData.dictType)?.push({value: dictData.value,label: dictData.label,colorType: dictData.colorType,cssClass: dictData.cssClass})})// 更新状态和缓存this.dictMap = dictDataMapthis.isSetDict = truewsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 }) // 缓存60秒}},/*** 根据字典类型获取对应的字典值* @param type 字典类型*/getDictByType(type: string) {if (!this.isSetDict) {this.setDictMap() // 如果未加载,先加载字典}return this.dictMap[type]},/*** 重置字典数据,清空缓存并重新加载*/async resetDict() {wsCache.delete(CACHE_KEY.DICT_CACHE) // 清空缓存const res = await getSimpleDictDataList()const dictDataMap = new Map<string, any>()res.forEach((dictData: DictDataVO) => {// 重新构建字典映射if (!dictDataMap.has(dictData.dictType)) {dictDataMap.set(dictData.dictType, [])}dictDataMap.get(dictData.dictType)?.push({value: dictData.value,label: dictData.label,colorType: dictData.colorType,cssClass: dictData.cssClass})})this.dictMap = dictDataMapthis.isSetDict = truewsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 }) // 更新缓存}}
})/*** 提供一个不带 store 的字典 Store 实例*/
export const useDictStoreWithOut = () => {return useDictStore(store)
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用于 RGB-D 显著目标检测的点感知交互和 CNN 诱导的细化网络

摘要 通过整合来自RGB图像和深度图的互补信息&#xff0c;能够提升在复杂且具有挑战性场景下的显著性目标检测&#xff08;SOD&#xff09;能力。近年来&#xff0c;卷积神经网络&#xff08;CNNs&#xff09;在特征提取和跨模态交互方面的重要作用已得到充分挖掘&#xff0c;但…

基于SpringBoot的“校园周边美食探索及分享平台”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“校园周边美食探索及分享平台”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 校园周边美食探索及分享平台结构图…

chrome浏览器插件拓展捕获页面的响应体内容

因为chrome extension官方没有的直接获取响应体的方法&#xff0c;所以需要自己实现方法来获取&#xff0c;实现的方式有很多种&#xff0c;这是记录的第二种&#xff0c;第一种就是使用vconsole来实现&#xff0c;vconsole是一个开源框架&#xff0c;一个轻量、可拓展、针对手…

【Linux指北】Linux的重定向与管道

一、了解Linux目录配置标准FHS FHS本质&#xff1a;是一套规定Linux目录结构&#xff0c;软件建议安装位置的标准。 (使用Linux来开发产品或者发布软件的公司、个人太多&#xff0c;如果每家公司或者个人都按照自己的意愿来配置文件或者软件的存放位置&#xff0c;这无疑是一…

Qt6.8.2中JavaScript调用WebAssembly的js文件<1>

前段时间已经学习了如何在QtAssembly中编译FFmpeg资源了&#xff0c;接下来需要使用Html来调用QtCreator中WebAssembly套件写的功能&#xff0c;逐步实现javascrpt与c复杂功能的视线。 接下来我先为大家介绍一个非常简单的加法调用吧&#xff01; 功能讲解 开发环境&#xf…

3.13-进程

进程 进程和程序 程序&#xff1a;编译好的二进制文件&#xff0c;不占用系统资源&#xff08;内存&#xff09;。进程&#xff1a;活跃的程序&#xff0c;不消耗系统图资源&#xff08;内存&#xff09;。 MMU PCB 进程控制块 本质&#xff1a;结构体&#xff1a;struct …

在 CentOS 7 上安装 PHP 7.3

在 CentOS 7 上安装 PHP 7.3 可以按照以下步骤进行操作&#xff1a; 1. 安装必要的依赖和 EPEL 仓库 EPEL&#xff08;Extra Packages for Enterprise Linux&#xff09;是为企业级 Linux 提供额外软件包的仓库&#xff0c;yum-utils 用于管理 yum 仓库。 sudo yum install -…

DeepSeek模型本地化部署方案及Python实现

DeepSeek实在是太火了&#xff0c;虽然经过扩容和调整&#xff0c;但反应依旧不稳定&#xff0c;甚至小圆圈转半天最后却提示“服务器繁忙&#xff0c;请稍后再试。” 故此&#xff0c;本文通过讲解在本地部署 DeepSeek并配合python代码实现&#xff0c;让你零成本搭建自己的AI…

C++从入门到入土(七)——多态

目录 前言 多态的概念 多态的定义 虚函数的介绍 虚函数的重写/覆盖 析构函数的重写 override和final关键字 纯虚函数和抽象类 重写/重载/隐藏总结 多态的原理 小结 前言 C一共有三个特性&#xff0c;封装、继承和多态&#xff0c;在前面的文章中&#xff0c;我们分别…

浅谈时钟启动和Systemlnit函数

时钟是STM32的关键&#xff0c;是整个系统的心脏&#xff0c;时钟如何启动&#xff0c;时钟源如何选择&#xff0c;各个参数如何设置&#xff0c;我们从源码来简单分析一下时钟的启动函数Systemlnit&#xff08;&#xff09;。 Systemlnit函数简介 我们先来看一下源程序的注释…

【数据结构】6栈

0 章节 3&#xff0e;1到3&#xff0e;3小节。 认知与理解栈结构&#xff1b; 列举栈的操作特点。 理解并列举栈的应用案例。 重点 栈的特点与实现&#xff1b; 难点 栈的灵活实现与应用 作业或思考题 完成学习测试&#xff12;&#xff0c;&#xff1f; 内容达成以下标准(考核…

HOT100——链表篇Leetcode160. 相交链表

文章目录 题目&#xff1a;Leetcode160. 相交链表原题链接思路代码 题目&#xff1a;Leetcode160. 相交链表 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表…

江科大51单片机笔记【16】AD/DA转换(下)

写在前言 此为博主自学江科大51单片机&#xff08;B站&#xff09;的笔记&#xff0c;方便后续重温知识 在后面的章节中&#xff0c;为了防止篇幅过长和易于查找&#xff0c;我把一个小节分成两部分来发&#xff0c;上章节主要是关于本节课的硬件介绍、电路图、原理图等理论知识…

【CF】Day5——Codeforces Round 921 (Div. 2) BC

B. A Balanced Problemset? 题目&#xff1a; 思路&#xff1a; 这道题要我们分成n个子问题&#xff0c;我们假设这几个子问题分别是a1,a2,a3,...an&#xff0c; 那么就是让我们求 gcd(a1,a2,a3,....,an)&#xff0c;我们假设这个值是d 那么就有 d | a1&#xff0c;d | a2…

Mininet 自定义拓扑类型详解

Mininet 通过 --topo 参数支持多种自定义网络拓扑结构&#xff0c;适用于不同场景的网络模拟需求。以下是所有内置拓扑类型及其参数说明&#xff1a; 一、基础拓扑类型 拓扑类型参数格式说明示例命令singlesingle,<n>单一交换机连接所有主机&#xff08;默认 2 台主机&a…

图论part3|101.孤岛的总面积、沉没孤岛、417. 太平洋大西洋水流问题

101. 孤岛的总面积 &#x1f517;&#xff1a;101. 孤岛的总面积思路&#xff1a;和昨天的岛的区别是&#xff1a;是否有挨着边的岛屿 所以可以先遍历四条边挨着的岛屿&#xff0c;把他们标记为非孤岛再计算其他岛屿当中的最大面积 代码&#xff1a;&#xff08;深度搜索&…

第十一届蓝桥杯单片机国赛

什么&#xff1f;4T模拟赛和省赛做起来轻轻松松&#xff1f;不妨来挑战一下第十一届国赛&#xff0c;这一届的国赛居然没考超声波、串口通信&#xff01;只要你正确地理解了题目的意思&#xff0c;规避出题人挖的坑&#xff0c;拿个国一轻轻松松。 附件&#xff1a;第十一届蓝桥…

【Unity6打包Android】游戏启动的隐私政策弹窗(报错处理)

Unity版本&#xff1a;Unity6000.0.24 增加弹窗步骤 1. 自定义AndroidManifest 1.1 在Player Setting > Publishing Settings > Build下勾选Custom Main Manifest&#xff0c;在Assets/Plugins/Android路径下生成AndroidManifest.xml文件 1.2 修改AndroidManifest.xml…

记录一个SQL自动执行的html页面

在实际工作场景中&#xff0c;需要运用到大量SQL语句更新业务逻辑&#xff0c;对程序员本身&#xff0c;写好的sql语句执行没有多大问题&#xff08;图1&#xff09;&#xff0c;但是对于普通用户来说还是有操作难度的。因此我们需要构建一个HTML页面&#xff08;图2&#xff0…

mac安装mysql之后报错zsh: command not found: mysql !

在Mac上安装MySQL后&#xff0c;如果终端中找不到mysql命令&#xff0c;通常是 因为MySQL的命令行工具&#xff08;如mysql客户端&#xff09;没有被正确地添加到你的环境变量中。 检查 MySQL 是否已安装 ps -ef|grep mysql查看到路径在 /usr/local/mysql/bin 查看 .bash_pro…