Vue3 + Vite 实战:接入钉钉 OAuth 扫码登录(内嵌二维码 + 跳转授权)
Vue3 Vite 实战接入钉钉 OAuth 扫码登录内嵌二维码 跳转授权本文基于 Vue 3 Vite TypeScript Pinia 的登录页工程完整演示钉钉开放平台OAuth2 授权码模式内嵌扫码DTFrameLogin与整页跳转授权两条链路并说明前后端如何用code换取业务 Token。照着步骤做本地即可跑通。一、先搞清楚我们要接的是哪一种「钉钉登录」钉钉开放能力里常见两类登录容易混类型典型场景前端关键字段本文是否覆盖OAuth2 网站应用登录PC 网页扫码 / 跳转授权拿code换用户身份client_id、redirect_uri、scopeopenid是企业内部 H5 / JSAPI钉钉客户端内打开 H5用corpId、dd.readycorpId、AgentId 等否本文方案是用户打开登录页 → 扫码或跳转钉钉授权 → 前端拿到授权码code→ 交给自家后端 → 后端用 AppSecret 向钉钉换用户信息并签发业务 Token → 前端进入系统首页。要点一句话前端只持有 Client IDAppKey可以写进环境变量。AppSecret / Client Secret 只能放在服务端绝不能出现在前端仓库或浏览器包里。二、整体架构与登录时序┌─────────────┐ 加载 CDN SDK ┌──────────────────────┐ │ 登录页 │ ───────────────────▶ │ g.alicdn.com │ │ (Vue SPA) │ │ h5-dingtalk-login │ └──────┬──────┘ └──────────────────────┘ │ │ ① DTFrameLogin 内嵌二维码 │ 或 ② 跳转 login.dingtalk.com/oauth2/auth ▼ ┌──────────────────────┐ │ 钉钉授权页 / 扫码端 │ └──────────┬───────────┘ │ 返回 authCode / ?code ▼ ┌──────────────────────┐ POST { code } ┌─────────────────┐ │ handleLoginByCode │ ──────────────────▶ │ 业务后端 │ └──────────────────────┘ │ /api/login/ │ │ dingtalk │ └────────┬────────┘ │ 用 Secret 调钉钉 API │ 签发 accessToken ▼ 前端存 Token跳转系统首页两条前端入口最终汇合到同一接口内嵌扫码SDK 成功回调里直接拿到authCode。按钮跳转钉钉把用户重定向回redirect_uri?codexxxstateyyy登录页从 URL 读取code。三、开放平台侧准备可实操清单3.1 创建应用打开 钉钉开放平台登录开发者账号。创建企业内部应用或按文档创建具备「登录」能力的应用以控制台当前产品名为准。在应用详情中找到Client ID也常叫 AppKey—— 给前端用。Client Secret也常叫 AppSecret——只给后端用。3.2 配置回调地址最容易踩坑在「登录与分享」或「应用首页 / 回调域名」一类配置里把授权回调地址加入白名单。地址必须与代码里拼出来的redirect_uri完全一致含协议、域名、路径、查询串。示例请换成你自己的域名https://www.example.com/login?typeding本地调试时若走内嵌扫码且redirect_uri取当前页面源还需要额外加http://localhost:8007/login?typeding经验跳转授权路径若写死了生产域名本地点「钉钉登录」按钮会跳到生产环境而不是本机。内嵌二维码一般用window.location.origin两边要分开想清楚。3.3 权限与 scope网站扫码登录常用response_typecodescopeopenidpromptconsent首次或需要用户确认授权时后端换 Token、查用户信息所需的接口权限在开放平台按官方文档开通具体接口名以钉钉最新文档为准。四、前端工程准备4.1 技术栈约定本文示例栈Vue 3 Vue Router 4 PiniaVite 5 TypeScriptAxios钉钉登录 SDKCDN 引入不装 npm 包CDN 地址https://g.alicdn.com/dingding/h5-dingtalk-login/0.37.0/ddlogin.js加载成功后全局会挂上window.DTFrameLogin部分旧文档还会提到DDLogin本方案以DTFrameLogin为准。4.2 环境变量在项目根目录.env/.env.development/.env.production中配置# 钉钉 OAuth Client ID与开放平台应用一致VITE_DINGTALK_CLIENT_IDdingxxxxxxxxxxxxxxxxVITE_前缀才会被 Vite 注入到前端代码。types/global.d.ts里可为ImportMetaEnv补上类型interfaceImportMetaEnv{readonlyVITE_DINGTALK_CLIENT_ID?:string;// ...}4.3 TypeScript 声明 SDK新建types/dingtalk.d.tsdeclareglobal{interfaceWindow{DTFrameLogin?:(config:{id:string;width:number;height:number},authConfig:{redirect_uri:string;client_id:string;scope?:string;response_type?:string;state?:string;prompt?:string;},onSuccess:(result:{redirectUrl?:string;authCode?:string;state?:string;})void,onFail?:(error:string)void)void;}}export{};五、工具层加载 SDK、拼跳转 URL、生成 state建议单独建src/utils/dingtalkAuth.ts把「可配置项」集中管理。/** 整页跳转授权使用的回调地址须与开放平台白名单一致 */constREDIRECT_URIhttps://www.example.com/login?typeding;exportfunctiongetClientId():string{constidimport.meta.env.VITE_DINGTALK_CLIENT_IDasstring|undefined;return(idString(id).trim())||;}/** CSRF 防护用的 state */exportconstgenerateState(){if(window?.crypto?.randomUUID){returnwindow.crypto.randomUUID();}returnstate-Date.now();};/** 内嵌扫码按当前访问源动态生成 redirect_uri需 URL encode */exportconstgetEncodedRedirectUri(){if(window?.location){returnencodeURIComponent(window.location.origin/login?typeding);}returnencodeURIComponent(REDIRECT_URI);};/** 动态注入钉钉登录 SDK只加载一次 */exportconstloadLoginSdk(version0.37.0){returnnewPromisevoid((resolve,reject){if(window.DTFrameLogin){resolve();return;}constscriptdocument.createElement(script);script.srchttps://g.alicdn.com/dingding/h5-dingtalk-login/${version}/ddlogin.js;script.onload()resolve();script.onerror()reject(newError(钉钉SDK加载失败));document.head.appendChild(script);});};/** 整页跳转到钉钉授权页 */exportconstredirectToAuthPage(){constclientIdgetClientId();constredirectUriREDIRECT_URI;conststategenerateState();sessionStorage.setItem(dingtalk_login_state,state);consturlnewURL(https://login.dingtalk.com/oauth2/auth);url.searchParams.set(redirect_uri,redirectUri);url.searchParams.set(response_type,code);url.searchParams.set(client_id,clientId);url.searchParams.set(scope,openid);url.searchParams.set(prompt,consent);url.searchParams.set(state,state);window.location.hrefurl.toString();};说明generateStatesessionStorage用于防 CSRF回调落地后建议校验state是否与本地一致见后文「踩坑」。内嵌扫码与按钮跳转的redirect_uri可以不同策略一个跟当前域名一个跟生产域名。两边都必须在开放平台登记。可在App.vue的onMounted里提前loadLoginSdk()缩短用户打开登录页后的等待。六、UI 组件内嵌二维码 「钉钉登录」按钮组件职责挂载后加载 SDK调用DTFrameLogin渲染二维码。扫码成功 →emit(login, authCode)。点击按钮 →redirectToAuthPage()整页授权。失败展示错误文案与重试。核心逻辑示意src/components/QrLoginPanel/index.vuetemplate div classflex flex-col justify-center items-center w-full h-full div classdd-qr-wrap div iddingtalk-container classdd-qr-inner/div div v-ifisLoading classdd-login-overlay n-spin sizesmall description加载钉钉登录... / /div /div n-text v-iferrorMessage typeerror{{ errorMessage }}/n-text n-button v-iferrorMessage quaternary clickhandleRetry重试/n-button n-button typeprimary clickhandleAuthRedirect钉钉登录/n-button /div /template script langts import { ref, defineComponent, onMounted } from vue; import { loadLoginSdk, getClientId, generateState, redirectToAuthPage, getEncodedRedirectUri, } from /utils/dingtalkAuth; export default defineComponent({ name: QrLoginPanel, emits: [login, error], setup(_, { emit }) { const isLoading ref(false); const errorMessage ref(); const onAuthSuccess (result: { authCode?: string }) { emit(login, result.authCode); }; const onAuthFail (error: unknown) { const msg typeof error string ? error : String(error); errorMessage.value msg; emit(error, msg); }; const renderQrCode () { const clientId getClientId(); const state generateState(); const redirectUri getEncodedRedirectUri(); sessionStorage.setItem(dingtalk_login_state, state); window.DTFrameLogin?.( { id: dingtalk-container, width: 300, height: 300 }, { redirect_uri: redirectUri, client_id: clientId, scope: openid, state, response_type: code, prompt: consent, }, onAuthSuccess, onAuthFail ); }; const initLogin async () { errorMessage.value ; if (!window.DTFrameLogin) { await loadLoginSdk(); } renderQrCode(); }; const handleRetry async () { isLoading.value true; try { await initLogin(); } catch (e) { onAuthFail(e); } finally { isLoading.value false; } }; const handleAuthRedirect () { try { redirectToAuthPage(); } catch (e) { onAuthFail(e); } }; onMounted(async () { isLoading.value true; try { await initLogin(); } catch (e) { onAuthFail(e); } finally { isLoading.value false; } }); return { isLoading, errorMessage, handleAuthRedirect, handleRetry }; }, }); /script容器样式要点给#dingtalk-container固定宽高如 300×300与DTFrameLogin的width/height一致避免二维码被裁切。登录页挂上组件n-tab-pane nameding tab钉钉扫码登录 QrLoginPanel loginhandleLoginByCode errorhandleScanError / /n-tab-pane七、拿到 code 之后调后端换业务 Token7.1 API 封装// src/api/user.tsimporthttpfrom/utils/http/axios;/** 钉钉扫码 / 授权回调登录 */exportfunctionloginByCode(params:{code:string;state?:string}){returnhttp.request({url:/api/login/dingtalk,method:post,data:params,},{// 保留后端原始结构自行判断 success / accessTokenisTransformResponse:false,});}请求体字段名以你们后端约定为准。本文示例发送{ code }注意若类型里曾写成authCode要以实际请求体为准避免类型与报文不一致。7.2 Pinia Store// store 片段asyncloginWithCode(params:{code:string;state?:string}){constresponseawaitloginByCode(params);const{data,success}response;if(data?.accessToken){constex7*24*60*60*1000;storage.set(ACCESS_TOKEN,data.accessToken,ex);storage.set(CURRENT_USER,data,ex);this.setToken(data.accessToken);this.setUserInfo(data);}returnresponse;}7.3 登录页统一处理扫码回调 URL 回跳consthandleLoginByCodeasync(authCode:string|any){if(!authCode||typeofauthCode!string){message.warning(未获取到授权码请重试);return;}// 建议同时校验 state见第八节constpayload{code:authCode};try{constresawaituserStore.loginWithCode(payload);const{success,message:msg,data}resas{success?:boolean;message?:string;data?:{accessToken?:string;account?:{id?:string;personName?:string;username?:string};};};if(!success||!data?.accessToken){message.error(msg||登录失败);return;}message.success(登录成功即将进入系统);router.replace(/);}catch(e:unknown){message.error(einstanceofError?e.message:登录失败);}};consthandleScanError(msg:string){message.error(msg||钉钉登录异常);};onMounted((){consturlParamsnewURLSearchParams(window.location.search);constcodeurlParams.get(code);if(code){loginType.valueding;handleLoginByCode(code);}});后端期望响应形态示例{success:true,message:ok,data:{accessToken:eyJhbGciOi...,account:{id:10001,personName:张三,username:zhangsan}}}7.4 后端要做什么前端对接视角前端仓库通常不包含 Secret 换票逻辑但联调时你需要后端同事实现大致流程接收POST /api/login/dingtalk读取code。使用Client ID Client Secret调用钉钉「用 code 换 userAccessToken / 用户信息」接口以钉钉最新 OpenAPI 为准。用钉钉用户唯一标识如unionId/openId匹配或绑定本地账号。签发你们自己的accessToken返回给前端。切记Secret 只出现在服务端配置中心或密钥库。八、本地联调步骤按顺序打勾Step 1配置环境npminstall编辑.env.developmentVITE_PORT8007VITE_DINGTALK_CLIENT_IDdingxxxxxxxxxxxxxxxx VITE_GLOB_API_URL_PREFIX/api# 开发代理指向你的后端服务示例VITE_PROXY[[/api,https://api.example.com]]Step 2开放平台白名单至少登记生产https://www.example.com/login?typeding本地若用动态 origin 扫码http://localhost:8007/login?typedingStep 3启动前端npmrun dev浏览器打开http://localhost:8007/login默认切到「钉钉扫码登录」页签应看到二维码区域。Step 4验证扫码链路手机钉钉扫码并确认授权。浏览器 Network 出现POST /api/login/dingtalkRequest Payload 含code。响应success: true且带accessToken。前端保存 Token 后跳转到系统首页如/。Step 5验证跳转链路点击「钉钉登录」。跳转到https://login.dingtalk.com/oauth2/auth?...授权后回到配置的redirect_uri地址栏出现code。登录页onMounted读到code后自动走同一套换票逻辑。九、常见问题与踩坑1. 二维码空白 / SDK 加载失败检查 CDN 是否被公司网络拦截可在 Network 看ddlogin.js是否 200。确认#dingtalk-container在调用DTFrameLogin时已挂载到 DOM。提供「重试」按钮重新执行initLogin。2.redirect_uri不匹配钉钉会直接拒绝授权。核对协议http/https端口本地8007路径/login查询参数?typeding是否也写进了白名单若代码里带了查询串白名单一般也要带3. 本地扫码能用按钮跳转却去了生产站这是「动态 origin」与「写死生产回调」两套策略并存时的正常现象。开发阶段可把redirectToAuthPage的redirectUri也改成当前 origin或单独做环境分支。4. 前端发了code后端却说字段不对对齐字段名codevsauthCode。以实际 JSON 为准不要只信类型定义。5.state写了却没校验写入sessionStorage[dingtalk_login_state]后回调时应conststateFromUrlurlParams.get(state);conststateLocalsessionStorage.getItem(dingtalk_login_state);if(stateFromUrlstateLocalstateFromUrl!stateLocal){message.error(登录状态校验失败请重试);return;}内嵌扫码成功回调里也会带回state同样建议比对。6. 登录成功但不跳转换票成功后记得显式跳转如router.replace(/)。若只存了 Token 却没有路由跳转用户会感觉「卡住」。7. Client ID 写进前端是否安全Client ID 本身是公开标识会出现在授权 URL 和前端包中这是 OAuth 公开客户端的常态。真正敏感的是Secret以及后端签发的业务 Token。十、文件清单对照实现路径作用.env*VITE_DINGTALK_CLIENT_IDtypes/dingtalk.d.tsDTFrameLogin全局类型src/utils/dingtalkAuth.tsSDK 加载、Client ID、跳转授权、statesrc/components/QrLoginPanel/index.vue内嵌二维码 跳转按钮src/views/login/index.vue处理授权码换票并进入首页src/api/user.tsPOST /api/login/dingtalksrc/store/modules/user.tsloginWithCode持久化 Tokensrc/App.vue可选预加载 SDK十一、小结接入钉钉网页扫码登录可以按这条最短路径落地开放平台创建应用拿到 Client ID / Secret配齐回调白名单。前端 CDN 加载h5-dingtalk-login用DTFrameLogin做内嵌扫码必要时再做oauth2/auth整页跳转。两条路都只负责拿到授权码用 Secret 换用户身份、发业务 Token 必须在服务端完成。登录成功后保存 Token并跳转到系统首页。把回调地址、字段名、state校验这三处对齐联调成功率会高很多。其余 UI、Tab、加载态按你们设计系统微调即可。参考链接钉钉开放平台钉钉登录 JS SDKCDNhttps://g.alicdn.com/dingding/h5-dingtalk-login/OAuth 授权入口https://login.dingtalk.com/oauth2/auth具体换票、用户信息接口以开放平台当前文档版本为准接口路径偶有迭代联调时请对照最新文档。

相关新闻

自动化文档命名系统:解决无标题文件管理难题

自动化文档命名系统:解决无标题文件管理难题

1. 项目概述作为一名从业多年的技术博主,我经常遇到一个令人头疼的问题——那些随手保存的代码片段、临时记录的想法、未完成的草稿,总是以"无标题"的形式散落在电脑各处。这个看似简单的命名问题,实际上反映了数字时代知识管理的普…

2026/8/7 17:18:12 阅读更多 →
LangChain与LangGraph:大模型应用开发中的链与图架构解析

LangChain与LangGraph:大模型应用开发中的链与图架构解析

如果你刚开始接触大语言模型应用开发,大概率会同时遇到 LangChain 和 LangGraph 这两个名字。它们都带着“Lang”前缀,都出现在各种教程和项目里,甚至很多文章会把它们混在一起讲。这很容易让人困惑:它们到底是一个东西的两个部分…

2026/8/7 17:18:34 阅读更多 →
Java转大模型:会调API的人很多,能搞定权限日志的为什么少?

Java转大模型:会调API的人很多,能搞定权限日志的为什么少?

聊《Java转大模型,真正值钱的为什么不是会调 API?》之前,先说一句实在的:别急着背概念,先看它在真实项目里到底解决什么问题。摘要最近面试了几个从Java后端转大模型的同学,有个现象挺有意思:简…

2026/8/6 15:09:28 阅读更多 →

最新新闻

Scroll Reverser终极指南:如何在Mac上为触控板和鼠标设置独立的滚动方向

Scroll Reverser终极指南:如何在Mac上为触控板和鼠标设置独立的滚动方向

Scroll Reverser终极指南:如何在Mac上为触控板和鼠标设置独立的滚动方向 【免费下载链接】Scroll-Reverser Per-device scrolling prefs on macOS. 项目地址: https://gitcode.com/gh_mirrors/sc/Scroll-Reverser 你是否曾在MacBook触控板上习惯了自然滚动&a…

2026/8/7 17:18:27 阅读更多 →
VC++运行库缺失修复指南:从诊断到安装的完整解决方案

VC++运行库缺失修复指南:从诊断到安装的完整解决方案

1. 项目概述:VC运行库问题的本质与影响如果你在打开某个软件或游戏时,突然弹出一个窗口,提示“找不到VCRUNTIME140.dll”、“MSVCP140.dll丢失”或者“应用程序无法正常启动(0xc000007b)”,那么恭喜你,你遇到了一个让无…

2026/8/7 17:18:27 阅读更多 →
TrollInstallerX:iOS 14-16.6.1 越狱级应用安装的终极解决方案

TrollInstallerX:iOS 14-16.6.1 越狱级应用安装的终极解决方案

TrollInstallerX:iOS 14-16.6.1 越狱级应用安装的终极解决方案 【免费下载链接】TrollInstallerX A TrollStore installer for iOS 14.0 - 16.6.1 项目地址: https://gitcode.com/gh_mirrors/tr/TrollInstallerX 在iOS生态系统中,应用安装一直是个…

2026/8/7 17:18:27 阅读更多 →
《CI/CD 流水线优化自动化交付 线上高并发排障实战》

《CI/CD 流水线优化自动化交付 线上高并发排障实战》

《CI/CD 流水线优化自动化交付 线上高并发排障实战》 作者: 苏沁宁 (苏沁宁)技术方向: AI Agent 编排、云原生 AI 应用部署、AI 音乐生成、Kubernetes 驱动的智能运维 💡 导语与现场排障背景 在生产环境重构 CI/CD 流水线优化与自动化交付 时,高并发场景…

2026/8/7 17:18:27 阅读更多 →
5分钟快速上手:Wand-Enhancer终极免费增强工具完全指南

5分钟快速上手:Wand-Enhancer终极免费增强工具完全指南

5分钟快速上手:Wand-Enhancer终极免费增强工具完全指南 【免费下载链接】Wand-Enhancer Advanced UX and interoperability extension for Wand (WeMod) app 项目地址: https://gitcode.com/GitHub_Trending/we/Wand-Enhancer Wand-Enhancer是一款专为WeMod客…

2026/8/7 17:18:27 阅读更多 →
简易门式钢结构安装及彩钢板房

简易门式钢结构安装及彩钢板房

简易门式钢结构安装及彩钢板房 施工技术及要点 近年来,由于钢结构的特点及国家钢产量的提高,使钢结构技术迅猛发展。在我国兴建了大量的钢结构轻钢厂房,大跨度厂房及高层建筑物。尤其以门式刚架结构应用最为广泛,其原因由钢结构它本身特点所定。 一、钢结构特点 1、建筑钢…

2026/8/7 17:17:27 阅读更多 →

日新闻

为什么scrcpy成为Android投屏的终极解决方案:完整实战指南

为什么scrcpy成为Android投屏的终极解决方案:完整实战指南

为什么scrcpy成为Android投屏的终极解决方案:完整实战指南 【免费下载链接】scrcpy Display and control your Android device 项目地址: https://gitcode.com/GitHub_Trending/sc/scrcpy 想要将Android手机屏幕完美投射到电脑上,享受大屏操作的自…

2026/8/7 0:00:19 阅读更多 →
如何在5分钟内掌握Tom Select:打造现代化表单选择器的终极指南

如何在5分钟内掌握Tom Select:打造现代化表单选择器的终极指南

如何在5分钟内掌握Tom Select:打造现代化表单选择器的终极指南 【免费下载链接】tom-select Tom Select is a lightweight (~16kb gzipped) hybrid of a textbox and select box. Forked from selectize.js to provide a framework agnostic autocomplete widget wi…

2026/8/7 0:00:19 阅读更多 →
5分钟快速上手:NSZ压缩工具终极指南,轻松管理Switch游戏文件

5分钟快速上手:NSZ压缩工具终极指南,轻松管理Switch游戏文件

5分钟快速上手:NSZ压缩工具终极指南,轻松管理Switch游戏文件 【免费下载链接】nsz NSZ - Homebrew compatible NSP/XCI compressor/decompressor 项目地址: https://gitcode.com/gh_mirrors/ns/nsz 你是否在为Nintendo Switch游戏文件占用大量存储…

2026/8/7 0:00:19 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/6 22:02:27 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/6 22:02:27 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/6 22:02:27 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/7 17:02:37 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/6 22:02:28 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/7 17:02:36 阅读更多 →