发现这些功能不可用、求一个教程,最好免费。或者小钱。 还有就是测试压力有推荐app么 1 个帖子 - 1 位参与者 阅读完整话题
小白开发插件,在期间看文件的时候学到的一些东西,特地分享给大家: 博文链接: https://418122.xyz/posts/project/chrome-extension-basic-structure-beginners-guide 插件仓库链接: https://github.com/V-IOLE-T/tab-harbor GitHub 正在尝试上架 Chrome 插件商店 很多人第一次打开一个 Chrome 插件项目时,会看到一堆文件名,然后立刻陷入混乱。 index.html 、 style.css 、 manifest.json 、 background.js 、 content.js 、 app.js 看起来都像"代码文件",可它们根本不在同一个层面上工作。想真正看懂插件,关键不是背文件名,而是理解这些文件分别处在什么位置,承担什么职责,以及它们如何配合浏览器、网页和用户界面一起运转。 这篇文章想做的事很直接,就是把这些文件背后的逻辑彻底串起来,让你在看到一个插件目录时,能迅速判断每个文件到底在做什么。 index.html 是插件界面的骨架 在 Chrome 插件里, index.html 通常用来描述某个页面的内容和结构。它决定页面上会出现什么元素,比如标题、按钮、输入框、文本区域,也负责把样式文件和脚本文件接进来。你可以把它理解为一个界面的骨架,因为页面里"有什么"这件事,主要就是由 HTML 决定的。 如果一个插件有弹窗界面,那么点击插件图标后看到的内容,通常来自某个 HTML 文件。有些项目把它命名为 popup.html ,有些项目也会叫 index.html 。名字并不重要,重要的是它是否被浏览器当成插件页面真正加载了。 下面这个例子足够说明它的角色: <!DOCTYPE html> <html> <head> <link rel="stylesheet" href="style.css"> </head> <body> <h1>Hello Chrome Extension</h1> <button>点击我</button> </body> </html> 这段代码里,页面里有哪些内容,完全由 HTML 决定。它告诉浏览器,这个页面有一个标题,有一个按钮,同时还引入了样式文件。你在页面里看到的一切界面元素,本质上都从这里开始。 style.css 决定界面看起来是什么样 如果说 HTML 负责把页面元素摆出来,那么 style.css 负责让这些元素有可读性、有层次感,也更像一个真正能用的界面。字体大小、颜色、背景、边距、按钮的外观、元素之间的排列方式,这些都属于 CSS 的领域。 比如下面这段代码: body { width: 250px; font-family: Arial, sans-serif; padding: 10px; } h1 { color: blue; font-size: 18px; } button { background-color: green; color: white; } 这段样式并没有改变页面里"有什么",但它显著改变了页面"长什么样"。这正是 CSS 的作用。很多初学者一开始会把 HTML 和 CSS 混着理解,实际上它们解决的是两个完全不同的问题。HTML 决定内容和结构,CSS 决定视觉和排版。 放到插件环境里也是一样。无论这个页面是弹窗页、选项页,还是插件扩展出来的其他界面,CSS 的职责都很稳定,就是把原本生硬的结构变成可以阅读、可以操作、也更符合界面习惯的样子。 在插件里,HTML、CSS、JavaScript 分别站在不同位置上 只看 HTML 和 CSS,还只是理解了插件界面的静态部分。一个真正可用的插件,还必须让界面"动起来",这时候 JavaScript 才会加入进来。 HTML 负责搭出页面结构,CSS 负责赋予它视觉效果,JavaScript 负责让页面对用户的操作做出反应。比如用户点击按钮后获取信息,或者把某个结果显示到页面上,这些都属于 JavaScript 的工作。 下面这段代码很简单,但它能准确展示 JavaScript 的作用: document.getElementById("btn").addEventListener("click", () => { console.log("按钮被点击"); }); 这时候你就能看到三者之间的协作关系。HTML 放了一个按钮,CSS 把这个按钮变得更清晰、易用,JavaScript 让这个按钮具备"点击以后发生事情"的能力。它们共同组成了插件界面这一层的完整逻辑。 manifest.json 是插件的入口和规则中心 当你把眼光从界面移开,就会看到插件最核心的配置文件,也就是 manifest.json 。这个文件的重要性非常高,因为 Chrome 浏览器安装和加载插件时,最先读取的就是它。没有它,插件无法被识别。写错了它,插件也可能根本无法运行。 它承担的职责可以概括为一件事,就是告诉浏览器:这个插件是谁,它有哪些页面,有哪些脚本,想申请哪些权限,以及这些能力应该如何被组织起来。 最简单的内容通常长这样: { "name": "My Extension", "version": "1.0", "manifest_version": 3 } 这里记录了插件的基本身份信息。接着你还会看到它声明插件弹窗页面: { "action": { "default_popup": "popup.html" } } 浏览器读到这里,就知道用户点击插件图标时,应该打开 popup.html 。如果插件带后台脚本,还会出现类似配置: { "background": { "service_worker": "background.js" } } 如果插件需要把脚本注入网页,则可能是这样: { "content_scripts": [ { "matches": ["https://*/*"], "js": ["content.js"], "css": ["content.css"] } ] } 如果插件要访问某些浏览器能力,还得显式申请权限: { "permissions": ["storage", "tabs", "activeTab"] } 所以,从更本质的角度看, manifest.json 的作用,就是定义插件"能做什么、在哪里做、通过谁去做"。这也是为什么它像一个总开关。你后面看到的页面、脚本、权限,最终都要回到这里去确认。 background.js 像插件的后台调度中心 理解了 manifest.json 后,再去看 background.js 就容易多了。这个文件通常不负责展示界面,也不会直接嵌在某个网页里。它更像插件的后台控制层,负责监听浏览器事件、处理全局逻辑、协调不同模块之间的通信。 比如插件刚被安装时,它可以执行初始化逻辑: chrome.runtime.onInstalled.addListener(() => { console.log("插件已安装"); }); 它也可以监听某些全局事件,或者接收来自界面页和内容脚本的消息: chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === "getData") { sendResponse({ data: "这是后台返回的数据" }); } }); 为什么插件需要这样的文件?因为有些事情不适合写在界面脚本里,也不适合写在网页注入脚本里。比如统一管理状态、协调多个标签页、处理浏览器级别的事件、访问某些只允许后台使用的 API,这些任务都更适合放在后台脚本中。 如果你使用的是 Manifest V3,那么这里还有一个重要变化。 background.js 在很多情况下是以 service_worker 的方式运行的。它不会一直常驻,而是事件来了就唤醒,执行完任务后可能进入休眠。这背后体现的是 Chrome 的设计取向,它希望插件更节省资源,也更容易控制风险。 content.js 是插件进入网页内部后的执行者 如果说后台脚本站在浏览器层面处理逻辑,那么 content.js 站在网页现场工作。它会被注入某个网页中,因此可以直接访问这个网页的 DOM,也就是页面上的标题、按钮、正文、输入框这些真实存在的元素结构。 举个最简单的例子: const title = document.querySelector("h1"); console.log(title.innerText); 这段代码能直接读取网页上的标题内容。它也可以修改页面: document.body.style.backgroundColor = "lightyellow"; 甚至还能监听页面中的某些操作。也就是说, content.js 的核心价值,在于让插件真正进入网页环境,看到页面内容,并对页面进行读取或修改。 不过这里有一个非常容易被忽略的边界。 content.js 虽然运行在网页里,但它依然属于插件。它拥有插件赋予的能力,也受到插件环境的限制。它和页面原生脚本之间并不是完全共享一切的关系,因为浏览器会通过隔离机制防止它们互相污染。这个细节很关键,因为很多初学者会误以为内容脚本和网页自身的 JavaScript 完全是一回事,实际情况并没有这么简单。 插件的核心难点,在于多个运行环境同时存在 当你把 popup.js 、 background.js 、 content.js 放在一起看时,很容易觉得它们全都是 JavaScript,所以好像只是写法不同。真正的区别并不在语法,而在运行环境。 界面脚本运行在插件自己的页面里,只有当这个页面被打开时它才活跃。后台脚本运行在插件后台,专门处理全局事件和中转逻辑。内容脚本运行在目标网页中,负责接触页面本身。这三种脚本虽然都写成 .js 文件,但它们能访问的对象、拥有的权限、存在的生命周期都不一样。 这正是 Chrome 插件学习曲线真正陡峭的地方。你卡住的往往不是 API,而是脑中没有建立"多环境协作"的图景。一旦建立起这个图景,再看文件结构就会清晰很多。 一个最常见的协作流程,到底是怎样跑起来的 假设我们做一个非常简单的插件。用户点击插件图标,弹出一个小窗口,窗口里有一个按钮,点击按钮后读取当前网页标题并显示出来。这个功能很小,但它足以把前面提到的所有角色串到一起。 首先浏览器会读取 manifest.json : { "manifest_version": 3, "name": "Title Reader", "version": "1.0", "action": { "default_popup": "popup.html" }, "permissions": ["activeTab"], "background": { "service_worker": "background.js" } } 这一步完成后,浏览器已经知道这个插件长什么样,有什么弹窗页面,有什么后台脚本,以及它申请了当前标签页权限。 当用户点击插件图标时,浏览器会根据 default_popup 打开 popup.html 。页面一打开,HTML 会把结构渲染出来,CSS 负责样式,页面脚本负责交互逻辑。如果 popup.html 里有一个按钮和一个显示结果的区域,那么脚本就可以写成这样: document.getElementById("btn").addEventListener("click", async () => { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); document.getElementById("result").textContent = tab.title; }); 如果需求只是读取当前标签页的标题,这样就够了。但如果你想读取网页内部更细的内容,比如某段正文、某个按钮文本、某个元素属性,那仅靠弹窗脚本通常不够,你就得让 content.js 进入网页现场去执行。 它可以先读取网页内容,然后把结果通过消息机制发回插件系统: const pageTitle = document.title; chrome.runtime.sendMessage({ type: "pageTitle", data: pageTitle }); 这时候如果流程稍微复杂一些,后台脚本就会出场,承担协调者角色。比如弹窗先给后台发消息,后台再联系当前标签页里的内容脚本,内容脚本拿到网页数据后返回给后台,后台再把结果转给弹窗。这个链路看起来绕了一层,但你会发现它的分工很清晰。界面只处理用户交互,后台处理协调和调度,内容脚本只关注网页现场。 示例代码大致可以写成这样。 popup.js : chrome.runtime.sendMessage({ type: "getPageInfo" }, (response) => { document.getElementById("result").textContent = response.data; }); background.js : chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === "getPageInfo") { chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { chrome.tabs.sendMessage(tabs[0].id, { type: "readTitle" }, (response) => { sendResponse({ data: response.title }); }); }); return true; } }); content.js : chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === "readTitle") { sendResponse({ title: document.title }); } }); 把这段流程真正看懂后,你对插件的整体架构就已经入门了。因为你会意识到,插件开发的本质不是单纯地写一个页面,而是把浏览器环境、插件界面和网页环境连接成一个系统。 app.js 到底是什么,它为什么总让人困惑 很多人学到这里,又会看到一个新的文件名,叫 app.js ,然后开始怀疑自己是不是漏学了某种"官方角色"。其实这里最需要澄清的一点是, app.js 并不是 Chrome 插件规范里规定必须存在的文件。它通常只是开发者自己起的名字。 这意味着,当你在一个插件项目里看到 app.js 时,不能直接根据名字判断它一定负责什么。判断它职责的关键,始终只有两件事,就是它在哪里被加载,以及它运行在什么环境中。 如果 popup.html 里有这样的代码: <script src="app.js"></script> 那这个 app.js 很可能就是弹窗页面的主逻辑脚本。它可能负责监听按钮点击、获取输入框内容、调用浏览器 API、更新页面文本等交互行为。比如: document.getElementById("btn").addEventListener("click", async () => { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); document.getElementById("result").textContent = tab.title; }); 如果它被 options.html 引入,它就可能是设置页脚本。如果它出现在 manifest.json 的后台配置中: { "background": { "service_worker": "app.js" } } 那它虽然名字叫 app.js ,实际承担的就是后台脚本的职责。如果它被声明在 content_scripts 中: { "content_scripts": [ { "matches": ["<all_urls>"], "js": ["app.js"] } ] } 那它本质上就是内容脚本。 所以,理解 app.js 最重要的一句话是, 文件名本身不决定身份,加载位置和运行环境才决定身份 。很多初学者容易被文件名带偏,以为名字像什么就是什么。实际开发里, main.js 、 index.js 、 app.js 这类名字都很常见,它们更多反映的是工程命名习惯,而不是浏览器规范。 判断一个脚本作用时,最可靠的方法是什么 如果你以后再打开一个陌生的插件项目,最稳妥的阅读方式就是先看 manifest.json ,再看 HTML 文件,最后才去看具体脚本内容。 manifest.json 会告诉你有哪些页面、有哪些后台脚本、有哪些内容脚本、申请了哪些权限。HTML 文件会告诉你哪些 JavaScript 是页面脚本,因为它们会被 <script src="..."> 直接引入。脚本内容本身才会进一步告诉你,这个文件内部具体写了什么业务逻辑。 这个阅读顺序很重要,因为它会强迫你从"运行上下文"去理解代码,而不是从"文件名猜测"去理解代码。你一旦形成这种习惯,不光看 Chrome 插件会快很多,将来学 Vue、React,甚至看更复杂的前端工程,也会有更强的拆解能力。 学插件时,最推荐的理解路径 对于初学者来说,最容易迷失的地方,是一上来就想把所有文件和 API 全部记住。这样做通常效果很差,因为你的脑海里没有一张系统图,记忆就没有挂钩点。 更好的路径,是先看清谁负责界面,也就是 HTML、CSS 和页面脚本。然后理解谁负责进入网页读取和修改内容,也就是 content.js 。最后再理解谁负责监听全局事件、连接各个模块、处理中间调度,也就是 background.js 。当这条主线建立起来以后,消息传递、权限管理、脚本注入这些内容都会顺势变得可理解。 如果你一定要用一句话概括整个插件结构,那么这句话可以写成这样: manifest.json 先注册角色并声明权限,HTML 和 CSS 负责界面呈现,JavaScript 负责交互逻辑, background.js 负责后台调度, content.js 负责进入网页执行具体动作,而 app.js 是否重要,取决于它究竟被谁加载、运行在哪个环境里。 结尾:真正该建立的,是"环境意识" 学 Chrome 插件,表面上像是在学很多零散文件。更深一层看,你其实是在学习多个受控运行环境如何协作。浏览器层、插件界面层、网页内容层,这三层有各自的边界,也通过消息和配置互相连接。你一旦抓住这个系统视角,就不会再被文件名和目录结构轻易迷惑。 所以当你下次看到一个插件项目时,先别急着问"这个文件名是什么意思"。更值得问的是,这个文件由谁加载,它运行在哪里,它能访问什么,它和谁通信。只要这四个问题想清楚,整个项目的骨架就会慢慢显形。 【拓展思考】 Chrome 插件很像一个小型多进程系统。弹窗脚本、后台脚本、内容脚本分别处在不同上下文中,消息传递像协议,权限声明像访问控制,页面注入像受限部署。你如果从系统设计的视角理解插件,后面学浏览器扩展、Electron、前端工程化,很多概念都会互相打通。 2 个帖子 - 2 位参与者 阅读完整话题
我个人观察到的现象是,会话创建后的首条消息正常,之后的消息会延迟数秒才能发出,在 mac 和 windows 平台都出现了此问题; 并且 mac 版本还出现了运行时 cpu 使用率过高,发热严重的问题。 github 上也有不少类似的反馈,首个发现的版本应该是 26.415.21839 ;有人反映关闭记忆功能后可绕过此问题,但对大多数人包括我自己无效,目前只能通过降级解决。 github.com/openai/codex Fix: Desktop App - Message send is delayed for ~8 seconds in new sessions after the latest update 已打开 04:31AM - 17 Apr 26 UTC JavierPiedra bug app session ### What version of the Codex App are you using (From “About Codex” dialog)? 26 … .415.21839 ### What subscription do you have? Pro ### What platform is your computer? Darwin 25.3.0 arm64 arm ### What issue are you seeing? ## Summary In the Codex desktop app, sending a message can take around 8 seconds before the message is actually sent. This appears to have started after the latest update. It did not happen in older sessions before. ## Environment - App: Codex desktop app - Codex version: `26.415.21839 (1763)` - macOS: `26.3.1 (build 25D771280a)` - Hardware: `MacBook Air (Apple M3, 24 GB RAM)` ## Actual behavior After pressing Enter to send a message, the message does not send immediately. Observed behavior: - The spinner appears in the send button - The message remains pending for about `8 seconds` - Lowering reasoning does not help This is affecting sessions created after the latest update. ### What steps can reproduce the bug? ## Reproduction Observed repro: 1. Open Codex desktop 2. Open a newer thread created after the latest update 3. Type a normal message 4. Press Enter 5. Observe the spinner in the send button and a delay of about `8 seconds` before the message is sent Additional observation: - In a brand-new thread, the first message sent immediately - The second message in that same new thread then started taking a very long time to send ## Scope / pattern - Happens in newer sessions created after the latest update - Did **not** happen in older sessions tested by the user - Not fixed by lowering reasoning - Seen with `GPT-5.4` and `Extra High`, but reasoning level does not appear to be the cause ### What is the expected behavior? ## Expected behavior Pressing Enter should enqueue and send the message immediately, or at least show near-instant client acknowledgment. ### Additional information ## Notes This feels like client-side send latency rather than raw network latency. Local diagnostics from the machine at the time: - Network path looked healthy in macOS logs - Codex renderer/helper processes showed noticeable CPU activity while the issue was occurring - No obvious network failure was visible from the local system logs ## Impact This makes normal back-and-forth use of Codex frustrating because even short messages feel blocked before they are sent. If you want, I can also compress this into a shorter GitHub-style version, or turn it into a more technical issue with a small `Additional diagnostics` section at the end. 本以为是梯子又出问题,但看了下发现是 app 的锅,记录一下 9 个帖子 - 8 位参与者 阅读完整话题
<script setup> import { ref } from 'vue'; import { onLoad } from '@dcloudio/uni-app'; import request from '@/utils/request'; import { getFiles, getRandomFile, getFileArray } from '@/utils/yangUtils'; const imageSuffix = ['png', 'jpg', 'jpeg']; const videoSuffix = ['mp4', 'avi', 'mov']; const imageNum = ref(0); const videoNum = ref(0); // 获取父级传入的数据 const props = defineProps(["modelValue", "limit", "message", "isDelete", "isAdd"]); // 定义 emits const emit = defineEmits(['update:modelValue']); // const listData = ref(["https://yxdjpw.oss-cn-beijing.aliyuncs.com/uploadDefault/20260417192542-d3ea46.png", "https://yxdjpw.oss-cn-beijing.aliyuncs.com/uploadDefault/20260417194449-bfb1ba.mp4"]); const listData = ref([]) const onSelectFile = async () => { let result; // #ifdef APP || APP-PLUS || MP-WEIXIN || MP-TOUTIAO || MP-LARK || MP-JD || MP-HARMONY || MP-XHS result = await uni.chooseMedia({ count: props.limit || 9, mediaType: ['image', 'video'], sourceType: ['album', 'camera'], maxDuration: '30s' }) // #endif // #ifdef WEB || H5 result = await uni.chooseFile({ count: props.limit || 9, type: 'all', }) // #endif let arr = []; console.log(result); for (let filePath of result.tempFiles) { // 判断文件是否超出10M if (filePath.size > 10 * 1024 * 1024) { uni.showModal({ title: '提示', content: '文件大小不能超过10M!', showCancel: true, }) return; } if (imageSuffix.includes(filePath.name.replaceAll('"', '').split('.').pop()?.toLowerCase())) { imageNum.value++; } else if (videoSuffix.includes(filePath.name.replaceAll('"', '').split('.').pop()?.toLowerCase())) { // 判断之前有没有上传过视频或者图片 if (imageNum.value >= 1 || videoNum.value >= 1) { uni.showModal({ title: '提示', content: '图片和视频不能同时上传,并且视频只能上传一个!', showCancel: true, }) return; } videoNum.value++; } const data = await request("/upload/uploadFile", filePath.path, "post"); arr.push(getFileArray(data)[0]); } listData.value = listData.value.concat(arr); // 将数据返回出去 emit("update:modelValue", listData.value.join(",")); } // 删除 const onDelete = (index) => { // 1. 先拿到要删除的项(必须在 splice 之前拿!) const deletedItem = listData.value[index]; // 2. 判断类型,更新计数 const ext = deletedItem.split('.').pop()?.toLowerCase(); if (imageSuffix.includes(ext)) { imageNum.value--; } else if (videoSuffix.includes(ext)) { videoNum.value--; } // 3. 再删除元素 listData.value.splice(index, 1); // 4. 更新双向绑定 emit("update:modelValue", listData.value.join(",")); } // 查看 const onView = (item) => { } // 类型判断 const isSuffix = () => { return listData.value.some(item => { // 获取后缀(转小写,避免大小写问题) const ext = item.split('.').pop()?.toLowerCase() return videoSuffix.includes(ext) }) } </script> <template> <view> <view class="header"> <view v-if="props.message" class="message">{{ props.message }}</view> <view class="numCount">{{ `${listData.length}/${props.limit || 9}` }}</view> </view> <view class="image-grid" :class="isSuffix() ? 'grid-video' : ''"> <template v-for="(item, index) in listData" :key="index"> <view class="grid-item grid-item-content" :class="isSuffix() ? 'grid-item-video' : ''" @click="onView(item)"> <image v-if="imageSuffix.includes(item.split('.').pop())" class="img" :src="item" mode="aspectFill" /> <video v-else-if="videoSuffix.includes(item.split('.').pop())" class="video" :src="item" /> <view class="grid-item-delete" v-if="(isAdd ?? true)" @click="onDelete(index)"><uni-icons class="icon-delete" type="closeempty" color="#D3D4D6" size="24" /></view> </view> </template> <view class="grid-item icon-add" v-if="(isAdd ?? true) && !isSuffix() && listData.length < (props.limit ?? 9)" @click="onSelectFile()"> <uni-icons type="plusempty" size="60" color="#F1F1F1"></uni-icons> </view> </view> </view> </template> <style lang="scss" scoped> .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20rpx; } .image-grid { display: grid; grid-template-columns: repeat(3, 1fr); /* 一行3个,自动均分 */ gap: 20rpx; /* 格子间距 */ box-sizing: border-box; } .grid-item { width: 240rpx; height: 240rpx; } .grid-video { grid-template-columns: repeat(1, 1fr) !important; } .grid-item-video { width: 100%; height: 400rpx; } .numCount { display: flex; flex-direction: row-reverse; font-size: 26rpx; } .img, .video { width: 100%; height: 100%; } .grid-item-content { position: relative; } .icon-add { background: #FFFFFF; line-height: 240rpx; text-align: center; border: 1rpx solid #EEEEEE; border-radius: 6rpx; } .grid-item-delete { position: absolute; top: 0rpx; right: 0rpx; z-index: 1; } </style> 方法解析 文件后端返回的是 /api/upload/xxx.png getFiles 将图片拼接成正常能访问的 比如 http://域名.com/api/upload/xxx.png getRandomFile 是随机访问 getFileArray 是拼接成数组 limit → 最多上传数量 默认为9 message → 提示 比如请上传视频 可不填 isDelete → 是否可以删除重新上传 默认为true isAdd → 是否可以新增,比如有些地方可以直接回显 比如产品的图片,但是你不想让它显示新增的框 可以为false 默认为true // 文件加载 export const getFiles = (url) => { if (!url) { return ""; } if (url.startsWith("http://") || url.startsWith("https://")) { return cleanString(url); } else { return baseURL + cleanString(url); } }; // 随机加载文件 export const getRandomFile = (url) => { if (!url) { return ""; } const urls = url.split(","); const randomIndex = Math.floor(Math.random() * urls.length); const selectedUrl = urls[randomIndex]; if ( cleanString(selectedUrl).startsWith("http://") || cleanString(selectedUrl).startsWith("https://") ) { return cleanString(selectedUrl); } else { return baseURL + cleanString(selectedUrl); } }; export const getFileArray = (url) => { if (!url) { return []; } let baseUrl = []; url.split(",").forEach((v) => { if ( cleanString(v).startsWith("http://") || cleanString(v).startsWith("https://") ) { baseUrl.push(cleanString(v)); } else { baseUrl.push(baseURL + cleanString(v)); } }); console.log(baseUrl); return baseUrl; }; 请求就是 uni.request的请求 你们按照你们自己的改 当前只支持H5 你们需要的话 我会再更新 如果上传了视频就直接独占一行 并且不能上传其他视频和图片 仿照的是朋友圈 其他页面调用就是 比如文件名叫uploadFile.vue吧 import UpLoadFile from '@/components/uploadFile.vue' // 页面代码 <UpLoadFile v-model="form.media" /> 第一次封装 还有些问题 我会持续更新的 1 个帖子 - 1 位参与者 阅读完整话题
apple store有很方便的比价网站,可以看各个区的订阅价格,有针对google play的网站吗?菲律宾土耳其有的订阅价格也很低诶 2 个帖子 - 2 位参与者 阅读完整话题
Local web app where you set up two LMs with custom prompts and watch them talk. Supports OpenRouter, GitHub Copilot, Codex, and Claude Code. Demo at https://youtu.be/WuS2g5VZ-NM Comments URL: https://news.ycombinator.com/item?id=47815950 Points: 1 # Comments: 0
大家最近有使用gemini for mac吗?为什么登录Google 账号授权成功后 App无任何反应? 3 个帖子 - 2 位参与者 阅读完整话题
150 applications. One offer. Each application took 5+ manual steps. Separate tools, separate tabs, separate sites — none of them talking to each other. Generic output. Over an hour per application. Paste a job description — or pull it from any job site with the Chrome extension — and five AI agents run an orchestrated pipeline in under 30 seconds: analyzing the role, scoring your fit, researching the company, writing a targeted cover letter, and tailoring your resume to the role. Sequential where it needs to be, parallel where it can be, each agent's output feeding the next. Also includes a dashboard to track every application. And tools for everything around it: interview prep with mock sessions, salary negotiation, job comparison, follow-ups, thank you notes, and references. Runs on your machine. No subscriptions, no data stored on our servers — just your own Gemini API key connecting directly to Google. Comments URL: https://news.ycombinator.com/item?id=47815326 Points: 1 # Comments: 0
上海办公室线下活动 GCP 5刀赠金(限4月18日) trygcp.dev Google Cloud Credit and Account Distribution App 5 个帖子 - 5 位参与者 阅读完整话题
Article URL: https://greatapps.net/ Comments URL: https://news.ycombinator.com/item?id=47814857 Points: 3 # Comments: 1
我默认名称是惠普,现在用codex apply patch有问题,我已急哭 15 个帖子 - 15 位参与者 阅读完整话题
windows的codex app今天更新完为什么发送消息这么慢?要等一分钟才能发出去 1 个帖子 - 1 位参与者 阅读完整话题
稍微蹬了一会直接把5小时额度干完了,一个plus直接不够用了 4 个帖子 - 3 位参与者 阅读完整话题
当前模型已被配置系统提示词为kimi k2.6,网页/app一致 3 个帖子 - 3 位参与者 阅读完整话题
LobeHub的ios app是不是还不支持连接私有化部署? 2 个帖子 - 2 位参与者 阅读完整话题
今早看到 Claude Design 发布了,正好准备用 Claude Code 开发一个 App 给我自己看论文用,奈何 Claude Code 自己设计的页面实在是没眼看。。。然后用 Claude Design 把页面截图+页面的代码发给它进行设计。 这是原始 Claude code 给我的页面: emm,我虽然不懂设计,没有具体的美学训练,但是一眼看下来就不好看,然后丢给Claude Design,他会出一个类似问卷的东西,有几个问题让我选(没截图这个页面的设计,所以放了一个其他页面的问题交互): 它会给我好几个风格的备选: 当然,也可以修改,我让他融合 A+C 在统一成卡片式的设计,最后又给了一个方案 D: 我很喜欢方案D,Claude Design 是分别给出了每个方案的代码的,我再把这些东西丢到 Claude Code 里就帮我实现的很好了 下面都是 Claude Design 跑出来,然后我用 Claude Code 实现后的运行截图: 额度跑的飞起,可能和我使用方式有关。。。跑一个页面,每周的 Claude Design 限制从 44% 涨到56%了 当然,页面布局对于专业人员来说肯定还能做的更好,但是对于我个人来说,已经相当相当满意了 1 个帖子 - 1 位参与者 阅读完整话题
Apple 产品京东自营旗舰店 4 月大促已开启,iPhone 17 Pro Max 以旧换新至高补贴 1300 元,指定产品享 24 期免息: 点此前往 。 iPhone Air 于去年 10 月 22 日开售,上市起售价为 7999 元。 今日京东自营叠加国补后仅需 5099 元,无需换新等额外操作,本次京东直接降。 此外,若大家所在地区的国补支持京东支付,本次活动还可享 12 期免息分期: 京东 iPhone Air 12+256GB 京东自营 国补后 5099 元 直达链接 iPhone 17 今日也支持 5099 元 +12 期免息哦: 京东 iPhone 17 8+256GB 京东自营 国补后 5099 元 直达链接
render部署的cpa遇到Access blocked by Cloudflare. This usually happens when connecting from a restricted region (status 403 Forbidden), u rl: https://www.xxxx.top/v1/responses , cf-ray: 9ee0d216d8856e64-HKG 现在在本机使用codex会报错,尝试过切换网络、切换节点依然报错,直接访问网页是正常的。然而换了机器后是可以正常连接的。怀疑是被render的cf通过tls指纹给ban了。有佬友遇到过知道怎么解决吗? 1 个帖子 - 1 位参与者 阅读完整话题
I found myself reaching for SF Symbols' 'Copy Image As…' quite often during agentic design sessions, so I made a command-line tool that the agent can use by itself. It exports Apple SF Symbols as SVG, PDF, or PNG. The vector paths come directly from macOS's symbol renderer. Internally it reaches a private ivar on NSSymbolImageRep to get the CUINamedVectorGlyph, draws into a CGPDFContext, then walks the PDF content stream back out as SVG `d` commands. The output matches what the system draws, rather than an approximation traced from rasters. A few things about it: - Every subcommand accepts `--json`, and `sfsym schema` returns a machine-readable description of the whole CLI. - Symbol enumeration reads the OS's Assets.car BOM tree, so the list of 8,300+ names stays current with macOS updates without a version table in the binary. - Each SVG ` ` carries a `data-layer` attribute, so you can retheme in CSS without touching geometry. It's been saving me a bunch of clicking. Please let me know if you have any other ideas for it. Comments URL: https://news.ycombinator.com/item?id=47812964 Points: 15 # Comments: 3
想让claude给我的app ui给个意见,claude直接给我画出来新ui了 惊讶到我了 2 个帖子 - 2 位参与者 阅读完整话题