name: sieta-web-style-guide description: SIETA 内部门户设计系统 — 当前 Neo-brutalism(新粗野主义)风格。旧手绘风已废弃。所有新页面/重构都加载此 skill。 version: 2.1.0 aliases: ["sieta-ui", "neo-brutalism", "管理门户风格"] metadata: hermes: tags: [design, UI, neo-brutalism, portal, SIETA] evaluations: path: evaluations/golden.jsonl description: Golden eval cases validating Neo-brutalism style compliance and SIETA portal conventions.
⚠️ 风格已更新:当前部署的是 Neo-brutalism(新粗野主义) 风格,已替代旧手绘风(Hand-Drawn)。旧手绘风规范保留在下方供参考,但不要用旧风格生成新页面。
创建或修改 SIETA 域名下的任何管理页面时必须加载此 skill,包括:
- admin.html(登录页)
- index.html(导航页)
- 后续新增的管理/内部页面
- 文件上传等交互组件
不适用:外部面向公众的 landing page(如 sieta.vip 备案页)
大头哥2026-05-12切换至此风格,旧手绘风已废弃。所有后续页面/修改必须遵循此规范。
Neo-brutalism 拒绝平滑、精致、圆润的现代UI,拥抱粗粝、高对比、有冲击力的视觉语言。从包豪斯海报和建筑粗野主义中汲取灵感——粗活、直接、毫不掩饰的边界感。
情感目标: 强大、自信、直接、有态度。让用户感觉到这是一个"被设计过"的系统,有明确的视觉个性。
+/-)——纯装饰,用 CSS/文字替代.container-status.running(绿)/.exited(红)+/-,不用箭头符号box-shadow: 8px 8px 0px 0px #000(大偏移、零模糊)#FFD93D、紫 #C4B5FD、红 #FF6B6Blinear-gradient(135deg, #FFFDF5 0%, #F5F0E8 100%)| Token | 值 | 用途 |
|---|---|---|
--bg |
#FFFDF5 |
米白背景 |
--black |
#000 |
纯黑边框/文字 |
--yellow |
#FFD93D |
品牌/高亮块 |
--purple |
#C4B5FD |
用户名/成功状态 |
--red |
#FF6B6B |
危险/强调色 |
--white |
#fff |
卡片/按钮白色面 |
--gradient |
linear-gradient(135deg, #FFFDF5 0%, #F5F0E8 100%) |
index 页面背景渐变 |
<link href="https://fonts.loli.net/css2?family=Space+Grotesk:wght@500;700;900&display=block" rel="stylesheet">
font-weight: 900 + text-transform: uppercase + letter-spacing: -0.02emfont-weight: 500 或 7004px dashed #000)box-shadow: 8px 8px 0px 0px #000box-shadow: 12px 12px 0px 0px #000box-shadow: 4px 4px 0px 0px #000box-shadow: 0 6px 0px 0px #000当前部署的两个页面背景不同:
| 页面 | 背景 |
|---|---|
| admin.html(登录页) | #FFFDF5 + 半色调小点 radial-gradient(circle, #000 1.5px, transparent 1.5px) size 20px |
| index.html(导航页) | 无点渐变 linear-gradient(135deg, #FFFDF5 0%, #F5F0E8 100%) |
注意:背景风格由大头哥那组 prompt 决定。如果用户说"换背景",先问他指的是哪个 prompt 页面的背景。
当前部署的 favicon(sieta.vip 根域名):
| 文件 | 路径 | 用途 |
|---|---|---|
| SVG | /var/www/html/favicon.svg |
标签页图标(主流浏览器首选) |
| ICO | /var/www/html/favicon.ico |
收藏夹图标(Chrome 存储 bookmark 用) |
| PNG | /var/www/html/favicon.png |
备用/高分辨率(可加 64x64) |
设计参数(与 gallery favicon 完全一致的结构,仅颜色不同):
- 64×64 viewBox,rx=12 圆角方形
- 黄色 #FFD93D 底 + 黑色粗体 "S"(gallery 为红底 #b91c1c + 白 "S")
- SVG 源文件:/home/ubuntu/svicon.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="12" fill="#FFD93D"/>
<text x="32" y="50" text-anchor="middle" font-family="Arial, sans-serif" font-size="52" font-weight="bold" fill="black">S</text>
</svg>
<head> 中需要 3 个 link(缺一不可——已踩过坑)<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="shortcut icon" href="/favicon.ico">
<link rel="icon" type="image/png" sizes="64x64" href="/favicon.png">
⚠️ 每个 SIETA 管理页面必须包含全部 3 个 link。缺 shortcut icon → Chrome bookmark 不存图标;缺 SVG → 标签页模糊;缺 PNG → 高分辨率屏无图标。
nginx location 必须加缓存头,否则浏览器重启后 favicon 不持久:
location ~* \.(svg|ico|png)$ {
root /var/www/html;
expires 7d;
add_header Cache-Control "public, max-age=604800, immutable";
}
不配置的话,浏览器默认不缓存 → 重启后 bookmark 图标空白(已因此修了2次)。这条是 repeat-offense 极高的坑。
用 Python PIL 从 SVG 规格转 ICO(64×64,黄底黑 S),或直接运行 skill 内置脚本:
python3 ~/.hermes/skills/design/sieta-web-style-guide/scripts/gen-favicon.py
# 可选参数:--dir /var/www/html --color #FFD93D --letter S --letter-color black
from PIL import Image, ImageDraw, ImageFont
img = Image.new("RGBA", (64, 64), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
draw.rounded_rectangle([(0, 0), (63, 63)], radius=12, fill="#FFD93D")
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 36)
bbox = draw.textbbox((0, 0), "S", font=font)
x = (64 - (bbox[2]-bbox[0])) / 2 - bbox[0]
y = (64 - (bbox[3]-bbox[1])) / 2 - bbox[1] - 1
draw.text((x, y), "S", fill="black", font=font)
img.save("/tmp/favicon.png")
img.save("/tmp/favicon.ico", format="ICO", sizes=[(64, 64)])
# 部署:sudo cp /tmp/favicon.* /var/www/html/
所有 favicon 文件放在 sieta.vip 根目录 /var/www/html/,因为 link 引用的是绝对路径 /favicon.*。nginx 的 location ~* \.(svg|ico|png)$ 负责提供服务。
/home/ubuntu/svicon.svg(SVG 内容)sudo cp /tmp/favicon.* /var/www/html/svg 源 → ICO 转换 → HTML link → nginx cacheFeather Icons(同旧风格,未变):
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
初始化:feather.replace()
#FF6B6B)text-transform: uppercase,letter-spacing: 0.1emgrid-template-columns: 1fr 1fr),gap 16pxopacity: 0.4 + pointer-events: none#FFD93D,虚线黑边框,占一个卡片位销售分析/库存分析页面使用 Chart.js v4 绘制的双坐标轴并列水平柱状图(indexAxis: 'y'),遵循 neo-brutalist 配色。
双坐标轴不适合做成叠加柱(两个 dataset 用不同 xAxisID,柱子从 x=0 按各自 scale 延伸)。由于两个轴的量级不同(金额×万 vs 双数×百),叠加柱的视觉位置不匹配,看起来奇怪。必须用分组并列柱(grouped bars),dataset index 决定柱子在组内的上下位置。
所有柱子加 2px 黑色边框(borderWidth: 2, borderColor: '#000')。组内两柱间距 0(barPercentage: 1.0),中间共享一条 4px 黑框。组间间距保持 categoryPercentage: 0.78。
| 维度 | 各店图 | 品牌图(环形图) |
|---|---|---|
| 销售额(上轴,dataset[0]) | #FF6B6B 热红 |
品牌名映射(见下方) |
| 双数(下轴,dataset[1]) | #C4B5FD 淡紫 |
半透明品牌色 +'88' |
const colorMap={'依歌尔诗':'#C4B5FD','娇娅媚妮':'#F472B6','是她':'#DC2626','迷人角色':'#4ADE80'};
const fallback=['#FF6B6B','#FFD93D','#4361ee'];
const colors=lbls.map(l=>l==='其他'?'#94A3B8':(colorMap[l]||fallback[fi++%fallback.length]));
max: 最大双数 × 1.5(动态计算,非硬编码),确保金额柱始终更长border: { display: true, color: '#000', width: 2 }, grid: { drawOnChartArea: false }(独立 border 配置,不在 grid.display 里控制)| 参数 | 值 | 效果 |
|---|---|---|
barPercentage |
1.0 |
柱子撑满自己分配的 50% 空间,组内两柱紧挨无间隙,边框重叠为 4px |
categoryPercentage |
0.78 |
每组占分类槽 78%,剩余 22% 上下均分 ≈4px 组间距 |
new Chart(canvas, {
type: 'bar',
data: {
labels: items.map(i => i.label),
datasets: [
{ label: '销售额', data: items.map(i => i.amt),
backgroundColor: '#FF6B6B', borderWidth: 2, borderColor: '#000',
yAxisID: 'y', xAxisID: 'x1',
barPercentage: 1.0, categoryPercentage: 0.78,
datalabels: { anchor: 'end', align: 'end', offset: 4,
color: '#000', font: { weight: 'bold', size: 13 },
formatter: v => '¥' + v.toLocaleString() } },
{ label: '双数', data: items.map(i => i.qty),
backgroundColor: '#C4B5FD', borderWidth: 2, borderColor: '#000',
yAxisID: 'y', xAxisID: 'x',
barPercentage: 1.0, categoryPercentage: 0.78,
datalabels: { anchor: 'start', align: 'end', offset: 3,
color: '#000', font: { weight: 'bold', size: 11 },
formatter: v => v + '双' } }
]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: { font: { weight: 'bold', size: 11 },
boxWidth: 16, boxHeight: 16, padding: 12,
usePointStyle: true, pointStyle: 'rectRounded' }
}
},
scales: {
x: { position: 'bottom', title: { display: true, text: '双数', font: { size: 11, weight: 'bold' } },
grid: { color: '#ddd' }, beginAtZero: true, max: maxQty },
x1: { position: 'top', title: { display: true, text: '销售额 (¥)', font: { size: 11, weight: 'bold' } },
grid: { drawOnChartArea: false }, beginAtZero: true },
y: { border: { display: true, color: '#000', width: 2 },
grid: { drawOnChartArea: false },
ticks: { font: { size: 11, weight: 'bold' } } }
}
}
});
⚠️ 重要:不要用 3 层嵌套环形图(weight:2/1/7 + cutout:'2%')。 该配置在静态页面中会静默崩溃(new Chart() 抛出未捕获异常),导致整个页面 JS 停止执行,所有图表和动态内容消失。
静态页面使用单层环形图:
new Chart(canvas, {
type: 'doughnut',
data: {
labels: ['娇娅媚妮', '依歌尔诗', '是她', '迷人角色', '其他'],
datasets: [{ data: [29120, 21167, 10940, 7871, 3994], backgroundColor: colors }]
},
options: {
responsive: true, maintainAspectRatio: false,
cutout: '40%',
plugins: { legend: { position: 'bottom' } }
}
});
数据标签只标注前4名:
datalabels: {
display: function(ctx) { return ctx.dataIndex < 4; },
// ...
}
品牌颜色映射保持不变(见下方),'其他' 统一用 '#94A3B8'。
旧版嵌套环形图(v2)配置保留在下文供参考——仅用于动态 SPA 页面(如 sales_analysis_mobile.html),不要在新静态页面中使用。
品牌排名使用 Chart.js doughnut 类型,三层嵌套结构(weight 控制比例):
| 层 | weight | 作用 | 样式 |
|---|---|---|---|
| 内饼(销售额) | 7 | 主数据展示 | 品牌色实心,borderWidth: 3, borderColor: '#000' |
| 间隙 | 1 | 视觉分隔 | backgroundColor: 'transparent', borderWidth: 0 |
| 外环(双数) | 2 | 次要数据 | 品牌色 +'CC' 半透明(80%不透明),borderWidth: 3, borderColor: '#000' ⚠️ 不要用 '88'——深色(如#DC2626)会跟内饼颜色相差太远 |
cutout: '2%'(极小空心,接近实心饼图)font-size: 15),双数在外环外(font-size: 12)页面规范:
- 标题 font-size: 12px; font-weight: 900; uppercase; letter-spacing: 0.05em
- 卡片白底 4px solid #000 硬边框 + shadow: 6px 6px 0 #000
- KPI 卡片 5 列 grid,日均值显示在 .s 小字行
- 营业员表格:姓名加 <strong> 粗体,数字 right-align
- 白底卡片,8px 硬阴影(比导航卡轻),padding 20px
- 标题 14px 900 weight,下划线分隔
- 拖拽区:3px 虚线黑边框,padding 20px,hover 变黄底实线
- 进度条:14px 高,红底填充,3px 黑边框外包
- 文件列表:2px 黑边框 item,hover 淡紫底
- 整体比导航卡片更紧凑(上传是次要功能)
创建新页面到 SIETA portal 的标准流程:
1. 从已有工作模板复制(销售分析手机版 = KPI + 图表结构,销售图片 = 商店分组 + 图片卡片),不要从零写
2. 创建 HTML 文件到 /var/www/html/portal/ 下(遵循 Neo-brutalism CSS 规范)
3. 添加对应的 API 端点到 /home/ubuntu/admin-auth-server.py(需 auth 保护)
4. 在 index.html 的导航 grid 中添加卡片
5. 所有内部跳转路径使用 /portal/xxx 格式
6. 重复使用已有组件类(.nav-card, .section-title, .btn-refresh 等)
本页实现了以下可复用的 UI 组件模式:
.path-pill):Home、Webroot、Portal、Nginx、Sietadata、Hermes.path-input):可输入绝对路径或预设名,按 Enter 或点 GO 按钮跳转#showHiddenCheck):勾选后隐藏文件(. 开头)也会显示#pathCurrent)loadedDirs 和 expandedDirs,重新从新根加载GET /api/scan-roots 获取预设列表,GET /api/server-tree?root=&show_hidden=&path= 获取目录内容.level-0 padding-left 12px,每加深一级 +16px,最多 level-4.tree-toggle 切换,用 JS 维护 expandedDirs 对象[DIR] 目录 / [MD] .md / [ ] 其他文件(灰色等宽标签 .tree-icon)#6B21A8 下划线,点击新标签页打开 /api/serve-file?path=....tree-size 11px,紧贴文件名右侧.tree-mtime 10px 灰色,今日显示 HH:MM,非今日显示 MM/DD.tree-container 嵌入在关联目录下,紫底 #C4B5FD15 左边框标记. 开头),可在路径选择器勾选 show hidden 后显示.git、node_modules、.venv、__pycache__、.cache、.npm).disk-card 白底 8px 阴影,顶部独立于其他 sectionDISK USAGE 12px 900 weight uppercase.disk-bar-bg 20px 高,4px 黑边框,内部用 .disk-bar-fill 填充font-size: 12px,数字用 font-weight: 900GET /api/disk-usage 拉取(受 auth 保护).docker-card 白底 8px 阴影,固定宽度 400px,竖屏自适应.dot.running 绿/.exited 红) + 名称 + 镜像 + 端口.associated 类:紫底 + 左边框;无关联的用黑底白字分隔标题 "UNASSOCIATED"directory 字段匹配目录名,关联容器也在对应目录树节点下方重复显示.btn-refresh 与基础按钮规格一致(44px 黑边框白底),hover 变黄底.spinning 类,图标旋转动画(0.8s linear infinite)在 directory tree 顶部增加了可切换扫描根路径的组件(预设按钮 + 自定义路径输入 + show hidden 开关)。
详见 references/server-directory-path-selector.md。
从 SIETA portal (index.html) 点击导航卡片进入子服务时,不需要额外 token(浏览器自动携带 admin_token cookie 免检),但外部直接访问仍需 token:
| 子服务 | 路径 | 外部认证 | Portal 跳转 |
|---|---|---|---|
| 相册 Gallery | /gallery/ |
Bearer Sieta@2006 (Express) |
admin_token cookie 免检 |
| 财务 Finance | /finance/ |
finance_auth=Sieta@2006 cookie (nginx) |
admin_token cookie 免检 |
Portal 的 JWT cookie 是统一认证屏障。登录后跳转子服务,浏览器自动带入 cookie 通过检查。
关键原则:不要直接删除外部认证(影响5个店已有用户)。应保留原有 token/cookie 认证,叠加 admin_token cookie 豁免通道。
修改方法(详见 nginx-reverse-proxy-patterns skill 的 references/sieta-unified-auth.md):
- Finance:nginx location 用 $variable_flag 做 OR 条件($cookie_finance_auth 或 $cookie_admin_token)
- Gallery:server.js authMiddleware 中增加 req.headers.cookie.includes('admin_token=') 检查
所有内部管理页面统一放在 /portal/ 子路径下(2026-05-13 重组):
| 页面 | URL | 说明 |
|---|---|---|
| 登录页 | /portal/admin.html |
用户名密码 JWT 登录 |
| 导航页 | /portal/ |
导航卡片 + 文件上传 + Hermes profiles |
| 服务器目录 | /portal/server-directory.html |
目录树 + Docker + 磁盘监控 |
| 财务 | /portal/finance/ |
财务仪表盘(独立 cookie auth) |
旧路径(/admin.html, /index.html, /server-directory.html, /finance/)已设 301 永久跳转到新路径。
新页面开发原则:所有新的内部管理页面都放在 /var/www/html/portal/ 下,URL 为 /portal/<page-name>.html。
当前 index.html 的导航区块用两个 .nav-grid(内置卡片 + 自定义卡片):
★ 系统导航,而是 Internal Navigation(与 sidebar Hermes Profiles 同规格:14px 900 uppercase,下划线分隔)/sietadata/index.html 的「财务分析」卡片),Portal 不再直接显示sieta_links),hover 显示红色 ✕ 删除按钮当前 index.html 采用 三栏 Grid 居中布局(.layout grid container,grid-template-columns: 280px minmax(420px, 600px) 1fr,max-width=1400px):
.sidebar,grid column 1,position:sticky top=88px):显示 Hermes Profiles.profile-card):4px 黑边框,4px 硬阴影,白底,padding 10px 14px,margin-bottom 8px.profile-detail flex row space-between:model(12px #555)+ provider(11px #999 uppercase)GET /api/profiles 获取(admin-auth-server.py 端点,读 ~/.hermes/profiles/<name>/config.yaml),每次刷新页面拉取最新配置.content,grid column 2,minmax(420px, 600px)):原导航卡片 + 上传区,居中显示1fr)为空列,用于视觉对称平衡,确保导航卡片在屏幕中央而非偏右grid-template-columns: 1fr,sidebar 在上、content 在下布局设计原则:导航卡片是页面视觉重心,必须在屏幕中央。Sidebar(Hermes Profiles)是附属信息,紧贴导航左侧但不抢占中心位置。三栏 grid 280px | 居中 420-600px | 右侧平衡 实现了这个效果。
sietadata 相关页面放在 /sietadata/ 子路径下,不经过 /api/ auth(nginx 直接 serve 静态文件):
| 页面 | URL | 说明 |
|---|---|---|
| 数据入口 | /sietadata/index.html |
4 块卡片网格:库存分析、销售分析、财务分析、开放文档 |
| 开发文档 | /sietadata/docs.html |
分类文档索引页(即时销售表、SietaData 项目等) |
| 销售分析 | /sietadata/sales_analysis.html |
动态页,双日期选择器,调 API 拉数据 |
| 库存分析 | /sietadata/inventory_analysis.html |
动态页,单日期选择器,调 API 拉数据 |
| 项目文档 | /sietadata/soul.html |
项目说明 |
入口页布局(/sietadata/index.html):
- 2×2 卡片网格(grid-template-columns: 1fr 1fr,gap 20px)
- 沿用 Neo-brutalism 样式:Space Grotesk 字体、4px 黑边框、8px 硬阴影、hover 上浮
- 每张卡片含 emoji 图标(32px)+ 标题(22px 900 uppercases)+ 描述(13px opacity 0.5)
- 底部「← 返回 SIETA Portal」链接
- 「开放文档」卡片指向 docs.html(非 soul.html——用户纠正:开放文档应该是索引页,不是单一文档)
- 财务分析卡片指向 /portal/finance/(需 admin_token cookie 免检)
- 移动端单列堆叠
开发文档索引页(/sietadata/docs.html):
- 分类文档列表,按 section 分组(如「⚡ 即时销售表」「📦 SietaData 项目」)
- 每个 section:.section-title(18px 900 uppercase,3px 下划线)+ .doc-list(垂直排列)
- 每条文档链接(.doc-link):白底、3px 黑边框、4px 硬阴影,hover 变黄底
- 左侧小黑点(.dot 8×8)+ 文档名(.label)+ 右侧类型标签(.tag,小字号 uppercase 标签)
- 只放开发文档(spec、guide、prd),不放数据报表(用户明确纠正)
- md 源文件用 Python markdown 库转 HTML 后部署到 /var/www/sieta/sietadata/(sudo tee 写入)
- 所有文档页加 ← 返回文档索引 链接指向 docs.html
- 底部「← 返回 SietaData」链接
⚠️ 财务分析原来在 Portal 导航页,已迁移到 SietaData 入口页。Portal 导航页不再显示财务卡片。
sietadata 页面全部公开访问(nginx location /sietadata/ 无 auth)。API 端点也不设 auth 检查(get_current_user 已移除)。
文件:/home/ubuntu/admin-auth-server.py(uvicorn,port 7891,nginx 代理 /api/ → 7891)
| 路径 | 方法 | 说明 | Auth |
|---|---|---|---|
/api/auth/login |
POST | JWT 登录 | 否 |
/api/auth/verify |
GET | 校验 cookie | 是 |
/api/auth/logout |
POST | 清除 cookie | 否 |
/api/auth/users |
GET/POST | 用户管理 | POST 需 auth |
/api/profiles |
GET | Hermes profile model/provider | 否(内部展示) |
/api/upload |
POST | 文件上传 | 是 |
/api/uploads |
GET | 上传文件列表 | 是 |
/api/server-tree?path=&root=home&show_hidden=false |
GET | 目录懒加载(可选 root 预设名或绝对路径,show_hidden 显示隐藏文件) | 是 |
/api/scan-roots |
GET | 可用根目录预设列表(含 label/path) | 是 |
/api/docker-info |
GET | Docker 容器 + 关联目录 | 是 |
/api/disk-usage |
GET | 磁盘空间 | 是 |
/api/serve-file?path=&root=home |
GET | 返回 .md 文件内容(需指定 root) | 是 |
/api/sietadata/sales?date=YYYY-MM-DD |
GET | 单日销售数据 | 否(公开) |
/api/sietadata/sales-range?start_date=YYYY-MM-DD&end_date=YYYY-MM-DD |
GET | 日期范围销售聚合(含每日明细) | 否(公开) |
/api/sietadata/available-dates |
GET | 有数据的日期列表(sales + inventory) | 否(公开) |
/api/sietadata/inventory?date=YYYY-MM-DD |
GET | 单日库存快照 | 否(公开) |
auth 检查:_auth_check(request) 函数解密 JWT cookie,失败返回 401。sietadata API 已移除 auth 检查。
⚠️ 用户数据文件 admin-users.json — 易丢失
路径:/home/ubuntu/admin-users.json,存 bcrypt hash 用户数据。
- 进程重启不会丢失(只读不写),但文件被删除/从未创建时任何人无法登录
- JWT 过期时间:7 天(JWT_EXPIRE_HOURS = 24 * 7),关闭浏览器后 7 天内自动登录,无需重新输密码
- 重建命令(admin / sieta2024):
bash
python3 -c "import bcrypt, json; h = bcrypt.hashpw(b'sieta2024', bcrypt.gensalt()).decode(); open('/home/ubuntu/admin-users.json','w').write(json.dumps({'admin':{'hash':h}}))"
- 不要在源码中硬编码明文密码做初始化(见 references/internal-auth-pattern.md)
依赖:PyYAML(/api/profiles 用)、bcrypt(登录用)、PyJWT(JWT)。
@media (max-width: 640px):header padding 缩小,grid 变 1 列,section padding 减少以下内容仅作历史参考。新页面不要用。
box-shadow: 4px 4px 0px 0px #2d2d2d(不用模糊阴影)| Token | 值 | 用途 |
|---|---|---|
--bg |
#fdfbf7 |
暖纸背景 |
--fg |
#2d2d2d |
铅笔黑(不用纯黑) |
--muted |
#e5e0d8 |
旧纸/擦除色 |
--accent |
#ff4d4d |
红修正笔 |
--border |
#2d2d2d |
铅笔线 |
--blue |
#2d5da1 |
蓝圆珠笔 |
--yellow |
#fff9c4 |
便利贴黄 |
<link href="https://fonts.loli.net/css2?family=Kalam:wght@400;700&family=Patrick+Hand&display=swap" rel="stylesheet">
font-family: 'Kalam', cursive; font-weight: 700;font-family: 'Patrick Hand', cursive;不要用标准 border-radius 或 tailwind rounded-*。用多值不规则 radius:
/* 标准 - 小容器/按钮 */
border-radius: 255px 15px 225px 15px / 15px 225px 15px 255px;
/* 中等 - 卡片/大框 */
border-radius: 60px 15px 55px 15px / 15px 55px 15px 60px;
边框宽度至少 border: 3px solid #2d2d2d,次要元素可用 border: 2px dashed #2d2d2d。
不用 blur。纯硬偏移:
/* 标准 */
box-shadow: 4px 4px 0px 0px #2d2d2d;
/* 强调(大卡片/弹窗) */
box-shadow: 8px 8px 0px 0px #2d2d2d;
/* Hover 缩小模拟抬起 */
box-shadow: 2px 2px 0px 0px #2d2d2d;
body {
background-color: #fdfbf7;
background-image: radial-gradient(#e5e0d8 1px, transparent 1px);
background-size: 24px 24px;
}
#fff,3px 黑边,黑字,4px 硬阴影#ff4d4d 白字,阴影缩小至 2pxPatrick Hand,text-lg md:text-2xlPatrick Hand 字体#2d5da1,box-shadow: 0 0 0 2px rgba(45,93,161,0.2)color: rgba(45,45,45,0.4).card {
background: #fff;
border: 3px solid #2d2d2d;
border-radius: 255px 15px 225px 15px / 15px 225px 15px 255px;
box-shadow: 4px 4px 0px 0px #2d2d2d;
transform: rotate(-1deg);
transition: transform 0.15s;
}
.card:hover {
transform: rotate(0deg) translate(-2px, -2px);
box-shadow: 6px 6px 0px 0px #2d2d2d;
}
#fff9c4 用于特色卡片max-width: 64rem(max-w-5xl)rotate-1 / -rotate-2gap-8md: 断点展开多列py-20transition-transform duration-100(快速干脆)hover:rotate-1 / hover:-rotate-2hidden md:block)保持核心审美使用 Feather Icons,不用 emoji。 Emoji 在不同平台渲染差异大,不适合手绘风格的管理页面。
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<!-- 静态图标 -->
<i data-feather="image" width="36" height="36"></i>
<i data-feather="dollar-sign" width="36" height="36"></i>
<i data-feather="bar-chart-2" width="36" height="36"></i>
<i data-feather="upload-cloud" width="28" height="28"></i>
<i data-feather="folder-plus" width="48" height="48"></i>
<i data-feather="file-text" width="20" height="20"></i>
<i data-feather="map-pin" width="28" height="28"></i>
// 页面加载后
feather.replace();
// 动态注入图标后(如文件列表渲染后),再次调用 feather.replace()
| 用途 | Feather Icon | 尺寸 |
|---|---|---|
| 相册/图片 | image |
36px |
| 财务 | dollar-sign |
36px |
| 数据/统计 | bar-chart-2 |
36px |
| 上传 | upload-cloud |
28px |
| 文件夹/拖拽 | folder-plus |
48px |
| 文件列表 | file-text |
20px |
| 图钉装饰 | map-pin |
28px(红色) |
stroke-width={2.5} 加粗线条匹配手绘风colorreferences/design-spec-original.md — 用户的完整设计系统原始规范(含组件装饰细节、非泛化设计选择、情感目标等)references/financial-analysis-engine.md — Finance Dashboard 深度财务分析引擎:指标计算、门限值、四卡报告结构references/neo-brutalism-spec.mdreferences/h5-mobile-adaptation.md — H5 手机版适配规范:排版变化、KPI 左右结构、图表高度、表格自适应、触控优化scripts/gen-favicon.py — favicon 三件套(SVG + ICO + PNG)一键生成脚本本页是 SIETA 门户内
/portal/finance/的特定配置。此内容原为独立 skillfinance-dashboard-config,已整合至此。
对比柱状图:
- 本期(current):紫罗兰 #8B5CF6
- 环比(MoM):琥珀 #FBBF24
- 同比(YoY):浅灰 #CBD5E1
趋势图:
- 销售:紫罗兰 #8B5CF6
- 毛利:琥珀 #FBBF24
- 净利润:粉色 #F472B6
Chart.js grouped bar 的宽度和间距控制:
| 需求 | 方案 | 说明 |
|---|---|---|
| 固定柱宽 | dataset.barThickness: 24 |
每根柱子固定 24px |
| 限制最大宽度 | dataset.maxBarThickness: 28 |
dataset 级别属性(非 scale) |
| 组间间距 | scale.x.categoryPercentage: 0.85 |
分类槽占可用宽度百分比 |
| 组内间距 | scale.x.barPercentage: 0.9 |
组内柱子占分类槽百分比 |
坑: maxBarThickness 是 dataset 级别属性,写在 scale 上会被 Chart.js 忽略。
依赖:
- chart.umd.min.js:/var/www/sieta/finance/chart.umd.min.js(Chart.js 本地副本)
- 源代码:~/sietadata/dashboard/index.html
- nginx alias:/var/www/sieta/finance/
三种模式(selectedMode: 'summary' | 'all' | 'multi'):
核心函数:getTargetStores() 代替旧的 selectedStores.includes(0) 判断。
页面底部 #analysisSection 区块,纯文字 + 突出数字:
| 卡片 | 内容 |
|---|---|
| 期间概览 | 总营收、毛利、净利润、费用 + 环比变化 |
| 波动提醒 | 各指标环比波动 >20% 的项,按幅度排序 |
| 店铺诊断 | 亏损店标红、费用率>50%标黄、其余标绿 |
切换时间/店铺时自动更新。
从 finance_source.xlsx 的 资产负债表 sheet 提取 12 个字段嵌入 HTML 为 BALANCE_DATA 常量:
| 卡片 | 指标 |
|---|---|
| 经营概览 | 三月营收趋势(连续升/降判断)、毛利率趋势、费用结构 + 后台费用率告警 |
| 资产负债表 | 银行存款及变化、库存及变化、库存周转率(季度)、周转天数、应收/应付/信用卡负债、净资产、流动比率 |
| 逐店诊断 | 每家店营收/净利/毛利率/费用率 + 环比波动 >20% 标记 + 健康标签 |
| 重点关注与建议 | 亏损店汇总、库存积压、费用控制、现金流紧张、营收下滑提示 |
BALANCE_DATA 嵌入方式:从 xlsx 读取 → json.dumps → 插入 HTML 中 DATA 常量之后(用 html.replace() 单次替换)。
Dashboard 的 index.html ~95KB,含 40 月 × 7 店 JSON 数据 + 资产负债表。
read_file 截断风险:分页读后写回会截断 DATA。必须用 patch() 做针对性替换,或 Python 脚本全量读写。storeSel(改成了 stores)momAllZero / yoyAllZeroselectedStores.includes(0)(改成了 getTargetStores())activeStores(改成了 getTargetStores())审计命令:grep -n 'storeSel\|includes(0)\|activeStores\|momAllZero\|yoyAllZero' index.html
references/financial-analysis-engine.md — 分析引擎完整指标计算和诊断规则、门限值表references/patch-audit-checklist.md — 大文件 patch 后 5 步审计清单和回滚流程rounded-* 类,必须 inline style 或自定义 class 实现 wobbly radiusbox-shadow 的 blur 值,硬阴影是核心特征#000000 或纯白 #ffffff 作为背景