feat(m0): 后端骨架 + Web/安卓前端骨架 + 设计文档

- backend: FastAPI 应用工厂、统一错误信封、JWT、短信登录、工作区/成员/邀请、幂等中间件
- alembic 0001: workspace/member/invitation/audit_log/app_user + RLS 策略 + 双角色 GRANT
- tests: 认证/工作区/租户隔离/幂等 四组用例(尚未在本机执行)
- web: React18+Vite 骨架(登录、工作区、邀请落地页)
- android: Compose 骨架(登录、工作区列表)
- 2.Design: PRODUCT-PLAN / ROADMAP-M0-M1 / NAMING / DEVELOPMENT
This commit is contained in:
Steven
2026-10-28 02:38:53 +08:00
commit 0c53aa7062
72 changed files with 5320 additions and 0 deletions
@@ -0,0 +1,69 @@
package com.dadao.task
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.dadao.task.data.Api
import com.dadao.task.data.TokenStore
import com.dadao.task.ui.DadaoTheme
import com.dadao.task.ui.HomeScreen
import com.dadao.task.ui.LoginScreen
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
DadaoTheme {
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
AppRoot()
}
}
}
}
}
private enum class Screen { Splash, Login, Home }
@Composable
private fun AppRoot() {
val context = androidx.compose.ui.platform.LocalContext.current
val tokens = remember { TokenStore(context) }
val api = remember { Api(tokens) }
var screen by remember { mutableStateOf(if (tokens.isLoggedIn) Screen.Splash else Screen.Login) }
// 有本地令牌就先静默校验一次:令牌过期/被吊销要退回登录页,而不是进了主页再崩。
LaunchedEffect(screen) {
if (screen == Screen.Splash) {
screen = try {
api.me()
Screen.Home
} catch (e: Exception) {
tokens.clear()
Screen.Login
}
}
}
when (screen) {
Screen.Splash -> Unit // 校验中,空白过渡即可
Screen.Login -> LoginScreen(api = api, onLoggedIn = { screen = Screen.Home })
Screen.Home -> HomeScreen(
api = api,
onLogout = {
tokens.clear()
screen = Screen.Login
},
)
}
}