feat: 多用户权限体系、AI 助手、SKU 目录与合同清洗(开源准备)

本地长期开发成果的整体入库快照,包含后端鉴权/权限、AI 接入、合同解析、
SKU 目录等模块,以及前端相应页面与样式改版。

后端
- 新增鉴权与会话:scrypt 密码哈希、HttpOnly cookie(happy_sid)、登录失败锁定
- 新增「角色 → 权限组 → 权限点」三级权限模型与审计日志
- 新增 AI 提供商接入与预设(兼容 OpenAI 协议,内置 DeepSeek / 通义千问等)
- 新增合同解析、合同文件保留策略、客户归属与访问控制
- 新增 SKU 目录、组织品牌、新客开发、系统日志等路由

前端
- 新增登录页与用户 / 权限组 / 系统日志管理页
- 新增客户详情、客户公海、询盘详情、报价编辑器、合同清洗中心
- 新增 AI 配置、组织品牌、包装参数、SKU 选择器等组件
- 新增英文大写金额、未保存改动拦截、抽屉与骨架屏
- 新增 stage1 样式层;产品、供应商、订单等页面改版
- EmbeddedSku 页面并入现有产品体系后删除

开源准备
- 新增 AGPL-3.0 LICENSE
- README:移除未启用的 SQLCipher 加密声明(改为如实的「已知限制」)、
  补充环境变量说明、许可证由「私有项目」改为 AGPL-3.0
- 移除历史遗留的空文件 _index_backup.txt
- 文档中的项目路径更新为 happy-export
This commit is contained in:
Drsakura
2026-09-17 14:53:02 +08:00
parent 436519e0a6
commit 13d67c804f
77 changed files with 18407 additions and 955 deletions
+5 -5
View File
@@ -143,7 +143,7 @@ a { color: inherit; }
border-radius: var(--radius-md);
background: var(--surface);
color: var(--text-primary);
font-size: 13px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: transform var(--transition-fast), background var(--transition-fast), border-color var(--transition-fast), box-shadow var(--transition-fast);
@@ -155,7 +155,7 @@ a { color: inherit; }
.btn-secondary { background: var(--surface-raised); }
.btn-danger { color: var(--danger); }
.btn-danger:hover { background: var(--danger-bg); border-color: var(--danger-border); }
.btn-sm { min-height: 30px; padding: 5px 10px; font-size: 12px; }
.btn-sm { min-height: 30px; padding: 5px 10px; font-size: 13px; }
.btn-icon { min-width: 34px; padding: 7px; }
.btn-link { border: 0; background: transparent; color: var(--primary-light); font-weight: 600; cursor: pointer; padding: 5px 0; }
.btn-link:hover { color: var(--text-primary); }
@@ -191,7 +191,7 @@ input:focus, input:not([type]):focus, select:focus, textarea:focus, .form-input:
padding: 2px 8px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
font-size: 12px;
font-size: 13px;
font-weight: 700;
white-space: nowrap;
}
@@ -201,8 +201,8 @@ input:focus, input:not([type]):focus, select:focus, textarea:focus, .form-input:
.badge-danger, .status-shipped, .priority-high { background: var(--danger-bg); color: var(--danger); border-color: var(--danger-border); }
.badge-neutral, .status-draft, .priority-low { background: var(--surface-alt); color: var(--text-secondary); }
.data-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px; }
.data-table thead th { position: sticky; top: 0; z-index: 1; padding: 11px 14px; background: var(--surface-raised); color: var(--text-secondary); border-bottom: 1px solid var(--border-strong); text-align: left; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; }
.data-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 14px; }
.data-table thead th { position: sticky; top: 0; z-index: 1; padding: 11px 14px; background: var(--surface-raised); color: var(--text-secondary); border-bottom: 1px solid var(--border-strong); text-align: left; font-size: 12.5px; letter-spacing: 0.08em; text-transform: uppercase; }
.data-table tbody td { padding: 12px 14px; color: var(--text-secondary); border-bottom: 1px solid var(--border-light); vertical-align: middle; }
.data-table tbody tr:hover td { background: var(--primary-bg); color: var(--text-primary); }
.data-table tbody tr:last-child td { border-bottom: 0; }