feat: 负责人下钻视图+项目负责人编辑管理+企微授权自助绑定

- 任务/项目负责人 union scope:任务中心分组视图、工作台项目进度卡、收录验证列
- 项目负责人可管理自己项目:编辑/删除项目、编辑任务、分配负责人、推送、项目内建任务;堵死转移归属/越权新建/任务迁移
- 只读依赖放开给已登录:inspect_feishu、wecom_list_internal_contacts
- 企微 OAuth 自助绑定:/api/wecom-bind(state 签名校验+登录态一致性+归属冲突检查),未绑定 banner 增加入口
- 工作台项目进度卡与未绑定 banner 排版修复
- 修 user-auth 判空分支被 Turbopack dev 折叠删除导致匿名请求 500
This commit is contained in:
ABAPPLO
2026-09-17 15:21:24 +08:00
parent dd5b603149
commit 8f1ebf7697
24 changed files with 1812 additions and 213 deletions

View File

@@ -27,10 +27,23 @@ type DueSoonRow = {
wecom_external_user_id: string | null;
task_id: string;
task_name: string;
task_type: string;
due_at: string;
content_title: string;
};
function pendingActionLabel(taskType: string) {
return taskType === "screenshot_collect" ? "截图待提交" : "待发布";
}
function pendingSummary(rows: DueSoonRow[]) {
const shot = rows.filter((row) => row.task_type === "screenshot_collect").length;
const publish = rows.length - shot;
if (shot === 0) return `${rows.length} 条笔记待发布`;
if (publish === 0) return `${rows.length} 份截图待提交`;
return `${publish} 条笔记待发布、${shot} 份截图待提交`;
}
export function computeDueCutoff(today: string, dueDays: number) {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(today);
if (!match) return today;
@@ -60,6 +73,7 @@ export async function runDueSoonWecomNotifications(
p.wecom_external_user_id,
t.id AS task_id,
t.name AS task_name,
t.task_type,
t.due_at,
c.title AS content_title
FROM distributions d
@@ -115,13 +129,13 @@ export async function runDueSoonWecomNotifications(
? [entry.externalUserId]
: [];
const lines = entry.rows.slice(0, 5).map((row) => {
return `· 《${truncate(row.task_name, 24)}》— ${truncate(row.content_title, 24)}(截止 ${row.due_at})`;
return `· 《${truncate(row.task_name, 24)}》— ${truncate(row.content_title, 24)}(${pendingActionLabel(row.task_type)},截止 ${row.due_at})`;
});
const overflow =
entry.rows.length > 5 ? `\n…还有 ${entry.rows.length - 5} 条` : "";
const link = portalUrl ? `\n领取链接:${portalUrl}` : "";
const content =
`${entry.partnerName},你有 ${entry.rows.length} 条内容待发布:\n${lines.join("\n")}${overflow}${link}`;
`${entry.partnerName},你有 ${pendingSummary(entry.rows)}:\n${lines.join("\n")}${overflow}${link}`;
const appResult = await sendWecomAppMessage(
external,
content,
@@ -166,7 +180,7 @@ export async function runDueSoonWecomNotifications(
await sendTaskChatMessage(
db,
groupTaskId,
`【催办】《${truncate(entry.taskName, 24)}》有 ${entry.rows.length} 条内容待发布(截止 ${entry.dueAt}):\n${partners.slice(0, 5).join("、")}${overflow}`,
`【催办】《${truncate(entry.taskName, 24)}》有 ${pendingSummary(entry.rows)}(截止 ${entry.dueAt}):\n${partners.slice(0, 5).join("、")}${overflow}`,
config,
fetchImpl,
);