|
|
||
|---|---|---|
| .workbuddy/memory | ||
| app | ||
| data | ||
| scripts | ||
| web | ||
| .DS_Store | ||
| .dockerignore | ||
| .env | ||
| .env.example | ||
| Dockerfile | ||
| README.md | ||
| deploy.sh | ||
| docker-compose.yml | ||
| requirements.txt | ||
| run.sh | ||
README.md
DMS管理平台
基于《软件开发部管理工作执行表.xlsx》构建的 DMS 管理平台。
侧栏是四个业务模块 + 三个支撑页:
| 模块 | 适用 | 关心什么 | 数据表 |
|---|---|---|---|
| ① 工作看板 | 每天进来的第一屏 | 三类工作本周期谁更新了、谁没动,点一条从一览看到详情 | 只读汇总(/api/weekly + /api/focus) |
| ② 项目管理 | NEX云桌面、AI Agent、智飞无人机平台、内部运营… | 长期重点项目的迭代执行、子任务进展、风险阻塞 | projects / tasks / updates |
| ③ 定制项目 | 各省市客户定制子项目 | 商务阶段流转(签单→开发→交付→验收→计收)+ 按进度确认收入 | custom_projects / custom_updates |
| ④ 每周重点工作 | 不值得单独建项目档案的短期活儿 | 一条就是一件事,面板里录,归在录入周这一周,跟到完成 | weekly_focus |
支撑页三个:人力资源(staff,全系统「负责人」的唯一来源)、更新录入(执行 / 交付记录的唯一写入口:粘贴原文 → AI 整理 → 确认写入)、设置(周期管理、AI 配置)。
②③ 是两套彼此独立的项目体系(数据表也完全分开),共用一个人力资源库:项目与定制项目的「负责人」都从花名册里选,人员进出只维护一处。④ 的条目不是项目,不挂子任务与执行记录。① 只做汇总与跳转,自己不落数据。
周期管理
新增周期
三个入口,都会自动接续年份:
| 入口 | 操作 |
|---|---|
| 顶部周期下拉 | 选「+ 新建周期…」 |
| 更新录入页(常规 / 定制都有) | 周期下拉选「+ 新建周期…」 |
| 设置页 → 周期管理 | 「+ 新建周期」按钮 |
输入格式 9月第1周(也可写 2027年1月第1周 显式指定年份),年份按以下规则自动推断:
- 月份 大于等于 最新周期的月份 → 用最新周期的年份(9月已有第4周时补录第1周,仍留在同年)
- 月份 小于 最新周期的月份 → 进入下一年(8月之后建
1月第1周→ 次年)
重复创建同一周期会直接返回已存在的那个,不会产生重复数据。
删除周期
设置页 → 周期管理,表格里列出所有周期及各自的记录数。只有不含任何执行记录的空周期才能删除,已有记录的会置灰并说明原因,接口层面也会二次校验。
年份整体平移
历史数据的年份口径需要整体调整时:
python scripts/shift_year.py --dry-run # 先预演,看清楚了再执行
python scripts/shift_year.py # 全部 +1 年
python scripts/shift_year.py --years -1 # 反向回滚
内部按年份从大到小更新,避免 2024→2025 撞上尚未更新的 2025;执行后会校验 sort_key 无重复,有重复则整体回滚。 执行前建议手动备份:
cp data/board.db data/board.db.bak
快速开始
cd /Users/jiliu/WorkSpace/unis_manager
./run.sh
首次运行 run.sh 会自动完成:选择带依赖的 Python 解释器 → 缺依赖则安装 → 建库 → 导入历史 Excel → 拆分定制项目,然后启动并打开浏览器。
启动方式与排错
| 场景 | 命令 |
|---|---|
| 正常启动 | ./run.sh |
报 permission denied |
chmod +x run.sh 后重试(不要用 sudo) |
| 不想改权限 | bash run.sh |
| 换端口 | PORT=8771 ./run.sh |
| 关闭热加载 | DEV=0 ./run.sh(默认是开着的) |
| 不自动开浏览器 | NO_OPEN=1 ./run.sh |
| 提示"端口已被占用" | 原窗口 Ctrl+C,或 lsof -ti:8770 | xargs kill 后重跑 |
| 用 Docker 部署(不装 Python) | ./deploy.sh up,见下面「Docker 部署」 |
⚠️ 不要用
sudo启动:数据库文件会变成 root 属主,后续写入会失败。
热更新(默认开启):uvicorn 除
*.py外还会监听web/**的 js/css/html 与.env,改动即重启; 页面里的web/livereload.js每秒读一次/health的启动代际,发现服务重启后自动刷新浏览器。 也就是说改完代码不用手动重启、也不用手动刷新。DEV=0 ./run.sh可关掉(关闭后该脚本首轮即自行停止)。data/*.db的写入不会触发重启。
手动分步执行(等价于 run.sh 干的事)
pip install -r requirements.txt
python scripts/init_db.py
python scripts/import_excel.py "/Users/jiliu/WorkSpace/定开管理/软件开发部管理工作执行表.xlsx"
python scripts/migrate_custom.py
python -m uvicorn app.main:app --port 8770
重新导入:
python scripts/import_excel.py <xlsx> --reset之后再跑一次python scripts/migrate_custom.py(迁移脚本会删除常规表里的「定制开发」容器,重复执行前请先 reset)
Docker 部署(换机器 / 上服务器)
不想装 Python,或者要放到服务器上常开,用 Docker:
cd /path/to/unis_manager
./deploy.sh up # 构建镜像 + 后台启动 + 等健康检查
打开 http://127.0.0.1:8770。首次启动自动建表、预置分类,并创建内置角色与 admin 账号(密码见 .env)。
| 文件 | 作用 |
|---|---|
Dockerfile |
运行镜像:装依赖 + 拷 app/ web/ scripts/;启动时先跑 init_db.py(幂等)再起 uvicorn |
docker-compose.yml |
编排:./data 挂进容器存数据库、HOST_PORT 控对外端口、restart: unless-stopped 随 Docker 自启 |
deploy.sh |
命令封装:up / update / restart / down / logs / status / backup / mirror,顺带检查 docker 是否在跑、.env 与 data/ 是否存在,并在构建前探测国内可用源 |
.dockerignore |
保证 data/、.env、.git 不会被烤进镜像 |
| 场景 | 命令 |
|---|---|
| 改完代码上线 | ./deploy.sh update(重建镜像并替换容器) |
改了 .env(AI Key / 端口) |
./deploy.sh restart |
| 看日志排错 | ./deploy.sh logs |
| 看状态与健康检查 | ./deploy.sh status |
| 停止(数据保留) | ./deploy.sh down |
| 换对外端口 | .env 里 HOST_PORT=8080,再 ./deploy.sh up |
| 裸 compose 命令 | docker compose up -d --build / docker compose down |
| 构建时基础镜像 / pip 下载超时 | ./deploy.sh mirror 重探,或手工指定 PIP_INDEX_URL=... BASE_IMAGE=... ./deploy.sh up(见下「国内网络加速」) |
| 把现有数据带到新机器 | ./deploy.sh backup → 拷到目标机 data/board.db → ./deploy.sh up |
国内网络加速(docker.io / pypi.org 连不上时)
国内构建会撞两道互不相干的墙,得分开治,只治一道必然还剩一道:
| 卡在哪 | 典型报错 | 治法 |
|---|---|---|
| ① 守护进程拉基础镜像 | failed to resolve source metadata for docker.io/library/python:3.12-slim: ... TLS handshake timeout |
换仓库前缀 BASE_IMAGE(走加速站,或给守护进程配 registry-mirrors) |
| ② 构建容器里装依赖 | [4/8] RUN pip install ... -i https://pypi.org/simple 非零退出 |
换 pip 源 PIP_INDEX_URL(清华 / 阿里 / 腾讯) |
./deploy.sh up 在构建前会自动把两道源都探出来(判据是真的 docker pull、是在容器里 urlopen,不信宿主机 curl,也不信 docker manifest inspect 的假 denied),结果写进 .env,下次以及裸跑 compose 都复用:
./deploy.sh up # 自动探测 → 写 .env → 构建 → 等健康检查
./deploy.sh mirror # 只想重探一遍源(不会冲掉 .env 里手工钉死的 BASE_IMAGE)
不想用脚本,等价的两条 build-arg:
docker compose build \
--build-arg BASE_IMAGE=docker.1ms.run/library/python:3.12-slim \
--build-arg PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
docker compose up -d
几个必须知道的点:
- 基础镜像拉成功 ≠ 构建能过:
docker pull只治①,容器里的pip install照旧撞 pypi.org,必须另外给②换源。 - shell 里的
HTTP(S)_PROXY对构建没用:Docker 守护进程和构建容器都不读宿主机的代理变量。要走代理得在 Docker Desktop → Settings → Resources → Proxies 里配http://host.docker.internal:7897(端口换成你本机代理的)。 - 境外机器 / 内网有自建源:
CN_MIRROR=0 ./deploy.sh up关掉探测,源完全由.env说了算。 - 目标机完全不能上网:走离线路。
docker save -o unis-board.tar unis-board:latest→ 拷过去docker load -i unis-board.tar→NO_BUILD=1 ./deploy.sh up(跳过构建直接用本地镜像)。 - 构建失败时脚本会自己打印一份排查清单(
deploy.sh的build_hint),照抄命令即可。
几个坑:
- 数据只落在
./data:重建镜像、down再up都不动数据;备份依旧是拷走data/board.db。 - 时区必须设:容器默认 UTC,而周期/周次/入库时间都按本地日期算,所以 compose 固定传
TZ=Asia/Shanghai,别删。 - 容器内端口固定 8770:
.env的PORT是本机run.sh用的,compose 里覆盖成 8770;要换端口只改HOST_PORT。 - 别和
run.sh同时跑:一是 SQLite 是文件库,同一份board.db不要同时被本机进程和容器读写;二是本机进程绑的是127.0.0.1:8770,比 Docker 发布的*:8770更精确,浏览器和curl 127.0.0.1:8770会全打到本机服务上(症状:/health里dev是true,而容器是false)。两者要共存就给容器换端口:HOST_PORT=8899 ./deploy.sh up。 - 换 MySQL:放开
Dockerfile里pymysql那行重新 build,再把.env的DATABASE_URL指过去(见「迁移到 MySQL」)。
带着现有数据部署
镜像里故意不含数据(.dockerignore 排除了 data/),容器读的是宿主机挂载进来的 ./data/board.db,所以:
- 就在本机这个目录部署 → 什么都不用做,
./deploy.sh up起来读的就是当前这份库;连user_sessions里的登录态都延续,别人不用重新登录。 - 换一台机器部署 → 先把数据取出来,落到目标机的
./data/board.db:
./deploy.sh backup # 一致性快照 → data/backups/board-YYYYmmdd_HHMMSS.db
scp data/backups/board-*.db user@host:/srv/unis_manager/data/board.db
ssh user@host 'cd /srv/unis_manager && ./deploy.sh up'
data/board.db本身在 git 里,git clone也会带一份,但那是最后一次提交的状态;要「当前最新」请用./deploy.sh backup的快照(走 SQLite 备份 API,本机服务正在写也能拿到一致的库)。- 启动时的
init_db.py和ensure_seed只会补缺失的表 / 分类 / 内置角色,users非空就不再创建admin,不会覆盖或清空任何数据。实测:拿当前生产库快照起容器,启动前后逐表行数完全一致,123 个定制项目、22 名人员、老 token 全部照常可用。- 目标机上的
./data属主要和运行容器的人对得上(compose 以 root 跑则无此顾虑);SQLite 是文件库,同一份库别让两个实例同时写。
功能
1. 工作看板
进来第一屏:三类工作在这一页从「一览」点到「详情」,左栏挑一条,右栏跟着联动。
| 区域 | 内容 |
|---|---|
| 顶部 KPI | 重点项目(含本期已更新数)/ 重点项目未更新 / 本周重点工作(本周到期的重点工作条数,脚注「逾期 N 条 · 已完成 N 条」)/ 执行记录 / 定制合同额(含项目数与本期计收)/ 风险阻塞 |
| 左栏 | 三组清单,顺序固定:① 本周工作重点(列表,逾期的置顶)② 重点项目(列表:is_key 的项目,另含本期补了执行记录的非重点项目,条目前用 ★ 区分)③ 定制项目——只给一个入口「本周定制项目动态」,左栏不铺清单(100 多个项目会淹没看板),点开才在右栏列本周有动态的。组头显示条数与「几个已更新 / 几条逾期 / 本周动态几个」;每条带负责人、状态或阶段、本期摘要、风险条数,本期有记录的点亮标记 |
| 右栏 | 点左栏任意一条即可联动。重点项目 → 本期执行记录 / 进度与状态(含子任务)/ 近 8 期趋势;定制项目入口 → 本周动态总览(顶部标签:本周新增 / 本周验收 / 有交付动态 / 合同额合计 / 本期计收,下面分「本周新增」「本周验收」「本周有变化」三段,每条带阶段、负责人、金额、本期计收与摘要),点某一条再下钻到该项目 → 交付环节五段条 / 财务跟踪四联卡 + 开票·回款行内输入 / 本期交付记录 / 更早的交付记录,改开票、回款、环节日期即时保存(改日期会自动推进商务阶段);每周重点工作 → 只读的说明与状态,要改点「修改」开抽屉 |
| 工具栏 | 「负责人」筛选、「导出本周期记录」,提示里带本期项目数与定制动态数;顶栏可切周 / 月视图(月视图自动汇总该月所有周)。「列入全部在途」开关(all_custom)不在工具栏,挪到了右栏定制总览的头部 |
| 统计概览 | 页面最底部的折叠区(原「执行面板」,已不占主导航位):周期活跃度柱状图、分类环形图、状态分布、进度 Top10、低进度预警、风险清单,数据来自 GET /api/stats;加载失败也不影响看板主体 |
数据分两路:GET /api/weekly?period_id=|year&month&owner=&all_custom= 一次返回重点项目清单 + 定制项目清单 + 顶部 KPI(含 custom_new 本期新入库数、custom_updated 本期有交付记录数);「本周工作重点」不在 /api/weekly 里,前端并发取 GET /api/focus 后合并,分堆也在这一步判定(按条目的录入周:本周录入的 + 更早周次标了「延期」又没做完的结转条目)。
定制项目的口径:/api/weekly 返回本期有交付记录、被标为重点、本周期新入库(is_new:入库日期落在本周期首末日之内)、本周期新下单(sign_date 落在本周期)或本周期已验收(update_date 落在本周期且项目状态为「已验收」)的定制项目;右栏总览默认列「本周新增」「本周验收」「本周有变化」三段,其中前两段与「定制项目 → 本周进展」的「新增项目 / 验收情况」口径完全一致(前者按「下单时间」、后者按「更新时间 + 状态=已验收」),其余只在组头计数里出现,并在底部提示「另有 N 个在途定制项目本周没有动态」。勾选总览头部的「列入全部在途」后,后端补上处于在途阶段(商机 → 已验收)的项目,总览多出一段「其他在途项目」。头部几枚标签与「合同额合计」只加总当前列出来的行(同一项目同时落在两段时只算一次),勾与不勾数字会跟着清单变。
2. 项目管理
长期重点项目(projects + tasks + updates)。执行记录的写入逻辑没变,仍旧统一走「更新录入」。
| 模块 | 说明 |
|---|---|
| 项目管理 | 表格展示分类、负责人、状态、进度、更新情况(标签只在详情抽屉里维护,列表不放,避免挤占名称列);列表只读,行内不放任何输入框和下拉,每行只有 详情 / 修改 / 归档 三个按钮(操作列吸附在表格右侧,横向滚动也不会被挤出可视区);负责人筛选下拉来自人力资源库;支持新建与「含归档」筛选。详情抽屉里的周期反馈可切「按周 / 按月」与「本周 · 近 4 周 · 近 12 周 · 全部」(按月为「本月 · 近 3 月 · 近 6 月 · 全部」),用 ‹ › 翻页看更早的周期,默认跟随顶栏选中的周期 |
「执行面板」不再单列模块,已折进工作看板底部的「统计概览」折叠区。
项目维护操作
| 操作 | 说明 |
|---|---|
| 面板分区 | 抽屉用标准对话框式标题栏:标题在左(下面一行副标题),基础信息的操作按钮成排在右,✕ 在最右,按钮条嵌在标题栏里(不悬浮、不吸顶,滚动时跟着标题栏常驻)。详情档是「删除 · 归档/恢复 · 标记重点 · 修改」,修改与新增档是「取消 · 保存/添加」,主按钮永远在最右、危险动作在最左,按钮文字不再重复对象名(标题栏已经写了是哪个项目/条目)。附表的写入另放一处:绿虚线 = 附表 · 更新记录条仍留在它自己的附表标题下面,只有「+ 录一笔「某周期」执行记录 / 交付记录」,点它跳到「更新录入」并自动带上该项目与顶栏当前选中的周期。附表的写入统一走更新录入(粘贴原文 → AI 整理 → 确认写入),抽屉里不另做一套表单,避免两条路写出两份数据。基础信息的按钮在标题栏、附表的更新按钮在附表上方,两类按钮不在同一个位置混着放;编辑与新增档的报错信息显示在正文最上方;周期反馈上方的 按周/按月 + 本周/近 4 周/… 标成「查看范围(只读筛选)」,只翻看不改数据 |
| 改名 | 改名就是改基本信息:在详情面板点「修改」,表单第一行即项目名称(项目管理与定制项目同一套逻辑)。名称非空校验 + 重名检测,失败信息显示在面板正文最上方,不弹提示跑路 |
| 负责人与团队 | 多负责人只适用于重点项目:重点项目可以指定多个负责人与团队成员,定制项目只有一个交付负责人(单人下拉)外加团队成员。人名一律从人力资源库下拉里选。界面上负责人不分主次:名单横向排开自动换行,每一位都带 × 可删(没有「主」标记、没有不给删的首行),「+ 添加负责人 / + 添加成员」跟在名单末尾。名单只在详情抽屉的「修改」档维护;只读处一律一排对等的小标签:重点项目的列表与详情把负责人摆成对等的一排,定制项目的列表与详情只显示一个交付负责人加团队成员标签。存储与统计口径不变:重点项目 owner 字段仍是 owners[0](删掉第一位就由后一位顶上),看板、导出、人力负载与 KPI 按它归属,修改档的提示行写明了这点;重点项目所有负责人都算命中,定制项目按交付负责人命中;团队成员不参与该筛选 |
| 归档 | 项目从看板与列表默认视图中隐藏,数据完整保留;勾选「含归档」可查看,随时可恢复 |
| 删除 | 高危操作:弹窗列出将连带删除的子任务数与执行记录数,并且必须逐字输入项目名称,「永久删除」按钮才会点亮。删除后不可恢复,仅想隐藏请用归档 |
定制项目的「项目明细」表同理:列表只读,操作列只有 详情 / 修改 / 归档,改名 / 名单 / 删除都在抽屉里做。
项目合并
把两个项目合并成一个(例如把「智空无人机平台」并入「智飞无人机平台」):
python scripts/merge_project.py --from "智空无人机平台" --to "智飞无人机平台" --dry-run
python scripts/merge_project.py --from "智空无人机平台" --to "智飞无人机平台"
- 子任务同名 → 归并到目标项目的同名子任务,执行记录一并转挂
- 子任务仅源项目有 → 直接转到目标项目名下(保留原名)
- 分类 / 负责人 / 描述 / 标签仅在目标缺失时继承
- 合并后按最新记录回算目标项目的进度与状态
- 执行前建议备份:
cp data/board.db data/board.db.bak
3. 定制项目
| 模块 | 说明 |
|---|---|
| 交付流水线(默认页签) | 卡头给「在库 N 个 · 重点 N 个 · 本期更新 N 个」,漏斗本身按环节给项目数与金额(页签上不再叠一条全局 KPI 带,金额与项目数按环节累计);「入库 → 签单 → 交付 → 验收 → 计收」五段漏斗,每段给项目数与金额;口径为商务阶段 + 已录环节日期取先到哪一步算哪一步(环节日期还没补全时由商务阶段兜底)。下方「卡点项目」列出最后一个有日期的环节停留超过 CUSTOM_STEP_AGING_DAYS(默认 45)天且尚未计收的项目,可一键「处理」跳到详情抽屉补录 |
| 阶段看板 | 按「商机 → 已签单 → 开发中 → 交付完成 → 已验收 → 已计收 → 暂停/异常」分列展示,卡片带金额、区域、开发进度、可确认收入;列头显示项目数与金额合计。卡片只是入口,点卡片开详情抽屉;卡上的 ★ 只读(重点标记在详情面板的基础信息区改),外层不做任何直接写入 |
| 财务跟踪 | 页签内一排 KPI:合同总额 / 按进度可确认收入 / 已计收 / 待计收 / 已开票 / 已回款 / 待开票 / 待回款(各带比例脚注);再往下是阶段金额分布、周期活跃度、按负责人与区域汇总、待回款排行。财务口径的汇总只在这一页出现,环节与账期一屏对齐,方便财务逐月对账 |
| 项目明细 | 表头一行给出当前列表的合同额 / 可确认 / 已计收 / 开票 / 回款合计;按入库时间倒序的只读总表:区域 / 负责人 / 入库 · 签单 · 交付 · 验收日期 / 阶段 / 合同额 / 进度 / 可确认 / 已计收 / 开票 / 回款(金额列统一以「万」计)/ 备注;改数据点行尾「修改」 |
| 详情抽屉 | 分「详情 / 修改」两档:详情只读,修改才给表单(含项目名称、手动确认比例、交付环节五个日期、区域下拉、开票与回款金额)+ 收入确认卡 + 周期执行记录 + 阶段流转时间线。负责人从人力资源库里选。基础信息的动作在标题栏按钮条上(修改/保存,正文里标明不会动下面的交付记录),开票与回款金额跟着标题栏的「保存」一起提交;附表的绿虚线条(「+ 录一笔」跳更新录入 · 定制项目并带上项目与周期)留在附表标题下 |
4. 每周重点工作
不值得建项目档案的短期活儿:一条就是一个活儿,不用先建项目,录进来就能按周跟踪。
| 区块 | 说明 |
|---|---|
| 新增 | 工具栏「+ 新增重点工作」开独立面板(列表顶部那张内嵌录入卡已撤掉):工作内容(必填,一句话说清干什么)/ 说明(可选)/ 录入周(只读=顶栏当前选中的那一周,切周再录就归到那一周)/ 预计完成时间(日历日期,只是目标日期,不决定条目归到哪一周)/ 执行状态(取 constants.STATUSES)/ 结果反馈 / 是否延期(人工开关) |
| 列表 | 工作内容 / 说明(截断预览)/ 预计完成时间(下面小字标「录入 X月第N周」)/ 状态(标了延期且没完成的挂红色「延期」徽章)/ 更新日期 / 操作 详情 · 修改 · 归档 · 删除——和两张项目列表同一套规矩:列表只读,行内不放输入框 |
| 筛选 | 本周 / 延期 / 已完成 / 全部 四个堆(chip 上实时显示各堆条数)+「含归档条目」;顶栏切周即换基准周,工具栏标明「本周/延期按录入周统计」;顶部全局搜索同时匹配工作内容与说明 |
| 详情抽屉 | 同样分「详情 / 修改」两档,基础信息的按钮全在抽屉标题栏右侧的按钮条上:详情档是「删除 · 归档 · 修改」,修改与新增档是「取消 · 添加/保存」,报错信息显示在正文最上方。点「修改」才出表单,录入周只读(改预计完成时间不会把条目搬去别的周)。重点工作条目没有附表,所以抽屉里不出现绿的更新条 |
| 删除 | 列表行与抽屉顶部都能删,走同一个确认弹窗,并提示「只是这周不再跟进?请改用归档」。条目不挂子任务与执行记录,不要求逐字输入名称——逐字输入项目名称只用在项目管理与定制项目的项目删除上 |
| 导出 | 右上角「导出 Excel」→ GET /api/export/focus,带上当前筛选条件 |
| 联动 | 工作看板左栏第一组「本周工作重点」与 KPI「本周重点工作」读的就是这张表;看板右栏点条目给只读详情,改内容走抽屉 |
分堆口径(基准周 = 顶栏选中的那一周,一律按录入周算,和预计完成时间无关):录入周等于基准周 → 本周;录入周早于基准周且「是否延期」打开且未完成 → 延期(上周及更早没做完的活儿结转进来);状态为「已完成」→ 已完成(交叉视图,条目仍留在自己那一周)。 「更早周次录入、没标延期又没完成」的条目不进当周视图,只在全部里查得到——所以每周收尾时,没做完的记得勾上「延期」,它才会结转进下周。库里不存「逾期」字段,延期是人工开关。
人力资源库
侧栏「人力资源」,是全系统负责人姓名的唯一来源(staff 表)。
| 区块 | 说明 |
|---|---|
| 顶部 KPI | 在册人数与带项目人数、各归属小组人数、人员归属(汇智 / 云数 / 外服)分布,以及花名册外的负责人核对(历史数据里对不上花名册的人名,一个都不藏) |
| 花名册 | 姓名 / 归属小组 / 人员归属 / 说明 + 该人的在管项目数、定制项目数、定制合同额、名下待计收、最近更新;小组与归属可行内改(下拉),说明行内编辑失焦即存 |
| 筛选与导出 | 在编 / 已归档 / 全部 + 按小组筛选;「导出 Excel」按当前筛选导出 |
| 新增 | 弹窗录入:姓名必填且全库唯一,小组与归属下拉取自 constants.STAFF_GROUPS / STAFF_AFFILIATIONS |
| 改名 | 走「编辑」改姓名时,名下项目与定制项目的负责人、其他负责人、团队成员一并同步改掉,历史数据不会断链 |
| 归档 / 恢复 | 归档后不再出现在「负责人」下拉里,但历史项目仍完整保留其负责人;随时可恢复。离职人员用归档,不要用删除 |
| 删除 | 名下还挂着项目或定制项目时接口直接返回 400 并说明原因,防止把在管项目变成无主项目 |
导入名单(表头按名称匹配:序号 / 姓名 / 归属小组 / 人员归属 / 说明):
python scripts/import_staff.py --dry-run # 只看解析结果
python scripts/import_staff.py # 增量导入(同名跳过,不覆盖页面上的编辑)
python scripts/import_staff.py --reset # 清空后重新导入
python scripts/import_staff.py <xlsx> # 指定别的文件;首次运行 ./run.sh 会自动导入
负责人下拉(ownerSelectHtml)在项目新建/详情、定制项目新建/详情、工作看板等位置统一使用,选项来自花名册:
已归档的人不再作为可选项出现,只有他本来就是这条记录的负责人时才带「(已归档)」标注出现一次;
历史数据里花名册没有的人名同样补一个「(不在花名册)」选项,保证打开老项目不会把负责人改没。
多负责人与团队成员复用同一个下拉(peopleEditorHtml,现在只有重点项目的抽屉用它):人名下拉横向排开、自动换行,选项同样只有花名册里的人,所以新增成员不需要另外维护一份名单;名单里的每一位都带 × 可删,界面上不区分主次。定制项目的负责人是普通的单个 ownerSelectHtml 下拉,只有团队成员用这套名单编辑器。
入库时间
定制项目新增「入库时间」字段,当前口径为 该项目最早记录所在周期的第一天(周一):
- 优先取原始《项目明细表》台账中该项目所在的周(该列是稀疏填写的,导入时按周分组向下继承)
- 台账没有的,取《周工作情况总结》「定制开发」块中该项目首次出现的周
- 再没有,依次退回 custom_updates 最早记录周期 → 项目所属周期 → 记录创建日期
周期换算规则:以该月第一个周一所在周为第 1 周,第 N 周顺延 7×(N-1) 天。
例如 2026年8月第4周 → 2026-08-24(2026-08-01 是周六,第一个周一是 08-03)。
字段可在明细表中直接改,也可在抽屉里改;重新计算用:
python scripts/add_entry_date.py --dry-run # 预演
python scripts/add_entry_date.py # 回填(跳过已有值)
python scripts/add_entry_date.py --force # 全部重算
区域(省级行政区)
- 编辑时是规范下拉,34 个省级行政区按大区分组(华北 / 东北 / 华东 / 华中 / 华南 / 西南 / 西北)
- 列表筛选下拉同样使用规范名称,只列出实际有项目的区域
- 历史数据里若出现「内蒙古自治区」这类非规范写法,保存时会自动归一为「内蒙古」
- 早期从项目名推断的区域(
infer_region)与标准列表口径一致,无需迁移
界面设计语言
前端是零构建的原生 JS(web/index.html + web/styles.css + web/app.js + web/icons.js),设计口径固定如下,新增页面请沿用:
- 禁用「彩色描边」:卡片、列表、步骤条、表格一律中性灰边框(
--border/--border-strong)或无边框,不用圆角多色边框表达状态。 - 重点色只出现在三处:图标、关键数字/文本、小面积 tinted 底板(
.ic-badge、.tag-mini、.kpi.accent-*)。状态色(statusMeta)与阶段色(stageMeta)是唯一配色来源,避免各页面自己调色。 - 图标系统:
web/icons.js内置 50 个 24×24 线性 SVG,stroke=currentColor,颜色跟着文字走。对外 API:svg(name)裸图标;badge(name)图标 + tinted 底板(KPI 用);status()/stage()/pipe()按状态·阶段·交付环节取图标;nameOfStatus()/nameOfStage()/statusBadge()出文案与徽标;hydrate()填[data-icon];auto()自动补前导图标;names()列全部图标名。 - 自动图标:
auto()+MutationObserver在每次渲染后按标题关键词给.card-head h4/.sec-title/.kcol-title/.wk-group-head/.notice/.empty加前导图标,并填充[data-icon];用data-ico属性防重复,标题里自带<svg class="ic">(如阶段看板按阶段取图标)时跳过自动补图,不会出现两个图标。新增区块只要标题里带对应关键词就自动带图标,不用手写。 - 阶段看板列头不带色条:列头用
Ico.stage(code)+--c注入阶段色(.kcol-title .ic { color: var(--c) }),早期那种border-top:3px solid 阶段色的彩条已去掉。 - 交付环节图标按
code取:后端CUSTOM_PIPELINE下发的字段是code(不是key),前端一律Ico.pipe(s.code);写成s.key会静默退化成通用圆点。 - 表格防竖排:
table.tbl th/.pill/.tag-mini统一white-space:nowrap,容易挤窄的列(区域、负责人)在<td>上加class="nowrap";实在挤不下交给.tbl-wrap { overflow:auto }横向滚动。 - 抽屉不被内容撑破:
.form-row > div { min-width: 0 }(grid 子项默认min-width:auto,会被交付环节步骤条撑出整表横向滚动),步骤条本身flex:1 1 132px(132px = 日期输入框含日历图标的最小可用宽度)。
备注:
styles.css里的.pcard、.board-grid当前没有任何 JS 引用(历史遗留样式),此处只做记录,未删除。
数据导出
各列表页右上角均有 导出 Excel,导出内容会带上页面当前的筛选条件:
| 位置 | 导出内容 | 接口 |
|---|---|---|
| 定制项目 | 定制项目明细(含区域、金额、进度、收入确认、入库时间、备注 + 合计行) | GET /api/export/custom |
| 定制项目 | 定制项目周期记录 | GET /api/export/custom-updates |
| 项目管理 | 项目管理清单(分类、负责人、状态、标签、子任务数等) | GET /api/export/projects |
| 工作看板 | 本周期(或本月)的执行记录明细 | GET /api/export/updates |
| 每周重点工作 | 重点工作清单(工作内容 / 说明 / 预计完成时间 / 录入周 / 执行状态 / 是否延期 / 结果反馈 / 更新) | GET /api/export/focus |
| 人力资源库 | 花名册(含在管 / 定制项目数、合同额、待计收) | GET /api/export/staff |
导出的 xlsx 带表头样式、冻结首行、自动筛选,金额列按千分位格式;文件名含导出日期。
收入确认口径
定制项目按完工百分比法确认收入:
| 阶段 | 确认比例 |
|---|---|
| 商机/报备 | 0% |
| 已签单/下单 | 10% |
| 开发中 | 10% + 40% × 开发进度 |
| 交付完成 | 70% |
| 已验收 | 90% |
| 已计收 | 100% |
| 暂停/异常 | 30% |
| 已关闭 | 100% |
- 可确认收入 = 合同额 × 上表比例
- 待计收 = 可确认收入 − 已计收
- 待开票 = 合同额 − 已开票、待回款 = 合同额 − 已回款(均不小于 0,随开票 / 回款金额自动更新)
- 开票与回款金额在项目详情抽屉、定制项目详情抽屉、工作看板右栏都能直接填
- 项目上可填「手动确认比例」覆盖阶段推算(用于预付、分批收款等特殊情形)
- 比例定义见
app/constants.py::CUSTOM_STAGES与custom_revenue_ratio(),改一处即可全局生效
数据模型
# 项目管理(长期重点项目)
Period(周期: 年/月/周, sort_key)
└── Project(分类, 负责人 owners[0], 其他负责人, 团队成员, 状态, 优先级, 重点标记, 进度, 标签)
├── Task(子任务)
└── Update(执行情况: 正文, 摘要, 进度, 状态, 风险, 下一步, 来源, 原文)
# 定制项目(独立)
CustomProject(名称, 客户, 区域, 合同额, 阶段, 开发进度, 手动确认比例,
已计收金额, 交付负责人(单人), 团队成员, 入库/签单/交付/验收/计收日期, 开票/回款金额, 重点, 归档)
# 定制项目不做多负责人:other_owners 列保留在库里但不再读写
└── CustomUpdate(周期, 阶段, 内容, 进度, 本期新增下单, 本期新增计收, 风险, 下一步)
# 每周重点工作(独立:条目不是项目,不挂子任务与执行记录)
WeeklyFocus(工作内容, 说明, 录入周→Period, 预计完成时间, 执行状态, 是否延期, 结果反馈, 归档)
Staff(姓名, 归属小组, 人员归属, 说明, 排序, 归档) # 花名册:所有「负责人 / 团队成员」下拉的唯一来源
Category / Tag / Setting / ImportLog
LedgerItem(客户项目台账) # 只剩模型与 Excel 导入解析,界面无入口、接口已下线
原表映射关系:
周工作情况总结:时间 → Period;工作内容 → Project;内容说明 → Task;完成情况 → Update (三列均为稀疏填充,导入时逐级向下继承;仅填「完成情况」的行作为上一条记录的续行合并)- 原「定制开发」的 92 个子任务 →
custom_projects(名称尾部的金额如「(39,400.00)」会被解析成合同额) 项目明细表39 条台账 →custom_projects(区域 / 金额 / 类别→阶段 / 状态说明),与子任务同名时自动合并
AI 整理
两套体系各有独立提示词:
-
项目管理(常规执行记录):抽取 摘要 / 进度 / 状态 / 子任务列表 / 风险 / 下一步
-
定制项目类:抽取 商务阶段 / 开发进度 / 合同额 / 累计计收 / 本期新增计收 / 摘要 / 风险 / 下一步 / 本期明细
-
每周重点工作不过 AI:在独立面板里直接填(工作内容 / 说明 / 预计完成时间填日历日期 / 执行状态)
-
默认走 OpenAI 兼容协议,支持 DeepSeek、通义千问(兼容模式)、Moonshot、智谱、OpenAI、本地 Ollama 等。
- Ollama 示例:Base URL
http://localhost:11434/v1,模型qwen2.5:14b,Key 任意填。
- Ollama 示例:Base URL
-
未配置 API Key 时自动降级为本地规则解析:按编号切分事项、正则提取百分比与金额、 关键词判定阶段 —— 无需 Key 也能完成录入。
- 阶段判定做了特殊处理:文本里同时出现「已计收」和「进度 60%」时判为开发中,计收金额由
revenue_delta承载。
- 阶段判定做了特殊处理:文本里同时出现「已计收」和「进度 60%」时判为开发中,计收金额由
-
解析结果写入前始终提供可编辑预览,确认后才落库,并留痕到
import_logs。
迁移到 MySQL
- 安装驱动:
pip install pymysql cryptography - 修改
.env:DATABASE_URL=mysql+pymysql://user:password@127.0.0.1:3306/unis_board?charset=utf8mb4 - 重启服务(表结构自动创建),再依次执行
import_excel.py --reset与migrate_custom.py。
数据访问层全部使用 SQLAlchemy ORM,无原生 SQL、无 SQLite 专有语法,切换方言无需改业务代码。
目录结构
app/
main.py FastAPI 入口(API 挂载在 /api,静态页面在 /)
config.py 配置与 .env 加载(DATABASE_URL、AI 参数)
db.py engine / Session(按 URL 自动适配方言)
models.py ORM 模型(项目 + 定制项目 + 每周重点工作 + 花名册)
constants.py 状态/阶段常量、收入确认比例、区域与阶段识别
ai_parser.py LLM 解析 + 本地规则兜底(通用 / 定制两套)
routers/ board(周期/元数据/统计) · weekly(工作看板数据) · projects(项目管理) ·
custom(定制项目) · focus(每周重点工作) · staff(人力) · ai · export
web/ index.html · styles.css · app.js(零依赖,无构建步骤)
scripts/ init_db.py · import_excel.py · migrate_custom.py · import_staff.py · add_entry_date.py · merge_project.py · shift_year.py
data/board.db SQLite 数据库
Dockerfile 运行镜像(依赖 + 代码,启动时自动补表/预置分类)
docker-compose.yml 部署编排(./data 持久化、HOST_PORT、健康检查)
deploy.sh 部署命令封装:up / update / restart / down / logs / status
.dockerignore 排除 data/ 与 .env,不把数据和密钥打进镜像
API 速览
# 通用
GET /api/meta 状态字典 / 优先级 / AI 配置状态
GET /api/periods 周列表 + 按月聚合(含各周期记录数;today_id = 今天所在的周,顶栏默认落它)
POST /api/periods 新增周期(传 label 如「9月第1周」,年份自动接续)
DELETE /api/periods/{id} 删除空周期(有记录则拒绝)
GET /api/board?period_id=|year&month 周期汇总(页面主链路不走它,统计走 /api/stats)
GET /api/stats?period_id=|year&month
GET /api/weekly?period_id=|year&month&owner=&all_custom= 工作看板:重点项目 + 定制项目本周动态 + KPI
(custom_new 本期新入库 / custom_updated 本期有交付记录;行上带 is_new)
(左栏第一组「本周工作重点」前端另取 /api/focus 合并)
GET /api/projects (?archived=false|true|all)
POST /api/projects PATCH /api/projects/{id} DELETE /api/projects/{id}
(owners/members 传人名数组,整体替换:不传=不改,传 []=清空;owners[0] 回写 owner 列供归属统计,界面不再区分主次)
GET /api/categories POST /api/categories PATCH /api/categories/{id} DELETE /api/categories/{id}
# 定制项目
GET /api/custom/meta 阶段字典 + 区域列表 + 交付环节(pipeline) + 卡点阈值(aging_days)
GET /api/custom/projects?stage=®ion=&keyword=
POST /api/custom/projects PATCH /api/custom/projects/{id} DELETE /api/custom/projects/{id}
(owner 是单个人名;members 传人名数组,整体替换:不传=不改,传 []=清空。不再接受 owners,多负责人只属于重点项目)
GET /api/custom/board?period_id=|year&month 阶段看板
GET /api/custom/stats?period_id=|year&month 收入统计
POST /api/custom/updates
POST /api/custom/ai/parse 粘贴文本 → 结构化预览
POST /api/custom/ai/commit 确认写入
# 每周重点工作
GET /api/focus?archived=false|true|all&status=&keyword= 条目清单(period_id/period_label/sort_key 都是**录入周**,分堆由调用方判定)
POST /api/focus PATCH /api/focus/{id} DELETE /api/focus/{id}
单条增改删;工作内容非空。period_id=录入周(前端传顶栏当前周,
不传后端按今天所在的周补;due_date 只是目标日期,不改归属周)
或直接给 period_id(必须存在);两者都不给 → 400
# 人力资源库
GET /api/staff?archived=false|true|all&group=&keyword= 花名册 + 各自工作量 + 花名册外负责人 + 小组/归属字典
POST /api/staff PATCH /api/staff/{id} DELETE /api/staff/{id}(名下有项目时拒绝,离职请归档)
# 其他
GET /api/settings PUT /api/settings
# 导出(均返回 xlsx)
GET /api/export/custom ?region=&stage=&keyword=&archived=
GET /api/export/custom-updates ?period_id=|year&month
GET /api/export/projects ?category_id=&keyword=&archived=
GET /api/export/staff ?archived=false|true|all
GET /api/export/focus ?archived=false|true|all&status=&keyword=
GET /api/export/updates ?period_id=|year&month&project_id=