Files
nexus/Project/fonrey/UI_SYSTEM/组件清单.md
2026-04-25 09:10:07 +08:00

1265 lines
44 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 技术选型
- Frontend: HTMX + Alpine.js + Tailwind CSS
- Backend: Django 4.x (ASGI mode)
- Multi-tenant: django-tenants (Postgres schema isolation)
- Database: PostgreSQL + PgBouncer
- Cache: Redis
- Tasks: Celery + Celery Beat
- Storage: Cloudflare R2 (or AWS S3)
- CDN: Cloudflare
- Server: Gunicorn + Uvicorn workers + Nginx
- Monitoring: Sentry + Grafana
## Data Table (数据表格)
![[IMG-20260425085420706.png]]
#### 1. Data Table数据表格— 核心组件
**你描述的"数据列表"正式名称是Sortable Data Table with Column Visibility Control**
图中包含的子特性:
|子特性|技术实现方式|可行性|
|---|---|---|
|多列表头,带排序箭头(↑↓)|Django 后端排序 + HTMX 请求 + Tailwind 样式|✅ 完全可行|
|行 Checkbox 多选|Alpine.js 管理选中状态|✅ 完全可行|
|行高亮(图中蓝色高亮行)|Alpine.js `:class` 绑定|✅ 完全可行|
|红色/橙色 Tag 标签(买卖、出租)|Tailwind `badge` 样式|✅ 完全可行|
|行内小图标按钮(电梯、满五等)|Tailwind + inline SVG icon|✅ 完全可行|
|价格字段带趋势小箭头(↑绿色)|Alpine.js 条件渲染|✅ 完全可行|
|列固定宽度 + 横向滚动条|Tailwind `overflow-x-auto` + `min-w`|✅ 完全可行|
---
#### 2. Pagination分页组件
图中右下角的 `共3629条 < 1 2 3 4 5 … 182 > 20条/页 跳至`
|子特性|实现方式|可行性|
|---|---|---|
|页码导航|Django Paginator + HTMX `hx-get`|✅ 完全可行|
|每页条数选择20条/页)|Alpine.js 下拉 + HTMX 请求|✅ 完全可行|
|跳至指定页|Alpine.js 输入框 + HTMX 请求|✅ 完全可行|
|省略号(…)页码|Django 后端分页逻辑生成|✅ 完全可行|
---
#### 3. Column Visibility Panel自定义列显示
图中右上角的 **"自定义列表"** 按钮
|子特性|实现方式|可行性|
|---|---|---|
|弹出面板,勾选显示/隐藏列|Alpine.js `x-show` + `x-data`|✅ 完全可行|
|列显示状态持久化|Alpine.js + `localStorage` 或后端存储|✅ 完全可行|
|动态隐藏表格列|Alpine.js `:class="{'hidden': !col.visible}"`|✅ 完全可行|
---
#### 4. Toolbar操作工具栏
图中顶部的 `房源海报 批量分享 批量收藏 取消收藏 设置保护房 更多▼`
|子特性|实现方式|可行性|
|---|---|---|
|批量操作按钮(依赖 Checkbox 选中状态)|Alpine.js 全局选中状态 + 按钮 disabled 控制|✅ 完全可行|
|"更多"下拉菜单Dropdown Menu|Alpine.js `x-show` + `@click.away`|✅ 完全可行|
|共 N 条(动态数字)|HTMX 局部刷新 或 Alpine.js 绑定|✅ 完全可行|
---
#### 5. Export Button导出按钮
图中右上角 **"导出"** 按钮
|实现方式|可行性|
|---|---|
|Django 后端生成 CSV/Excel`openpyxl`),返回文件流|✅ 完全可行,无需前端特殊处理|
|异步导出(数据量大时走 Celery 任务,邮件通知)|✅ 你的 Celery 栈已完全支持|
---
#### 6. Smart Sort智能排序
图中右上角 **"按智能排序"** 标签
| 实现方式 | 可行性 |
| ------------------------------- | ------ |
| 后端 Django ORM 多字段排序HTMX 切换排序模式 | ✅ 完全可行 |
## Modal Dialog (模态对话框)
![[IMG-20260425085420782.png]]
### 🪟 组件名称Modal Dialog模态对话框
图中这个"编辑房源价格"弹窗,包含以下子组件:
#### 弹窗本体结构
|部位|组件名称|实现方式|可行性|
|---|---|---|---|
|灰色半透明遮罩层|Backdrop / Overlay|Tailwind `bg-black/50 fixed inset-0`|✅|
|白色弹窗卡片|Modal Panel|Tailwind `bg-white rounded-lg shadow-xl`|✅|
|顶部标题栏 + ✕ 关闭按钮|Modal Header|Alpine.js `@click="open=false"`|✅|
|底部确定/取消按钮组|Modal Footer / Button Group|Tailwind 按钮样式|✅|
|拖拽手柄(左上角 ⠿ 图标)|Draggable Handle|需要少量原生 JS 或 Alpine.js 插件|⚠️|
#### 弹窗内的表单组件
|组件|名称|实现方式|可行性|
|---|---|---|---|
|带星号必填标记的标签|Required Field Label|Tailwind `text-red-500` + `*` 字符|✅|
|数字输入框 + 单位后缀(万)|Input with Suffix Addon|Tailwind Input Group|✅|
|普通文本输入框(售价/备案价)|Text Input|Tailwind `input` 样式|✅|
|多行文本域(更改理由)|Textarea|Tailwind `textarea` + 字数统计 `0/200`|✅|
|字数计数(右下角 0/200|Character Counter|Alpine.js `x-model` + `:text="val.length + '/200'"`|✅|
---
### ⚠️ 唯一需要注意的点:弹窗拖拽
图中左上角有一个 **⠿ 拖拽图标**,说明这个弹窗支持**拖动位置**。这个功能:
- Alpine.js 原生**不内置**拖拽
- 可以用 **Alpine.js 官方插件 `@alpinejs/drag`** 解决,无需引入额外大型库
### ✅ 结论
> 这个 Modal 弹窗及其内部所有表单组件,**用 Alpine.js + Tailwind CSS 完全可以实现**,包括开关状态、表单验证、字数统计。拖拽功能用 Alpine 官方插件即可覆盖,无需引入 Vue/React。
## Tree Select/Cascading Dropdown(树形下拉选择器)
### 🌲 组件名称Tree Select / Cascading Dropdown树形下拉选择器
![[IMG-20260425085420857.png]]
#### 结构拆解
|部位|组件名称|说明|
|---|---|---|
|触发框(点击展开)|Select Trigger Input|显示已选内容,点击展开下拉|
|下拉面板|Dropdown Panel|绝对定位浮层,`z-index` 覆盖页面|
|可折叠父节点(▶ 沪居地产)|Tree Node / Collapsible Group|点击 ▶ 展开/收起子节点|
|子节点列表(上海豪园店等)|Tree Leaf Node|可点击选中|
|带头像的叶节点(沪居地产共享)|Tree Leaf with Avatar|头像 + 姓名 + 编号 + 操作链接|
|带状态标签的节点(沪居 关闭)|Node with Badge|橙色"关闭"tag|
|底部操作行(隐藏离职员工)|Footer Action|固定在下拉底部的全局操作|
---
### 技术实现评估
#### ✅ 可行部分Alpine.js 处理)
```
树形数据展开/折叠 → Alpine.js x-data 维护每个节点的 open 状态
选中节点高亮 → Alpine.js :class 绑定
点击外部关闭 → Alpine.js @click.away
头像/Badge/标签 → 纯 Tailwind CSS
底部固定操作行 → Tailwind sticky bottom
```
#### ⚠️ 需要重点关注的点
**这是你上传的三张图里技术难度最高的组件**,原因:
1. **树形数据递归渲染**HTMX 本身不擅长递归组件,需要 Alpine.js 配合后端返回的 JSON 数据,在前端用 `template` + `x-for` 递归渲染树节点
2. **多级嵌套状态管理**:每个父节点独立维护 `open/close` 状态,需要 Alpine.js `x-data` 设计合理的数据结构
3. **搜索过滤**(如果需要在下拉内搜索员工):需要 Alpine.js 实时过滤树节点,或 HTMX 请求后端过滤
---
### 💡 实现建议
**方案一推荐Alpine.js 纯前端渲染**
> 后端一次性返回完整树形 JSONAlpine.js 前端递归渲染,适合数据量不大的组织架构树
**方案二HTMX 懒加载节点**
> 点击展开节点时HTMX `hx-get` 请求该节点的子数据,适合层级很深、数据量很大的场景
---
### ✅ 结论
> 可以实现,但这是你目前三个组件里**唯一需要认真设计前端数据结构**的组件,不能直接套 Flowbite 现成组件Flowbite 没有 TreeSelect。建议直接基于 Alpine.js 手写,逻辑量约 80~120 行 JS可复用。
## 带搜索的 Tree Select 完整功能清单
![[IMG-20260425085420932.png]]
### 🔍 带搜索的 Tree Select 完整功能清单
#### 新增的搜索相关特性
| 特性 | 说明 | 实现方式 | 可行性 |
| ------------ | -------------- | --------------------------- | --- |
| 下拉顶部搜索输入框 | 打开下拉后可直接键入关键词 | Alpine.js `x-model="query"` | ✅ |
| 实时过滤节点 | 输入时同步过滤匹配的节点 | Alpine.js computed 过滤 | ⚠️ |
| 搜索命中时自动展开父节点 | 子节点匹配时其父节点强制展开 | 需要递归遍历树形数据 | ⚠️ |
| 无结果时显示空状态 | "暂无匹配结果" | Alpine.js `x-show` | ✅ |
| 清空搜索恢复原始树结构 | 删除关键词后还原展开状态 | Alpine.js 状态重置 | ✅ |
## Data Range Picker (日期范围选择)
![[IMG-20260425085421006.png]]
这个组件的正式名称是 **Date Range Picker日期范围选择器**,也叫 **Dual-Month Calendar Picker**
---
### 📅 组件名称Date Range Picker / 双月日历范围选择器
#### 结构拆解
|部位|说明|
|---|---|
|顶部输入框组(开始日期 → 结束日期)|Range Input Group两个输入框用箭头连接点击任意一个触发日历展开|
|双月并排日历面板|Dual Calendar Panel左侧当月、右侧次月联动翻页|
|月份导航(« < 2026年4月 > »)|单箭头切换月,双箭头切换年|
|今日高亮23日红框|当天日期特殊样式|
|选中日期高亮26日蓝底|已选日期样式|
|范围区间高亮(开始~结束之间蓝色背景)|Range Highlight区间内日期浅蓝背景|
|跨月灰色日期(上下月溢出日)|灰色不可选日期|
---
### 技术实现评估
#### ⚠️ 这是目前为止难度最高的组件
**难点在于:**
1. **双月联动**:左右两个月份需要同步,翻页时右月始终等于左月+1
2. **区间拖选高亮**:鼠标 hover 时实时预览区间,需要精细的状态管理
3. **日期计算**:需要处理月份天数、周几起始、跨年等边界情况
---
### 💡 强烈推荐:直接用成熟库,不要手写
用 Alpine.js 从零手写一个双月 Date Range Picker 工作量巨大400600 行),且边界情况多,极易出 Bug。
#### 推荐方案:**Pikaday** 或 **Flatpickr**
|库|特点|与你技术栈的兼容性|
|---|---|---|
|**Flatpickr**(强烈推荐)|轻量、无依赖、原生支持 Range 模式和双月显示,样式可用 Tailwind 覆盖|✅ 完美兼容CDN 引入即用|
|**Pikaday**|更轻量,但双月需手动配置|✅ 可用,略需配置|
**Flatpickr 双月 Range 模式只需三行配置:**
javascript
```javascript
flatpickr("#dateRange", {
mode: "range",
showMonths: 2,
});
```
> 样式用 Tailwind 自定义覆盖即可达到图中效果,不破坏你"不混用技术"的原则——Flatpickr 是无框架依赖的纯 JS 工具库,不是 React/Vue 组件。
---
### ✅ 结论
> **不建议用 Alpine.js 手写**,引入 **Flatpickr**CDN约 16KB是最优解与你的 HTMX + Alpine.js + Tailwind 技术栈**完全兼容、无冲突**,实现图中效果只需少量配置。
## Tab Navigation (标签页导航)
### 🗂️ 组件名称Tabs / Tab Navigation标签页导航
#### 结构拆解
|部位|说明|
|---|---|
|Tab 标签栏|横向排列的标签按钮组,当前激活项(修改跟进)有橙色下划线 + 橙色文字|
|激活指示器|Active Indicator图中为橙色底部边框线|
|Tab 内容面板|切换标签时显示对应内容区域|
|内容区Timeline时间线|按日期分组的操作记录列表,左侧橙色圆点|
|内容区Banner 提示条|顶部"智能总结"广告/提示横条|
|内容区Activity Item活动记录项|每条记录含【操作类型】+ 操作人 + 时间 + 详情文字|
|每条记录右侧Visibility Toggle|"公开/隐藏"文字切换按钮|
|底部Load More Button|"查看全部跟进"加载更多按钮|
---
### 技术实现评估
#### Tab 切换本体
这是所有组件里**实现最简单**的之一:
html
```html
<!-- Alpine.js 实现 Tab 切换,核心逻辑 -->
<div x-data="{ activeTab: 'all' }">
<button @click="activeTab = 'modify'"
:class="activeTab === 'modify' ? 'border-b-2 border-orange-500 text-orange-500' : ''">
修改跟进
</button>
<div x-show="activeTab === 'modify'">
<!-- 内容区 -->
</div>
</div>
```
#### 各子组件评估
|子组件|实现方式|可行性|
|---|---|---|
|Tab 标签切换 + 激活样式|Alpine.js `x-data` + `:class`|✅ 极简单|
|切换时局部刷新内容|HTMX `hx-get` + `hx-target`|✅ 完全可行|
|Timeline 时间线布局|Tailwind `border-l` + 圆点 `rounded-full`|✅ 纯 CSS 实现|
|活动记录条目|Django 模板循环渲染|✅ 完全可行|
|公开/隐藏 Toggle|HTMX `hx-post` 局部更新|✅ 完全可行|
|查看全部(加载更多)|HTMX `hx-get` + `hx-swap="beforeend"`|✅ 完全可行,无需写 JS|
---
### 💡 HTMX 在这里特别适合
"查看全部跟进"这个**加载更多**的场景,正是 HTMX 的强项:
html
```html
<button hx-get="/logs/?page=2"
hx-target="#log-list"
hx-swap="beforeend">
查看全部跟进
</button>
```
点击后自动追加新内容到列表末尾,**零 JS**。
---
### ✅ 结论
> 这张图里的所有组件都是你技术栈的**舒适区**Tab 切换 + Timeline + 加载更多,正是 HTMX + Alpine.js + Tailwind 组合最擅长的经典场景,实现难度低,代码量少。
## Collapsible Card Grid折叠展开面板
![[IMG-20260425085421096.png]]
### 🪗 组件名称Collapsible Card Grid可折叠卡片网格
#### 结构拆解
|部位|说明|
|---|---|
|外层容器|Card / Panel带边框圆角的白色区块|
|标题栏(相关员工 + 编辑)|Section Header右侧"编辑"为文字链接按钮|
|内容网格|3列 Grid Layout每格一个员工卡片|
|员工卡片|Avatar + 姓名 + 门店 + 电话 + 更多按钮(橙色 ···) + 角色标签 + 日期|
|空状态格子(暂无 暂未分配)|Empty State Cell灰色占位|
|底部展开/收起按钮|Expand Toggle Button 箭头图标|
---
### 技术实现评估
#### 折叠展开本体 — 极其简单
html
```html
<div x-data="{ expanded: false }">
<!-- 内容区,默认显示前两行,展开后显示全部 -->
<div :class="expanded ? 'max-h-full' : 'max-h-[某个高度] overflow-hidden'">
<!-- 员工网格卡片 -->
</div>
<!-- 展开/收起按钮 -->
<button @click="expanded = !expanded">
<span x-text="expanded ? '收起' : '展开全部'"></span>
<svg :class="expanded ? 'rotate-180' : ''" .../>
</button>
</div>
```
#### 各子组件评估
|子组件|实现方式|可行性|
|---|---|---|
|展开/收起动画|Tailwind `transition-all duration-300` + Alpine.js|✅ 极简单|
|3列网格布局|Tailwind `grid grid-cols-3 gap-4`|✅ 纯 CSS|
|员工头像|Tailwind `rounded-full` + `<img>`|✅ 纯 CSS|
|橙色 ··· 更多按钮|Alpine.js Dropdown `@click.away`|✅ 简单|
|角色标签(出售方/钥匙方…)|Tailwind `text-blue-500` 文字链接样式|✅ 纯 CSS|
|空状态格子|Django 模板条件渲染 `{% if %}`|✅ 后端处理|
|箭头旋转动画(∨ → ∧)|Tailwind `rotate-180 transition`|✅ 一个 class 搞定|
---
### ✅ 结论
> 这是你上传的所有截图里**实现最简单的组件**Alpine.js 核心逻辑只需 2 行,配合 Tailwind 的 `transition` + `max-h` 控制即可实现带动画的折叠展开效果,完全不需要任何额外库。
## Photo Gallery Manager (相册管理器)
![[IMG-20260425085421159.png]]
![[IMG-20260425085421220.png]]
这两张图合在一起是一个完整的 **Photo Gallery Manager相册管理器**,是你上传的所有截图里**功能最复合、复杂度最高**的页面。下面逐一拆解:
---
### 🖼️ 组件清单 & 可行性分析
#### 1. Scrollable Tab Bar可横向滚动的标签栏
图一顶部的分类标签,超出宽度后有 `···` 溢出菜单
|特性|实现方式|可行性|
|---|---|---|
|横向滚动标签|Tailwind `overflow-x-auto flex`|✅|
|溢出 `···` 下拉菜单|Alpine.js Dropdown|✅|
|激活状态(橙色下划线)|Alpine.js `:class` 绑定|✅|
|切换后刷新图片网格|HTMX `hx-get` + `hx-target`|✅|
---
#### 2. Image Grid with Checkbox可多选的图片网格
|特性|实现方式|可行性|
|---|---|---|
|多列图片网格|Tailwind `grid grid-cols-6 gap-2`|✅|
|每张图左上角 Checkbox|Alpine.js 管理选中状态数组|✅|
|封面角标(红色"封面"标签)|Tailwind 绝对定位 `absolute top-0 right-0`|✅|
|图片底部信息条(类别+尺寸)|Tailwind `absolute bottom-0` 半透明黑条|✅|
|全选 Checkbox|Alpine.js `selectAll` 方法|✅|
---
#### 3. Batch Action Toolbar批量操作工具栏
`批量修改类别 ` `批量删除` `批量下载`
|特性|实现方式|可行性|
|---|---|---|
|按钮依赖选中状态启用/禁用|Alpine.js `:disabled="selected.length === 0"`|✅|
|批量删除|HTMX `hx-delete` 发送选中 ID 列表|✅|
|批量下载|Django 后端打包 zip返回文件流Celery 异步)|✅|
|批量修改类别下拉|Alpine.js Dropdown + HTMX `hx-post`|✅|
---
#### 4. Drag-and-Drop File Upload拖拽上传
图二弹窗中的 `+ 上传图片` 区域
|特性|实现方式|可行性|
|---|---|---|
|点击选择文件|原生 `<input type="file" multiple>`|✅|
|拖拽上传区域|Alpine.js 监听 `dragover` / `drop` 事件|✅|
|上传预览(传后显示缩略图)|Alpine.js `FileReader` API|✅|
|上传进度|`XMLHttpRequest` / `fetch` + Alpine.js 进度绑定|✅|
|上传到 Cloudflare R2|Django 后端接收后转存 R2boto3|✅|
> ⚠️ **建议引入 [Filepond](https://pqina.nl/filepond/)(轻量无框架依赖)**:拖拽、预览、进度、多文件队列全部内置,与你的技术栈完全兼容,避免手写大量事件处理代码。
---
#### 5. Drag-to-Reorder拖拽调整图片顺序 最高难度
图一右上角"调整图片顺序"功能
|特性|实现方式|可行性|
|---|---|---|
|图片网格内拖拽排序|需要专门的拖拽排序库|⚠️|
|排序后保存到后端|HTMX `hx-post` 发送新顺序 ID 数组|✅|
> ⚠️ **建议引入 [SortableJS](https://sortablejs.github.io/Sortable/)3KB无框架依赖**:专为网格/列表拖拽排序设计,有官方 Alpine.js 集成方案,与你的技术栈完美兼容。
---
#### 6. Upload Modal 内的批量分类(图二)
|特性|实现方式|可行性|
|---|---|---|
|上传后在弹窗内全选/选择|Alpine.js 管理已上传文件的选中状态|✅|
|批量设置分类下拉|Alpine.js Select + HTMX 提交|✅|
---
### 📦 最终引入库汇总建议
|库|用途|大小|是否破坏技术栈一致性|
|---|---|---|---|
|**Filepond**|拖拽上传 + 预览 + 进度|~50KB|❌ 无框架依赖,安全引入|
|**SortableJS**|图片拖拽排序|~3KB|❌ 无框架依赖,安全引入|
---
### ✅ 总结
> 这个相册管理器的**90%功能**可以用 HTMX + Alpine.js + Tailwind 原生实现。仅**拖拽上传**和**拖拽排序**这两个功能,强烈建议引入 Filepond 和 SortableJS两者都是无框架依赖的纯 JS 工具库,不破坏你技术栈的一致性,且都有成熟的 Alpine.js 集成方案。
## Image Lightbox View (全屏图片灯箱预览器)
![[IMG-20260425085421290.jpg]]
### 🔍 组件名称Image Lightbox Viewer全屏图片灯箱预览器
上一个是**管理后台的相册管理器**(用于上传/删除/分类),这个是**面向用户的全屏预览器**,两者职责完全不同。
#### 结构拆解
|部位|组件名称|可行性|
|---|---|---|
|全屏黑色遮罩背景|Fullscreen Overlay|✅ Tailwind `fixed inset-0 bg-gray-800`|
|左上角图片信息栏(上传人/时间/尺寸/来源)|Image Meta Header|✅ Django 模板渲染|
|右上角 ✕ 关闭按钮|Close Button|✅ Alpine.js `@click="open=false"`|
|左右切换箭头(`<` `>`|Prev/Next Arrow Navigation|✅ Alpine.js 索引切换|
|中央主图显示区|Main Image Viewer|✅|
|底部工具栏(刷新/缩小/100%/放大/旋转/下载)|Image Toolbar|⚠️ 见下方说明|
|底部分类标签栏(全部/户型图/客厅/卧室…)|Category Filter Tab|✅ Alpine.js 过滤|
|底部缩略图条Thumbnail Strip|Thumbnail Filmstrip|✅ Tailwind 横向滚动|
|当前激活缩略图高亮(橙色边框)|Active Thumbnail|✅ Alpine.js `:class`|
---
### ⚠️ 底部工具栏的特殊说明
图中工具栏包含:**缩放(放大/缩小/100%+ 旋转 + 下载**,这是难点:
|功能|建议方案|可行性|
|---|---|---|
|图片缩放 + 拖拽平移|引入 **[Viewer.js](https://fengyuanchen.github.io/viewerjs/)**5KB无依赖|✅ 强烈推荐|
|图片旋转|Viewer.js 内置|✅|
|下载|原生 `<a download>` 或 Django 返回文件流|✅|
---
### 💡 最佳实现方案
> 直接引入 **Viewer.js**,它能覆盖这张图里**所有**预览交互(缩放/旋转/全屏/翻页/缩略图条),配合 Alpine.js 控制开关Tailwind 覆盖样式,**完全不破坏技术栈一致性**。
---
### 📦 更新引入库汇总
|库|用途|大小|
|---|---|---|
|Filepond|拖拽上传|~50KB|
|SortableJS|图片拖拽排序|~3KB|
|**Viewer.js**|图片灯箱预览(新增)|~5KB|
|Flatpickr|日期范围选择|~16KB|
这四个库全部无框架依赖,与你的技术栈完全兼容。继续上传!
## Accordion Progress Panel可折叠进度检查面板
![[IMG-20260425085421354.png]]
### 📊 组件名称Accordion Progress Panel可折叠进度检查面板
#### 整体结构拆解
|部位|组件名称|可行性|
|---|---|---|
|顶部标题 + 总进度百分比69%|Section Header with Score|✅|
|顶部橙色进度条|Progress Bar|✅ Tailwind `w-[69%] bg-orange-500`|
|说明文字 + 蓝色文字链接|Description with Text Link|✅|
|可折叠父行(重点信息 ∧)|Accordion Header Row|✅ Alpine.js|
|折叠后的子条目列表|Accordion Body|✅ Alpine.js `x-show`|
|不可折叠的普通行(实勘/VR…|Static Row|✅ 纯 HTML|
|每行右侧分数4% / 8%|Score Display|✅|
|未达标分数红色高亮0%|Conditional Color|✅ Alpine.js / Django 模板条件|
|行内蓝色操作链接(去上传/新增委托)|Inline Action Link|✅|
---
### 技术实现评估
#### 折叠逻辑 — 与上一个 Collapsible 完全一致,极简单
html
```html
<div x-data="{ open: true }">
<!-- 父行:点击折叠 -->
<div @click="open = !open" class="flex justify-between cursor-pointer">
<span>重点信息
<svg :class="open ? 'rotate-180' : ''" .../> <!-- 箭头旋转 -->
</span>
<span>8% / 8%</span>
</div>
<!-- 子条目列表 -->
<div x-show="open" x-collapse>
<div class="flex justify-between pl-4">
<span>唯一住房</span>
<span>1% / 1%</span>
</div>
<!-- ... -->
</div>
</div>
```
#### 各细节评估
|细节|实现方式|可行性|
|---|---|---|
|箭头 ∧/ 旋转动画|Tailwind `:class="open ? 'rotate-0' : 'rotate-180'"`|✅|
|折叠动画(高度过渡)|Alpine.js 官方插件 **`x-collapse`**官方出品1KB|✅|
|已得分红色/黑色条件样式|Django 模板 `{% if score == 0 %}text-red-500{% endif %}`|✅|
|多个独立折叠块互不影响|每个父行独立 `x-data="{ open: true }"`|✅|
|进度条宽度动态绑定|Django 后端计算百分比,模板输出 `style="width:69%"`|✅|
---
### 💡 一个小提示
图中箭头标注了 4 处可折叠行(重点信息、附件、营销、带看),其余行(实勘/VR/钥匙等)是**不可折叠的普通行**。建议后端返回数据时带一个 `is_collapsible` 字段Django 模板根据此字段决定渲染折叠版还是普通版,逻辑清晰且易维护。
---
### ✅ 结论
> 这个组件完全在 Alpine.js + Tailwind 的舒适区内,加上官方 `x-collapse` 插件处理高度动画,实现难度**很低**,与之前分析的 Collapsible Section 是同一类组件,可以复用同一套折叠逻辑。
## Inline Edit Mode (页面级读写切换)
### ✏️ 组件名称Inline Edit Mode / 页面级读写切换
![[IMG-20260425085421448.png]]
#### 整体页面结构拆解
|部位|组件名称|可行性|
|---|---|---|
|顶部面包屑导航(系统/设置/…)|Breadcrumb|✅ 纯 Tailwind|
|顶部搜索框(输入设置项名称)|Settings Search Input|✅ Alpine.js 实时过滤|
|左侧多级导航菜单|Sidebar Navigation with Accordion|✅ Alpine.js与之前折叠组件同理|
|右上角"编辑"按钮|Edit Toggle Button|✅ Alpine.js|
|内容区分组标题(员工信息设置…)|Section Divider with Title|✅ 纯 Tailwind|
|左侧橙色竖线分组标签(个人信息)|Labeled Group Divider|✅ Tailwind `border-l-4 border-orange-500`|
|Toggle 开关(橙色/灰色)|Toggle Switch|✅ Alpine.js + Tailwind|
|只读文字 → 编辑后变输入框|Read/Edit Mode Switch|✅ Alpine.js `x-show`|
---
### 核心交互Read/Edit Mode Toggle
这是本页最关键的设计模式,实现思路非常清晰:
html
```html
<div x-data="{ editing: false }">
<!-- 编辑按钮:点击切换模式 -->
<button @click="editing = true" x-show="!editing">编辑</button>
<button @click="editing = false" x-show="editing">保存</button>
<!-- 某一行设置项 -->
<div class="flex justify-between">
<span>工龄计算方式</span>
<!-- 只读态 -->
<span x-show="!editing">从首次入职开始计算</span>
<!-- 编辑态 -->
<select x-show="editing">
<option>从首次入职开始计算</option>
...
</select>
</div>
<!-- Toggle 开关类设置项(编辑/只读都显示,只读时禁用) -->
<input type="checkbox"
:disabled="!editing"
x-model="settings.autoId">
</div>
```
---
### 各组件详细评估
|组件|只读态|编辑态|可行性|
|---|---|---|---|
|文字类设置(工龄计算方式)|纯文本 `<span>`|`<select>``<input>`|✅ Alpine.js `x-show` 切换|
|Toggle 开关|显示当前状态,`disabled`|可操作|✅ `:disabled="!editing"`|
|保存操作|—|HTMX `hx-post` 提交表单|✅ 无需页面跳转|
|取消编辑|—|恢复原始值|✅ Alpine.js 保存快照 `JSON.parse(JSON.stringify(data))`|
---
### Toggle Switch 组件
图中橙色开关是高频组件,纯 Tailwind + Alpine.js 实现:
html
```html
<button @click="val = !val"
:class="val ? 'bg-orange-500' : 'bg-gray-300'"
:disabled="!editing"
class="relative w-10 h-5 rounded-full transition-colors">
<span :class="val ? 'translate-x-5' : 'translate-x-0'"
class="absolute left-0.5 top-0.5 w-4 h-4 bg-white rounded-full transition-transform">
</span>
</button>
```
---
### 左侧 Sidebar 导航
图中左侧菜单含**二级展开**人事OA设置 ∧ 展开子项),与之前分析的折叠组件完全相同的实现方式,可直接复用。
---
### ✅ 结论
> 这整个页面的所有交互,包括**读写模式切换、Toggle 开关、左侧折叠导航、保存提交**,全部在 Alpine.js + HTMX + Tailwind 的能力范围内,**无需引入任何新库**。
> 唯一需要注意的是**取消编辑时恢复原始值**,建议进入编辑态时用 Alpine.js 做一次数据快照,取消时还原,这是标准做法,约 3 行代码。
## Drawer / Slide-over Panel右侧抽屉面板
![[IMG-20260425085421515.png]]
### 🗂️ 组件名称Drawer / Slide-over Panel右侧抽屉面板
#### 整体页面结构拆解
|部位|组件名称|可行性|
|---|---|---|
|左侧主内容区(数据表格)|Data Table与第一张图同类|✅|
|右侧从屏幕边缘滑入的面板|Drawer / Slide-over|✅ Alpine.js + Tailwind|
|右侧面板顶部标题栏|Drawer Header|✅|
|右侧面板底部确定/取消按钮|Drawer Footer固定在底部|✅ Tailwind `sticky bottom-0`|
|半透明遮罩(左侧内容变暗)|Backdrop Overlay|✅|
|右侧面板内的表格|Settings Table with Radio + Toggle|✅|
---
### 核心交互Drawer 滑入滑出
html
```html
<div x-data="{ open: false }">
<!-- 触发按钮 -->
<button @click="open = true">编辑</button>
<!-- 遮罩层 -->
<div x-show="open"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
@click="open = false"
class="fixed inset-0 bg-black/30 z-40">
</div>
<!-- 抽屉面板 -->
<div x-show="open"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="translate-x-full"
x-transition:enter-end="translate-x-0"
class="fixed right-0 top-0 h-full w-[480px] bg-white z-50 shadow-xl flex flex-col">
<!-- 固定标题栏 -->
<div class="p-4 border-b font-bold flex justify-between">
住宅/商住/别墅——出售字段填写要求和新增页展示设置
<button @click="open = false"></button>
</div>
<!-- 可滚动内容区 -->
<div class="flex-1 overflow-y-auto p-4">
<!-- 设置表格内容 -->
</div>
<!-- 固定底部按钮 -->
<div class="p-4 border-t flex justify-end gap-2">
<button @click="open = false">取消</button>
<button class="bg-orange-500 text-white">确定</button>
</div>
</div>
</div>
```
---
### 右侧面板内的设置表格拆解
|列|组件|可行性|
|---|---|---|
|录入字段(文字列)|普通文本|✅|
|填写要求(必填/选填 Radio|Radio Button Group|✅ Alpine.js `x-model`|
|录入页显示Toggle 开关)|Toggle Switch与上张图同款|✅ 复用之前的 Toggle 组件|
|整列表格可滚动|Drawer 内容区 `overflow-y-auto`|✅|
---
### Drawer vs Modal 的选择原则
图中选择用 Drawer 而非 Modal是因为
|场景|推荐组件|
|---|---|
|编辑内容**字段多、需要滚动**|✅ Drawer右侧抽屉|
|编辑内容**字段少、简单确认**|Modal居中弹窗如第二张图|
|需要**同时参考主页面内容**|✅ Drawer主页面仍可见|
---
### ✅ 结论
> Drawer 是 Alpine.js + Tailwind 的经典实现场景,核心是 `x-show` + `x-transition` 的 `translate-x-full → translate-x-0` 动画,**无需引入任何新库**,与之前所有组件复用同一套 Alpine.js 状态管理模式。
> 这也是你系统里**Modal 和 Drawer 两种编辑入口并存**的合理设计——字段少用 Modal字段多用 Drawer体验上各司其职。
## Multi-select Tag Input多选标签选择器
![[IMG-20260425085421572.png]]
### 🏷️ 组件名称Multi-select Tag Input多选标签选择器
#### 结构拆解
|部位|组件名称|可行性|
|---|---|---|
|外层输入框容器(橙色边框激活态)|Tag Input Container|✅ Tailwind `ring-2 ring-orange-400`|
|已选项显示为 Tag/Chip出售 ×|Tag / Chip|✅ Alpine.js `x-for` 循环渲染|
|Tag 右侧 × 删除按钮|Tag Remove Button|✅ Alpine.js `@click` 移除数组元素|
|末尾光标输入框(可继续输入搜索)|Inline Search Input|✅ Alpine.js `x-model="query"`|
|下拉选项列表|Dropdown Option List|✅ Alpine.js `x-show`|
|已选项显示橙色 ✓ 勾选状态|Selected Indicator|✅ Alpine.js 判断是否在已选数组中|
|点击已选项再次点击取消选中|Toggle Selection|✅ Alpine.js 数组 push/splice|
---
### 核心实现逻辑
javascript
```javascript
// Alpine.js 数据结构
{
selected: ['出售', '出租', '租售', '他售/不售', '他租/不租', '暂缓'],
options: ['出售', '出租', '租售', '他售/不售', '他租/不租', '暂缓'],
open: false,
toggle(option) {
const i = this.selected.indexOf(option)
i === -1 ? this.selected.push(option) : this.selected.splice(i, 1)
},
isSelected(option) {
return this.selected.includes(option)
},
remove(option) {
this.selected = this.selected.filter(s => s !== option)
}
}
```
html
```html
<!-- Tag Input 容器 -->
<div @click.away="open = false"
class="flex flex-wrap gap-1 border rounded p-2 cursor-text"
:class="open ? 'ring-2 ring-orange-400' : ''"
@click="open = true">
<!-- 已选 Tags -->
<template x-for="item in selected" :key="item">
<span class="flex items-center bg-gray-100 px-2 py-0.5 rounded text-sm">
<span x-text="item"></span>
<button @click.stop="remove(item)" class="ml-1 text-gray-400">×</button>
</span>
</template>
<!-- 末尾输入框 -->
<input x-model="query" class="outline-none flex-1 min-w-[60px]" />
</div>
<!-- 下拉列表 -->
<div x-show="open" class="border rounded mt-1 bg-white shadow">
<template x-for="option in options" :key="option">
<div @click="toggle(option)"
class="flex justify-between px-4 py-2 hover:bg-gray-50 cursor-pointer">
<span x-text="option"></span>
<span x-show="isSelected(option)" class="text-orange-500"></span>
</div>
</template>
</div>
```
---
### 与之前组件的关系
|组件|选择结果呈现方式|适用场景|
|---|---|---|
|Tree Select|下拉关闭后输入框显示文字|单选,层级数据(员工/门店)|
|**Multi-select Tag Input**|每个选中项显示为可删除 Tag|**多选,平级数据(状态/标签)**|
|Date Range Picker|显示为日期区间文字|日期范围选择|
---
### ✅ 结论
> 这是一个**中等难度**组件Alpine.js 完全胜任,核心是维护一个**已选项数组**Tag 的增删、下拉勾选状态、× 删除按钮全部围绕这个数组操作,逻辑清晰,约 3050 行 JS**无需引入任何新库**。
## Dynamic Form Table动态可增删行表格
![[IMG-20260425085421637.png]]
### 组件名称Dynamic Form Table动态可增删行表格
#### 结构拆解
|部位|组件名称|可行性|
|---|---|---|
|表格主体(字段名称/类型/可选内容/必填/操作)|Editable Table|✅|
|每行"是否必填"列的 Toggle+Label 组合|Toggle with Status Label|✅ Alpine.js|
|必填橙色底(必填●)/ 灰色(非必填●)|Conditional Badge + Toggle|✅ `:class` 绑定|
|操作列"隐藏不使用"文字按钮|Text Action Button|✅|
|操作列"-"(系统预置行不可删除)|Disabled State|✅ `:disabled`|
|底部"+ 添加"按钮|Add Row Button|✅ Alpine.js|
---
### 核心交互:动态增删行
javascript
```javascript
// Alpine.js 数据结构
{
rows: [
// 系统预置行(不可删除)
{ id: 1, name: '意向日期', type: '日期选择', options: '-', required: true, system: true },
{ id: 2, name: '意向价格', type: '金额输入', options: '单位-元', required: true, system: true },
// 用户自定义行(可删除/隐藏)
{ id: 3, name: '意向截止日', type: '日期选择', options: '-', required: false, system: false, hidden: false },
],
addRow() {
this.rows.push({
id: Date.now(),
name: '', type: '', options: '-',
required: false, system: false, hidden: false
})
},
removeRow(id) {
this.rows = this.rows.filter(r => r.id !== id)
},
toggleHidden(row) {
row.hidden = !row.hidden
}
}
```
---
### 关键业务逻辑拆解
#### 系统预置行 vs 用户自定义行的差异
|行为|系统预置行|用户自定义行|
|---|---|---|
|字段名称|不可编辑(纯文本)|可编辑(`<input>`|
|删除|操作列显示"-",不可删|可删除(显示删除按钮)|
|隐藏不使用|无此操作(前两行)|有"隐藏不使用"按钮|
|必填 Toggle|禁用不可改(系统锁定)|可自由切换|
html
```html
<!-- 操作列条件渲染 -->
<template x-if="row.system">
<span class="text-gray-400">-</span>
</template>
<template x-if="!row.system">
<button @click="toggleHidden(row)"
:class="row.hidden ? 'text-gray-400' : 'text-blue-500'">
<span x-text="row.hidden ? '显示使用' : '隐藏不使用'"></span>
</button>
</template>
```
#### 必填 Toggle + Badge 联动
html
```html
<div class="flex items-center gap-1">
<!-- Badge 文字随状态变 -->
<span :class="row.required ? 'bg-orange-500 text-white' : 'bg-gray-200 text-gray-500'"
class="text-xs px-2 py-0.5 rounded-full"
x-text="row.required ? '必填' : '非必填'">
</span>
<!-- Toggle 开关 -->
<button @click="row.required = !row.required"
:disabled="row.system"
:class="row.required ? 'bg-orange-500' : 'bg-gray-300'"
class="w-9 h-5 rounded-full relative transition-colors">
<span :class="row.required ? 'translate-x-4' : 'translate-x-0.5'"
class="absolute top-0.5 w-4 h-4 bg-white rounded-full transition-transform">
</span>
</button>
</div>
```
#### 保存到后端
html
```html
<!-- 点击保存时HTMX 将整个 rows 数组提交 -->
<button hx-post="/api/field-settings/"
hx-vals="js:{rows: JSON.stringify(rows)}"
hx-target="#feedback">
保存
</button>
```
---
### ✅ 结论
> 这个组件完全在 Alpine.js + Tailwind + HTMX 的能力范围内,**无需引入任何新库**。核心是 Alpine.js 维护一个 `rows` 数组增删行操作数组即可DOM 自动响应更新。
> 唯一需要注意的是**系统预置行和用户自定义行的权限差异**,建议后端返回数据时携带 `is_system` 字段,前端据此控制哪些列可编辑、哪些操作可用,逻辑清晰且安全。
## Sortable Table with Drag Handle带拖拽手柄的可排序表格
![[IMG-20260425085421706.png]]
### ↕️ 组件名称Sortable Table with Drag Handle带拖拽手柄的可排序表格
#### 新增特性拆解
|部位|组件名称|说明|
|---|---|---|
|每行左侧 ⠿ 图标|Drag Handle|鼠标按住此处才能拖动,非整行拖动|
|拖动中的行样式|Dragging State|图中"业主"行被拖起,蓝色高亮背景|
|拖动目标位置指示|Drop Indicator|图中橙色虚线,显示将要放置的位置|
|拖动后顺序持久化|Order Persistence|拖完后保存新顺序到后端|
---
### 实现方案
这个功能之前分析相册排序时已经推荐过 **SortableJS**,在表格场景同样适用:
html
```html
<!-- 引入 SortableJSCDN3KB -->
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
<tbody id="sortable-table" x-init="initSort()">
<template x-for="row in rows" :key="row.id">
<tr>
<!-- 拖拽手柄:只有抓住这里才能拖 -->
<td class="drag-handle cursor-grab text-gray-400"></td>
<td x-text="row.name"></td>
<!-- Toggle、操作列... -->
</tr>
</template>
</tbody>
```
javascript
```javascript
// Alpine.js 初始化 SortableJS
initSort() {
Sortable.create(document.getElementById('sortable-table'), {
handle: '.drag-handle', // 只有手柄可拖
animation: 150, // 拖动动画时长
ghostClass: 'bg-blue-50', // 拖动中行的样式(图中蓝色)
chosenClass: 'opacity-50',
onEnd: (evt) => {
// 拖完后同步 Alpine.js 数据顺序
const moved = this.rows.splice(evt.oldIndex, 1)[0]
this.rows.splice(evt.newIndex, 0, moved)
// 保存新顺序到后端
this.saveOrder()
}
})
},
saveOrder() {
const ids = this.rows.map(r => r.id)
// HTMX 或 fetch 提交新顺序
fetch('/api/field-options/reorder/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ids })
})
}
```
---
### Drop Indicator橙色虚线放置指示线
图中橙色虚线是视觉上最精细的部分SortableJS 的 `ghostClass` 可以控制占位样式:
css
```css
/* 拖动占位行显示橙色虚线边框 */
.sortable-ghost {
border: 2px dashed #f97316; /* Tailwind orange-500 */
background: transparent;
opacity: 0.4;
}
```
---
### 与上一张图的差异对比
|特性|上张(动态表格)|本张(可拖拽排序)|
|---|---|---|
|新增行|✅ + 添加|✅ + 新增|
|删除行|✅|✅|
|Toggle 开关|✅|✅|
|**行拖拽排序**|❌|✅ SortableJS|
|**拖拽手柄 ⠿**|❌|✅|
|**Drop 虚线指示**|❌|✅ CSS ghostClass|
---
### ✅ 结论
> SortableJS 在这个场景和之前相册排序场景是**同一个库**,引入一次即可在整个系统复用。与 Alpine.js 的集成非常自然,拖拽完成后通过 `onEnd` 回调同步 Alpine.js 数据状态,再用 `fetch` 或 HTMX 提交新顺序,**整体方案无缝衔接你的技术栈**。
## Multi-Table Independent Pagination同页多表格独立分页
![[IMG-20260425085421774.png]]
### 📋 组件名称Multi-Table Independent Pagination同页多表格独立分页
#### 页面结构拆解
|部位|组件名称|可行性|
|---|---|---|
|顶部搜索栏 + 橙色搜索按钮 + 重置|Search Bar|✅|
|顶部 Tab 导航(交易信息/房产信息…)|Tab Navigation与之前同款|✅|
|右上角"已移除参数 / 新增参数"按钮组|Action Button Group|✅|
|多个独立表格分区(业客信息/合同应收费用…)|Section Table|✅|
|每个表格右下角**独立分页器**|Per-Table Pagination|✅ HTMX 精准局部刷新|
|分区标题 + 说明文字|Section Header with Description|✅|
---
### 核心难点:同页多表格独立分页
这正是 **HTMX 最擅长的场景**,每个表格是独立的 `hx-target`,互不干扰:
html
```html
<!-- 表格区块 1业客信息 -->
<div id="section-customer">
<h3>业客信息</h3>
<!-- 表格内容区,分页刷新只更新这里 -->
<div id="table-customer">
{% include "partials/table_customer.html" %}
</div>
<!-- 该表格的独立分页器 -->
<div class="flex justify-end mt-2">
<button hx-get="/params/customer/?page=1"
hx-target="#table-customer"
hx-swap="innerHTML"></button>
<span>1</span>
<button hx-get="/params/customer/?page=2"
hx-target="#table-customer" <!-- 只刷新本表格 -->
hx-swap="innerHTML">2</button>
<button hx-get="/params/customer/?page=3"
hx-target="#table-customer"
hx-swap="innerHTML">3</button>
<button hx-get="/params/customer/?page=2"
hx-target="#table-customer"
hx-swap="innerHTML"></button>
</div>
</div>
<!-- 表格区块 2合同-应收费用(完全独立) -->
<div id="section-fee">
<h3>合同-应收费用</h3>
<div id="table-fee">
{% include "partials/table_fee.html" %}
</div>
<!-- 该表格的独立分页器hx-target 指向自己 -->
<div class="flex justify-end mt-2">
<button hx-get="/params/fee/?page=1"
hx-target="#table-fee" <!-- 只刷新本表格不影响其他 -->
hx-swap="innerHTML"></button>
<span>1</span>
<button hx-get="/params/fee/?page=2"
hx-target="#table-fee"
hx-swap="innerHTML"></button>
</div>
</div>
```
---
### 关键设计原则
每个表格区块的**三要素相互隔离**
```
表格1 表格2 表格3
│ │ │
├─ id="table-1" ├─ id="table-2" ├─ id="table-3"
├─ hx-target 指向 ├─ hx-target 指向 ├─ hx-target 指向
│ #table-1 │ #table-2 │ #table-3
└─ /api/t1/?page=N └─ /api/t2/?page=N └─ /api/t3/?page=N
翻表格1的页 → 只有 #table-1 的 DOM 更新表格2、3完全不动
```
---
### 顶部搜索的联动逻辑
顶部搜索框搜索时,需要**同时刷新所有表格**
html
```html
<form hx-get="/params/search/"
hx-target="#all-tables" <!-- 刷新整个多表格容器 -->
hx-swap="innerHTML">
<input name="q" placeholder="请输入参数名称">
<button type="submit" class="bg-orange-500">搜索</button>
<button type="button"
hx-get="/params/"
hx-target="#all-tables">重置</button>
</form>
<div id="all-tables">
<!-- 所有表格区块放这里 -->
</div>
```
---
### ✅ 结论
> 这个模式是 HTMX **`hx-target` 精准局部刷新**的最典型应用场景:每个表格的分页器只更新自己的 `target`**天然实现多表格独立分页**,无需任何前端状态管理,**零 JS 代码**,完全由 HTML 属性声明驱动。
> Django 后端只需为每个表格区块提供独立的分页视图接口即可,架构清晰,维护成本极低。