当前位置: 首页 > news >正文

鸿蒙 HarmonyOS 6 | Pura X Max 鸿蒙原生适配 14:大屏弹窗改成侧边面板

前言我在做材料列表详情查看的时候最早用的是居中弹窗。外屏下这个写法没什么违和感用户点一条记录页面弹出一个详情窗口确认完再关掉流程短注意力也集中。手机空间本来就小弹窗把当前任务单独拎出来用户不会被列表里的其他内容干扰。把同样的交互放到 Pura X Max 展开态里我开始觉得不太对。列表区域明明还有很多空间用户也能看到多条材料记录但居中弹窗一出现原来的列表和选中项都被遮住了。弹窗本身占据了屏幕中心左右两边却空着不少区域。用户想对照原列表里的记录或者继续切换另一条材料就得先关掉弹窗再回到列表里重新找。这种情况在展开态里很常见尤其是下面这些轻量任务查看一条记录的补充说明编辑一两个字段临时筛选列表查看备注或处理建议确认一条识别结果给当前记录补充状态或标签这些任务都有一个共同点它们需要依附在当前页面上完成不一定值得跳到完整详情页。小屏里可以用弹窗或底部面板承接展开态里右侧面板通常会更符合页面结构。左侧仍然保留列表或原内容右侧承接当前详情和操作用户不会丢掉自己是从哪条记录点进来的。Pura X Max 在外屏、展开态、分屏和自由窗口之间切换时页面可用宽度变化很大。弹层交互不能只按手机外屏的思路处理窗口宽起来以后页面有条件保留上下文弹层也可以从遮住页面变成贴着页面补充信息。这次我用一个材料列表页来模拟这个场景。点击“查看详情”后窄窗口使用底部面板展开态使用右侧面板。状态和数据仍然是一套只是面板出现的位置跟着窗口宽度变化。这个处理方式比较适合详情补充、筛选条件、备注编辑这类轻量任务。一、弹窗在展开态里会遮住上下文1.1 外屏里弹窗可以集中注意力外屏里用弹窗处理详情补充很多时候是可以接受的。比如用户在一个窄屏列表里点开某条材料弹出一个居中的详情卡片或者从底部拉起一个面板用户的注意力会集中到这条记录上。页面空间有限原列表本来就无法和详情同时展开弹窗相当于给当前任务临时开出一块区域。我在手机外屏里也会经常这样写。比如编辑一个标题、确认一个提醒、查看一段识别结果弹窗或底部面板能把任务收得比较干净。用户看完以后关掉弹层回到原页面继续操作。这个模式在小屏里并不违和反而能减少页面跳转。在代码里这类写法通常很简单。点一条记录把showPanel设为 true再把当前记录 id 存下来。private openPanel(itemId: number) { this.selectedId itemId; this.showPanel true; }这个状态本身可以保留。真正要调整的是弹层在不同窗口宽度下的呈现方式。外屏可以从底部出来展开态就没必要继续遮住页面中心。1.2 展开态里弹窗会抢掉参照物我把这个页面切到展开态后再点查看详情第一个感受就是弹窗挡住了列表。原来的材料列表还在背后但用户已经看不到自己点的是哪条记录也看不到上下几条记录之间的关系。对于只查看一条详情来说这还勉强能用如果用户需要连续切换记录居中弹窗就开始影响操作。展开态的价值之一是能把原页面和补充内容同时放下。比如左侧继续保留列表右侧显示详情补充。用户在查看详情时还能看到列表里其他材料也能确认当前记录的上下文。这个时候居中弹窗反而把展开态空间浪费掉了。我会把这类弹层分成两种用途来区分。需要强打断、强确认的内容比如删除确认、支付确认、危险操作提醒仍然适合弹窗只是查看详情、筛选条件、备注编辑、轻量补充信息就更适合放到侧边面板里。它们不需要遮住整个页面也不需要让用户离开当前上下文。二、小屏继续用底部面板2.1 窄窗口更适合聚焦处理在外屏或较窄窗口里我仍然会保留底部面板。原因很简单窄窗口里很难同时放下列表和详情用户点开一条记录时页面优先让他处理当前内容。底部面板从屏幕下方出现覆盖原页面的一部分用户会自然把注意力放到当前记录上。这类交互适合短任务。比如查看一条提醒、确认一段识别结果、保存一个处理建议。用户不需要保留大量上下文只要知道当前处理对象是什么以及下一步能点哪个按钮。示例里小屏下的底部面板只在showPanel为 true 且窗口没有达到展开态时出现。if (this.showPanel !this.isExpanded()) { this.BottomSheet() }这个判断看起来很简单但它决定了小屏里的交互节奏。窄窗口不强行分栏也不把详情塞在右侧而是用底部面板集中当前任务。等窗口宽起来以后同样的详情内容再换到右侧面板。2.2 底部面板要控制高度底部面板不能无限长。小屏里高度本来就有限如果面板展开后把整个屏幕都占满用户会感觉像跳进了另一个页面但返回关系又没有完整页面那么清楚。示例里底部面板用了固定高度。.height(430)这个值不是固定标准。真实项目里要根据内容决定外屏页面里可以略高一点悬浮窗里要更克制。一般来说底部面板适合放标题、摘要、少量元信息和一到两个按钮。如果内容继续增长就要考虑进入完整详情页而不是让底部面板一直加高。我在项目里通常会把底部面板看作临时处理区不会把完整详情全部塞进去。它可以承接当前动作但不适合承担一个复杂流程。这样后续迁移到展开态侧边面板时也能保持同一套信息层级。三、展开态改成右侧面板3.1 右侧面板保留原页面参照展开态下我更愿意把详情补充内容放到右侧。左侧仍然是列表右侧面板显示当前记录的详情、建议和操作按钮。这样用户打开详情时不会失去原页面参照也能继续知道自己是从哪条记录点进来的。示例里的判断从窗口宽度开始。private readonly expandedWidth: number 820; private isExpanded(): boolean { return this.getEffectiveWidth() this.expandedWidth; }820vp是这个示例里的门槛。真实项目里要看页面主体宽度、侧边面板宽度、左右 padding 和列表卡片宽度。比如右侧面板需要 360vp左侧列表至少要保留 420vp再加上间距和页面边距阈值就不能设得太低。大屏下显示侧边面板的判断也很简单。if (this.showPanel this.isExpanded()) { this.SidePanel() }我在真实项目里会把这个判断放在页面层而不是让某个按钮组件自己决定弹出方式。按钮只负责打开详情至于详情出现在底部还是右侧交给页面根据窗口宽度处理。3.2 面板宽度要给主页面留空间示例里的右侧面板宽度是 360vp。.width(360)这个宽度适合展示标题、摘要、元信息、补充说明和两个按钮。它不会太窄正文还可以阅读也不会太宽左侧列表仍然能保留足够空间。如果面板内容更少可以降到 320vp如果需要展示表单字段可以增加到 400vp 左右。这里我会特别关注左侧列表。侧边面板出现以后左侧仍然应该能看清列表标题、选中态和至少几条记录。如果右侧面板过宽左侧列表被挤得只剩窄条那就和居中弹窗一样失去了保留上下文的意义。四、用 Stack 还原两种形态4.1 页面层控制遮罩和面板这里我没有直接调用系统弹窗 API而是在页面里用Stack叠出遮罩、底部面板和右侧面板。这样做的好处是方便验证布局逻辑也方便在同一个页面里根据窗口宽度切换不同面板形态。核心结构放在build()里。if (this.showPanel) { Column() .width(100%) .height(100%) .backgroundColor(this.isExpanded() ? #00000000 : #66000000) if (this.isExpanded()) { this.SidePanel() } else { this.BottomSheet() } }小屏下遮罩是半透明黑色点击遮罩可以关闭面板。这个交互更接近普通弹窗用户知道当前任务是临时打开的。展开态下遮罩保持透明面板出现在右侧左侧列表仍然可见。关闭动作放在面板内部避免用户误触左侧列表时把面板关掉。这两个细节很容易被忽略。很多时候我们只处理面板位置却忘了遮罩也要跟着变化。小屏需要遮罩帮助用户聚焦大屏更需要保留页面上下文。遮罩颜色、关闭方式、面板位置其实都应该和窗口状态一起调整。4.2 详情内容复用同一份这里底部面板和右侧面板都使用同一个DetailContent()。也就是说详情内容没有拆成两份只是外层容器不同。Builder private DetailContent(item: MaterialItem) { Column({ space: 16 }) { // 标题、摘要、补充说明、处理建议和按钮 } }这样写的好处是后面改详情字段时不需要同时改底部面板和右侧面板两套内容。小屏和大屏的差异主要体现在外层容器小屏是从底部出现大屏是贴右侧出现。详情内部的字段结构可以保持一致再根据宽度做少量字号或行高调整。真实项目里也建议这样处理。弹层形态可以有两种内容尽量保持一份。否则后面加字段、改文案、调整按钮状态时很容易出现小屏和大屏不一致。五、实际运行效果这里顶部有外屏和展开态两个演示按钮方便在同一台模拟器里观察面板形态。真实项目里可以删掉这些按钮页面直接根据真实窗口宽度判断。外屏状态下点击任意一条记录的查看详情详情会从底部弹出背景有一层半透明遮罩。这个状态适合窄窗口用户的注意力集中在当前记录上底部面板也更贴近小屏操作习惯。展开态状态下再点击查看详情详情会出现在右侧左侧列表不会被遮住。选中的列表项会保留高亮用户能看到自己打开的是哪条记录也可以继续对照列表里的其他材料。六、如何迁移到实际项目6.1 演示宽度要删掉示例里的previewWidth只是为了在同一个模拟器里切换外屏和展开态。真实项目里不需要这些按钮页面应该直接使用真实窗口宽度。private getEffectiveWidth(): number { if (this.previewWidth 0) { return this.previewWidth; } return this.pageWidth; }迁回项目时可以直接返回pageWidth。private getEffectiveWidth(): number { return this.pageWidth; }页面宽度可以继续通过onAreaChange写入。这里记录的是页面根容器宽度而不是设备名称。对 Pura X Max 来说同一台设备可能处在外屏、展开态、分屏和自由窗口里面板形态要看当前窗口给了多少空间。6.2 不是所有弹窗都适合改成侧边面板侧边面板适合轻量补充任务比如查看详情、筛选条件、备注编辑、状态确认。它的价值在于保留原页面上下文让用户不离开列表也能完成一小段操作。如果是删除确认、支付确认、权限授权这类需要强提醒的动作我仍然会用弹窗。它们本来就需要打断用户让用户明确确认当前操作。侧边面板太轻反而不适合这类高风险动作。如果是多步骤表单、长文编辑、图片裁剪、复杂审批流程我也不会放在侧边面板里。右侧面板宽度有限复杂流程放进去会让用户一直滚动还容易丢失表单上下文。这类任务应该进入完整页面或者使用更大的编辑页面承接。6.3 面板状态要和选中项保持一致示例里用了selectedId保存当前选中记录用showPanel控制面板是否显示。点击不同记录时面板内容会跟着更新。private openPanel(itemId: number) { this.selectedId itemId; this.showPanel true; }真实项目里也要留意这个状态关系。用户在展开态里点击列表 A右侧面板显示 A 的详情继续点击列表 B右侧面板应该切到 B而不是重新弹出一个新的弹窗。这样列表和面板之间的关系才是连续的。我会把这类状态放在页面层而不是放在单个列表卡片里。列表卡片负责触发打开动作页面负责保存选中项和面板状态。这样底部面板和右侧面板都能复用同一份状态不会因为窗口宽度变化导致当前详情丢失。总结Pura X Max 展开态里弹窗继续放在屏幕中间很多时候会把原页面关系打断。外屏空间小底部面板可以让用户先处理当前记录展开态空间变宽以后详情补充、筛选条件、备注编辑这类内容放到右侧左侧列表还能留在原位用户知道自己刚才点的是哪条记录也能继续对照上下几条材料。我后面处理这类弹层时会先看它承担的任务如果只是查看详情、补充说明、备注编辑、筛选条件右侧面板更适合展开态。如果是删除、支付、授权这类强确认动作居中弹窗仍然更合适因为它需要让用户停下来确认。如果是多步骤表单、长文编辑、图片裁剪这类复杂流程应该进入完整页面不适合塞进侧边面板。如果只是外屏上的短任务比如看一条记录、点一下保存、稍后处理底部面板已经够用。弹层的位置要看当前窗口能不能保留原页面参照。窗口窄的时候先让用户集中处理当前记录窗口宽的时候就不要急着遮住列表把补充内容放到右侧让原页面和详情内容同时留在视野里。这样处理以后弹层不再只是一个固定样式而是会根据任务轻重和窗口宽度换一种呈现方式。附完整代码interface MaterialItem { id: number; title: string; status: string; source: string; time: string; tag: string; summary: string; detail: string; suggestion: string; } Entry Component struct Index { // 页面真实宽度由 onAreaChange 写入 State private pageWidth: number 0; // 演示宽度只用于在同一个模拟器里观察外屏和展开态 State private previewWidth: number 0; // 当前选中项。底部面板和右侧面板都读取这个状态 State private selectedId: number 1; // 面板是否打开。窗口宽度变化时面板位置会跟着切换 State private showPanel: boolean false; // 模拟保存次数用来观察面板切换后操作状态是否保留 State private saveCount: number 0; private readonly expandedWidth: number 820; private readonly materials: MaterialItem[] [ { id: 1, title: 社区物业缴费提醒, status: 待处理, source: 拍照整理, time: 09:20, tag: 通知, summary: 识别到缴费截止日期、金额明细和办理地点。, detail: 这条记录来自一张社区物业缴费通知。内容包含缴费周期、应缴金额、截止日期和办理地点。小屏下适合通过底部面板快速查看大屏下可以用右侧面板保留列表上下文。, suggestion: 保存为待办提醒并在截止日期前一天提醒。 }, { id: 2, title: Pura X Max 适配会议纪要, status: 待确认, source: 语音转写, time: 10:45, tag: 会议, summary: 整理出弹窗、侧边面板、分屏窗口和横屏结构几类问题。, detail: 会议纪要类记录经常需要对照多个条目。展开态下右侧详情面板能减少页面跳转列表仍然保留在原位置切换记录也更方便。, suggestion: 确认适配任务并同步到开发清单。 }, { id: 3, title: 客户需求变更记录, status: 待处理, source: 文本整理, time: 13:10, tag: 项目, summary: 本次变更涉及首页布局、权限配置和消息提醒。, detail: 需求变更类记录适合在右侧面板里查看补充信息。主页面保留列表右侧承接详情、处理建议和操作按钮。, suggestion: 同步项目负责人并拆分到研发排期。 }, { id: 4, title: 活动报名确认单, status: 已保存, source: 相册导入, time: 15:25, tag: 表单, summary: 提取到报名人、联系方式、活动时间和签到地址。, detail: 报名确认类材料通常只是补充查看不一定需要进入完整详情页。小屏弹出底部面板宽屏使用侧边面板即可。, suggestion: 保存记录并在活动前一天提醒。 }, { id: 5, title: 门诊复查预约提示, status: 已整理, source: 拍照整理, time: 16:40, tag: 提醒, summary: 提取到复查时间、科室、楼层和注意事项。, detail: 提醒类信息适合轻量处理。侧边面板可以承接确认、保存、稍后处理等动作避免把用户带到另一个页面。, suggestion: 加入日程提醒并保留原始记录。 } ]; // Demo 中优先使用演示宽度真实项目里可以直接返回 pageWidth private getEffectiveWidth(): number { if (this.previewWidth 0) { return this.previewWidth; } return this.pageWidth; } private isExpanded(): boolean { return this.getEffectiveWidth() this.expandedWidth; } private getContentWidth(): Length { if (this.previewWidth 0) { return this.previewWidth; } return 100%; } private getPagePadding(): number { return this.isExpanded() ? 24 : 16; } private getTitleSize(): number { return this.isExpanded() ? 28 : 23; } private getModeText(): string { return this.isExpanded() ? expanded · 右侧面板 : compact · 底部面板; } private getModeDesc(): string { if (this.isExpanded()) { return 宽窗口下详情进入右侧面板左侧列表仍然保留。; } return 窄窗口下详情从底部弹出当前任务先聚焦处理。; } private getSelectedItem(): MaterialItem { const found this.materials.find((item: MaterialItem) item.id this.selectedId); return found ? found : this.materials[0]; } private setPreview(width: number) { this.previewWidth width; this.showPanel false; } private openPanel(itemId: number) { this.selectedId itemId; this.showPanel true; } private closePanel() { this.showPanel false; } private save() { this.saveCount 1; } private getStatusColor(status: string): string { if (status 待处理) { return #B25E00; } if (status 待确认) { return #7C3AED; } return #276749; } private getStatusBgColor(status: string): string { if (status 待处理) { return #FFF4E5; } if (status 待确认) { return #F1EAFE; } return #E7F5EE; } Builder private PreviewButton(text: string, width: number) { Text(text) .fontSize(12) .fontColor(this.previewWidth width ? #FFFFFF : #2F8F83) .textAlign(TextAlign.Center) .padding({ left: 10, right: 10, top: 7, bottom: 7 }) .backgroundColor(this.previewWidth width ? #2F8F83 : #E6F4F1) .borderRadius(999) .onClick(() { this.setPreview(width); }) } Builder private StatusPill(status: string) { Text(status) .fontSize(12) .fontColor(this.getStatusColor(status)) .padding({ left: 8, right: 8, top: 4, bottom: 4 }) .backgroundColor(this.getStatusBgColor(status)) .borderRadius(999) } Builder private MetaPill(text: string) { Text(text) .fontSize(12) .fontColor(#4B5563) .padding({ left: 8, right: 8, top: 4, bottom: 4 }) .backgroundColor(#F3F4F6) .borderRadius(999) } Builder private HeaderPanel() { Column({ space: 10 }) { Row() { Column({ space: 4 }) { Text(大屏弹窗改成侧边面板) .fontSize(this.getTitleSize()) .fontWeight(FontWeight.Bold) .fontColor(#111827) Text(this.getModeText()) .fontSize(14) .fontColor(#2F8F83) } .layoutWeight(1) Text(窗口 Math.round(this.pageWidth).toString() vp) .fontSize(12) .fontColor(#374151) .padding({ left: 10, right: 10, top: 6, bottom: 6 }) .backgroundColor(#FFFFFF) .borderRadius(999) } .width(100%) Text(演示宽度 Math.round(this.getEffectiveWidth()).toString() vp。 this.getModeDesc()) .fontSize(14) .fontColor(#6B7280) .lineHeight(21) Row({ space: 8 }) { this.PreviewButton(自动, 0) this.PreviewButton(外屏, 430) this.PreviewButton(展开态, 960) } .width(100%) } .width(100%) } Builder private MaterialCard(item: MaterialItem) { Column({ space: 12 }) { Row({ space: 8 }) { this.StatusPill(item.status) this.MetaPill(item.tag) Blank() Text(item.time) .fontSize(12) .fontColor(#6B7280) } .width(100%) Text(item.title) .fontSize(17) .fontWeight(FontWeight.Medium) .fontColor(#111827) .maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis }) Text(item.summary) .fontSize(13) .fontColor(#6B7280) .lineHeight(19) .maxLines(this.isExpanded() ? 2 : 1) .textOverflow({ overflow: TextOverflow.Ellipsis }) Row({ space: 10 }) { Text(item.source) .fontSize(12) .fontColor(#4B5563) Blank() Button(查看详情) .fontSize(13) .fontColor(#FFFFFF) .height(34) .padding({ left: 12, right: 12 }) .backgroundColor(#2F8F83) .borderRadius(17) .onClick(() { this.openPanel(item.id); }) } .width(100%) } .width(100%) .padding(16) .backgroundColor(this.selectedId item.id this.showPanel ? #EEF7F5 : #FFFFFF) .borderRadius(20) .border({ width: this.selectedId item.id this.showPanel ? 1.5 : 1, color: this.selectedId item.id this.showPanel ? #2F8F83 : #E5E7EB }) .shadow({ radius: this.selectedId item.id this.showPanel ? 12 : 8, color: #12000000, offsetX: 0, offsetY: 4 }) } Builder private ListArea() { Scroll() { Column({ space: 12 }) { ForEach(this.materials, (item: MaterialItem) { this.MaterialCard(item) }, (item: MaterialItem) item.id.toString()) } .width(100%) .padding({ bottom: 24 }) } .layoutWeight(1) .width(100%) .edgeEffect(EdgeEffect.Spring) } Builder private DetailContent(item: MaterialItem) { Column({ space: 16 }) { Row() { this.StatusPill(item.status) Blank() Text(关闭) .fontSize(13) .fontColor(#6B7280) .padding({ left: 10, right: 10, top: 6, bottom: 6 }) .backgroundColor(#F3F4F6) .borderRadius(999) .onClick(() { this.closePanel(); }) } .width(100%) Column({ space: 8 }) { Text(item.title) .fontSize(this.isExpanded() ? 24 : 21) .fontWeight(FontWeight.Bold) .fontColor(#111827) .lineHeight(this.isExpanded() ? 31 : 28) Text(item.summary) .fontSize(14) .fontColor(#4B5563) .lineHeight(22) } .width(100%) .alignItems(HorizontalAlign.Start) Row({ space: 8 }) { this.MetaPill(item.source) this.MetaPill(item.time) this.MetaPill(item.tag) } .width(100%) Column({ space: 8 }) { Text(详情补充) .fontSize(16) .fontWeight(FontWeight.Medium) .fontColor(#111827) Text(item.detail) .fontSize(14) .fontColor(#4B5563) .lineHeight(23) } .width(100%) .padding(14) .backgroundColor(#F9FAFB) .borderRadius(16) Column({ space: 8 }) { Text(处理建议) .fontSize(16) .fontWeight(FontWeight.Medium) .fontColor(#111827) Text(item.suggestion) .fontSize(14) .fontColor(#4B5563) .lineHeight(23) } .width(100%) .padding(14) .backgroundColor(#F3F8F7) .borderRadius(16) Text(已保存 this.saveCount.toString() 次) .fontSize(13) .fontColor(#6B7280) Button(保存处理结果) .fontSize(15) .fontColor(#FFFFFF) .height(44) .width(100%) .backgroundColor(#2F8F83) .borderRadius(22) .onClick(() { this.save(); }) Button(稍后处理) .fontSize(15) .fontColor(#2F8F83) .height(42) .width(100%) .backgroundColor(#E6F4F1) .borderRadius(21) if (this.isExpanded()) { Text(展开态下右侧面板不会遮住左侧列表用户可以继续保留原页面参照。) .fontSize(13) .fontColor(#6B7280) .lineHeight(20) } } .width(100%) .height(100%) } Builder private SidePanel() { Row() { Blank() Column() { this.DetailContent(this.getSelectedItem()) } .width(360) .height(100%) .padding(20) .backgroundColor(#FFFFFF) .borderRadius({ topLeft: 24, topRight: 0, bottomLeft: 24, bottomRight: 0 }) .shadow({ radius: 16, color: #18000000, offsetX: -4, offsetY: 0 }) } .width(100%) .height(100%) } Builder private BottomSheet() { Column() { Blank() Column() { this.DetailContent(this.getSelectedItem()) } .width(100%) .height(430) .padding(18) .backgroundColor(#FFFFFF) .borderRadius({ topLeft: 24, topRight: 24, bottomLeft: 0, bottomRight: 0 }) .shadow({ radius: 16, color: #18000000, offsetX: 0, offsetY: -4 }) } .width(100%) .height(100%) } build() { Stack() { Column() { Column({ space: 16 }) { this.HeaderPanel() this.ListArea() } .width(this.getContentWidth()) .height(100%) .padding({ left: this.getPagePadding(), right: this.getPagePadding(), top: 18, bottom: 16 }) } .width(100%) .height(100%) .alignItems(HorizontalAlign.Center) if (this.showPanel) { Column() .width(100%) .height(100%) .backgroundColor(this.isExpanded() ? #00000000 : #66000000) .onClick(() { if (!this.isExpanded()) { this.closePanel(); } }) if (this.isExpanded()) { this.SidePanel() } else { this.BottomSheet() } } } .width(100%) .height(100%) .backgroundColor(#F6F7F9) .onAreaChange((_: Area, newValue: Area) { const width Number(newValue.width); if (!Number.isNaN(width) width 0) { this.pageWidth width; } }) } }
http://www.rkmt.cn/news/1414519.html

相关文章:

  • 新买的SSD移动硬盘到手别急着用!先搞懂exFAT和NTFS怎么选(附T7实测)
  • 企业级AGI商业价值评估与选型白皮书
  • 2026年北京搬家公司完全指南:从居民搬迁到企业运营的全链条对标评测 - 年度推荐企业名录
  • 京东e卡回收注意事项,这几点不看准吃亏 - 京顺回收
  • 广东主流滑轨供应商一览,这些家居五金企业值得推荐! - 资讯焦点
  • Cookie反爬:破解某网站Set-Cookie时的反爬逻辑[特殊字符] 深度实战:手撕某网站Cookie反爬——从JS混淆到Python自动化绕过
  • Spark算子 - Python
  • Labelme 3.16.7 保姆级安装与避坑指南:为什么我推荐这个特定版本?
  • 保姆级避坑指南:在Ubuntu 18.04 ROS Melodic上,用LeGO-LOAM跑通KITTI 00序列(附完整配置流程)
  • 干货汇总2026冷冻机厂家TOP5推荐 筛选适配冷链运作优质生产商 - 资讯速览
  • 摄影师进阶:深度解析i1Profiler制作ICC曲线背后的色彩科学(从D50光源到色域图解读)
  • 超越CRUD:用人人开源的代码生成器(renren-generator)定制你的专属业务模块
  • 在自动化内容生成工作流中集成 Taotoken 实现模型灵活切换
  • U盘版小龙虾教程
  • 2026 年 10 款桌面云横评 靠谱选型解决权限难管控痛点
  • 学习笔记。
  • 保姆级教程:用NumPy和SciPy从零实现DeLong检验(附完整代码与避坑指南)
  • VRX水面机器人仿真平台:构建智能水上机器人的终极解决方案
  • 2026年前置仓便利店加盟避坑及主流品牌盘点 - 资讯焦点
  • 3步搞定MySQL元数据管理:OpenMetadata实战指南
  • 中小团队如何利用taotoken统一管理多个ai项目的api调用
  • 猫抓浏览器扩展:3分钟掌握终极网页资源嗅探工具
  • 2026年维普算法突袭:如何应对更严苛AIGC检测?实测好用降AI工具清单 - 降AI实验室
  • 基于Brainy Pi部署私有Bitwarden密码库:从Docker容器化到安全加固全流程
  • 2026年石家庄空气能热泵厂家口碑推荐榜:空气能、超低温空气能、商用多联机、空气能热水系统厂家选择指南,产能、工艺、品控三维度权威解析 - 海棠依旧大
  • 5.26未做完
  • 杭州禾沐再生资源:临平靠谱的废铁回收公司有哪些 - LYL仔仔
  • 5分钟构建完整电商系统:新蜂商城实战入门指南
  • 猫抓浏览器插件:三步掌控全网视频音频下载的艺术
  • Steam游戏自动化破解工具完整指南:三步实现游戏备份与离线运行自由