1. 明确边界:cms配置的 PageInstance.sections 数组只用来配置
【默认内容 + 主题颜色(未来开放)】
[section_display]props:// 商户定义“这个模块默认展示哪些分类”category_ids_default: ['software', 'education'], sort_tag_default: 'latest' priceRangeBreakpoints: [20, 40, 80] pagination: { pageSize: 12 },CTA: {title: "现在开始",buttonText: "立即购买",description: "xxxxx"position: 6} }[section_detail] ------ 更新设计:“持久化时扁平,运行时结构化”-------props: {category_ids_default: ['software', 'education'], // 或者nullsort_tag_default: 'latest',price_range_breakpoints: [20, 40, 80],page_size: 12,CTA_title: '立即购买',CTA_buttonText: '现在开始',CTA_description: 'xxxxx',CTA_position: 6 }
渲染前将扁平字段还原成对象:
const CTA = {title: props.CTA_title,description: props.CTA_description,buttonText: props.CTA_buttonText,position: props.CTA_position,
}
数据型,如category,依然是放在数据库的table里面
2. 细说price range设计
CMS部分
1. 💡 商户只输入数字数组(比如在 UI 上输入 tag:20
、40
、80
):
type PriceBreakpoints = number[] // e.g. [20, 40, 80]
SectionInstance.props存储
props: {priceRangeBreakpoints: [20, 40, 80] }
前端渲染部分:根据 priceRangeBreakpoints 转化成标签
2. ✅ 平台自动转成区间列表:
type PriceRangeOption = {label: stringmin?: numbermax?: number }function generatePriceRanges(breakpoints: number[]): PriceRangeOption[] {const sorted = [...breakpoints].sort((a, b) => a - b)const ranges: PriceRangeOption[] = []if (sorted.length === 0) return []// Less than first ranges.push({max: sorted[0],label: `Less than $${sorted[0]}`})for (let i = 0; i < sorted.length - 1; i++) {ranges.push({min: sorted[i],max: sorted[i + 1],label: `$${sorted[i]} - $${sorted[i + 1]}`})}// More than last ranges.push({min: sorted[sorted.length - 1],label: `More than $${sorted[sorted.length - 1]}`})return ranges }
sql查询部分:
标签为:
[{ max: 20, label: "Less than $20" },{ min: 20, max: 40, label: "$20 - $40" },{ min: 40, max: 80, label: "$40 - $80" },{ min: 80, label: "More than $80" } ]
当用户点击某个 price tag,你将这个 { min?, max? }
作为 query param 发给后端:
GET /api/products?price_min=20&price_max=40
后端查询:
function getProducts({ price_min, price_max }) {const conditions = []if (price_min !== undefined) {conditions.push(`price >= ${price_min}`)}if (price_max !== undefined) {conditions.push(`price < ${price_max}`)}const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''const sql = `SELECT * FROM products${whereClause}ORDER BY price ASCLIMIT 20 OFFSET 0`return db.query(sql) }
3. display页面整体流程分析
【layout的时候初始化redux】
把sorttagdefault,categorydefault, page=1,
pricerange = null,
注入redux;【涉及用户交互】
【SSR的时候,】
1. 查出sectionInstance.props,比如可以组装成CTA object+pagesize【商家固定】传给client comp
2. 根据pagesize+sortTagdefault+categorydefault(如果是all,就把这个condition忽视),进行products数据库查询。
3. products_count (用于pagination组件赋值)
【client component的时候,】
从redux里面拿出sorttagdefault,categorydefault, page,pricerange 和对应的dispatch方法
用于赋值初始值和onClick
【CSR的时候 】
1. priceRange发生改变,dispatch priceRange,触发react-query:用redux里面的pagesize,sortTag,category,到后台,组装的priceRange condition,进行sql查询。
查完了dispatch products_count
2. pagination发生改变, dispatch page ,触发react-query
3. category发生改变 ( dispatch category ;dispatch page = 1),触发react-query
查完了dispatch products_count
4. sortTag发生变化 ( dispatch sortTag; dispatch page = 1),触发react-query
查完了dispatch products_count
4. 具体修改实操
1. 把shopstack进行router改变:把原先所有路由加入/products下。
【首页】
【layout】的redux里面themeoptions里面拿sceneMap初始化,sceneList。
以及themeName
tenantId+tenantName
获得首页Header:目前暂时直接为null
Client Comp中间层引入themeHook:
1. dymamic import "cool-landing-header" 来setComponent
得到Styled_Header_component。
【page.tsx】目前暂时直接为null
Client Comp中间层引入themeHook:
dynamic import "cool-landing-mainPage"来setComponent
2. /products下的layout里面的查询和redux初始化:
涉及用户auth的redux初始化
把sorttagdefault,categorydefault, page=1,
pricerange = null, 注入redux;
=> 如果商家没有这个scene,那么根本查不到这些数据!!!可以直接return null
Client Comp中间层引入themeHook:
- dynamic import "cool-shop-header"来setComponent
- 往这个component里面注入 redux的用户auth数据!
得到 Styled_Product_Header。
3. /products的【page.tsx】里面
=> SSR里面判断themeOptions里面是否有这个scene,没有就404!
所有sectionInstance.props暂时都写死!