feat: customize workflow category labels
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-04-02 20:52:36 +08:00
parent ba539ce236
commit 5ce1e48392
9 changed files with 247 additions and 107 deletions

View File

@ -913,6 +913,8 @@ export function AgentWorkflowSettingsClient({
() => getAgentWorkflowCategoryLabels(config),
[config]
);
const firstCategoryLabel = categoryLabels.firstCategory;
const secondCategoryLabel = categoryLabels.secondCategory;
const thirdCategoryLabel = categoryLabels.thirdCategory;
const fourthCategoryLabel = categoryLabels.fourthCategory;
const fifthCategoryLabel = categoryLabels.fifthCategory;
@ -1013,12 +1015,12 @@ export function AgentWorkflowSettingsClient({
const handleAddBrandEntry = () => {
const nextValue = normalizeInputOptionValue(brandDraft);
if (!nextValue) {
toast.error("请输入品牌名称");
toast.error(`请输入${firstCategoryLabel}名称`);
return;
}
if (brandStoreEntries.some((item) => item.brandName.toLowerCase() === nextValue.toLowerCase())) {
toast.error("该品牌已存在");
toast.error(`该${firstCategoryLabel}已存在`);
return;
}
@ -1074,7 +1076,7 @@ export function AgentWorkflowSettingsClient({
const draft = storeDrafts[brandName] || "";
const nextValue = normalizeInputOptionValue(draft);
if (!nextValue) {
toast.error("请输入店铺名称");
toast.error(`请输入${secondCategoryLabel}名称`);
return;
}
@ -1085,7 +1087,7 @@ export function AgentWorkflowSettingsClient({
) ||
targetEntry?.stores.some((store) => store.toLowerCase() === nextValue.toLowerCase())
) {
toast.error("该店铺已存在");
toast.error(`该${secondCategoryLabel}已存在`);
return;
}
@ -2014,7 +2016,7 @@ export function AgentWorkflowSettingsClient({
const handleDownloadBrandTemplate = async () => {
try {
await downloadAgentWorkflowBrandImportTemplate(categoryLabels);
toast.success("品牌导入模板已开始下载");
toast.success("层级导入模板已开始下载");
} catch (error) {
toast.error(error instanceof Error ? error.message : "下载模板失败");
}
@ -2031,7 +2033,7 @@ export function AgentWorkflowSettingsClient({
setIsImportingBrandSheet(true);
const { entries, rowCount } = await parseAgentWorkflowBrandImportFile(file, categoryLabels);
updateInputOptions([...brandStoreEntries, ...entries]);
toast.success(`已从表格导入 ${rowCount} 行,品牌树已自动合并去重`);
toast.success(`已从表格导入 ${rowCount} 行,层级树已自动合并去重`);
} catch (error) {
toast.error(error instanceof Error ? error.message : "导入表格失败");
} finally {
@ -3477,7 +3479,9 @@ export function AgentWorkflowSettingsClient({
</div>
<div>
<CardTitle className="text-base">基础下拉选项</CardTitle>
<CardDescription>配置品牌、店铺与分类层级关系,以及第一页可选的价格带区间</CardDescription>
<CardDescription>
配置{firstCategoryLabel}、{secondCategoryLabel}与分类层级关系,以及第一页可选的价格带区间
</CardDescription>
</div>
</div>
</CardHeader>
@ -3485,14 +3489,14 @@ export function AgentWorkflowSettingsClient({
<div className="space-y-2">
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label className="text-xs">品牌树</Label>
<Label className="text-xs">层级树</Label>
<p className="text-xs text-muted-foreground">
支持一键下载模板并导入品牌、店铺、{thirdCategoryLabel}、{fourthCategoryLabel}、{fifthCategoryLabel}表格
支持一键下载模板并导入{firstCategoryLabel}、{secondCategoryLabel}、{thirdCategoryLabel}、{fourthCategoryLabel}、{fifthCategoryLabel}表格
</p>
</div>
<div className="flex flex-wrap items-center justify-end gap-2">
<span className="text-xs text-muted-foreground">
{brandStoreEntries.length} 个品牌
{brandStoreEntries.length} 个{firstCategoryLabel}
</span>
<Button
type="button"
@ -3526,7 +3530,23 @@ export function AgentWorkflowSettingsClient({
className="hidden"
onChange={handleBrandSheetImport}
/>
<div className="grid grid-cols-1 gap-3 rounded-xl border bg-background/70 p-3 md:grid-cols-3">
<div className="grid grid-cols-1 gap-3 rounded-xl border bg-background/70 p-3 md:grid-cols-2 xl:grid-cols-5">
<div className="space-y-2">
<Label className="text-xs">第一级名称</Label>
<Input
value={firstCategoryLabel}
onChange={(e) => handleCategoryLabelChange("firstCategory", e.target.value)}
placeholder="例如:品牌分类"
/>
</div>
<div className="space-y-2">
<Label className="text-xs">第二级名称</Label>
<Input
value={secondCategoryLabel}
onChange={(e) => handleCategoryLabelChange("secondCategory", e.target.value)}
placeholder="例如:门店分类"
/>
</div>
<div className="space-y-2">
<Label className="text-xs">第三级名称</Label>
<Input
@ -3563,7 +3583,7 @@ export function AgentWorkflowSettingsClient({
handleAddBrandEntry();
}
}}
placeholder="输入一个品牌名称后点击添加"
placeholder={`输入一个${firstCategoryLabel}名称后点击添加`}
/>
<Button
type="button"
@ -3571,7 +3591,7 @@ export function AgentWorkflowSettingsClient({
className="shrink-0"
>
<Plus className="mr-2 h-4 w-4" />
添加品牌
添加{firstCategoryLabel}
</Button>
</div>
@ -3587,13 +3607,13 @@ export function AgentWorkflowSettingsClient({
type="button"
onClick={() => handleRemoveBrandEntry(entry.brandName)}
className="rounded-full p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-destructive"
aria-label={`删除品牌 ${entry.brandName}`}
aria-label={`删除${firstCategoryLabel} ${entry.brandName}`}
>
<Trash2 className="h-3.5 w-3.5" />
</button>
</div>
<span className="text-xs text-muted-foreground">
{(entry.storeEntries || []).length} 个店铺
{(entry.storeEntries || []).length} 个{secondCategoryLabel}
</span>
</div>
</div>
@ -3614,7 +3634,7 @@ export function AgentWorkflowSettingsClient({
handleAddStoreToBrand(entry.brandName);
}
}}
placeholder={`给品牌“${entry.brandName}”添加店铺`}
placeholder={`给${firstCategoryLabel}“${entry.brandName}”添加${secondCategoryLabel}`}
/>
<Button
type="button"
@ -3623,7 +3643,7 @@ export function AgentWorkflowSettingsClient({
variant="outline"
>
<Plus className="mr-2 h-4 w-4" />
添加店铺
添加{secondCategoryLabel}
</Button>
</div>
@ -3649,7 +3669,7 @@ export function AgentWorkflowSettingsClient({
)
}
className="rounded-full p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-destructive"
aria-label={`删除店铺 ${storeEntry.storeName}`}
aria-label={`删除${secondCategoryLabel} ${storeEntry.storeName}`}
>
<Trash2 className="h-3.5 w-3.5" />
</button>
@ -3679,7 +3699,7 @@ export function AgentWorkflowSettingsClient({
);
}
}}
placeholder={`给店铺“${storeEntry.storeName}”添加${thirdCategoryLabel}`}
placeholder={`给${secondCategoryLabel}“${storeEntry.storeName}”添加${thirdCategoryLabel}`}
/>
<Button
type="button"
@ -3908,7 +3928,7 @@ export function AgentWorkflowSettingsClient({
</div>
) : (
<div className="mt-3 rounded-lg border border-dashed bg-background/70 px-3 py-4 text-sm text-muted-foreground">
这个店铺下还没有添加任何{thirdCategoryLabel}。
这个{secondCategoryLabel}下还没有添加任何{thirdCategoryLabel}。
</div>
)}
</div>
@ -3918,7 +3938,7 @@ export function AgentWorkflowSettingsClient({
</div>
) : (
<div className="mt-3 rounded-lg border border-dashed bg-background/70 px-3 py-4 text-sm text-muted-foreground">
这个品牌下还没有添加任何店铺。
这个{firstCategoryLabel}下还没有添加任何{secondCategoryLabel}。
</div>
)}
</div>
@ -3927,7 +3947,7 @@ export function AgentWorkflowSettingsClient({
</div>
) : (
<div className="mt-3 rounded-lg border border-dashed bg-background/70 px-3 py-4 text-sm text-muted-foreground">
还没有添加品牌。先添加品牌,再在每个品牌下继续添加店铺以及{thirdCategoryLabel}到{fifthCategoryLabel}。
还没有添加{firstCategoryLabel}。先添加{firstCategoryLabel},再在每个{firstCategoryLabel}下继续添加{secondCategoryLabel}以及{thirdCategoryLabel}到{fifthCategoryLabel}。
</div>
)}
</div>
@ -3985,7 +4005,7 @@ export function AgentWorkflowSettingsClient({
</div>
) : (
<div className="mt-3 rounded-lg border border-dashed bg-background/70 px-3 py-4 text-sm text-muted-foreground">
还没有添加价格带。添加后,第一页品牌和店铺右侧会显示价格带下拉框。
还没有添加价格带。添加后,第一页{firstCategoryLabel}和{secondCategoryLabel}右侧会显示价格带下拉框。
</div>
)}
</div>
@ -4036,7 +4056,7 @@ export function AgentWorkflowSettingsClient({
<div>
<CardTitle className="text-base">PSD 文字颜色选项</CardTitle>
<CardDescription>
按品牌配置套用 PSD 模板时可选的两组文字颜色,前台会跟随当前品牌读取。
按{firstCategoryLabel}配置套用 PSD 模板时可选的两组文字颜色,前台会跟随当前{firstCategoryLabel}读取。
</CardDescription>
</div>
</div>
@ -4044,22 +4064,22 @@ export function AgentWorkflowSettingsClient({
<CardContent className="space-y-4">
{psdTextColorBrandOptions.length > 0 ? (
<div className="space-y-2">
<Label className="text-xs">选择品牌</Label>
<Label className="text-xs">选择{firstCategoryLabel}</Label>
<SearchableSelect
value={selectedPsdTextColorBrand}
options={psdTextColorBrandOptions}
placeholder="选择一个品牌后配置颜色"
searchPlaceholder="输入品牌名筛选"
emptyText="暂无品牌可选"
placeholder={`选择一个${firstCategoryLabel}后配置颜色`}
searchPlaceholder={`输入${firstCategoryLabel}名筛选`}
emptyText={`暂无${firstCategoryLabel}可选`}
onValueChange={setSelectedPsdTextColorBrand}
/>
<p className="text-xs text-muted-foreground">
未单独配置的品牌会回退系统默认颜色;当前编辑品牌:{selectedPsdTextColorBrand}
未单独配置的{firstCategoryLabel}会回退系统默认颜色;当前编辑{firstCategoryLabel}:{selectedPsdTextColorBrand}
</p>
</div>
) : (
<div className="rounded-lg border border-dashed bg-muted/10 px-4 py-3 text-sm text-muted-foreground">
请先在上方“品牌树”里配置品牌,再按品牌设置 PSD 文字颜色。
请先在上方“层级树”里配置{firstCategoryLabel},再按{firstCategoryLabel}设置 PSD 文字颜色。
</div>
)}
@ -4146,14 +4166,14 @@ export function AgentWorkflowSettingsClient({
<UserCircle className="h-4 w-4 text-rose-600" />
</div>
<div>
<CardTitle className="text-base">品牌模特图库</CardTitle>
<CardTitle className="text-base">{firstCategoryLabel}模特图库</CardTitle>
<CardDescription>
为每个品牌配置模特图,第二步会按品牌展示供用户选择
为每个{firstCategoryLabel}配置模特图,第二步会按{firstCategoryLabel}展示供用户选择
</CardDescription>
</div>
</div>
<Badge variant="secondary">
{brandModelConfig.entries.length} 个品牌
{brandModelConfig.entries.length} 个{firstCategoryLabel}
</Badge>
</div>
</CardHeader>
@ -4254,7 +4274,7 @@ export function AgentWorkflowSettingsClient({
<Package className="h-6 w-6 text-muted-foreground" />
</div>
<p className="mt-4 text-sm text-muted-foreground">
先在"基础配置"中添加品牌,然后为每个品牌上传模特图
先在“基础配置”中添加{firstCategoryLabel},然后为每个{firstCategoryLabel}上传模特图
</p>
</div>
)}

View File

@ -740,6 +740,8 @@ const updateSchema = z.object({
.optional(),
categoryLabels: z
.object({
firstCategory: z.string().trim().min(1).max(24).optional(),
secondCategory: z.string().trim().min(1).max(24).optional(),
thirdCategory: z.string().trim().min(1).max(24).optional(),
fourthCategory: z.string().trim().min(1).max(24).optional(),
fifthCategory: z.string().trim().min(1).max(24).optional(),

View File

@ -10,6 +10,7 @@ import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";
import {
defaultAgentWorkflowUiConfig,
getAgentWorkflowCategoryLabels,
getAgentWorkflowBrandStoreEntries,
getAgentWorkflowFifthCategoryOptionsByBrandStoreThirdFourth,
getAgentWorkflowFourthCategoryOptionsByBrandStoreThirdCategory,
@ -92,6 +93,15 @@ export function AgentWorkflowPanel({
onNextStep,
onSkipStep,
}: AgentWorkflowPanelProps) {
const categoryLabels = useMemo(
() => getAgentWorkflowCategoryLabels(config),
[config]
);
const firstCategoryLabel = categoryLabels.firstCategory;
const secondCategoryLabel = categoryLabels.secondCategory;
const thirdCategoryLabel = categoryLabels.thirdCategory;
const fourthCategoryLabel = categoryLabels.fourthCategory;
const fifthCategoryLabel = categoryLabels.fifthCategory;
const brandOptions = useMemo(
() => config?.inputOptions?.brands || defaultAgentWorkflowUiConfig.inputOptions?.brands || [],
[config?.inputOptions?.brands]
@ -248,9 +258,9 @@ export function AgentWorkflowPanel({
<SearchableSelect
value={inputs.brand}
options={brandOptions}
placeholder="品牌"
searchPlaceholder="输入品牌名筛选"
emptyText="暂无品牌可选,请先到系统设置配置"
placeholder={firstCategoryLabel}
searchPlaceholder={`输入${firstCategoryLabel}名筛选`}
emptyText={`暂无${firstCategoryLabel}可选,请先到系统设置配置`}
onValueChange={(value) =>
onInputsChange({
...inputs,
@ -271,9 +281,13 @@ export function AgentWorkflowPanel({
<SearchableSelect
value={inputs.store}
options={storeOptions}
placeholder="店铺"
searchPlaceholder="输入店铺名筛选"
emptyText={inputs.brand ? "当前品牌下暂无店铺可选" : "请先选择品牌"}
placeholder={secondCategoryLabel}
searchPlaceholder={`输入${secondCategoryLabel}名筛选`}
emptyText={
inputs.brand
? `当前${firstCategoryLabel}下暂无${secondCategoryLabel}可选`
: `请先选择${firstCategoryLabel}`
}
disabled={!inputs.brand || (brandStoreEntries.length > 0 && storeOptions.length === 0)}
onValueChange={(value) =>
onInputsChange({
@ -291,9 +305,13 @@ export function AgentWorkflowPanel({
<SearchableSelect
value={inputs.thirdCategory}
options={thirdCategoryOptions}
placeholder="选择三级分类"
searchPlaceholder="输入三级分类筛选"
emptyText={inputs.store ? "当前店铺下暂无三级分类" : "请先选择店铺"}
placeholder={`选择${thirdCategoryLabel}`}
searchPlaceholder={`输入${thirdCategoryLabel}筛选`}
emptyText={
inputs.store
? `当前${secondCategoryLabel}下暂无${thirdCategoryLabel}`
: `请先选择${secondCategoryLabel}`
}
disabled={!inputs.store || thirdCategoryOptions.length === 0}
onValueChange={(value) =>
onInputsChange({
@ -310,9 +328,13 @@ export function AgentWorkflowPanel({
<SearchableSelect
value={inputs.fourthCategory}
options={fourthCategoryOptions}
placeholder="选择四级分类"
searchPlaceholder="输入四级分类筛选"
emptyText={inputs.thirdCategory ? "当前三级分类下暂无四级分类" : "请先选择三级分类"}
placeholder={`选择${fourthCategoryLabel}`}
searchPlaceholder={`输入${fourthCategoryLabel}筛选`}
emptyText={
inputs.thirdCategory
? `当前${thirdCategoryLabel}下暂无${fourthCategoryLabel}`
: `请先选择${thirdCategoryLabel}`
}
disabled={!inputs.thirdCategory || fourthCategoryOptions.length === 0}
onValueChange={(value) =>
onInputsChange({
@ -328,9 +350,13 @@ export function AgentWorkflowPanel({
<SearchableSelect
value={inputs.fifthCategory}
options={fifthCategoryOptions}
placeholder="选择五级分类"
searchPlaceholder="输入五级分类筛选"
emptyText={inputs.fourthCategory ? "当前四级分类下暂无五级分类" : "请先选择四级分类"}
placeholder={`选择${fifthCategoryLabel}`}
searchPlaceholder={`输入${fifthCategoryLabel}筛选`}
emptyText={
inputs.fourthCategory
? `当前${fourthCategoryLabel}下暂无${fifthCategoryLabel}`
: `请先选择${fourthCategoryLabel}`
}
disabled={!inputs.fourthCategory || fifthCategoryOptions.length === 0}
onValueChange={(value) => onInputsChange({ ...inputs, fifthCategory: value })}
/>
@ -346,7 +372,7 @@ export function AgentWorkflowPanel({
</div>
{(brandOptions.length > 0 || storeOptions.length > 0) && (
<div className="text-xs text-muted-foreground">
品牌和店铺支持输入关键字匹配下拉选择,候选列表由系统设置配置。
{firstCategoryLabel}和{secondCategoryLabel}支持输入关键字匹配下拉选择,候选列表由系统设置配置。
</div>
)}
<div className="text-xs text-muted-foreground">
@ -438,19 +464,19 @@ export function AgentWorkflowPanel({
<span className="rounded-full bg-background px-2 py-1">搭配SKU:{inputs.secondarySku}</span>
) : null}
{inputs.brand ? (
<span className="rounded-full bg-background px-2 py-1">品牌:{inputs.brand}</span>
<span className="rounded-full bg-background px-2 py-1">{firstCategoryLabel}:{inputs.brand}</span>
) : null}
{inputs.store ? (
<span className="rounded-full bg-background px-2 py-1">店铺:{inputs.store}</span>
<span className="rounded-full bg-background px-2 py-1">{secondCategoryLabel}:{inputs.store}</span>
) : null}
{inputs.thirdCategory ? (
<span className="rounded-full bg-background px-2 py-1">三级分类:{inputs.thirdCategory}</span>
<span className="rounded-full bg-background px-2 py-1">{thirdCategoryLabel}:{inputs.thirdCategory}</span>
) : null}
{inputs.fourthCategory ? (
<span className="rounded-full bg-background px-2 py-1">四级分类:{inputs.fourthCategory}</span>
<span className="rounded-full bg-background px-2 py-1">{fourthCategoryLabel}:{inputs.fourthCategory}</span>
) : null}
{inputs.fifthCategory ? (
<span className="rounded-full bg-background px-2 py-1">五级分类:{inputs.fifthCategory}</span>
<span className="rounded-full bg-background px-2 py-1">{fifthCategoryLabel}:{inputs.fifthCategory}</span>
) : null}
{inputs.priceBand ? (
<span className="rounded-full bg-background px-2 py-1">价格带:{inputs.priceBand}</span>

View File

@ -11,6 +11,7 @@ import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";
import {
defaultAgentWorkflowUiConfig,
getAgentWorkflowCategoryLabels,
getAgentWorkflowBrandStoreEntries,
getAgentWorkflowFifthCategoryOptionsByBrandStoreThirdFourth,
getAgentWorkflowFourthCategoryOptionsByBrandStoreThirdCategory,
@ -174,6 +175,15 @@ export function AgentWorkflowPanelDark({
onNextStep,
onSkipStep,
}: AgentWorkflowPanelDarkProps) {
const categoryLabels = useMemo(
() => getAgentWorkflowCategoryLabels(config),
[config]
);
const firstCategoryLabel = categoryLabels.firstCategory;
const secondCategoryLabel = categoryLabels.secondCategory;
const thirdCategoryLabel = categoryLabels.thirdCategory;
const fourthCategoryLabel = categoryLabels.fourthCategory;
const fifthCategoryLabel = categoryLabels.fifthCategory;
const brandOptions = useMemo(
() => config?.inputOptions?.brands || defaultAgentWorkflowUiConfig.inputOptions?.brands || [],
[config?.inputOptions?.brands]
@ -351,9 +361,9 @@ export function AgentWorkflowPanelDark({
<SearchableSelect
value={inputs.brand}
options={brandOptions}
placeholder="选择品牌"
searchPlaceholder="输入品牌名筛选"
emptyText="暂无品牌可选"
placeholder={`选择${firstCategoryLabel}`}
searchPlaceholder={`输入${firstCategoryLabel}名筛选`}
emptyText={`暂无${firstCategoryLabel}可选`}
onValueChange={(value) =>
onInputsChange({
...inputs,
@ -374,9 +384,13 @@ export function AgentWorkflowPanelDark({
<SearchableSelect
value={inputs.store}
options={storeOptions}
placeholder="选择店铺"
searchPlaceholder="输入店铺名筛选"
emptyText={inputs.brand ? "当前品牌下暂无店铺可选" : "请先选择品牌"}
placeholder={`选择${secondCategoryLabel}`}
searchPlaceholder={`输入${secondCategoryLabel}名筛选`}
emptyText={
inputs.brand
? `当前${firstCategoryLabel}下暂无${secondCategoryLabel}可选`
: `请先选择${firstCategoryLabel}`
}
disabled={!inputs.brand || (brandStoreEntries.length > 0 && storeOptions.length === 0)}
onValueChange={(value) =>
onInputsChange({
@ -394,9 +408,13 @@ export function AgentWorkflowPanelDark({
<SearchableSelect
value={inputs.thirdCategory}
options={thirdCategoryOptions}
placeholder="选择三级分类"
searchPlaceholder="输入三级分类筛选"
emptyText={inputs.store ? "当前店铺下暂无三级分类" : "请先选择店铺"}
placeholder={`选择${thirdCategoryLabel}`}
searchPlaceholder={`输入${thirdCategoryLabel}筛选`}
emptyText={
inputs.store
? `当前${secondCategoryLabel}下暂无${thirdCategoryLabel}`
: `请先选择${secondCategoryLabel}`
}
disabled={!inputs.store || thirdCategoryOptions.length === 0}
onValueChange={(value) =>
onInputsChange({
@ -413,9 +431,13 @@ export function AgentWorkflowPanelDark({
<SearchableSelect
value={inputs.fourthCategory}
options={fourthCategoryOptions}
placeholder="选择四级分类"
searchPlaceholder="输入四级分类筛选"
emptyText={inputs.thirdCategory ? "当前三级分类下暂无四级分类" : "请先选择三级分类"}
placeholder={`选择${fourthCategoryLabel}`}
searchPlaceholder={`输入${fourthCategoryLabel}筛选`}
emptyText={
inputs.thirdCategory
? `当前${thirdCategoryLabel}下暂无${fourthCategoryLabel}`
: `请先选择${thirdCategoryLabel}`
}
disabled={!inputs.thirdCategory || fourthCategoryOptions.length === 0}
onValueChange={(value) =>
onInputsChange({
@ -431,9 +453,13 @@ export function AgentWorkflowPanelDark({
<SearchableSelect
value={inputs.fifthCategory}
options={fifthCategoryOptions}
placeholder="选择五级分类"
searchPlaceholder="输入五级分类筛选"
emptyText={inputs.fourthCategory ? "当前四级分类下暂无五级分类" : "请先选择四级分类"}
placeholder={`选择${fifthCategoryLabel}`}
searchPlaceholder={`输入${fifthCategoryLabel}筛选`}
emptyText={
inputs.fourthCategory
? `当前${fourthCategoryLabel}下暂无${fifthCategoryLabel}`
: `请先选择${fourthCategoryLabel}`
}
disabled={!inputs.fourthCategory || fifthCategoryOptions.length === 0}
onValueChange={(value) => onInputsChange({ ...inputs, fifthCategory: value })}
/>

View File

@ -285,9 +285,17 @@ export function AgentWorkflowBrandStorePanel({
() => getAgentWorkflowCategoryLabels(config),
[config]
);
const firstCategoryLabel = categoryLabels.firstCategory;
const secondCategoryLabel = categoryLabels.secondCategory;
const thirdCategoryLabel = categoryLabels.thirdCategory;
const fourthCategoryLabel = categoryLabels.fourthCategory;
const fifthCategoryLabel = categoryLabels.fifthCategory;
const resolvedTitle =
title === "品牌层级选择" ? `${firstCategoryLabel}层级选择` : title;
const resolvedDescription =
description === "先选择品牌、店铺、分类层级和价格带,后续品牌模特与风格步骤会按这里的选择联动。"
? `先选择${firstCategoryLabel}、${secondCategoryLabel}、分类层级和价格带,后续品牌模特与风格步骤会按这里的选择联动。`
: description;
const brandOptions = useMemo(
() => config?.inputOptions?.brands || defaultAgentWorkflowUiConfig.inputOptions?.brands || [],
[config?.inputOptions?.brands]
@ -422,19 +430,19 @@ export function AgentWorkflowBrandStorePanel({
>
<div className="mb-4 space-y-1">
<div className={cn("text-sm font-semibold", isDark ? "text-white" : "text-foreground")}>
{title}
{resolvedTitle}
</div>
<div className={cn("text-xs leading-5", isDark ? "text-white/48" : "text-muted-foreground")}>
{description}
{resolvedDescription}
</div>
</div>
<div className="grid max-w-7xl grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-6">
<SearchableSelect
value={inputs.brand}
options={brandOptions}
placeholder="选择品牌"
searchPlaceholder="输入品牌名筛选"
emptyText="暂无品牌可选"
placeholder={`选择${firstCategoryLabel}`}
searchPlaceholder={`输入${firstCategoryLabel}名筛选`}
emptyText={`暂无${firstCategoryLabel}可选`}
theme={theme}
onValueChange={(value) =>
onInputsChange({
@ -456,9 +464,13 @@ export function AgentWorkflowBrandStorePanel({
<SearchableSelect
value={inputs.store}
options={storeOptions}
placeholder="选择店铺"
searchPlaceholder="输入店铺名筛选"
emptyText={inputs.brand ? "当前品牌下暂无店铺可选" : "请先选择品牌"}
placeholder={`选择${secondCategoryLabel}`}
searchPlaceholder={`输入${secondCategoryLabel}名筛选`}
emptyText={
inputs.brand
? `当前${firstCategoryLabel}下暂无${secondCategoryLabel}可选`
: `请先选择${firstCategoryLabel}`
}
theme={theme}
disabled={!inputs.brand || (brandStoreEntries.length > 0 && storeOptions.length === 0)}
onValueChange={(value) =>
@ -480,7 +492,11 @@ export function AgentWorkflowBrandStorePanel({
options={thirdCategoryOptions}
placeholder={`选择${thirdCategoryLabel}`}
searchPlaceholder={`输入${thirdCategoryLabel}筛选`}
emptyText={inputs.store ? `当前店铺下暂无${thirdCategoryLabel}` : "请先选择店铺"}
emptyText={
inputs.store
? `当前${secondCategoryLabel}下暂无${thirdCategoryLabel}`
: `请先选择${secondCategoryLabel}`
}
theme={theme}
disabled={!inputs.store || thirdCategoryOptions.length === 0}
onValueChange={(value) =>
@ -570,6 +586,12 @@ export function AgentWorkflowPanelDarkV2({
embedded = false,
showBrandStoreSection = true,
}: AgentWorkflowPanelDarkV2Props) {
const categoryLabels = useMemo(
() => getAgentWorkflowCategoryLabels(config),
[config]
);
const firstCategoryLabel = categoryLabels.firstCategory;
const secondCategoryLabel = categoryLabels.secondCategory;
const resolvedConfig = useMemo(() => {
const source = config || defaultAgentWorkflowUiConfig;
return {
@ -866,7 +888,7 @@ export function AgentWorkflowPanelDarkV2({
<span className="flex h-6 w-6 items-center justify-center rounded-md bg-emerald-500/20 text-xs font-bold text-emerald-400">
1
</span>
品牌和店铺选择
{firstCategoryLabel}和{secondCategoryLabel}选择
</h2>
<AgentWorkflowBrandStorePanel
config={config}

View File

@ -11,6 +11,7 @@ import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";
import {
defaultAgentWorkflowUiConfig,
getAgentWorkflowCategoryLabels,
getAgentWorkflowBrandStoreEntries,
getAgentWorkflowFifthCategoryOptionsByBrandStoreThirdFourth,
getAgentWorkflowFourthCategoryOptionsByBrandStoreThirdCategory,
@ -186,6 +187,15 @@ export function AgentWorkflowPanelNew({
onNextStep,
onSkipStep,
}: AgentWorkflowPanelNewProps) {
const categoryLabels = useMemo(
() => getAgentWorkflowCategoryLabels(config),
[config]
);
const firstCategoryLabel = categoryLabels.firstCategory;
const secondCategoryLabel = categoryLabels.secondCategory;
const thirdCategoryLabel = categoryLabels.thirdCategory;
const fourthCategoryLabel = categoryLabels.fourthCategory;
const fifthCategoryLabel = categoryLabels.fifthCategory;
const brandOptions = useMemo(
() => config?.inputOptions?.brands || defaultAgentWorkflowUiConfig.inputOptions?.brands || [],
[config?.inputOptions?.brands]
@ -386,9 +396,9 @@ export function AgentWorkflowPanelNew({
<SearchableSelect
value={inputs.brand}
options={brandOptions}
placeholder="选择品牌"
searchPlaceholder="输入品牌名筛选"
emptyText="暂无品牌可选"
placeholder={`选择${firstCategoryLabel}`}
searchPlaceholder={`输入${firstCategoryLabel}名筛选`}
emptyText={`暂无${firstCategoryLabel}可选`}
onValueChange={(value) =>
onInputsChange({
...inputs,
@ -409,9 +419,13 @@ export function AgentWorkflowPanelNew({
<SearchableSelect
value={inputs.store}
options={storeOptions}
placeholder="选择店铺"
searchPlaceholder="输入店铺名筛选"
emptyText={inputs.brand ? "当前品牌下暂无店铺可选" : "请先选择品牌"}
placeholder={`选择${secondCategoryLabel}`}
searchPlaceholder={`输入${secondCategoryLabel}名筛选`}
emptyText={
inputs.brand
? `当前${firstCategoryLabel}下暂无${secondCategoryLabel}可选`
: `请先选择${firstCategoryLabel}`
}
disabled={!inputs.brand || (brandStoreEntries.length > 0 && storeOptions.length === 0)}
onValueChange={(value) =>
onInputsChange({
@ -429,9 +443,13 @@ export function AgentWorkflowPanelNew({
<SearchableSelect
value={inputs.thirdCategory}
options={thirdCategoryOptions}
placeholder="选择三级分类"
searchPlaceholder="输入三级分类筛选"
emptyText={inputs.store ? "当前店铺下暂无三级分类" : "请先选择店铺"}
placeholder={`选择${thirdCategoryLabel}`}
searchPlaceholder={`输入${thirdCategoryLabel}筛选`}
emptyText={
inputs.store
? `当前${secondCategoryLabel}下暂无${thirdCategoryLabel}`
: `请先选择${secondCategoryLabel}`
}
disabled={!inputs.store || thirdCategoryOptions.length === 0}
onValueChange={(value) =>
onInputsChange({
@ -448,9 +466,13 @@ export function AgentWorkflowPanelNew({
<SearchableSelect
value={inputs.fourthCategory}
options={fourthCategoryOptions}
placeholder="选择四级分类"
searchPlaceholder="输入四级分类筛选"
emptyText={inputs.thirdCategory ? "当前三级分类下暂无四级分类" : "请先选择三级分类"}
placeholder={`选择${fourthCategoryLabel}`}
searchPlaceholder={`输入${fourthCategoryLabel}筛选`}
emptyText={
inputs.thirdCategory
? `当前${thirdCategoryLabel}下暂无${fourthCategoryLabel}`
: `请先选择${thirdCategoryLabel}`
}
disabled={!inputs.thirdCategory || fourthCategoryOptions.length === 0}
onValueChange={(value) =>
onInputsChange({
@ -466,9 +488,13 @@ export function AgentWorkflowPanelNew({
<SearchableSelect
value={inputs.fifthCategory}
options={fifthCategoryOptions}
placeholder="选择五级分类"
searchPlaceholder="输入五级分类筛选"
emptyText={inputs.fourthCategory ? "当前四级分类下暂无五级分类" : "请先选择四级分类"}
placeholder={`选择${fifthCategoryLabel}`}
searchPlaceholder={`输入${fifthCategoryLabel}筛选`}
emptyText={
inputs.fourthCategory
? `当前${fourthCategoryLabel}下暂无${fifthCategoryLabel}`
: `请先选择${fourthCategoryLabel}`
}
disabled={!inputs.fourthCategory || fifthCategoryOptions.length === 0}
onValueChange={(value) => onInputsChange({ ...inputs, fifthCategory: value })}
/>

View File

@ -60,8 +60,8 @@ const TEMPLATE_SAMPLE_ROWS = [
function getTemplateHeaders(labels?: Partial<AgentWorkflowCategoryLabels>) {
const resolvedLabels = normalizeAgentWorkflowCategoryLabels(labels);
return {
brandName: "品牌",
storeName: "店铺",
brandName: resolvedLabels.firstCategory,
storeName: resolvedLabels.secondCategory,
thirdCategory: resolvedLabels.thirdCategory,
fourthCategory: resolvedLabels.fourthCategory,
fifthCategory: resolvedLabels.fifthCategory,
@ -71,8 +71,12 @@ function getTemplateHeaders(labels?: Partial<AgentWorkflowCategoryLabels>) {
function getHeaderAliases(labels?: Partial<AgentWorkflowCategoryLabels>) {
const headers = getTemplateHeaders(labels);
return {
brandName: STATIC_HEADER_ALIASES.brandName,
storeName: STATIC_HEADER_ALIASES.storeName,
brandName: Array.from(
new Set([headers.brandName, ...STATIC_HEADER_ALIASES.brandName].filter(Boolean))
),
storeName: Array.from(
new Set([headers.storeName, ...STATIC_HEADER_ALIASES.storeName].filter(Boolean))
),
thirdCategory: Array.from(
new Set([headers.thirdCategory, ...STATIC_HEADER_ALIASES.thirdCategory].filter(Boolean))
),
@ -230,21 +234,21 @@ export async function downloadAgentWorkflowBrandImportTemplate(
instructionSheet.views = [{ state: "frozen", ySplit: 1 }];
instructionSheet.addRows([
{
field: "品牌",
field: resolvedLabels.firstCategory,
required: "是",
description: "每一行都必须填写品牌名称;相同品牌会自动合并。",
description: `每一行都必须填写${resolvedLabels.firstCategory}名称;相同${resolvedLabels.firstCategory}会自动合并。`,
example: "朴美",
},
{
field: "店铺",
field: resolvedLabels.secondCategory,
required: "否",
description: "不填时只导入品牌;填写后会挂到对应品牌下。",
description: `不填时只导入${resolvedLabels.firstCategory};填写后会挂到对应${resolvedLabels.firstCategory}下。`,
example: "天猫朴美家居旗舰店",
},
{
field: resolvedLabels.thirdCategory,
required: "否",
description: `填写后会挂到对应店铺下;如果不填,${resolvedLabels.fourthCategory}/${resolvedLabels.fifthCategory}也必须留空。`,
description: `填写后会挂到对应${resolvedLabels.secondCategory}下;如果不填,${resolvedLabels.fourthCategory}/${resolvedLabels.fifthCategory}也必须留空。`,
example: "桌类",
},
{
@ -290,7 +294,7 @@ export async function parseAgentWorkflowBrandImportFile(
matchColumn(firstRecord, headerAliases.brandName) ||
Object.keys(firstRecord).find((key) => normalizeHeader(key) === normalizeHeader(templateHeaders.brandName));
if (!brandColumn) {
throw new Error("未找到品牌列,请使用模板文件填写");
throw new Error(`未找到${templateHeaders.brandName}列,请使用模板文件填写`);
}
const storeColumn = matchColumn(firstRecord, headerAliases.storeName);
@ -373,10 +377,12 @@ export async function parseAgentWorkflowBrandImportFile(
}
if (!brandName) {
throw new Error(`第 ${lineNumber} 行缺少品牌名称`);
throw new Error(`第 ${lineNumber} 行缺少${resolvedLabels.firstCategory}名称`);
}
if (!storeName && (thirdCategory || fourthCategory || fifthCategory)) {
throw new Error(`第 ${lineNumber} 行缺少店铺名称,不能直接填写分类`);
throw new Error(
`第 ${lineNumber} 行缺少${resolvedLabels.secondCategory}名称,不能直接填写分类`
);
}
if (!thirdCategory && (fourthCategory || fifthCategory)) {
throw new Error(

View File

@ -14,6 +14,8 @@ export const AGENT_WORKFLOW_UI_CONFIG_KEY = "agent_workflow_ui_config";
export const AGENT_WORKFLOW_MAX_TREE_DEPTH = 8;
export const AGENT_WORKFLOW_MAX_OPTION_DEPTH = 8;
export const defaultAgentWorkflowCategoryLabels: AgentWorkflowCategoryLabels = {
firstCategory: "品牌",
secondCategory: "店铺",
thirdCategory: "三级分类",
fourthCategory: "四级分类",
fifthCategory: "五级分类",
@ -183,6 +185,14 @@ export function normalizeAgentWorkflowCategoryLabels(
};
return {
firstCategory: normalizeLabel(
record.firstCategory ?? record.firstCategoryLabel ?? record.brand ?? record.brandLabel,
defaultAgentWorkflowCategoryLabels.firstCategory
),
secondCategory: normalizeLabel(
record.secondCategory ?? record.secondCategoryLabel ?? record.store ?? record.storeLabel,
defaultAgentWorkflowCategoryLabels.secondCategory
),
thirdCategory: normalizeLabel(
record.thirdCategory ?? record.thirdCategoryLabel,
defaultAgentWorkflowCategoryLabels.thirdCategory

View File

@ -116,6 +116,8 @@ export interface AgentWorkflowUiConfig {
}
export interface AgentWorkflowCategoryLabels {
firstCategory: string;
secondCategory: string;
thirdCategory: string;
fourthCategory: string;
fifthCategory: string;