1. 实习第六天后台列表页从 Flex 布局到 KeepAlive 缓存全流程今天这篇日记记录的是我在实习第六天做的一个完整小需求用 Vue3 Element Plus 把后台管理系统的列表页从零搭起来先用 Flex CSS 把卡片区和表格区混排好再接入 KeepAlive 让筛选条件在页面切换后不丢失最后把请求的 Base URL 改到 TaoToken 上验证接口连通。整个流程走下来踩了几个坑也把几个知识点真正串起来了。如果你也在做类似的后台列表页或者正在学 Vue3 的 KeepAlive 缓存这篇内容可以直接跟着操作。核心检索词就是 Vue3 Element Plus 后台列表页、Flex 弹性布局、KeepAlive 缓存筛选状态、Base URL 配置。适合刚上手 Vue3 的实习生、转前端的新人以及需要快速搭一个可维护列表页的开发者。我今天的场景很具体一个能源行业的后台管理系统列表页需要展示设备卡片概览 详细数据表格顶部有筛选栏切换菜单再回来时筛选条件不能丢。技术栈是 Vue3 Element Plus Vite请求层用 axios 封装。下面按实际开发顺序拆开讲。2. 前置准备TaoToken 接入与项目环境确认在动手写布局之前先把请求层的事情理清楚。因为列表页的数据要从接口拿接口的 Base URL 如果配错后面调试布局时会被网络错误干扰分不清是样式问题还是请求问题。所以我的习惯是先把 Base URL 和 Key 配好再写页面。TaoToken 在这里的角色是统一的大模型 API 接入层。你可以把它理解成一个请求入口前端项目里所有需要调用模型能力的请求都通过它转发Base URL 统一、Key 统一管理不用在每个页面里散落不同的地址。对于后台系统来说这种统一入口的好处是后期换模型、加模型都不用改业务代码。你需要准备三样东西Base URL、API Key、Model ID。Base URL 用https://taotoken.net/api注意这个地址后面不加任何路径后缀axios 的 baseURL 直接写它就行。API Key 在控制台的 API Keys 页面生成生成后复制保存页面刷新后就看不到了。Model ID 根据你要用的模型填比如对话类、代码类各有对应的 ID。环境确认这块我建议先在项目根目录建一个.env.local文件把敏感信息放进去不要硬编码在代码里。Vite 项目里以VITE_开头的变量才能在客户端读取。配置片段如下# .env.local VITE_API_BASE_URLhttps://taotoken.net/api VITE_API_KEY你的Key VITE_MODEL_ID你的模型ID然后在src/utils/request.js里创建 axios 实例import axios from axios const request axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 30000, headers: { Content-Type: application/json, Authorization: Bearer ${import.meta.env.VITE_API_KEY} } }) request.interceptors.response.use( (response) response.data, (error) { console.error(请求失败:, error.response?.status, error.message) return Promise.reject(error) } ) export default request这里有个细节Authorization头用Bearer加 Key 的格式中间有一个空格少写空格会直接 401。我第一天就因为这个空格排查了十分钟。另外 timeout 设 30 秒模型类请求有时候响应慢设太短会误报超时。环境确认完可以写一个最小的连通性测试在App.vue的onMounted里发一个请求看控制台有没有正常返回。这一步过了再进入布局开发心里有底。3. 可复制配置Flex 容器与 KeepAlive 路由配置片段这一节是今天的核心交付。我把 Flex 布局的容器配置和 KeepAlive 的路由配置都整理成可以直接复制的片段你按自己的项目结构放进去就能跑。先说 Flex 布局。后台列表页的经典结构是顶部筛选栏 中间内容区卡片概览 表格。我用的是纵向 Flex 外层 横向 Flex 内层的嵌套方案。外层容器display: flex; flex-direction: column让筛选栏和内容区上下排列内容区里再开一个横向 Flex左边放卡片区右边放表格区或者上下再分。/* 页面根容器纵向排列 */ .list-page { display: flex; flex-direction: column; height: 100%; padding: 16px; gap: 16px; box-sizing: border-box; } /* 筛选栏横向排列自动换行 */ .filter-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; padding: 16px; background: var(--bg-card); border-radius: 8px; } /* 内容区横向排列卡片区固定宽表格区自适应 */ .content-area { display: flex; flex: 1; gap: 16px; min-height: 0; /* 关键防止 flex 子项溢出 */ } /* 卡片区固定宽度内部纵向排列 */ .card-panel { display: flex; flex-direction: column; gap: 12px; width: 280px; flex-shrink: 0; overflow-y: auto; } /* 表格区占满剩余空间 */ .table-panel { flex: 1; min-width: 0; /* 关键防止表格撑破容器 */ background: var(--bg-card); border-radius: 8px; padding: 16px; }这里有两个容易忽略的点。第一min-height: 0和min-width: 0。Flex 子项默认min-height: auto内容多了会把容器撑开导致滚动条出现在错误的位置。加上min-height: 0后子项才能正确收缩并触发内部滚动。第二flex-shrink: 0用在卡片区保证它不被表格区挤压变形。CSS 变量我统一放在:root里方便后期换肤:root { --bg-card: #ffffff; --bg-page: #f5f7fa; --text-primary: #303133; --border-color: #dcdfe6; --brand-color: #409eff; }然后是 KeepAlive 的路由配置。Vue3 里 KeepAlive 配合router-view使用关键是给需要缓存的组件加name并在路由 meta 里标记keepAlive: true。我项目里用的是script setup组件名通过defineOptions指定。// router/index.js import { createRouter, createWebHistory } from vue-router const routes [ { path: /device/list, name: DeviceList, component: () import(/views/device/DeviceList.vue), meta: { title: 设备列表, keepAlive: true } }, { path: /device/detail/:id, name: DeviceDetail, component: () import(/views/device/DeviceDetail.vue), meta: { title: 设备详情, keepAlive: false } } ] export default createRouter({ history: createWebHistory(), routes })在App.vue或布局组件里这样写template router-view v-slot{ Component, route } keep-alive :includecachedViews component :isComponent :keyroute.path / /keep-alive /router-view /template script setup import { computed } from vue import { useRouter } from vue-router const router useRouter() const cachedViews computed(() { return router.getRoutes() .filter(r r.meta?.keepAlive) .map(r r.name) }) /script注意:include匹配的是组件的name不是路由的name。这两个名字最好保持一致否则缓存不生效。我在DeviceList.vue里用defineOptions({ name: DeviceList })显式声明和路由 name 对齐。筛选状态不丢的关键在于筛选条件存在组件的reactive或ref里KeepAlive 缓存的是组件实例所以状态自然保留。但如果你把筛选条件同步到了 URL query那还需要在onActivated里从 query 恢复这个后面排障部分会讲。4. 验证请求与成功结果列表接口连通 缓存不丢筛选配置写完后我做了两轮验证。第一轮验证接口连通第二轮验证缓存效果。接口连通验证在DeviceList.vue里写一个加载函数调用列表接口。因为 Base URL 已经指向 TaoToken请求会经过统一入口。我用的是 POST 请求body 里带筛选参数。import { ref, reactive, onMounted } from vue import request from /utils/request const loading ref(false) const tableData ref([]) const filterForm reactive({ keyword: , status: , page: 1, pageSize: 10 }) async function fetchList() { loading.value true try { const res await request.post(/v1/chat/completions, { model: import.meta.env.VITE_MODEL_ID, messages: [ { role: user, content: 查询设备列表条件${JSON.stringify(filterForm)} } ] }) console.log(接口返回:, res) tableData.value res.choices?.[0]?.message?.content ? JSON.parse(res.choices[0].message.content) : [] } catch (err) { console.error(列表加载失败:, err) } finally { loading.value false } } onMounted(() { fetchList() })实测下来请求发出后控制台能看到正常的返回结构choices数组里有内容说明 Base URL 和 Key 都配对了。如果返回 401说明 Key 有问题如果返回 404说明路径写错了。TaoToken 的接口路径是/v1/chat/completions注意 baseURL 已经带了/api所以完整地址是https://taotoken.net/api/v1/chat/completions。缓存验证我在筛选栏输入关键词点查询表格刷新。然后点进详情页再返回列表页。返回后筛选栏里的关键词还在表格数据也没有重新请求可以在onMounted里打日志确认第二次进入不会触发onMounted而是触发onActivated。这就说明 KeepAlive 生效了。这里有个细节KeepAlive 缓存的组件第一次进入走onMounted之后每次进入走onActivated。如果你需要在每次进入时刷新数据就把请求逻辑放在onActivated里如果希望保留状态不刷新就放在onMounted里。我今天的场景是保留筛选状态所以请求放在onMountedonActivated里只做必要的轻量同步。成功结果的表现切换菜单再回来筛选条件保留、滚动位置保留、表格数据不重新加载页面切换瞬间完成没有白屏闪烁。这就是 KeepAlive 带来的体验提升。5. 本篇常见错误排查401、local proxy failed、reading choices、OAuth这一节把我今天遇到和预判到的报错整理出来对照着排查能省不少时间。401 Unauthorized最常见的原因是 Key 没带对。检查三点Authorization头是不是Bearer加 Key中间有空格.env.local里的变量名是不是以VITE_开头改完.env.local后有没有重启 dev server。Vite 的环境变量是构建时注入的不重启不生效。另外 Key 复制时前后有没有多余空格也会导致 401。local proxy failed这个报错通常出现在你配了 Vite 的 proxy 转发但目标地址写错或服务没起来。如果你直接用 TaoToken 的完整 Base URL不需要配 proxy把vite.config.js里的 proxy 配置去掉即可。如果确实需要 proxy检查target是不是https://taotoken.netchangeOrigin是不是true。reading choices这个报错说明返回结构里没有choices字段但你代码里直接读了res.choices[0]。原因可能是请求失败返回了错误对象或者返回的是流式数据。加一层防御res?.choices?.[0]?.message?.content并且在读取前判断res是否存在。另外检查请求路径是不是/v1/chat/completions路径错了返回的结构完全不同。OAuth 相关报错如果你在项目里集成了 OAuth 登录报错可能是 token 过期或回调地址不匹配。检查redirect_uri是否和平台配置的一致token 是否在有效期内。OAuth 和 API Key 是两套体系不要混用。KeepAlive 不生效检查三个地方。组件name和路由name是否一致:include里是否包含了这个 namecomponent的:key是不是用了route.path如果用route.fullPathquery 变化会导致 key 变化缓存失效。我一开始用了fullPath筛选条件一变 key 就变缓存直接没了改成path后正常。筛选条件丢失如果你把筛选条件同步到了 URL queryKeepAlive 缓存的是组件状态但 URL 变化会触发路由更新。需要在onActivated里从route.query恢复筛选条件或者在筛选变化时用router.replace更新 query 但不触发组件重建。对照这些报错排查基本能覆盖今天遇到的网络和缓存问题。核心原则是先确认请求通不通再确认缓存生不生效最后确认状态同步逻辑对不对。6. 后续接入与工具选择建议今天这个列表页跑通后后面要做的就是把请求层的能力用起来。如果你只是验证模型返回可以直接在模型对话页面测试接口和参数确认返回格式后再写进代码。如果你要长期做编码辅助或者 Agent 类功能建议了解一下 Coding Plan它更适合持续性的开发场景不用每次单独配 Key。接入文档里有完整的参数说明和示例遇到不确定的字段可以先查文档再写代码。API Keys 页面用来管理你的 Key建议按项目分 Key方便后期排查和回收。控制台可以看请求量和调用情况调试阶段很有用。今天最大的收获不是某个 API 怎么写而是把布局 → 请求 → 缓存这条链路完整走了一遍。Flex 的min-height: 0、KeepAlive 的include匹配、Base URL 的环境变量注入这三个点单独看都很小但组合在一起就是后台列表页的标准做法。明天准备拉项目代码上手实操把这些配置套到真实业务页面里再验证一遍。