Communitygithub.com

AxelBeary/Inkglean

多租户委托/市场平台的架构模式——包括模板化用户页面、可嵌入小部件和自定义页面托管。涵盖了用于提供托管模板同时允许外部站点集成的三层页面系统。

Inkglean 是什么?

该技能为构建多租户委托平台提供全面的架构指南,其中每个创作者(艺术家、设计师、自由职业者)都拥有一个公开的页面,并具备订单接收能力。它定义了一个核心的“三层页面系统”,以适应不同技术水平的创作者:第一层是即用型内置模板,第二层是可粘贴到现有网站的可嵌入小部件,第三层则是上传自定义 HTML/CSS/JS 实现完全托管。该系统通过将模板解耦为“布局”和“调色板”这两个正交维度,实现了仅用 8 个定义即可生成多达 16 种不同外观的效果,从而避免了设计上的冗余。技能详细描述了可复用的架构模式,包括用于获取艺术家数据的适配器组合式函数、可跨订单表单模板共享的业务逻辑组合式函数、以及对拖拽替换、预览模式和活动时间线等高级 UI 交互的实现。此外,还提供了 CSS 改进策略和针对平台特定需求的 UI 组件设计指导。

兼容平台~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/AxelBeary/Inkglean/tree/HEAD/docs/comms/archive/skills/software-development/commission-platform-architecture

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

委托平台架构

多租户委托平台,其中每个用户(艺术家、创作者、自由职业者)都有一个具有订购功能的公开页面。本技能涵盖为提供内置托管模板和拥有自己网站的创作者进行集成而做出的架构决策。

三层页面系统

核心架构模式:支持三个不同技术能力和独立性级别的创作者。

┌─────────────────────────────────────────────────┐
│  第三层:自定义页面托管                          │
│  上传您自己的 HTML/CSS/JS → 通过              │
│  /artist/alice 提供服务(来自 uploads/ 的静态文件)│
├─────────────────────────────────────────────────┤
│  第二层:可嵌入小部件                            │
│  在现有站点上粘贴一个 <script> 标签            │
│  → “预约委托”按钮 + 订单表单                    │
├─────────────────────────────────────────────────┤
│  第一层:内置模板                                │
│  选择一个布局变体,零设置                        │
│  → /artist/alice 的完整页面                      │
└─────────────────────────────────────────────────┘

第一层:内置模板

适用对象:没有自己网站或不想维护网站的创作者。

实现方式:使用支持 CSS 变量主题的 Vue 组件。

// 路由:/artist/:subdomain
routes.get('/artist/:subdomain', async (req, reply) => {
  const artist = await artistService.getBySubdomain(req.params.subdomain)
  
  if (artist.page_type === 'default') {
    // 渲染内置 Vue SPA(或 SSR)
    // page_theme 字段控制使用哪个模板变体
    return reply.sendFile('index.html')
  }
  // ... 否则由其他层(第 2-3 层)处理
})

模板 = 布局 × 调色板(正交轴)——这是最重要的建模决策。如果两个“模板”主要在颜色上不同,它们不是两个模板——而是一个布局穿上了两件不同颜色的外衣。将颜色捆绑到模板维度会因重复的结构代码而膨胀模板数量(用户会指出这一点:“这些看起来像是不同颜色下的同一页面”)。应拆分为创作者独立选择的轴:

轴谁选择示例控制项
布局(结构)创作者classic(双栏工作室)/ gallery(全屏博物馆)/ folio(单页落地)/ atelier(书本风格、温馨工作室)开场处理、列逻辑、画廊形状、导航
调色板(氛围)创作者paper / ink / dusk / moss——每个都定义了浅色和深色变体页面背景/表面/文本颜色(--pal-* CSS 变量)
强调色 + 模式访客5 色强调色选择器 + 浅色/深色偏好--color-primary、html.dark

关键规则:

  • 一个调色板是一种包含浅色和深色变体的氛围,而不仅仅是“深色主题”。创作者选择 ink;浅色模式下的访客看到浅灰色,深色模式下的访客看到炭灰色——氛围保持一致,亮度跟随访客。
  • 强调色(按钮/链接/高亮)始终使用访客的强调色变量(--color-primary),从不使用调色板变量——两个系统保持正交并持续工作。
  • 4 种布局 × 4 种调色板 = 8 个定义实现 16 种不同的外观。
  • 布局必须在结构上不同(不同的开场、不同的列逻辑),否则应合并为一个布局。

有效的布局原型——每个都以创作者最具特色的资产(他们的艺术作品)开场,而不是一个通用的居中式姓名+简介+按钮首屏:

布局开场标志性动作
gallery全视口艺术作品,名称作为小角落铭牌编辑型大/小交替画廊,悬停缩放,点击预览
classic标志性作品横幅,名称叠加在艺术作品上桌面双栏,粘性信息卡片,始终可见的委托 CTA
folio分屏:文本左侧,堆叠艺术作品右侧滚动跟踪导航高亮,移动端汉堡菜单 + 粘性底部 CTA
atelier温暖的书本风格跨页,衬线字体(Noto Serif SC)纸张纹理背景,编辑型边距,宽大的空白,艺术优先,文学氛围

共享套件架构(已验证):模板从不直接读取后端字段名——一个适配器组合式函数(useArtistData(props))提供 imgUrl()、statusText()(国际化)、socialLinks、heroArtwork;可复用的 Tpl* 组件通过 props 接受变体(TplHero variant="banner|fullscreen|split"、TplGallery layout="grid|editorial|masonry"),并通过插槽(例如层级网格上的 #addons)提供扩展点。完整模式(包括异步组件时序修复和遗留 ID 映射)请参见 references/template-system.md。

艺术家设置的强调色覆盖(R49 模式):艺术家可以设置一个持久的强调色(5 色白名单,存储为 artists.accent_color TEXT),该颜色会覆盖访客在其公共页面上通过 ThemePicker 所做的选择。在 ArtistHome.vue 中实现:

const ACCENT_INDEX = { '#34dbcb': '1', '#34c2db': '2', '#3498db': '3', '#346edb': '4', '#3445db': '5' }
const accentOverride = computed(() => {
  const raw = previewAccent.value || artist.value?.accentColor
  return raw ? (ACCENT_INDEX[String(raw).toLowerCase()] || null) : null
})
// watch(immediate) + onUnmounted: 保存/恢复 document.documentElement.dataset.accent

关键决策:(1) 全局 data-accent 属性注入——无需更改每个模板的 CSS 变量(theme.css 已经包含带有浅色和深色变体的 data-accent="1"~"5" 选择器)。为所有 4 个模板添加强调色支持 = 零模板文件更改。(2) 在艺术家页面上,艺术家的强调色优先于访客的 ThemePicker;在 onUnmounted 时恢复访客的选择。(3) 后端针对 5 色白名单 + null(清除)进行验证;写入时进行 toLowerCase() 规范化。(4) 设置 UI:圆形开关按钮(32px),激活时显示 ✓ 标记 + “默认”清除按钮。不使用 el-color-picker(自由形式的颜色会破坏白名单)。(5) C61 决策:强调色仅影响客户端页面,不影响艺术家仪表盘。

通过 URL 参数实现的预览模式(R50 模式):设置中的“预览主页”按钮会在新窗口中打开 /artist/:subdomain?_tpl=X&_pal=Y&_accent=Z。ArtistHome.vue 将 route.query._tpl/_pal/_accent 读取为渲染层覆盖(从不写入数据层)。当存在任何预览参数时,会显示一个粘性警告横幅(“预览模式 — 修改尚未保存”)。模板/调色板/强调色的计算属性按以下优先级降级:previewParam || artist.field || default。对公共访问是安全的——预览参数仅影响渲染,所有数据都来自公共 API。

用活动时间线替换平面笔记列表(R40 模式):当将状态更改记录(系统笔记)和艺术家笔记合并到单一时间线视图中时,使用 el-timeline 并区分类型/空心:

<el-timeline-item :type="note.created_by === 'system' ? 'info' : (note.image_path ? 'success' : 'primary')"
  :hollow="note.created_by === 'system'" :timestamp="formatDate(note.created_at)" placement="top">

类型指示符:🔄 状态更改 / 📝 笔记 / 🖼 带图片的笔记。系统笔记获得 tl-item--system 类(颜色变淡,字号更小)。删除按钮(R46)仅对非系统笔记显示,使用悬停显示,并带有 @media (hover: none) 触摸回退。输入框移至时间线底部。零后端更改——系统笔记已存在于 order_notes 中,并带有 created_by='system'。操作栏(推进/回滚/取消)位于时间线卡片之外。

具有上下文相关确认行为的拖放替换(R53 对比 R55):同一应用中两个拖放替换功能具有不同的确认要求——请不要统一它们:

R53 队列板焦点图像R55 层级示例图像
覆盖确认否(旧图像保留在订单图库中)是——ElMessageBox.confirm(旧图像丢失)
原因替换焦点 ≠ 删除图像覆盖示例 = 旧图像不可恢复
实现:共享拖放基础设施(dragover.prevent/dragleave 配合 contains(relatedTarget) 防抖/drop.prevent + 隐藏文件输入),但上传函数仅在 R55 中检查 if (row.example_image) { await ElMessageBox.confirm(...) }。两者都复用现有的上传 API。必须从 R53 的焦点图像中移除 el-image 的 preview-src-list 以防止其吞没点击事件(与 R18 相同的陷阱)。

订单表单组合式函数提取(R58-1 模式):当订单表单超过约 400 行(数据加载 + 定价 + 验证 + 上传 + 提交 + 草稿恢复)时,将所有业务逻辑提取到共享组合式函数(useOrderForm.js)中。页面组件变为纯布局(约 300 行的模板 + 样式,脚本部分仅为 const of = useOrderForm(subdomain, formRef) + 解构)。组合式函数返回一个扁平对象,包含按关注点分组的 refs/reactives/methods:数据加载(artist、tiers、rulesContent、loading)、表单状态 + 验证规则、提交状态(submitting、showSuccess、resultNo、submit)、参考图像(refFileList、handleRefUpload、handleRefRemove)、定价(addonSelections、pricePreview、pricingExpanded、selectedTier、onTierChange)和经过清理的规则 HTML。为什么这对多模板很重要:当订单页面支持多种布局模板(分步式、收据风格、杂志风格)时,每个模板都是一个调用相同组合式函数的精简布局外壳——零逻辑重复。组合式函数是“核心”,模板是“皮肤”。这与首页的 useArtistData 适配器模式相同,只是应用于订单表单。关键实现细节:组合式函数接收 formRef(来自模板的 el-form 的 ref)作为参数,因为验证(formRef.value.validate())必须调用到模板的表单元素中——组合式函数拥有逻辑但不拥有 DOM。

分步式订单表单布局(R58-2 模式,已验证):默认的订单布局是一个三步引导流程,完全构建在 useOrderForm() 之上(无需对 R58-1 的组合式函数做任何更改——验证了提取的正确性):

  • 步骤指示器:三个带编号的圆点 + 连接线。当前步骤:放大 + 主色。已完成:✓ + 主色填充。待处理:灰色空心。移动端(≤860px):仅圆点。
  • 第 1 步——选择层级:响应式卡片网格(repeat(auto-fill, minmax(240px, 1fr)))取代 el-select。选中项:--ease-bounce 过渡 + 旋转印章 ✓(@keyframes tier-stamp-in:缩放 0→1.2→1,旋转 -15°→0°)。R14 的渐进式公开信息仍保留在第 1 步内。
  • 第 2 步——描述 + 上传:文本区域 + 灵感标签(R58-4:硬编码默认标签,点击追加,带智能逗号 + 2000 字符截断)+ 参考上传(文件 + 粘贴不变)。
  • 第 3 步——联系:QQ + 姓名 + 规则同意。
  • 粘性摘要卡片:宽屏右侧栏(280px 粘性),实时显示层级 + 明细 + 总计 + 分期。移动端:活动步骤底部。
  • 收据确认(R58-3):提交 → 验证 → 咖啡馆收据样式 el-dialog(teleported 到 body,全局 CSS receipt-* 命名空间在 templates.css 中)。纸张 #fdfbf5,Courier 等宽字体,锯齿边缘(repeating-conic-gradient),虚线分隔符,条形码装饰,分期标签。固定浅色主题(纸张隐喻)。取消返回第 3 步;确认调用 submit()。
  • 复制订单信息(R58-5):成功对话框“复制约稿信息”→ 多行剪贴板(订单号 + 层级 + 附加项 + 总计)。
  • 步骤导航:“下一步”在前进前验证当前步骤;“上一步”始终允许;不可跳过。

多模板订单页面(R58 架构):订单页面支持艺术家可选择的布局模板,独立于首页模板。后端(已验证,迁移 v16):artists.order_template_id TEXT DEFAULT 'default'——服务层白名单验证(ORDER_TEMPLATES = ['default'],添加模板时扩展数组)。GET /api/artists/:subdomain 返回 orderTemplateId: artist.order_template_id || 'default'。PUT /api/artist/profile 接受 orderTemplateId(通过 keyMap 映射到 order_template_id)。无效值抛出 INVALID_ORDER_TEMPLATE(409)。空字符串回退为 'default'。前端:艺术家设置页面显示卡片式模板选择器(与首页模板选择相同的 UI 模式)。客户端路由:OrderForm.vue 读取 artist.orderTemplateId 并通过 <component :is="..."> 或模板注册表映射渲染对应的布局组件。所有布局组件共享 useOrderForm()——它们仅在视觉排列上不同(步骤指示器、卡片布局、确认对话框样式)。添加一个新的订单模板 = 一个包含布局外壳的新 .vue 文件 + 后端白名单数组中的一项,零逻辑更改。预览:复用首页预览的 _tpl URL 参数机制。

QQ 联系模式(R58-6):两种 QQ 联系方式:“跳转QQ”(window.open('tencent://message/?uin=' + encodeURIComponent(qq), '_self'))+ “复制QQ”(navigator.clipboard.writeText(qq) 带提示)。剪贴板回退:失败时(权限被拒绝,非 HTTPS),显示 ElMessage.warning(qq)——QQ 号码本身作为警告消息,以便用户可以从提示中手动复制。应用于两个地方:客户端的成功对话框(艺术家的 contactQq)和艺术家端的订单详情(客户的 qq)。国际化:每个上下文使用独立的键(orderForm.jumpQq 对比 orderDetail.jumpQq),因为提示消息不同(“QQ号已复制”对比 “客户QQ已复制”)。

CSS 工艺升级模式(v0.16 batch1,已验证):

  • 全局弹动缓动:--ease-bounce: cubic-bezier(.34, 1.56, .64, 1) 在 :root 中。仅用于 transform/box-shadow 过渡(绝不用 opacity——不能超过 1.0)。使用代码中的回退:var(--ease-bounce, cubic-bezier(0.22, 1, 0.36, 1))。
  • 按钮三态物理论:.el-button:hover:not(:disabled):not(.is-loading) { transform: translateY(-3px); box-shadow: deeper } + :active { transform: translateY(-1px) scale(0.98) }。:not() 排除项防止禁用/加载中的按钮产生动画。
  • clamp() 流体...

相关技能