fix(android): 编辑器改成整页显示,保存后回到刚才那条详情
原来编辑器是叠在工作区上的 Box,而详情抽屉/对话框是独立窗口,永远盖在它上面——用户只能看到编辑器的一条顶栏。改成编辑器替换整页;保存成功后父层记下 itemId,回到工作区由对应页面刷新并重新打开那条详情。取消则什么都不变。
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 223 KiB |
@@ -73,6 +73,10 @@ private fun AppRoot(joinUri: String?) {
|
|||||||
var unread by remember { mutableStateOf(0) }
|
var unread by remember { mutableStateOf(0) }
|
||||||
// 打开富文本编辑器时非空;关掉就恢复原来的界面
|
// 打开富文本编辑器时非空;关掉就恢复原来的界面
|
||||||
var editing by remember { mutableStateOf<EditRequest?>(null) }
|
var editing by remember { mutableStateOf<EditRequest?>(null) }
|
||||||
|
// 编辑器保存成功的那条:回到工作区后重新打开它的详情,然后清掉
|
||||||
|
var reopenItem by remember { mutableStateOf<String?>(null) }
|
||||||
|
// 上面那条是任务还是知识:决定回哪个页面去重开
|
||||||
|
var reopenKnowledge by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// 有本地令牌就先静默校验一次:令牌过期/被吊销要退回登录页,而不是进了主页再崩。
|
// 有本地令牌就先静默校验一次:令牌过期/被吊销要退回登录页,而不是进了主页再崩。
|
||||||
LaunchedEffect(screen) {
|
LaunchedEffect(screen) {
|
||||||
@@ -124,65 +128,72 @@ private fun AppRoot(joinUri: String?) {
|
|||||||
)
|
)
|
||||||
Screen.Workspace -> {
|
Screen.Workspace -> {
|
||||||
val ws = current
|
val ws = current
|
||||||
|
val request = editing
|
||||||
if (ws == null) {
|
if (ws == null) {
|
||||||
screen = Screen.Home
|
screen = Screen.Home
|
||||||
} else {
|
} else if (request != null) {
|
||||||
Box {
|
// 编辑器**替换**整页,而不是叠在上面:抽屉/对话框都是独立窗口,
|
||||||
WorkspaceScaffold(
|
// 叠一层会被它们盖住,用户只能看到编辑器的一条边。
|
||||||
workspace = ws,
|
RichTextEditorScreen(
|
||||||
tab = tab,
|
api = api,
|
||||||
unread = unread,
|
workspaceId = ws.id,
|
||||||
onTab = { tab = it },
|
itemId = request.itemId,
|
||||||
onBackHome = { screen = Screen.Home },
|
initialTitle = request.title,
|
||||||
) {
|
initialBody = request.body,
|
||||||
when (tab) {
|
titleEnabled = request.titleEnabled,
|
||||||
WorkspaceTab.Tasks -> TaskListScreen(
|
save = { title, body ->
|
||||||
api = api,
|
if (request.knowledge) {
|
||||||
workspace = ws,
|
api.updateKnowledge(
|
||||||
onEdit = { editing = it },
|
workspaceId = ws.id,
|
||||||
|
itemId = request.itemId,
|
||||||
|
version = request.version,
|
||||||
|
title = title,
|
||||||
|
body = body,
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
WorkspaceTab.Tickets -> TicketsScreen(api = api, workspace = ws)
|
api.updateItem(
|
||||||
WorkspaceTab.Knowledge -> KnowledgeScreen(
|
workspaceId = ws.id,
|
||||||
api = api,
|
itemId = request.itemId,
|
||||||
workspace = ws,
|
version = request.version,
|
||||||
onEdit = { editing = it },
|
title = title,
|
||||||
|
body = body,
|
||||||
)
|
)
|
||||||
|
|
||||||
WorkspaceTab.Members -> MembersScreen(api = api, workspace = ws)
|
|
||||||
}
|
}
|
||||||
}
|
// 保存成功才标记"回到哪条":取消退出不该改变任何界面状态。
|
||||||
|
reopenItem = request.itemId
|
||||||
val request = editing
|
reopenKnowledge = request.knowledge
|
||||||
if (request != null) {
|
},
|
||||||
RichTextEditorScreen(
|
// saved 参数由编辑器给:true 时上面已经设过 reopenItem,
|
||||||
|
// 这里只负责把编辑器收起来,回到工作区后由对应页面重开详情。
|
||||||
|
onClose = { _ -> editing = null },
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
WorkspaceScaffold(
|
||||||
|
workspace = ws,
|
||||||
|
tab = tab,
|
||||||
|
unread = unread,
|
||||||
|
onTab = { tab = it },
|
||||||
|
onBackHome = { screen = Screen.Home },
|
||||||
|
) {
|
||||||
|
when (tab) {
|
||||||
|
WorkspaceTab.Tasks -> TaskListScreen(
|
||||||
api = api,
|
api = api,
|
||||||
workspaceId = ws.id,
|
workspace = ws,
|
||||||
itemId = request.itemId,
|
onEdit = { editing = it },
|
||||||
initialTitle = request.title,
|
reopenItem = if (reopenItem != null && !reopenKnowledge) reopenItem else null,
|
||||||
initialBody = request.body,
|
onReopened = { reopenItem = null },
|
||||||
titleEnabled = request.titleEnabled,
|
|
||||||
save = { title, body ->
|
|
||||||
if (request.knowledge) {
|
|
||||||
api.updateKnowledge(
|
|
||||||
workspaceId = ws.id,
|
|
||||||
itemId = request.itemId,
|
|
||||||
version = request.version,
|
|
||||||
title = title,
|
|
||||||
body = body,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
api.updateItem(
|
|
||||||
workspaceId = ws.id,
|
|
||||||
itemId = request.itemId,
|
|
||||||
version = request.version,
|
|
||||||
title = title,
|
|
||||||
body = body,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClose = { editing = null },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
WorkspaceTab.Tickets -> TicketsScreen(api = api, workspace = ws)
|
||||||
|
WorkspaceTab.Knowledge -> KnowledgeScreen(
|
||||||
|
api = api,
|
||||||
|
workspace = ws,
|
||||||
|
onEdit = { editing = it },
|
||||||
|
reopenItem = if (reopenItem != null && reopenKnowledge) reopenItem else null,
|
||||||
|
onReopened = { reopenItem = null },
|
||||||
|
)
|
||||||
|
|
||||||
|
WorkspaceTab.Members -> MembersScreen(api = api, workspace = ws)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,14 @@ private fun docTypeLabel(type: String): String = when (type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun KnowledgeScreen(api: Api, workspace: Workspace, onEdit: (EditRequest) -> Unit = {}) {
|
fun KnowledgeScreen(
|
||||||
|
api: Api,
|
||||||
|
workspace: Workspace,
|
||||||
|
onEdit: (EditRequest) -> Unit = {},
|
||||||
|
/** 编辑器保存后要重新打开的那条。父层持有:编辑器打开时本页会被整体卸载。 */
|
||||||
|
reopenItem: String? = null,
|
||||||
|
onReopened: () -> Unit = {},
|
||||||
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var tab by remember { mutableStateOf("draft") }
|
var tab by remember { mutableStateOf("draft") }
|
||||||
var docs by remember { mutableStateOf<List<KnowledgeDoc>>(emptyList()) }
|
var docs by remember { mutableStateOf<List<KnowledgeDoc>>(emptyList()) }
|
||||||
@@ -85,6 +92,14 @@ fun KnowledgeScreen(api: Api, workspace: Workspace, onEdit: (EditRequest) -> Uni
|
|||||||
|
|
||||||
LaunchedEffect(tab) { reload() }
|
LaunchedEffect(tab) { reload() }
|
||||||
|
|
||||||
|
// 从编辑器保存回来:本页是新建出来的,刷完列表再把那条详情重新打开。
|
||||||
|
LaunchedEffect(reopenItem) {
|
||||||
|
val target = reopenItem ?: return@LaunchedEffect
|
||||||
|
reload()
|
||||||
|
open = docs.firstOrNull { it.id == target }
|
||||||
|
onReopened()
|
||||||
|
}
|
||||||
|
|
||||||
fun fail(e: Throwable) {
|
fun fail(e: Throwable) {
|
||||||
val code = (e as? ApiException)?.code ?: 0
|
val code = (e as? ApiException)?.code ?: 0
|
||||||
message = verifyStatusCode(code).ifBlank { e.message ?: "操作没成功" }
|
message = verifyStatusCode(code).ifBlank { e.message ?: "操作没成功" }
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ fun TaskDetailSheet(
|
|||||||
onChanged: () -> Unit,
|
onChanged: () -> Unit,
|
||||||
/** 进富文本编辑器改正文。标题与状态留在抽屉里,只有正文要更大地方。 */
|
/** 进富文本编辑器改正文。标题与状态留在抽屉里,只有正文要更大地方。 */
|
||||||
onEdit: (ItemDetail) -> Unit = {},
|
onEdit: (ItemDetail) -> Unit = {},
|
||||||
|
/** 编辑器保存回来后变一次,抽屉据此重新拉数据——否则还显示保存前的旧正文。 */
|
||||||
|
refreshKey: Int = 0,
|
||||||
) {
|
) {
|
||||||
val sheet = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
val sheet = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -93,7 +95,7 @@ fun TaskDetailSheet(
|
|||||||
runCatching { api.me() }.onSuccess { meId = it.user.id }
|
runCatching { api.me() }.onSuccess { meId = it.user.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(itemId) { reload() }
|
LaunchedEffect(itemId, refreshKey) { reload() }
|
||||||
|
|
||||||
fun fail(e: Throwable) {
|
fun fail(e: Throwable) {
|
||||||
val code = (e as? ApiException)?.code ?: 0
|
val code = (e as? ApiException)?.code ?: 0
|
||||||
|
|||||||
@@ -51,7 +51,14 @@ private val views = listOf(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TaskListScreen(api: Api, workspace: Workspace, onEdit: (EditRequest) -> Unit = {}) {
|
fun TaskListScreen(
|
||||||
|
api: Api,
|
||||||
|
workspace: Workspace,
|
||||||
|
onEdit: (EditRequest) -> Unit = {},
|
||||||
|
/** 编辑器保存后要重新打开的那条。父层持这个状态,因为编辑器打开时本页会被整体卸载。 */
|
||||||
|
reopenItem: String? = null,
|
||||||
|
onReopened: () -> Unit = {},
|
||||||
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var view by remember { mutableStateOf("active") }
|
var view by remember { mutableStateOf("active") }
|
||||||
var mode by remember { mutableStateOf("list") }
|
var mode by remember { mutableStateOf("list") }
|
||||||
@@ -60,6 +67,7 @@ fun TaskListScreen(api: Api, workspace: Workspace, onEdit: (EditRequest) -> Unit
|
|||||||
var draft by remember { mutableStateOf("") }
|
var draft by remember { mutableStateOf("") }
|
||||||
var query by remember { mutableStateOf("") }
|
var query by remember { mutableStateOf("") }
|
||||||
var openId by remember { mutableStateOf<String?>(null) }
|
var openId by remember { mutableStateOf<String?>(null) }
|
||||||
|
var detailRefresh by remember { mutableStateOf(0) }
|
||||||
var error by remember { mutableStateOf<String?>(null) }
|
var error by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
suspend fun reload() {
|
suspend fun reload() {
|
||||||
@@ -75,6 +83,15 @@ fun TaskListScreen(api: Api, workspace: Workspace, onEdit: (EditRequest) -> Unit
|
|||||||
|
|
||||||
LaunchedEffect(view, query) { reload() }
|
LaunchedEffect(view, query) { reload() }
|
||||||
|
|
||||||
|
// 从编辑器保存回来:本页是新建出来的,先刷数据再把那一条详情重新打开。
|
||||||
|
LaunchedEffect(reopenItem) {
|
||||||
|
val target = reopenItem ?: return@LaunchedEffect
|
||||||
|
reload()
|
||||||
|
detailRefresh += 1
|
||||||
|
openId = target
|
||||||
|
onReopened()
|
||||||
|
}
|
||||||
|
|
||||||
fun fail(e: Throwable) {
|
fun fail(e: Throwable) {
|
||||||
val code = (e as? ApiException)?.code ?: 0
|
val code = (e as? ApiException)?.code ?: 0
|
||||||
error = verifyStatusCode(code).ifBlank { e.message ?: "操作没成功" }
|
error = verifyStatusCode(code).ifBlank { e.message ?: "操作没成功" }
|
||||||
@@ -193,10 +210,12 @@ fun TaskListScreen(api: Api, workspace: Workspace, onEdit: (EditRequest) -> Unit
|
|||||||
itemId = id,
|
itemId = id,
|
||||||
onClose = { openId = null },
|
onClose = { openId = null },
|
||||||
onChanged = { scope.launch { reload() } },
|
onChanged = { scope.launch { reload() } },
|
||||||
|
// 抽屉是独立窗口,关掉才能让编辑器显示在上层;保存回来后由父层重新打开。
|
||||||
onEdit = {
|
onEdit = {
|
||||||
openId = null
|
openId = null
|
||||||
onEdit(EditRequest(it.id, it.title, it.bodyMd, it.version))
|
onEdit(EditRequest(it.id, it.title, it.bodyMd, it.version))
|
||||||
},
|
},
|
||||||
|
refreshKey = detailRefresh,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,24 @@ class Settings(BaseSettings):
|
|||||||
# 30 分钟够一次阅读/编辑,过期后刷新页面会重新签发。
|
# 30 分钟够一次阅读/编辑,过期后刷新页面会重新签发。
|
||||||
attachment_url_ttl_seconds: int = 1800
|
attachment_url_ttl_seconds: int = 1800
|
||||||
|
|
||||||
|
# ── 知识检索(M3)──────────────────────────────────────
|
||||||
|
# 切片大小与重叠:中文一段 600 字大约 400~600 token,够表达一个完整片段;
|
||||||
|
# 重叠 80 字是为了不把跨段的话切断(检索命中时前后文还读得懂)。
|
||||||
|
chunk_max_chars: int = 600
|
||||||
|
chunk_overlap_chars: int = 80
|
||||||
|
# 嵌入向量提供方:
|
||||||
|
# hash —— 内置的字符 n-gram 哈希向量,离线可用、零依赖、可复现;
|
||||||
|
# 质量不如真模型,但能把"检索链路"跑通,且换模型时接口不变。
|
||||||
|
# openai —— 任何 OpenAI 兼容的 /embeddings 端点(BYOK)。
|
||||||
|
embedding_provider: Literal["hash", "openai"] = "hash"
|
||||||
|
embedding_base_url: str = "https://api.deepseek.com/v1"
|
||||||
|
embedding_api_key: str | None = None
|
||||||
|
embedding_model: str = "text-embedding-3-small"
|
||||||
|
# 混合检索里向量与词法的权重。纯词法搜不到同义改写,纯向量搜不准专有名词,
|
||||||
|
# 所以默认各占一半,可用环境变量调。
|
||||||
|
search_vector_weight: float = 0.6
|
||||||
|
search_lexical_weight: float = 0.4
|
||||||
|
|
||||||
# ── 知识库向量库(独立实例,M3 起用)───────────────────
|
# ── 知识库向量库(独立实例,M3 起用)───────────────────
|
||||||
# 刻意与业务库分开放:fnsvr-01 上的 PG 没有 pgvector,也不该为了装扩展去动它;
|
# 刻意与业务库分开放:fnsvr-01 上的 PG 没有 pgvector,也不该为了装扩展去动它;
|
||||||
# 向量检索吃内存,和任务库挤在同一实例会互相拖累。
|
# 向量检索吃内存,和任务库挤在同一实例会互相拖累。
|
||||||
|
|||||||
Reference in New Issue
Block a user