ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JS实现动态一、二级标签展示:TaoToken 统一 Key 接入实战

JS实现动态一、二级标签展示:TaoToken 统一 Key 接入实战 1. 原生 JS 动态标签树从静态表格到可折叠一级/二级标签前端做标签筛选的场景太多了比如应用管理后台、商品分类、文章归档几乎都绕不开「一级标签点一下二级标签跟着变」这种交互。我见过不少项目还在用后端拼 HTML 表格的方式渲染标签改一个样式要动 Java 代码加一层标签要重写循环维护起来很痛苦。这篇就聊怎么用原生 JS 把一级标签、二级标签做成可折叠的树形结构数据从接口拿渲染和交互全在前端完成。核心检索词先摆出来JS 动态标签、一级标签、二级标签、可折叠标签树、原生 JS 递归渲染。这套方案能做什么简单说你给接口一个tagId它返回子标签列表前端递归渲染成树点一级展开二级点二级回填选中值。适合谁适合正在做后台管理系统、内容分类、筛选面板的前端同学尤其是项目里没上 React/Vue 框架、只能用原生 JS 或 jQuery 的场景。我试过把标签数据源从自建接口切到 TaoToken 统一 Key 通道好处是 Base URL 和 Key 统一管理前端不用关心后端标签服务部署在哪换环境只改一个配置。下面从数据结构、递归渲染、折叠交互到接口连通性验证一步步给可复制的代码。先明确整体思路。标签数据是典型的树形结构一级标签有id、name、children二级标签挂在children里。渲染时用递归函数遍历遇到有子节点的就生成可折叠容器点击一级标签切换展开状态点击二级标签触发选中回调。接口层统一走一个fetchTags(parentId)方法Base URL 指向 TaoToken 通道返回 JSON 后交给渲染函数。这里有个容易踩的坑很多人把一级标签和二级标签写成两套渲染逻辑结果加第三级时全崩。正确做法是只写一个递归函数层级由数据决定代码不关心是第几级。下面进入具体实现。2. TaoToken 统一 Key 通道前置准备Base URL 与标签接口对接在写渲染代码之前先把数据通道打通。标签数据本质是一次 HTTP 请求返回 JSON。传统做法是前端直接请求后端某个/app/getAppTags接口但这样 Base URL 散落在代码各处测试环境和生产环境切换要改多处。用 TaoToken 统一 Key 通道的思路是所有模型/数据类请求走同一个 Base URLKey 统一在请求头里带前端只维护一份配置。TaoToken 是什么它是一个统一 Key 接入通道把不同来源的接口调用收敛到一个 Base URL 和一套鉴权方式上。对前端来说你不需要知道标签服务具体部署在哪个域名只需要把请求发到统一入口带上 Key 和模型/服务标识即可。适合需要频繁切换数据源、或者多个小服务想统一鉴权的团队。前置准备分三步。第一步拿到 API Key。访问https://taotoken.net/api-keysdeep link 带归因参数?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite在控制台创建一个 Key复制保存。第二步确认 Base URL 是https://taotoken.net/api注意这个地址不带 UTM 参数是纯接口地址。第三步确认你要调用的模型或服务 ID标签数据这类结构化请求通常走对话模型返回 JSON或者走专门的数据接口具体看你的通道配置。这里要强调一个安全点Key 不要硬编码在前端源码里提交到仓库。正确做法是通过环境变量注入或者由后端代理转发。前端直连时至少要做域名白名单和请求频率限制。下面配置片段里我用占位符YOUR_API_KEY你替换成自己的。配置统一请求头这是所有后续请求的基础// config.js —— 统一通道配置前端只维护这一份 const TAOTOKEN_CONFIG { baseUrl: https://taotoken.net/api, apiKey: YOUR_API_KEY, // 实际项目请从环境变量注入 modelId: your-model-id, // 按通道文档填写对应模型/服务 ID timeout: 8000 }; // 通用请求封装标签接口和后续其他接口都复用它 async function requestTaoToken(path, payload) { const controller new AbortController(); const timer setTimeout(() controller.abort(), TAOTOKEN_CONFIG.timeout); try { const resp await fetch(${TAOTOKEN_CONFIG.baseUrl}${path}, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${TAOTOKEN_CONFIG.apiKey} }, body: JSON.stringify(payload), signal: controller.signal }); if (!resp.ok) { throw new Error(HTTP ${resp.status}: ${await resp.text()}); } return await resp.json(); } finally { clearTimeout(timer); } }如果你用的是 Claude Code 这类编码工具做联调配置方式略有不同。Claude Code 走的是 Anthropic 兼容协议需要在 settings 里指定 Base URL 和 Key。配置文件路径通常是~/.claude/settings.json内容如下{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: YOUR_API_KEY, ANTHROPIC_MODEL: your-model-id } }三件套记牢Base URL、Key、Model ID缺一个都连不上。Cline 的 MCP 配置也是同样逻辑在 MCP server 配置里填这三个值。Codex 的auth.json则是把 Key 和 Base URL 写进认证文件。不管哪个工具本质都是把请求指向统一通道。前置准备做完你应该有一个可用的 Key、确认过的 Base URL、以及一个能返回标签 JSON 的模型或服务 ID。下一步写标签数据结构和渲染函数。3. 可复制配置标签数据结构与递归渲染函数标签数据用树形结构最自然。一级标签是根节点数组每个节点有id、name、children。二级标签放在children里没有子节点时children为空数组。这种结构递归渲染时不用判断层级代码统一处理。先定义数据结构这是接口返回的 JSON 形态{ code: 0, data: [ { id: 1, name: 前端开发, children: [ { id: 101, name: JavaScript, children: [] }, { id: 102, name: CSS, children: [] }, { id: 103, name: HTML, children: [] } ] }, { id: 2, name: 后端开发, children: [ { id: 201, name: Java, children: [] }, { id: 202, name: Go, children: [] } ] }, { id: 3, name: 数据库, children: [ { id: 301, name: MySQL, children: [] }, { id: 302, name: Redis, children: [] } ] } ] }注意children即使为空也要保留字段这样递归时不用做undefined判断减少出错。如果你的接口返回的是扁平列表加parentId需要先在前端做一次「扁平转树」转换函数后面排障章节会给。接下来是递归渲染函数。核心逻辑遍历节点数组为每个节点创建 DOM如果有children就递归渲染子容器并绑定折叠事件。用document.createElement而不是拼字符串避免 XSS 和事件丢失。// renderTags.js —— 递归渲染标签树 function renderTagTree(container, nodes, level 1) { const ul document.createElement(ul); ul.className tag-level tag-level-${level}; nodes.forEach(node { const li document.createElement(li); li.className tag-node; li.dataset.id node.id; const label document.createElement(span); label.className tag-label; label.textContent node.name; const hasChildren Array.isArray(node.children) node.children.length 0; if (hasChildren) { // 一级标签加折叠箭头点击切换展开 const toggle document.createElement(span); toggle.className tag-toggle; toggle.textContent ▸; li.appendChild(toggle); li.appendChild(label); const childBox document.createElement(div); childBox.className tag-children; childBox.style.display none; // 默认折叠 renderTagTree(childBox, node.children, level 1); li.appendChild(childBox); li.classList.add(has-children); li.addEventListener(click, (e) { if (e.target label || e.target toggle) { const expanded childBox.style.display ! none; childBox.style.display expanded ? none : block; toggle.textContent expanded ? ▸ : ▾; li.classList.toggle(expanded, !expanded); } }); } else { // 二级标签点击触发选中 li.appendChild(label); li.addEventListener(click, () { document.querySelectorAll(.tag-node.selected) .forEach(el el.classList.remove(selected)); li.classList.add(selected); if (typeof window.onTagSelected function) { window.onTagSelected(node.id, node.name); } }); } ul.appendChild(li); }); container.appendChild(ul); }配套的 CSS 让层级清晰缩进和箭头状态用类控制.tag-level { list-style: none; padding-left: 0; margin: 0; } .tag-level-2 { padding-left: 20px; } .tag-node { padding: 6px 8px; cursor: pointer; border-radius: 4px; } .tag-node:hover { background: #f0f4ff; } .tag-node.selected { background: #dbeafe; font-weight: 600; } .tag-toggle { display: inline-block; width: 16px; color: #666; } .tag-children { margin-top: 4px; }调用方式很简单拿到数据后清空容器再渲染async function loadAndRenderTags() { const container document.getElementById(tagTree); container.innerHTML p加载中.../p; try { const result await requestTaoToken(/chat/completions, { model: TAOTOKEN_CONFIG.modelId, messages: [ { role: user, content: 返回标签树 JSON格式为 [{id,name,children}] } ] }); // 实际项目里这里解析 result 得到标签数组 const tags parseTagsFromResponse(result); container.innerHTML ; renderTagTree(container, tags); } catch (err) { container.innerHTML p classerror加载失败${err.message}/p; } }这里parseTagsFromResponse负责从模型返回里提取 JSON因为模型返回通常是文本需要JSON.parse。如果你的通道直接返回结构化数据这一步可以省掉。渲染函数和数据结构都给了接下来验证接口是否真的通。4. 验证请求一次接口连通性检查与成功结果写完代码别急着调 UI先单独验证接口通不通。很多「标签不显示」的问题根源不在渲染而在请求根本没成功。验证分两步先用命令行发一次请求确认 Key 和 Base URL 有效再在浏览器里看网络面板确认前端请求参数正确。命令行验证用 curl这是最直接的方式。注意 Base URL 是https://taotoken.net/api路径按你的通道文档填curl -X POST https://taotoken.net/api/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer YOUR_API_KEY \ -d { model: your-model-id, messages: [ {role: user, content: 返回一个标签树 JSON 示例} ] }成功的话你会看到类似这样的返回重点是choices数组里有内容{ id: chatcmpl-xxx, object: chat.completion, choices: [ { index: 0, message: { role: assistant, content: [{\id\:1,\name\:\前端开发\,\children\:[{\id\:101,\name\:\JavaScript\,\children\:[]}]}] }, finish_reason: stop } ] }看到choices[0].message.content里有 JSON 字符串说明通道通了。如果返回 401检查 Key 是否复制完整、有没有多余空格。如果返回 404检查路径和 Base URL 拼接是否正确注意不要重复加/api。浏览器端验证打开开发者工具的 Network 面板触发loadAndRenderTags看请求的 Request URL、Headers 里的 Authorization、以及 Response。一个常见问题是 CORS如果控制台报Access-Control-Allow-Origin说明通道没开跨域需要后端代理或确认通道支持浏览器直连。验证通过后页面应该能看到一级标签列表点击展开二级标签点击二级标签高亮并触发回调。你可以加一个简单的回调把选中值打到控制台window.onTagSelected function(id, name) { console.log(选中标签:, id, name); document.getElementById(selectedTag).textContent ${name} (${id}); };实测下来从请求发出到标签渲染完成正常在 1 秒内。如果超过 3 秒检查是不是模型返回太慢或者标签数据量太大导致递归渲染卡顿。数据量超过 500 个节点时建议做虚拟滚动或分批渲染这个后面排障会提。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth接入过程中报错集中在几类逐个对照排查。这些错误信息你大概率会遇到提前知道原因能省很多时间。401 Unauthorized。最常见Key 无效或没带上。检查三点Authorization 头格式是不是Bearer YOUR_API_KEYBearer 后面有空格Key 有没有过期或被删除请求有没有经过会剥离 Header 的代理。前端直连时有些浏览器扩展会改请求头用无痕模式排除干扰。local proxy failed。这个报错通常出现在本地开发环境请求被本地代理拦截了。检查你的开发服务器 proxy 配置或者系统代理设置。如果你在用 Claude Code 或 Cline检查它们的网络配置有没有指向一个不可用的本地端口。解决方法是把 Base URL 直接指向https://taotoken.net/api绕过本地代理。reading choices。报错形如Cannot read properties of undefined (reading choices)说明返回结构和你预期的不一样。可能是请求失败返回了错误对象但你直接取了result.choices。加一层判断if (!result || !result.choices || !result.choices[0]) { throw new Error(返回结构异常: JSON.stringify(result)); } const content result.choices[0].message.content;OAuth 相关报错。如果你用 Claude Code 的 OAuth 登录方式可能会遇到 token 刷新失败。这时候改用 API Key 方式在 settings 里配ANTHROPIC_API_KEY不要混用 OAuth 和 Key。Codex 的auth.json如果同时有 OAuth token 和 API Key也可能冲突清掉 OAuth 部分只留 Key。标签渲染出来但点击没反应。检查事件绑定是不是绑在了父容器上被阻止冒泡或者children字段是undefined导致hasChildren判断为 false二级标签被当成一级渲染了。统一用Array.isArray(node.children)判断。扁平数据转树。如果接口返回的是[{id, name, parentId}]需要先转换function flatToTree(list, parentId 0) { return list .filter(item item.parentId parentId) .map(item ({ ...item, children: flatToTree(list, item.id) })); }这个函数递归把扁平列表转成树parentId为 0 的是根节点。转换后再交给renderTagTree渲染。数据量大卡顿。超过 500 个节点时一次性渲染会阻塞主线程。改成点击一级标签时才请求并渲染其二级标签即懒加载。把renderTagTree里的递归渲染改成点击时异步加载children能显著提升首屏速度。排障的核心思路是先确认请求通不通再确认数据结构对不对最后确认渲染逻辑。三步定位基本能覆盖九成问题。6. 语义一致 CTA把标签通道接入你的项目标签树渲染本身不复杂难的是数据通道的稳定和统一。把 Base URL 收敛到 TaoToken 统一 Key 通道后前端只维护一份配置换环境、加服务、调模型都只改一处。这套思路不只适用于标签任何需要动态数据的场景都能复用。如果你正在做长期编码项目或者需要频繁调用模型做数据处理可以看看 Coding Plan它适合需要稳定通道和统一管理的开发场景https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite。如果只是想先验证模型返回结构用模型对话快速试一次https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite。接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite里面有各语言和各工具的完整配置示例。最后给一个实用技巧把标签请求的返回结果缓存到sessionStorage同一会话内切换页面不用重复请求。缓存 key 用tag_tree_${parentId}设置 5 分钟过期。这样既减少请求又保证数据不会太旧。代码就几行function getCachedTags(key, ttl 300000) { const raw sessionStorage.getItem(key); if (!raw) return null; const { data, ts } JSON.parse(raw); if (Date.now() - ts ttl) { sessionStorage.removeItem(key); return null; } return data; }渲染前先查缓存没有再请求请求成功后写入缓存。这个模式在标签、下拉选项、字典数据场景里都能直接用。
返回列表