Vue3+Vite+TypeScript+Element Plus开发-10.多用户动态加载菜单

news2025/4/13 1:55:12

 系列文档目录

Vue3+Vite+TypeScript安装

Element Plus安装与配置

主页设计与router配置

静态菜单设计

Pinia引入

Header响应式菜单缩展

Mockjs引用与Axios封装

登录设计

登录成功跳转主页

多用户动态加载菜单

Pinia持久化

动态路由-配置  


文章目录

目录

 系列文档目录

文章目录

前言

一、API调整

二、Mock模拟菜单数据

三、mock API

四、stores

五、Login.Vue

七、运行效果

后续 


前言

        本章节着重介绍如何实现基于用户角色的动态菜单加载功能


一、API调整

        更新   src/api/menu.ts   文件,以增强菜单API功能,添加用户名作为参数,实现更精细化的菜单数据定制

// src/api/menu.ts
// 引入 request、post 和 get 函数 
import { get } from '@/api/request'; // 绝对路径

// 菜单接口
/*
export const menuAPI = async () => {
  try {
    const result = await get('/menu'); // 使用封装的 get 方法
    return result  ;
  } catch (error) {
    console.error('获取菜单数据失败:', error);
    return [];
  }
};
*/
// 菜单接口 增加data
export const menuAPI = async (data: any) => {
  try {
    
    const result = await get('/menu',data); // 使用封装的 get 方法
    // console.log('result',result);
    console.log('result data',result.data );
    return result.data  ;// result.data  为了返回值统一,增加data
  } catch (error) {
    console.error('获取菜单数据失败:', error);
    return [];
  }
};

 

二、Mock模拟菜单数据

        编辑   src/mock/mockData/menuData.ts   文件,以扩展模拟数据集,包含针对不同用户的差异化菜单数据。将有助于在开发过程中更准确地模拟用户特定的菜单内容

// src/mock/mockData/menuData.ts
import Mock from 'mockjs';
import { Document, Setting } from '@element-plus/icons-vue'; // 假设你使用的是 Element Plus 的图标

// 模拟菜单数据,改为后面动态
/*
const menuData = Mock.mock({
  data: [
    { index: 'Home', label: '首页', icon: Document },
    {
      index: 'SysSettings',
      label: '系统设置',
      icon: Setting,
      children: [
        { index: 'UserInfo', label: '个人资料' },
        { index: 'AccountSetting', label: '账户设置' },
      ],
    },
  ],
});
*/

 
// 动态生成菜单数据
export default (data: any) => {
  // 解析传入的 data 参数
  const { username, password } = data;
 

  // 根据用户名和密码生成不同的响应
  if (username === 'admin') {
    return Mock.mock({
      status_code: 200,
      status: 'success',
      message: 'Operation successful.',
      data:  [
        { index: 'Home', label: '首页', icon: Document },
        {
          index: 'SysSettings',
          label: '系统设置',
          icon: Setting,
          children: [
            { index: 'UserInfo', label: '个人资料' },
            { index: 'AccountSetting', label: '账户设置' },
          ],
        },
      ],
    });
  } else if (username === 'user' ) {
    return Mock.mock({
      status_code: 200,
      status: 'success',
      message: 'Operation successful.',
      data: [
        { index: 'Home', label: '首页', icon: Document },
        {
          index: 'SysSettings',
          label: '系统设置',
          icon: Setting,
          children: [
            { index: 'UserInfo', label: '个人资料' },
           
          ],
        },
      ],
    });
  } else {
    return Mock.mock({
      status_code: 401,
      status: 'fail',
      message: 'Invalid username ,No Menu Data.',
      data: [],
    });
  }
};

三、mock API

        编辑 src/mock/index.ts   文件中菜单部分,添加用户管理相关的模拟数据。将测试模拟用户管理功能的菜单项,确保菜单界面能够正确加载不同的用户菜单权限

// src/mock/index.ts
import Mock from 'mockjs';
import menuData from '@/mock/mockData/menuData';
import loginData from '@/mock/mockData/loginData' ;

/*
Mock.mock(/menu/, 'get', (req: any) => {
  return menuData.data;
});
*/
Mock.mock(/menu/, 'get', (options) => {
  const { body } = options;
  const data = JSON.parse(body); // 解析请求体中的数据
  return menuData(data);
});

/*
Mock.mock(/login/, 'post', (req: any) => {
  return loginData.data;
});
*/
//  /\/ zheng'zhi'fa'zhe
Mock.mock(/\/login/, (options) => {
  const { body } = options;
  const data = JSON.parse(body); // 解析请求体中的数据
  return loginData(data); // 调用动态生成的登录数据函数
});

四、stores

说明:
文件路径:src/stores/index.ts
任务描述:增强现有的 Pinia store 以支持菜单数据的存储与获取功能。
具体步骤:
1. 在 store 中定义一个新的状态(menuData)属性,用于存储从服务器获取的菜单数据。
2. 创建一个 action  setMenuData用于异步存储菜单数据。
3. 创建一个 action getMenuData 用于异步获取菜单数据,并将获取到的数据保存到步骤2中定义的状态属性中

// src/stores/index.ts

import { defineStore } from 'pinia';

// 定义公共 store
export const useAllDataStore = defineStore('useAllData', {
  // 定义状态
  state: () => ({
    isCollapse: false, // 定义初始状态
    username: '',
    token_key: '',
    menuData:[],
  }),

  // 定义 actions
  actions: {
    // 设置用户名
    setUsername(username: string) {
      this.username = username;
    },

    // 获取用户名
    getUsername(): string {
      return this.username;
    },

    // 设置 token_key
    setTokenKey(token_key: string) {
      this.token_key = token_key;
    },

    // 获取 token_key
    getTokenKey(): string {
      return this.token_key;
    },
    // 设置菜单数据
    setMenuData(menuData: any){
      this.menuData = menuData
    },
    // 获取菜单数据
    getMenuData(): [] {
      return this.menuData;
    },
  },
  
});

五、Login.Vue

说明:文件路径:  src/views/Login.vue

 任务描述:在登录视图中实现菜单数据的获取和存储功能。

具体步骤

1. 增加   fetchMenuData  

方法:• 实现一个名为   fetchMenuData   的方法,该方法负责异步获取菜单数据。

         • 确保此方法能够处理异步操作,并在数据获取成功后将其存储在组件的状态中或 Pinia store 中。

2. 在   fetchLoginData   方法中调用   fetchMenuData  :

• 修改   fetchLoginData   方法,在用户登录成功后调用   fetchMenuData  。

• 确保   fetchMenuData   的调用在   fetchLoginData   的异步流程中正确等待,以便在后续操作中能够访问到菜单数据。

重点说明

1. 异步处理:

•   fetchMenuData   必须能够处理异步请求,这意味着它可能需要使用   async/await   语法或   .then()   方法来处理 Promise。

• 必须确保   fetchMenuData   在   fetchLoginData   中被等待,以避免在数据完全加载之前就尝试访问菜单数据。

2. 数据存储:

• 获取到的菜单数据应该被存储在适当的地方,如组件的响应式数据中或 Pinia store 中,以便在整个应用中访问。

• 确保存储逻辑不会导致状态管理问题,如数据竞态条件或不一致的状态。

3. 错误处理:

• 在   fetchMenuData   中添加错误处理逻辑,以便在请求失败时能够适当地处理错误,例如显示错误消息或进行重试。

重点代码:

const fetchLoginData = async () => {
  try {
    const responseData: LoginResponse = await login(loginForm); // 假设 login 返回的是 LoginResponse

    if (responseData.status_code === 200 && responseData.status === 'success') {
      store.setUsername(responseData.data?.username || '');
      store.setTokenKey(responseData.data?.token_key || '');
      await fetchMenuData(); // 确保菜单数据更新
      router.push('/main'); // 导航到 MainAsideCont.vue
    } else {
      ElMessage.error(`登录失败: ${responseData.message || '未知错误'}`);
    }
  } catch (error) {
    ElMessage.error('登录请求失败,请稍后再试');
  }
};

// 获取菜单数据
const fetchMenuData = async () => {
  try {
    const result = await menuAPI(loginForm); // 假设 loginForm 包含必要的参数
    store.setMenuData(result);
    console.log('login result 返回的数据:', result);
    console.log('login menuAPI 返回的数据:', store.getMenuData());
  } catch (error) {
    console.error('获取菜单数据失败:', error);
  }
};

完整代码: 

<template>
  <div class="login-container">
    <el-card class="box-card">
      <template #header>
        <span>登录</span>
      </template>
      <el-form :model="loginForm" :rules="rules" ref="loginFormRef" label-width="100px" class="demo-loginForm">
        <el-form-item label="用户名" prop="username">
          <el-input v-model="loginForm.username"></el-input>
        </el-form-item>
        <el-form-item label="密码" prop="password">
          <el-input type="password" v-model="loginForm.password" show-password></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="submitForm">登录</el-button>
          <el-button @click="resetForm">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>
  </div>
</template>

<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { ElForm, ElFormItem, ElInput, ElButton, ElCard, ElMessage } from 'element-plus';
import type { FormInstance, FormRules } from 'element-plus';
import { login } from '@/api/user';
import { useAllDataStore } from '@/stores';
import { useRouter } from 'vue-router';
import { menuAPI } from '@/api/menu';

const router = useRouter();
const loginForm = reactive({
  username: '',
  password: ''
});

const rules: FormRules = {
  username: [
    { required: true, message: '请输入用户名', trigger: 'blur' }
  ],
  password: [
    { required: true, message: '请输入密码', trigger: 'blur' }
  ]
};

const store = useAllDataStore();

const loginFormRef = ref<FormInstance | null>(null);

// 封装登录请求处理逻辑
interface LoginResponse {
  status_code: number;
  status: string;
  message?: string;
  data?: {
    api_key: string;
    username: string;
    token_key: string;
    role: string;
    email: string;
  };
}

const fetchLoginData = async () => {
  try {
    const responseData: LoginResponse = await login(loginForm); // 假设 login 返回的是 LoginResponse

    if (responseData.status_code === 200 && responseData.status === 'success') {
      store.setUsername(responseData.data?.username || '');
      store.setTokenKey(responseData.data?.token_key || '');
      await fetchMenuData(); // 确保菜单数据更新
      router.push('/main'); // 导航到 MainAsideCont.vue
    } else {
      ElMessage.error(`登录失败: ${responseData.message || '未知错误'}`);
    }
  } catch (error) {
    ElMessage.error('登录请求失败,请稍后再试');
  }
};

// 获取菜单数据
const fetchMenuData = async () => {
  try {
    const result = await menuAPI(loginForm); // 假设 loginForm 包含必要的参数
    store.setMenuData(result);
    console.log('login result 返回的数据:', result);
    console.log('login menuAPI 返回的数据:', store.getMenuData());
  } catch (error) {
    console.error('获取菜单数据失败:', error);
  }
};

const submitForm = () => {
  if (!loginFormRef.value) return;
  loginFormRef.value.validate((valid) => {
    if (valid) {
      fetchLoginData();
    } else {
      console.log('验证失败!');
      ElMessage.error('验证失败!');
    }
  });
};

const resetForm = () => {
  if (!loginFormRef.value) return;
  loginFormRef.value.resetFields();
};
</script>

<style scoped>
.login-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background-color: #f0f2f5;
}

.box-card {
  width: 480px;
}
</style>

六、Aside

说明:文件路径:  src/components/MainAsideCont.vue

 任务描述:在Aside视图中实现菜单数据的获取。

具体步骤

1.删除原获取menu数据的函数

2、增加   fetchMenuData,该方法负责异步获取菜单数据与存储。     

3. 在  生命周期方法中调用   fetchMenuData  与获取store的menuData

重点代码:

// 封装数据获取和处理逻辑
const fetchMenuData = () => {
  try {
    const result = store.getMenuData(); // 调用 store 获取数据
    console.log('main menuAPI 返回的数据:', store.getMenuData());
    console.error('main menuAPI :', result);

    if (Array.isArray(result)) {
      menuData.value = result as MenuItem[];
    } else {
      console.error('menuAPI 返回的数据不是数组:', result);
    }
  } catch (error) {
    console.error('获取菜单数据失败:', error);
  }
};
 
onMounted(() => {
  if (!store.getMenuData().length) {
    console.warn('菜单数据为空,尝试重新获取');
    fetchMenuData();
  } else {
    console.log('菜单数据已存在,无需重新获取');
    menuData.value = store.getMenuData() as MenuItem[];
    console.log('menuData.value:', menuData.value);
  }
});

完整代码:

<template>
  <el-menu
    :default-active="activeIndex"
    class="el-menu-vertical-demo"
    :collapse="isCollapse"
  >
    <h3 :key="TitleText">{{ TitleText }}</h3>
    <!-- 渲染没有子菜单的项 -->
    <el-menu-item
      v-for="item in noChilden"
      :key="item.index"
      :index="item.index"
      @click="handlemenu(item)"
    >
      <component v-if="item.icon" class="icon" :is="item.icon.name"></component>
      <span>{{ item.label }}</span>
    </el-menu-item>

    <!-- 渲染有子菜单的项 -->
    <el-sub-menu
      v-for="item in hasChilden"
      :key="item.index"
      :index="item.index"
    >
      <template #title>
        <component v-if="item.icon" class="icon" :is="item.icon.name"></component>
        <span>{{ item.label }}</span>
      </template>
      <el-menu-item
        v-for="subItem in item.children"
        :key="subItem.index"
        :index="subItem.index"
        @click="handlemenuchild(item, subItem)"
      >
        <span>{{ subItem.label }}</span>
      </el-menu-item>
    </el-sub-menu>
  </el-menu>
</template>

<script lang="ts" setup>
import { ref, computed, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { useAllDataStore } from '@/stores';

const store = useAllDataStore();

interface MenuItem {
  index: string;
  label: string;
  icon?: { name: string; __name: string };
  children?: MenuItem[];
}

// 确保 menuAPI 是一个数组,并赋值给 menuData
const menuData = ref<MenuItem[]>([]); // 初始化为空数组

// 封装数据获取和处理逻辑
const fetchMenuData = () => {
  try {
    const result = store.getMenuData(); // 调用 store 获取数据
    console.log('main menuAPI 返回的数据:', store.getMenuData());
    console.error('main menuAPI :', result);

    if (Array.isArray(result)) {
      menuData.value = result as MenuItem[];
    } else {
      console.error('menuAPI 返回的数据不是数组:', result);
    }
  } catch (error) {
    console.error('获取菜单数据失败:', error);
  }
};
 
onMounted(() => {
  if (!store.getMenuData().length) {
    console.warn('菜单数据为空,尝试重新获取');
    fetchMenuData();
  } else {
    console.log('菜单数据已存在,无需重新获取');
    menuData.value = store.getMenuData() as MenuItem[];
    console.log('menuData.value:', menuData.value);
  }
});


const hasChilden = computed(() => menuData.value.filter(item => item.children && item.children.length > 0));
const noChilden = computed(() => menuData.value.filter(item => !item.children || item.children.length === 0));

const activeIndex = ref('Home');
const router = useRouter();

const handlemenu = (item: MenuItem) => {
  router.push(item.index);
};

const handlemenuchild = (item: MenuItem, subItem: MenuItem) => {
  router.push(subItem.index);
};

const TitleText = computed(() => {
  return store.isCollapse ? '平台' : '测试平台管理';
});

const isCollapse = computed(() => store.isCollapse);

 
</script>

<style>
.el-menu {
  height: 100%; /* 设置整个布局的高度为 100%,确保布局占满整个视口 */
  border-right: none; /* 去掉右边框 */
}
.el-menu-vertical-demo:not(.el-menu--collapse) {
  width: 180px;
  min-height: 400px;
}
.el-menu-vertical-demo.el-menu--collapse {
  width: 60px; /* 收缩时的宽度 */
}

.icon {
  margin-right: 8px; /* 图标与文字之间的间距 */
  font-size: 18px; /* 图标的大小 */
  width: 18px;
  height: 18px;
  size: 8px;
  color: #606266; /* 图标的默认颜色 */
  vertical-align: middle; /* 垂直居中对齐 */
}

/* 鼠标悬停时的样式 */
.icon:hover {
  color: #409eff; /* 鼠标悬停时图标的颜色 */
}
</style>

七、运行效果

登录输入admin后菜单

输入user后菜单


后续 

        后面将重点解决,pinia持久化与动态路由

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端用户列表与后端分页协同设计

分页实现方案 在现代Web应用中&#xff0c;用户列表展示与分页是一个常见的功能需求。前端与后端通过API协同工作&#xff0c;使用PageHelper等工具实现高效分页。 例如&#xff1a; 后端实现 (使用PageHelper) public PageResult DishPage(DishPageQueryDTO dishPageQuery…

精准测试建设过程中遇到的一些问题

1.sqlite3 仅可以处理单个任务问题&#xff0c;多线程往往会面临数据库锁定 因为仅临时存储&#xff0c;后来在创建数据库时&#xff0c;给每个任务开了一个临时数据库&#xff0c;存储数据执行完毕后&#xff0c;删除db sql_insert_new:INSERT INTO analyze_api_resault_dynam…

【Docker】Dockerfile 编写实践

&#x1f47b;创作者&#xff1a;丶重明 &#x1f47b;创作时间&#xff1a;2025年4月8日 &#x1f47b;擅长领域&#xff1a;运维 目录 1. Dockerfile编写原则1.1.选择合适的基础镜像1.2.镜像层优化1.3.多阶段构建1.4.安全增强 2. 关键指令与技巧2.1.COPY vs ADD2.2.ENTRYPOIN…

LabVIEW商业软件开发注意问题

在 LabVIEW 商业软件开发进程中&#xff0c;性能优化、界面设计及兼容性与扩展性&#xff0c;对软件品质、用户体验和市场适配性起着决定性作用。下面&#xff0c;借助多个LabVIEW 编程特性的实际案例&#xff0c;深入分析这些方面的开发要点。 一、性能优化&#xff1a;提升软…

Java 中 SQL 注入问题剖析​

一、引言​ 在当今数字化时代&#xff0c;数据是企业和组织的核心资产之一。许多应用程序都依赖于数据库来存储和管理数据&#xff0c;而 Java 作为一种广泛使用的编程语言&#xff0c;常被用于开发与数据库交互的应用程序。然而&#xff0c;SQL 注入这一安全漏洞却如同隐藏在…

深度学习项目--分组卷积与ResNext网络实验探究(pytorch复现)

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 ResNext是分组卷积的开始之作&#xff0c;这里本文将学习ResNext网络&#xff1b;本文复现了ResNext50神经网络&#xff0c;并用其进行了猴痘病分类实验…

CSS 笔记——Flexbox(弹性盒布局)

目录 1. Flex 容器与 Flex 项目 2. 主轴与交叉轴 3. Flex 容器的属性 display flex-direction justify-content align-items align-content flex-wrap 4. Flex 项目的属性 flex-grow flex-shrink flex-basis flex align-self 5. Flexbox 的优点 6. Flexbox 的…

cpp(c++)win 10编译GDAL、PROJ、SQLite3、curl、libtiff

cpp&#xff08;c&#xff09;编译GDAL、PROJ、SQLite3 Sqlite3libtiffcurlprojGDAL Sqlite3 1、下载 Sqlite3 源码、工具、二进制预编译 exe Sqlite3 官网&#xff1a;https://www.sqlite.org/download.html 下载 sqlite-amalgamation-3430200.zipsqlite-dll-win64-x64-3430…

每日一题(小白)暴力娱乐篇23

由题意得知给我们一串数字&#xff0c;我们每次交换两位&#xff0c;最少交换多少次成功得到有顺序的数组。我们以平常的思维去思考&#xff0c;加入给你一串数字获得最少的交换次数&#xff0c;意味着你的交换后续基本不会变&#xff0c;比如说2 1 3 5 4 中1与2交换后不变&…

01-Redis-基础

1 redis诞生历程 redis的作者笔名叫做antirez&#xff0c;2008年的时候他做了一个记录网站访问情况的系统&#xff0c;比如每天有多少个用户&#xff0c;多少个页面被浏览&#xff0c;访客的IP、操作系统、浏览器、使用的搜索关键词等等(跟百度统计、CNZZ功能一样)。最开始存储…

【从零开始学习JVM | 第一篇】快速认识JVM

什么是JVM&#xff1f; JVM--Java虚拟机&#xff0c;它是Java实现平台无关性的基石。 Java程序运行的时候&#xff0c;编译器将Java代码编译为平台无关的Java字节码文件&#xff08;.class&#xff09;&#xff0c;接下来对应平台的JVM对字节码进行运行解释&#xff0c;翻译成…

使用RabbitMQ实现异步秒杀

搭建RabbitMQ 在虚拟机上用docker搭建RabbitMQ&#xff0c;首先拉取镜像 docker run --privilegedtrue -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:management mkdir -p /usr/local/docker/rabbitmq再创建rabbitmq容器&#xff0c;下面的命令已经能够创建之后…

解决华硕主板Z890m下载ubuntu20.04后没有以太网问题

问题描述&#xff1a; 华硕主板Z890m下载双系统ubuntu20.04后&#xff0c;发现ubuntu不能打开以太网。 问题原因&#xff1a; 华硕主板的网卡驱动是r8125,而ubuntu20.04的驱动版本是r8169&#xff0c;所以是网卡驱动不匹配造成 解决方案 开机界面按下F2进入BOIS模式&#…

xLua的Lua调用C#的2,3,4

使用Lua在Unity中创建游戏对象&#xff0c;组件&#xff1a; 相关代码如下&#xff1a; Lua --Lua实例化类 --C# Npc objnew Npc() --通过调用构造函数创建对象 local objCS.Npc() obj.HP100 print(obj.HP) local obj1CS.Npc("admin") print(obj1.Name)--表方法希…

Debian系统_主板作为路由器_测试局域网设备间网速

Debian系统_主板作为路由器_测试局域网设备间网速 一、360软件测网速 360测出来的网速实际上是宽带的速度&#xff0c;并不是路由器LAN口到电脑这一段的网速 二、使用iperf3 进行双向带宽测试 1、开发板端下载软件 //Debian系统或者/Ubuntu sudo apt update && sudo…

从 macos 切换到 windows 上安装的工具类软件

起因 用了很多年的macos, 已经习惯了macos上的操作, 期望能在windows上获得类似的体验, 于是花了一些时间来找windows上相对应的软件. 截图软件 snipaste​​​​​​ windows和macos都有的软件, 截图非常好用 文件同步软件 oneDrive: 尝试了不同的同步软件, 还是微软在各…

JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、复杂API请求、DOM操作、搜索和过滤等,array.map()的使用详解(附实际应用代码)

目录 JavaScript中通过array.map(&#xff09;实现数据转换、创建派生数组、异步数据流处理、复杂API请求、DOM操作、搜索和过滤等&#xff0c;array.map&#xff08;&#xff09;的使用详解&#xff08;附实际应用代码&#xff09; 一、什么时候该使用Array.map()&#xff0…

SQL优化技术分享:从 321 秒到 0.2 秒的性能飞跃 —— 基于 PawSQL 的 TPCH 查询优化实战

在数据库性能优化领域&#xff0c;TPC-H 测试集是一个经典的基准测试工具&#xff0c;常用于评估数据库系统的查询性能。本文将基于 TPCH 测试集中的第 20个查询&#xff0c;结合 PawSQL 自动化优化工具&#xff0c;详细分析如何通过 SQL 重写和索引设计&#xff0c;将查询性能…

密码学基础——DES算法

前面的密码学基础——密码学文章中介绍了密码学相关的概念&#xff0c;其中简要地对称密码体制(也叫单钥密码体制、秘密密钥体制&#xff09;进行了解释&#xff0c;我们可以知道单钥体制的加密密钥和解密密钥相同&#xff0c;单钥密码分为流密码和分组密码。 流密码&#xff0…

在 Linux 终端中轻松设置 Chromium 的 User-Agent:模拟手机模式与自定义浏览体验

在 Linux 系统中&#xff0c;通过终端灵活控制 Chromium 的行为可以大幅提升工作效率。本文将详细介绍如何通过命令行参数和环境变量自定义 Chromium 的 User-Agent&#xff0c;并结合手机模式模拟&#xff0c;实现更灵活的浏览体验。 为什么需要自定义 User-Agent&#xff1f;…