前端UI组件设计系统【免费下载链接】semi-designA modern, comprehensive, flexible design system and React UI library, AI-friendly built-in.Provide 3000 Design Tokens, easy to build your design system. Make Semi Design to Any Design. Design to Code in one click项目地址https://gitcode.com/gh_mirrors/se/semi-design点击查看免费下载本指南以 Semi Design 官方文档content/input/taginput/index-en-US.md为核心系统讲解 TagInput 标签输入框组件的能力边界与实战用法。你将掌握回车添加、分隔符批量输入、受控模式、数量/长度限制、自定义渲染与拖拽排序等全部配置方式并通过半组件源码packages/semi-ui/tagInput与packages/semi-foundation/tagInput理解其底层实现原理从而在实际业务中准确选型与排障。TagInput 组件定位与引入方式TagInput 是 Semi Design 输入类Input分类下的标签输入组件用户敲击回车键后输入内容会被转换为一个标签Tag多个标签以数组形式管理与展示。它适用于邮箱收件人、关键词、用户名单、标签筛选等需要批量录入 结构化存储的场景。在 Semi Design 中TagInput 由douyinfe/semi-ui统一导出组件入口定义在 packages/semi-ui/index.tsimport { TagInput } from douyinfe/semi-ui;从源码看TagInput 是继承自BaseComponent的 class 组件packages/semi-ui/tagInput/index.tsx其逻辑层与视图层分离交互状态由TagInputFoundation管理UI 结构复用Tag、Input、Popover、Paragraph、Sortable等半组件后续所有能力讲解都可以在 packages/semi-foundation/tagInput/foundation.ts 中找到对应实现。基本用法回车添加与空值过滤最简单的用法是配合defaultValue设置初始标签、placeholder设置占位文案、onChange监听变化import React from react; import { TagInput } from douyinfe/semi-ui; () ( TagInput defaultValue{[Semi, Design, Design2Code]} placeholderPlease enter... onChange{v console.log(v)} / );交互规则敲击回车键后输入内容成为标签若标签内容为空字符串或纯空格会被自动过滤。这一规则在 foundation 的_handleAddTags中实现foundation.ts待添加标签经过_splitArray切分后通过item.trim() ! 过滤空串与纯空格再与现有标签数组合并。// 源码节选packages/semi-foundation/tagInput/foundation.ts addTags addTags.filter((item, idx) { if (!allowDuplicates) { if (tagsArray.includes(item) || addTags.indexOf(item) ! idx) { return false; } } // Filter empty strings and pure space strings in new items return isString(item) item.trim() ! ; });同时回车键按下时会调用e.preventDefault()阻止表单提交源码位置见 foundation.ts避免在 Form 中使用时误触提交这一点在使用时无需额外处理。批量添加用 separator 分隔符一次输入多个标签separator用于设置批量输入的分隔符默认值为英文逗号,。支持单个分隔符string和多个分隔符string[]两种格式import React from react; import { TagInput } from douyinfe/semi-ui; () ( TagInput separator- placeholderUse - for batch input onChange{v console.log(v)} / br/br/ TagInput separator{[-, /, |, ]} placeholderSupport multiple separators for batch input onChange{v console.log(v)} / / );默认separator: ,在组件defaultProps中定义index.tsx。底层切分逻辑位于 packages/semi-foundation/tagInput/utils/getSplitedArray.ts分隔符为string时直接调用originString.split(separator)分隔符为string[]时先取第一个分隔符作为临时分隔符依次将其他分隔符替换为临时分隔符后再统一切分从而支持任意多个分隔符并存分隔符为null时整个输入串作为单个标签测试用例 when separator is null 验证了该行为见 tagInput.test.js分隔符甚至可以是数字类型测试中separator{1}可将semi1design切分为[semi, design]。自定义切分函数 splitv2.90.0从 2.90.0 版本起可以通过split属性自定义分隔符处理函数签名与内置getSplitedArray一致属性说明类型split自定义分隔符处理函数(value: string, separator: string) string[]在 foundation 的_splitArray中foundation.ts若传入split函数则优先使用自定义实现否则回退到getSplitedArray默认实现。这为分隔符带转义“按正则切分”等特殊场景提供了扩展入口。批量删除showClear 一键清空showClear用于控制是否显示一键清空按钮。开启后鼠标悬浮时输入框右侧会出现清空图标点击可同时删除全部标签与输入框内容import React from react; import { TagInput } from douyinfe/semi-ui; () ( TagInput showClear defaultValue{[Semi, Design2Code]} placeholderPlease enter... onChange{v console.log(v)} / );清空按钮的显隐逻辑在renderClearBtn中index.tsx仅在showClear为 true、且 hover 状态下、且有输入或存在标签时可见禁用状态下同样隐藏。清空动作在 foundation 的handleClearBtn中执行foundation.ts标签数组置空并触发onChange([])输入内容清空同时e.stopPropagation()防止事件冒泡到外层。禁用与尺寸disabled使组件只读、禁用一切交互size控制组件尺寸可选small、default、large默认defaultSIZE_SET定义于 constants.tsimport React from react; import { TagInput } from douyinfe/semi-ui; () ( TagInput disabled showClear defaultValue{[Semi, Design2Code]} placeholderPlease enter... / );import React from react; import { TagInput } from douyinfe/semi-ui; () ( TagInput sizesmall placeholdersmall/ br/br/ TagInput placeholderdefault/ br/br/ TagInput sizelarge placeholderlarge/ / );尺寸不仅影响整体高度还会联动内部标签大小renderTag中size small时内部 Tag 使用small尺寸否则使用large尺寸index.tsx。尺寸对应的圆角、高度、间距等由设计变量控制详见 packages/semi-foundation/tagInput/variables.scss。校验状态样式validateStatusvalidateStatus仅影响样式表现背景色、描边色等不影响实际校验逻辑可选值default、warning、errorimport React from react; import { TagInput } from douyinfe/semi-ui; () ( TagInput placeholderdefault/ br/br/ TagInput placeholderwarning validateStatuswarning/ br/br/ TagInput placeholdererror validateStatuserror/ / );样式映射在render()的 className 拼接中index.tsx分别追加semi-tagInput-warning、semi-tagInput-error。warning / error 态的完整背景色、描边色、hover 与 focus 色板定义在 variables.scss。此外validateStatuserror时还会自动设置aria-invalid{true}见 index.tsx测试用例对此有明确断言tagInput.test.js。前缀与后缀prefix / suffix通过prefix传入输入框前缀、suffix传入后缀支持文本或任意 ReactNodeimport React from react; import { TagInput } from douyinfe/semi-ui; import { IconVigoLogo, IconGift } from douyinfe/semi-icons; () ( TagInput prefix{IconVigoLogo /} showClear/ br/br/ TagInput prefixPrefix showClear/ br/br/ TagInput suffix{IconGift /}/ br/br/ TagInput suffixSuffix showClear/ / );间距规则当prefix/suffix传入的是 string 或半图标Semi Icon时自动带上左右间隔对应-prefix-text、-prefix-icon、-suffix-text、-suffix-icon样式类见 index.tsx若为自定义 ReactNode左右间隔为 0需要可在传入的 ReactNode 内自行设置。点击 prefix / suffix 区域会通过handleClickPrefixOrSuffix将焦点移入输入框foundation.ts。prefix / suffix 的水平外边距由$spacing-tagInput_prefix_suffix-marginX定义variables.scss。失焦自动创建标签addOnBluraddOnBlur用于设置 blur 事件触发时是否将当前输入值自动创建为标签import React from react; import { TagInput } from douyinfe/semi-ui; () ( TagInput addOnBlur{true} defaultValue{[Semi, Design, Design2Code]} placeholderPlease enter... onChange{v console.log(v)} / );实现位于 foundation 的handleInputBlurfoundation.ts当addOnBlur true时先调用_handleAddTags(e)将输入内容转为标签再重置聚焦状态并触发onBlur回调。注意该行为会复用与回车添加完全相同的过滤、去重与 max 限制逻辑。重复标签控制allowDuplicatesallowDuplicates控制是否允许创建相同标签默认trueimport React from react; import { TagInput } from douyinfe/semi-ui; () ( TagInput allowDuplicates{false} defaultValue{[Semi, Design, Design2Code]} placeholderPlease enter... onChange{v console.log(v)} / );过滤逻辑在_handleAddTags中foundation.ts当allowDuplicates false时既会过滤与现有tagsArray重复的项也会过滤同批输入中自身重复的项addTags.indexOf(item) ! idx。测试用例分别验证了allowDuplicates: true时可添加重复标签、false时不可tagInput.test.js。输入限制max 与 maxLength两个维度限制输入max限制标签总数量超出后触发onExceed()回调超出部分不会加入maxLength限制单个标签的最大长度超出后触发onInputExceed()回调且输入内容会被拦截。import React from react; import { TagInput } from douyinfe/semi-ui; () ( TagInput max{3} placeholdermax 3 onChange{v console.log(v)} onExceed{v { Toast.warning(Exceeds max); console.log(v); }} / TagInput maxLength{5} placeholdermaxLength 5 style{{ marginTop: 12 }} onChange{v console.log(v)} onInputExceed{v { Toast.warning(Exceeds maxLength); console.log(v); }} / / );两个限制的回调参数类型不同onExceed回调收到的是超出后的完整标签数组string[]onInputExceed回调收到的是触发超长的输入串string。实现细节max的拦截在_handleAddTags中foundation.ts合并后若超过max触发onExceed再通过slice(0, max)截断新标签列表maxLength的拦截在_checkInputChangeValid中foundation.ts按分隔符切分后逐段比较长度输入增长且超过maxLength时拒绝本次变更并触发onInputExceed。测试用例验证了输入tiktmaxLength3时输入框值停留在tiktagInput.test.js。输入法IME组合输入的处理值得注意当配置了maxLength时组件通过handleInputCompositionStart/handleInputCompositionEnd处理中文等输入法组合输入foundation.ts。组合输入过程中entering true不校验长度直接更新inputValue此时内容为拼音等中间态组合结束后在handleInputCompositionEnd中统一校验超长部分会被截断为maxLength长度。源码注释特别说明之所以在组合结束事件中更新值是因为 Firefox 下组合结束后 onChange 可能不再触发。这正是中文输入场景下 maxLength 生效但输入体验不被打断的原因。限制标签展示数量maxTagCount 与 NmaxTagCount限制渲染出来的标签数量超出部分以N形式展示showRestTagsPopover默认true控制 hover N 时是否通过 Popover 展示剩余标签restTagsPopoverProps可配置该 Popover 的属性如方向、zIndex、triggerimport React from react; import { TagInput } from douyinfe/semi-ui; () ( TagInput maxTagCount{2} showRestTagsPopover{true} restTagsPopoverProps{{ position: top }} defaultValue{[Semi, Design, Design2Code]} / );实现位于renderTagsindex.tsx当maxTagCount allTags.length时tags截取前 N 个restTags保存剩余项渲染N节点showRestTagsPopover为 true 时用Popover包裹N并将restTagsPopoverProps展开合并默认positiontop、triggerhover、showArrow。相关属性showContentTooltip 与 expandRestTagsOnClickshowContentTooltip默认true单个标签过长发生截断时hover 标签是否通过 Tooltip 展示全部内容。支持布尔值或对象{ type: tooltip | popover, opts: object }其中opts为透传给浮层组件的属性。在renderTag中通过Paragraph的ellipsis{{ showTooltip: showContentTooltip, rows: 1 }}生效index.tsx。expandRestTagsOnClick默认truev2.17.0在不可拖拽的情况下点击 TagInput 后是否展开多余的标签。当!active || !expandRestTagsOnClick时保持折叠active为 true组件被点击激活且expandRestTagsOnClick为 true 时全部展开index.tsx。受控模式标签受控与输入受控标签受控value onChange使用value设置标签数组配合onChange实现完全受控import React, { useCallback, useState } from react; import { TagInput } from douyinfe/semi-ui; function TagInputDemo() { const [value, setValue] useState([semi]); const handleChange useCallback(v setValue(v), []); return TagInput value{value} onChange{handleChange} /; }受控与非受控的判定逻辑在 foundation 的_isControlledComponent中体现_onAdd、_onRemove、handleSortEnd内部均先判断是否受控受控时不直接改写内部tagsArray只通知onChange由外部驱动 value 更新foundation.ts。getDerivedStateFromPropsindex.tsx保证传入value时以 props 为准若传入value{null}或value{undefined}标签数组会被清空测试用例见 tagInput.test.js。输入受控inputValue onInputChange使用inputValue控制输入框内容配合onInputChange实现输入受控import React, { useCallback, useState } from react; import { TagInput } from douyinfe/semi-ui; function TagInputDemo() { const [value, setValue] useState(semi); const handleInputChange useCallback((v, e) setValue(v), []); return TagInput inputValue{value} onInputChange{handleInputChange} /; }onInputChange签名(value: string, e: React.KeyboardEvent) voidgetDerivedStateFromProps中isString(inputValue)时以 props 为准index.tsx。回调函数全集onChange之外TagInput 还提供颗粒度更细的增删回调import React from react; import { TagInput } from douyinfe/semi-ui; () ( TagInput defaultValue{[Semi, Design2Code]} showClear onFocus{e {console.log(onFocus);}} onBlur{e{console.log(onBlur);}} onChange{tag{console.log(onChange ${tag});}} onAdd{tag{console.log(onAdd ${tag});}} onRemove{(v, i){console.log(onRemove ${v}, index${i});}} onInputChange{(input, e){console.log(onInputChange ${input});}} / );各回调触发时机与数据对照 foundation.ts回调触发时机参数onAdd新增标签时回车/失焦/批量添加成功(addedValue: string[])本次新增的标签数组onRemove移除标签时点关闭/退格键(removedValue: string, idx: number)被移除的标签值与下标onChange标签数组任何变化增、删、清空、排序(value: string[])最新标签数组onInputChange输入框内容变化(value: string, e)当前输入串与事件onFocus/onBlur输入框聚焦/失焦(e: React.MouseEvent)onKeyDown键盘按下v2.1.0(e: React.KeyboardEvent)onExceed/onInputExceed超出 max / maxLength见上文输入限制一个容易混淆的点onRemove的回调参数是被移除的单值 下标而onAdd的参数是本次新增的数组。测试用例对onAdd与onRemove的调用次数与参数均有断言tagInput.test.js。焦点管理focus() 与 blur()TagInput 通过 ref 暴露focus()与blur()两个实例方法用于外部接管焦点import React, { useCallback, useRef } from react; import { TagInput, Button } from douyinfe/semi-ui; function TagInputDemo() { const ref useRef(); const handleTagInputFocus useCallback(() { ref.current ref.current.focus(); }, []); return ( TagInput defaultValue{[Semi, Design2Code]} ref{ref} / Button style{{ marginTop: 10 }} onClick{handleTagInputFocus} focus /Button / ); }focus()实现index.tsx会调用inputRef.current.focus({ preventScroll })并在非禁用状态下激活组件注册点击外部关闭 handler使拖拽/展开等激活态生效blur()index.tsx则移出焦点并注销点击外部 handler。preventScroll属性默认false可控制focus()时浏览器是否滚动文档以显示新聚焦元素它作用于组件内部含 autoFocus、focus 方法、prefix/suffix 点击聚焦的 focus 调用但不影响用户自行传入元素的 focus 行为。自定义标签渲染renderTagItemrenderTagItem可完全自定义标签节点的渲染签名renderTagItem(value: string, index: number, onClose: function) React.ReactNode第三个参数onClose自 v2.23.0 起提供可用于在自定义节点中接入关闭逻辑。典型场景是头像 文本 关闭图标的组合标签import React, { useCallback, useMemo, useState } from react; import { TagInput, Avatar } from douyinfe/semi-ui; import { IconClose } from douyinfe/semi-ui-icons; function CustomRender() { const [value, setValue] useState([xiakeman]); const list useMemo(() ([ { name: xiakeman, avatar: https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/avatarDemo.jpeg }, { name: shenyue, avatar: https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/bf8647bffab13c38772c9ff94bf91a9d.jpg }, { name: quchenyi, avatar: https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/dbf7351bb779433d17c4f50478cf42f7.jpg }, { name: wenjiamao, avatar: https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/7abf810ff060ac3387bd027ead92c4e0.jpg }, ]), []); const mapList useMemo(() new Map(list.map(item [item.name, item])), [list]); const renderTagItem useCallback((value, index, onClose) { const data mapList.get(value); return ( div key{index} style{{ display: flex, alignItems: center, fontSize: 14, marginRight: 10 }} Avatar altavatar src{data ? data.avatar : https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/dy.png} sizeextra-small / span style{{ marginLeft: 8 }} {${value}semi.com} /span IconClose onClick{onClose} / /div ); }, [mapList]); return ( TagInput value{value} onChange{setValue} renderTagItem{renderTagItem} / ); }实现要点index.tsx传入renderTagItem后内部不再渲染默认 Tag而是直接调用自定义渲染函数组件会把onClose包装为调用handleTagClose(index)且阻止 Tag 内部setVisible(false)默认行为的闭包因此自定义节点内的关闭按钮也能正确联动标签删除index.tsx。启用拖拽时自定义节点前会额外渲染拖拽手柄见下文。拖拽排序draggable将draggable设为truev2.17.0可开启拖拽排序。需要注意拖拽排序模式下不允许添加相同 Tag因此必须同时将allowDuplicates设为false。交互上点击 TagInput 后 Tag 可拖拽点击 TagInput 外任意区域后 Tag 不可拖拽import React from react; import { TagInput } from douyinfe/semi-ui; () ( TagInput draggable allowDuplicates{false} defaultValue{[Semi, Design, Design2Code]} placeholderplease enter... onChange{v console.log(v)} / );源码层面拖拽由_sortable/Sortable组件承载index.tsx组件处于active点击激活状态且draggable为 true 时将标签数组渲染为可排序列表并注入拖拽手柄IconHandle排序结束时调用 foundation 的handleSortEndfoundation.ts通过arrayMove计算新数组并触发onChange。拖拽中的元素 z-index 为 2000$z-tagInput_drag_item_move见 variables.scss。API 参考完整属性表以下为 TagInput 全部 Props与文档 API 表一一对应类型定义见 index.tsx属性说明类型默认值版本addOnBlur是否在 blur 事件触发时将当前输入值自动创建为标签booleanfalse-allowDuplicates是否允许添加相同标签booleantrue-autoFocus初始渲染时是否自动聚焦booleanfalse-className样式类名string--defaultValue默认标签值string[]--disabled只读禁用交互booleanfalse-inputValue受控输入值string--maxLength单个标签的最大长度number--max允许的标签最大数量number--maxTagCount标签最大展示数量超出以 N 展示number--showRestTagsPopover超出 maxTagCount 后 hover N 是否通过 Popover 显示剩余内容booleantrue-restTagsPopoverPropsPopover 配置属性方向、zIndex、trigger 等PopoverProps{}-showContentTooltip标签过长截断时 hover 是否通过 Tooltip 显示全部内容传对象时可指定浮层组件类型与透传属性boolean | {type: tooltip | popover, opts: object}true-placeholder占位文案string--prefix前缀文本或 ReactNodeReactNode--preventScroll指示浏览器是否滚动文档以显示新聚焦元素作用于组件内部 focus 方法boolean--renderTagItem自定义标签渲染onClose 参数自 2.23.0 提供(value: string, index: number, onClose: function) React.ReactNode--separator批量输入分隔符string | string[],-showClear是否显示一键清空按钮booleanfalse-size尺寸small / large / defaultstringdefault-split自定义分隔符处理函数(value: string, separator: string) string[]-2.90.0style内联样式React.CSSProperties--suffix后缀文本或 ReactNodeReactNode--validateStatus校验状态样式default / warning / errorstringdefault-value受控标签值string[] | undefined--draggable是否可拖拽排序booleanfalse2.17.0expandRestTagsOnClick不可拖拽时点击 TagInput 后是否展开多余标签booleantrue2.17.0onAdd添加标签时的回调(addedValue: string[]) void--onBlur输入框失焦回调(e: React.MouseEvent) void--onChange标签变化回调(value: string[]) void--onExceed超过 max 时的回调(value: string[]) void--onFocus输入框聚焦回调(e: React.MouseEvent) void--onInputChange输入框内容变化回调(value: string, e: React.KeyboardEvent) void--onInputExceed超过 maxLength 时的回调(value: string) void--onKeyDownkeydown 回调(e: React.KeyboardEvent) void-2.1.0onRemove移除标签时的回调(removedValue: string, idx: number) void--Methodsref 实例方法绑定在组件实例上、可通过 ref 调用的方法名称描述版本blur()移出焦点-focus()获取焦点-用法见上文焦点管理一节focus()与blur()的底层实现位于 index.tsx。无障碍AccessibilityTagInput 内置了完整的 ARIA 支持对应源码 index.tsx 与renderClearBtn中的按钮实现支持传入aria-label表示该 TagInput 的作用组件根节点依据disabled与validateStatus分别设置aria-disabled、aria-invaliderror 时为 true输入框与清空按钮均具有aria-label表明元素作用输入框为input value清空按钮为Clear TagInput value清空按钮以rolebuttontabIndex{0}呈现并支持键盘 Enter 触发清空handleClearEnterPress见 foundation.ts默认渲染的每个 Tag 也带有语义化aria-label如Closable Tag: xxx见 index.tsx。设计变量Design TokensTagInput 的视觉样式全部通过设计变量Design Token驱动与 Semi Design 主题体系打通可在自定义主题时统一调整。以 packages/semi-foundation/tagInput/variables.scss 为例主要分类包括颜色默认/悬浮/聚焦态背景色--semi-color-fill-0/1、--semi-color-focus-border、warning/error 态背景与描边--semi-color-warning-*、--semi-color-danger-*、禁用态文字与背景--semi-color-disabled-*、prefix/suffix 文字色、拖拽元素竖线色等间距与尺寸small/default/large 三档高度与标签外边距$spacing-tagInput_small-Y等、prefix/suffix 水平外边距、拖拽手柄右外边距圆角与描边$radius-tagInput与三态描边宽度层级拖拽中元素的z-index2000。这些 token 与DesignToken文档区块对应开发者可在 Semi Design 主题定制体系中覆盖同名变量实现全局或局部换肤。附源码研读索引若希望进一步深入 TagInput 的实现细节可从以下文件入手组件视图层packages/semi-ui/tagInput/index.tsxProps/State 定义、渲染、ref 方法逻辑层packages/semi-foundation/tagInput/foundation.ts添加/删除/限制/输入法/拖拽排序全部交互逻辑分隔符切分工具packages/semi-foundation/tagInput/utils/getSplitedArray.ts常量定义packages/semi-foundation/tagInput/constants.ts尺寸与状态枚举样式变量packages/semi-foundation/tagInput/variables.scss单元测试packages/semi-ui/tagInput/test/tagInput.test.js覆盖受控、分隔符、限制、回调、拖拽、ARIA 等 40 场景中英文文档content/input/taginput/index.md、content/input/taginput/index-en-US.md综上TagInput 的核心设计是逻辑层foundation与视图层semi-ui分离所有输入校验、过滤、去重、截断规则都集中在 foundation 中视图层只负责渲染与事件转发。理解这一架构后无论是排查为什么标签没加上还是扩展自定义能力都能快速定位到 foundation.ts 中对应的处理函数。赞分享前端UI组件设计系统【免费下载链接】semi-designA modern, comprehensive, flexible design system and React UI library, AI-friendly built-in.Provide 3000 Design Tokens, easy to build your design system. Make Semi Design to Any Design. Design to Code in one click项目地址https://gitcode.com/gh_mirrors/se/semi-design点击查看免费下载相关推荐Semi Design TagInput 标签输入框实战指南从基础用法到源码级实现解析Semi Design TagInput 标签输入框实战指南从基础用法到源码级实现解析 Semi Design 的 TagInput标签输入框是一个将输入前端UI组件设计系统如何用 Playwright codegen 把浏览器操作录制为 LibreChat e2e 测试草稿e2e:record如何用 Playwright codegen 把浏览器操作录制为 LibreChat e2e 测试草稿e2e:record 你在维护或扩展 LibreCha前端UI组件设计系统darktable 实战指南RAW 到成片的 5 步工作流darktable 实战指南RAW 到成片的 5 步工作流 把一堆 RAW 倒进文件夹挑出能用的调白平衡、压曝光、修色彩最后变成可以发出去的成片——这条前端UI组件设计系统上一篇物理信息神经网络完整指南5大优势让你快速掌握微分方程求解新方法下一篇微信聊天记录终极指南如何从安卓设备解密和导出完整历史创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
