Files
DadaoTask/android
Steven ed31af3f46
CI / backend (push) Canceled after 0s
feat(knowledge): 知识改成备忘录形态,一次写全 + 三级分类,抽屉加宽
知识以前只能"填个标题就完成",正文、标签、归类都要点进去再补。现在新建
就是一张完整的备忘录:标题、类型、分类、标签、正文一次写完,存完直接打开
详情继续编辑;知识页左侧多了工作区三级分类树,可按分类子树 / 未分类 / 标签
筛选。凡是填信息的抽屉都加宽了,还能一键展开整屏。

后端
- 新增 `knowledge_category`(迁移 0007):`workspace_id NOT NULL` + 自引用
  `parent_id`、`depth` 1..3 检查约束、同层重名唯一索引(lower(name))、
  RLS 策略与 `dadao_app` 授权;`knowledge_doc.category_id` 外键 ON DELETE
  SET NULL。
- 新增 `/knowledge/categories` 增删改查:超三级 `7007`、同层重名 `7008`、
  跨租户或不存在 `7006`;`move=true` 才改父级,成环与移动后超过三级的子树
  一律拒绝,删除有知识的分类要 `force=true`(知识变未分类)。
- 知识列表支持 `category_id`(含子树)、`uncategorized`、`tag`;输出带
  `category`(含 `path` / `full_path`)。
- PATCH 只改分类或标签也会推进 `version`,避免"只动标签"之后下次保存撞
  乐观锁 `4009`。

Web
- `Sheet` 增 `size="md/lg/xl"` 与展开整屏按钮;任务、工单、知识抽屉统一
  加宽(`TaskDetail` / `Tickets` / `Knowledge`)。
- 知识页:左侧三级分类树(新建、按子树筛选、未分类)、`KnowledgeComposer`
  一次写全新的备忘录、列表卡片露出分类路径与标签、详情抽屉里可直接改分类
  与标签。
- 抽屉详情只拉一次:重复的 effect 或迟到的响应不再覆盖用户正在敲的内容。

安卓
- `Api.kt`:分类模型与接口、新建/更新带 `tag_names`/`category_id`、
  列表带分类与标签筛选。
- 知识页重做:新建改为近全屏大对话框(标题/类型/分类/标签/正文),列表
  顶部按分类筛选,详情里能改分类与标签后单独保存。

文档与测试
- `2.Design/API.md`:5.1/5.3/5.4 补分类与筛选参数,新增 5.5 分类接口章节,
  错误码补 `7006`/`7007`/`7008`。
- `2.Design/ROADMAP-M0-M1.md`:新增 3.11(三级分类)、3.12(备忘录形态)。
- 新增 `backend/tests/test_knowledge_categories.py`(8 例):备忘形态落库、
  三级到底、移动越界与成环、重名、按子树/未分类/标签/关键词筛选、删分类需
  `force`、跨租户隔离。
- 新增 `web/e2e/knowledge-notebook.mjs`(10 项,全绿):建两级分类 → 一次
  写全 → 卡片与筛选 → 抽屉改标签/分类 → 正文落库 → 重名 7008。

验证:`pytest` 93 passed、`ruff check` / `ruff format --check` 全过、
`tsc --noEmit` 通过、`vite build` 通过、`knowledge-notebook.mjs` 10 项全绿。
安卓端本机只有 JDK 25,Gradle 8.7 / AGP 8.7.3 不支持,本次未做编译验证。
2026-10-30 03:51:43 +08:00
..

安卓端

Kotlin + Jetpack Compose。功能与 Web 端同构:登录 → 工作区 → 任务 / 工单 / 知识。

它已经能编译出 APK 了(本机实测)

BUILD SUCCESSFUL in 38s
app/build/outputs/apk/debug/app-debug.apk   9.94 MB

而且真的在模拟器上跑通了:登录 → 建工作区 → 任务列表(中文标题、状态、截止日、勾选完成)→ 工单列表 → 知识列表,全部打到真实后端。

登录 任务列表
登录 任务

一键起模拟器 + 装包(环境变量都替你设好了):

pwsh -File android/scripts/emulator.ps1 -Install

本机从零把编译链搭起来踩过的坑都固化在仓库里了,别再走一遍:

现象 原因 仓库里的解法
Failed to load native library 'native-platform.dll' 旧 ~/.gradle 缓存坏了 用独立 GRADLE_USER_HOME(我用的 .local/gradle-home)
validateSigningDebug: AccessDeniedException debug.keystore.lock 沙箱/受限用户对 ~/.android 无写权限 工程内 keystore:android/keystore/debug.keystore,app/build.gradle.kts 显式指向
compileDebugKotlin: AccessDeniedException %TEMP%\kotlin-compiler-*.alive %TEMP% 不可写 构建前把 TEMP/TMP 指到工程内可写目录
Could not connect to Kotlin compile daemon 受限环境里 Kotlin 编译守护进程连不回来 gradle.properties 里 kotlin.compiler.execution.strategy=in-process
依赖下载极慢(5 分钟 2MB) Google Maven 直连慢 settings.gradle.kts 先走阿里云镜像,再回落 google/mavenCentral

需要:JDK 17(JAVA_HOME)、Android SDK platform 35 + build-tools。

cd android
gradle :app:assembleDebug          # 或 ./gradlew(wrapper 需 Android Studio 补齐)

连不上后端?先看这三件事

  1. 后端要监听所有网卡:uvicorn app.main:app --host 0.0.0.0 --port 8100。 默认的 127.0.0.1 只有本机可见,手机连不上。
  2. 地址要填对:包里的默认值是模拟器地址 http://10.0.2.2:8100。 真机在登录页点「改地址」填电脑的局域网 IP(如 http://192.168.8.151:8100), 点「测试连接」看到「连得通」再登录。改一次会记住,不用重新打包。
  3. 防火墙:Windows 没弹过「允许访问」的话,手机包会被丢。管理员 PowerShell 执行 New-NetFirewallRule -DisplayName "DadaoTask backend 8100" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 8100 -Profile Private。

还有一个容易查半天的点:主清单配了 network_security_config(白名单只有 10.0.2.2/127.0.0.1/localhost),这种情况下 android:usesCleartextTraffic="true" 不生效, 真机连 http://192.168.8.x 会被系统拦成 "CLEARTEXT communication not permitted", 表现就是"点了登录没反应/登录不上去",而模拟器一切正常。 所以 android/app/src/debug/res/xml/network_security_config.xml 单独给 debug 放开了明文, release 依旧只走 HTTPS 白名单。

连哪个后端

android/local.properties(不入库):

dadao.apiBase=http://10.0.2.2:8100      # 模拟器访问宿主机
# dadao.apiBase=http://192.168.8.160:8100   # 真机走局域网

真机调试时后端必须监听 0.0.0.0 而不是 127.0.0.1:

uvicorn app.main:app --host 0.0.0.0 --port 8100

app/src/debug/AndroidManifest.xml 对 debug 变体放开了明文 HTTP—— release 仍然只允许 network_security_config.xml 里列出的域,别把这条挪到 main。

安装到手机

adb install -r app/build/outputs/apk/debug/app-debug.apk

首次打开 → 手机号登录(开发环境验证码在接口响应里回显,形如 开发环境验证码:123456)。

结构

app/src/main/java/com/dadao/task/
├── MainActivity.kt          入口 + Splash/Login/Home/Tasks/Knowledge 路由
├── data/
│   ├── Api.kt               OkHttp + kotlinx.serialization;信封解包、业务码抛 ApiException
│   └── TokenStore.kt        令牌本地存储(TODO:换 EncryptedSharedPreferences)
└── ui/
    ├── Theme.kt             与 Web 一致的墨青 + 陶土色板
    ├── LoginScreen.kt       手机号 + 验证码
    ├── HomeScreen.kt        工作区列表 / 新建 / 生成邀请码
    ├── TaskListScreen.kt    任务视图切换、快速新建、勾选完成、详情与评论
    └── KnowledgeScreen.kt   知识四态 tab、手记、按状态给动作(发布需 owner/admin)

模拟器(本机搭建记录,别重复踩)

sdkmanager 在这台机器上拉不到远端仓库索引(Failed to download any source lists!,但直连 dl.google.com 的 zip 是通的),所以系统镜像是手动装的:

  1. 下 https://dl.google.com/android/repository/sys-img/google_apis/x86_64-35_r09.zip(1.66 GB), 解压到 %LOCALAPPDATA%\Android\Sdk\system-images\android-35\google_apis\x86_64 —— 注意压缩包里还有一层 x86_64/,要把内容提上一层,否则 AGP 找不到 system.img。
  2. 建 AVD 时把用户目录指到工程内(~/.android 在受限用户下不可写):
    $env:ANDROID_USER_HOME="$repo\.local\android-user"
    $env:ANDROID_AVD_HOME="$repo\.local\android-avd"
    avdmanager create avd -n dadao -k "system-images;android-35;google_apis;x86_64" -d pixel_6 --force
    
  3. 启动必须带 -no-window -gpu swiftshader_indirect(无头环境下 GL 走软件渲染); emulator -accel-check 应显示 WHPX ... installed and usable。

驱动模拟器点按不要凭截图猜坐标——adb shell input tap 用的是物理像素,而截图常被降采样。 用 adb shell uiautomator dump 拿 bounds 最稳。另外 adb shell input text 打不了中文(会 NPE), 中文数据走接口造。

已知待办

  • 工单页还没做(Web 已有):需要"我指派的 / 我发起的"两栏 + 接收/开工/提交/验收按钮; 接口在 Api.kt 里已经齐了(tickets / ticketAction / verifyTicket / promote)。
  • 明文令牌存储(TokenStore 里已标 TODO)。
  • 无离线缓存,弱网直接报错——与产品 D7「在线优先」一致。
  • 邀请链接 https://dadaotask.com/join/<wsId>/<code> 已在 Manifest 注册,落地处理尚未实现。
  • 未在本机跑过模拟器(无 system-image、无连接设备),端到端只验证到"能编译、能装"。