```
---
## 2. Pagination
### 2.1 视觉规格
| 属性 | 值 |
|---|---|
| 容器布局 | `flex items-center justify-between px-4 py-3 border-t border-neutral-200` |
| 总条数文字 | `text-sm text-neutral-500` |
| 页码按钮尺寸 | `w-8 h-8` `rounded-md` `text-sm` |
| 当前页 | `bg-primary-600 text-white font-medium` |
| 普通页码 | `text-neutral-600 hover:bg-neutral-100` |
| 禁用状态 | `opacity-40 cursor-not-allowed` |
| 省略号 | `text-neutral-400 px-1` |
### 2.2 HTML 结构
```html
共 {{ paginator.count }} 条
```
### 2.3 Django 后端辅助函数
```python
def get_page_range(page_obj, on_each_side=2, on_ends=1):
"""生成含省略号的页码列表,供模板渲染"""
paginator = page_obj.paginator
current = page_obj.number
total = paginator.num_pages
result = []
left = set(range(1, on_ends + 1))
right = set(range(total - on_ends + 1, total + 1))
middle = set(range(current - on_each_side, current + on_each_side + 1))
visible = sorted(left | right | middle)
prev = None
for num in visible:
if 1 <= num <= total:
if prev and num - prev > 1:
result.append('…')
result.append(num)
prev = num
return result
```
---
## 3. Column Visibility Panel
### 3.1 视觉规格
| 属性 | 值 |
|---|---|
| 触发按钮 | Secondary 按钮,`adjustments-horizontal` 图标 |
| 面板宽度 | `w-64` |
| 面板位置 | 右对齐弹出,`absolute right-0 top-full mt-1` |
| 面板层级 | `z-20` |
| 列项高度 | `h-9` |
| Checkbox 颜色 | `accent-primary-600` |
### 3.2 HTML 结构
```html
```
### 3.3 Alpine.js 数据结构
```javascript
function columnVisibility() {
const STORAGE_KEY = 'fonrey:table:property:cols'
return {
open: false,
columns: [
{ key: 'checkbox', label: '多选', visible: true, locked: true },
{ key: 'code', label: '房源编号', visible: true, locked: true },
{ key: 'title', label: '房源标题', visible: true, locked: false },
{ key: 'status', label: '状态', visible: true, locked: false },
{ key: 'price', label: '价格', visible: true, locked: false },
{ key: 'area', label: '面积', visible: true, locked: false },
{ key: 'district', label: '商圈', visible: false, locked: false },
{ key: 'agent', label: '经纪人', visible: true, locked: false },
{ key: 'created', label: '录入时间', visible: false, locked: false },
],
init() {
const saved = localStorage.getItem(STORAGE_KEY)
if (saved) {
const savedCols = JSON.parse(saved)
this.columns = this.columns.map(col => {
const s = savedCols.find(c => c.key === col.key)
return s ? { ...col, visible: s.visible } : col
})
}
},
savePreferences() {
localStorage.setItem(STORAGE_KEY,
JSON.stringify(this.columns.map(c => ({ key: c.key, visible: c.visible })))
)
},
resetToDefault() {
localStorage.removeItem(STORAGE_KEY)
this.columns.forEach(col => { if (!col.locked) col.visible = true })
}
}
}
```
---
## 4. Toolbar
### 4.1 视觉规格
| 属性 | 值 |
|---|---|
| 容器 | `flex items-center gap-2 py-2` |
| 批量按钮(未选中) | `opacity-50 cursor-not-allowed pointer-events-none` |
| 批量按钮(已选中) | Secondary 按钮正常态 |
| 选中计数 badge | `bg-primary-600 text-white text-xs px-1.5 py-0.5 rounded-full min-w-[20px] text-center` |
| "更多"下拉 | `w-40` Dropdown,`ellipsis-horizontal` 图标 |
### 4.2 HTML 结构
```html
共 {{ total_count }} 条
已选 条
```
---
## 5. Export Button
### 5.1 两种模式
| 模式 | 触发条件 | 实现方式 |
|---|---|---|
| 同步导出 | 数据量 ≤5000 条 | Django 视图直接返回 `FileResponse`,浏览器触发下载 |
| 异步导出 | 数据量 >5000 条 | Celery 任务异步生成,完成后推送 Toast 通知(含下载链接) |
### 5.2 HTML 结构
```html
导出
```
---
## 6. Smart Sort
### 6.1 视觉规格
| 状态 | 样式 |
|---|---|
| 未激活 | `border border-neutral-200 text-neutral-600 bg-white` |
| 激活 | `border border-primary-600 text-primary-600 bg-primary-50` |
### 6.2 HTML 结构
```html
```
---
## 7. Modal Dialog
### 7.1 视觉规格
| 属性 | 值 |
|---|---|
| 遮罩 | `fixed inset-0 bg-black/50 z-40` |
| 面板宽度 | `w-full max-w-lg`(默认)/ `max-w-2xl`(宽型) |
| 面板圆角 | `rounded-xl` |
| 面板阴影 | `shadow-2xl` |
| Header 高度 | `h-14`,`border-b border-neutral-200` |
| Footer 高度 | `h-16`,`border-t border-neutral-200` |
| 拖拽手柄 | `cursor-grab active:cursor-grabbing text-neutral-300 hover:text-neutral-500` |
### 7.2 HTML 结构
```html
```
### 7.3 Alpine.js 数据结构
```javascript
function modal() {
return {
isOpen: false,
reason: '',
open() { this.isOpen = true },
close() { this.isOpen = false; this.reason = '' }
}
}
```
---
## 8. Tree Select
### 8.1 视觉规格
| 属性 | 值 |
|---|---|
| 触发框 | `h-9 px-3 text-sm border border-neutral-200 rounded-md` |
| 面板宽度 | `w-72` |
| 父节点行高 | `h-9` |
| 子节点行高 | `h-9 pl-8`(左缩进) |
| 展开箭头 | `w-4 h-4 transition-transform`,展开时 `rotate-90` |
| 已选节点 | `bg-primary-50 text-primary-700` |
| 带头像叶节点 | `w-6 h-6 rounded-full bg-primary-100 text-primary-700 text-xs flex items-center justify-center` |
| Badge(关闭状态) | `bg-warning-50 text-warning-600 text-xs px-1.5 py-0.5 rounded-full` |
| 底部操作行 | `sticky bottom-0 border-t border-neutral-100 bg-white px-3 py-2` |
### 8.2 推荐实现方案
**方案一(默认,数据量 ≤200 节点)**:后端一次性返回完整 JSON,Alpine.js 前端递归渲染
**方案二(数据量大)**:HTMX 懒加载子节点(点击展开时 `hx-get` 请求子数据)
### 8.3 HTML 结构(方案一)
```html
```
### 8.4 Alpine.js 数据结构
```javascript
function treeSelect() {
return {
open: false,
query: '',
selected: null,
hideInactive: false,
tree: [], // 从 Django 后端注入:[{ id, label, badge, children: [{id, label, code}] }]
get filteredTree() {
if (!this.query) return this.tree
const q = this.query.toLowerCase()
return this.tree
.map(group => ({
...group,
children: group.children.filter(leaf =>
leaf.label.includes(q) || leaf.code?.includes(q)
)
}))
.filter(group => group.children.length > 0)
},
selectLeaf(leaf) {
this.selected = leaf
this.open = false
this.query = ''
}
}
}
```
---
## 9. Date Range Picker
### 9.1 技术选型
**使用 Flatpickr**(CDN,~16KB,无框架依赖),不手写。
```html
```
### 9.2 初始化配置
```javascript
flatpickr('#dateRange', {
mode: 'range',
showMonths: 2,
locale: 'zh',
dateFormat: 'Y-m-d',
allowInput: true,
onReady(_sel, _str, fp) {
fp.calendarContainer.classList.add('fonrey-calendar')
}
})
```
### 9.3 样式覆盖(配合主色 Teal)
```css
/* static/css/flatpickr-overrides.css */
.flatpickr-day.selected,
.flatpickr-day.startRange,
.flatpickr-day.endRange {
background: #0F766E;
border-color: #0F766E;
}
.flatpickr-day.inRange {
background: #CCFBF1;
border-color: #CCFBF1;
color: #115E59;
}
.flatpickr-day.today {
border-color: #D97706;
}
```
### 9.4 HTML 结构
```html
```
---
## 10. Tab Navigation
### 10.1 视觉规格
| 属性 | 值 |
|---|---|
| Tab 容器 | `border-b border-neutral-200` |
| Tab 按钮(未激活) | `h-10 px-4 text-sm text-neutral-500 hover:text-neutral-700` |
| Tab 按钮(激活) | `h-10 px-4 text-sm text-primary-600 font-medium border-b-2 border-primary-600` |
| 内容区顶部间距 | `mt-4` |
### 10.2 HTML 结构
```html
```
### 10.3 Timeline 子组件
```html
{% for log in logs %}
{{ log.operator }}
{{ log.action }}
{{ log.created_at }}
{{ log.detail }}
{% endfor %}
{% if has_more %}
{% endif %}
```
---
## 11. Collapsible Card Grid
### 11.1 视觉规格
| 属性 | 值 |
|---|---|
| 外层卡片 | `bg-white rounded-lg border border-neutral-200` |
| Section Header | `flex items-center justify-between px-4 py-3 border-b border-neutral-100` |
| 网格列数 | `grid grid-cols-3 gap-4` |
| 员工卡片 | `p-3 rounded-lg border border-neutral-100 hover:border-neutral-200` |
| 头像尺寸 | `w-10 h-10 rounded-full` |
| 展开按钮 | `w-full flex items-center justify-center h-9 text-sm text-neutral-500 hover:bg-neutral-50 border-t border-neutral-100` |
### 11.2 HTML 结构
```html
相关员工
{% for member in members %}
{{ member.name }}
{{ member.store }}
{{ member.phone }}
{{ member.role }}
{% endfor %}
{% if members|length < 3 %}
暂未分配
{% endif %}
```
---
## 12. Photo Gallery Manager
### 12.1 组件构成
| 子组件 | 实现方式 |
|---|---|
| Scrollable Tab Bar(分类标签) | Tailwind `overflow-x-auto flex` + Alpine.js 激活态 |
| Image Grid with Checkbox | Tailwind `grid grid-cols-6 gap-2` + Alpine.js 多选 |
| Batch Action Toolbar | Alpine.js `:disabled` 状态控制 |
| Drag-and-Drop Upload | **Filepond**(CDN,~50KB) |
| Drag-to-Reorder | **SortableJS**(CDN,~3KB) |
### 12.2 图片网格 HTML
```html
```
### 12.3 Filepond 上传初始化
```javascript
// static/js/filepond-init.js
FilePond.registerPlugin(FilePondPluginImagePreview)
FilePond.setOptions({
server: {
process: '/api/photos/upload/',
headers: { 'X-CSRFToken': getCookie('csrftoken') }
},
labelIdle: '拖拽图片到此处,或
点击选择',
acceptedFileTypes: ['image/*'],
allowMultiple: true,
maxFiles: 50,
})
```
---
## 13. Image Lightbox Viewer
### 13.1 技术选型
**使用 Viewer.js**(CDN,~5KB,无框架依赖)覆盖缩放/旋转/全屏/翻页/缩略图条。
```html
```
### 13.2 初始化
```javascript
const gallery = document.getElementById('photo-gallery')
const viewer = new Viewer(gallery, {
toolbar: {
zoomIn: true, zoomOut: true, oneToOne: true,
reset: true, rotateLeft: true, rotateRight: true, download: true,
},
navbar: true,
title: (image) => `${image.alt} · ${image.naturalWidth} × ${image.naturalHeight}`,
url: 'data-src', // 使用 data-src 存储原图 URL
})
```
### 13.3 触发按钮集成
```html
{% for photo in photos %}

{% endfor %}
```
---
## 14. Accordion Progress Panel
### 14.1 视觉规格
| 属性 | 值 |
|---|---|
| 进度条轨道 | `h-2 bg-neutral-100 rounded-full` |
| 进度条填充(正常) | `bg-warning-600 rounded-full transition-all` |
| 进度条填充(满分) | `bg-success-600` |
| 父行(折叠头) | `flex justify-between items-center h-10 px-4 cursor-pointer hover:bg-neutral-50` |
| 子行 | `flex justify-between items-center h-9 pl-8 pr-4 text-sm` |
| 分数(正常) | `text-sm text-neutral-600 tabular-nums` |
| 分数(0分/未达标) | `text-sm text-danger-600 font-medium tabular-nums` |
| 操作链接 | `text-xs text-primary-600 hover:text-primary-700 ml-2` |
### 14.2 HTML 结构
```html
信息完整度
69%
完善信息可提升房源曝光率。
了解更多
{% for group in progress_groups %}
{% if group.is_collapsible %}
{% for item in group.items %}
{{ item.label }}
{{ item.score }}% / {{ item.max_score }}%
{% if item.action %}
{{ item.action }}
{% endif %}
{% endfor %}
{% else %}
{{ group.label }}
{{ group.score }}% / {{ group.max_score }}%
{% if group.action %}
{{ group.action }}
{% endif %}
{% endif %}
{% endfor %}
```
> **注意**:需引入 Alpine.js 官方 `x-collapse` 插件(1KB)处理高度动画。
---
## 15. Inline Edit Mode
### 15.1 视觉规格
| 状态 | 区分方式 |
|---|---|
| 只读态 | 纯文本 `
`,无边框 |
| 编辑态 | `` / `