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:
@@ -400,6 +400,16 @@ export async function ensureSchema(database?: DatabaseClient) {
|
||||
"join_qr_code",
|
||||
"join_qr_code TEXT NOT NULL DEFAULT ''",
|
||||
);
|
||||
await ensureColumn(
|
||||
"tasks",
|
||||
"related_task_id",
|
||||
"related_task_id TEXT NOT NULL DEFAULT ''",
|
||||
);
|
||||
await ensureColumn(
|
||||
"contents",
|
||||
"related_content_id",
|
||||
"related_content_id TEXT NOT NULL DEFAULT ''",
|
||||
);
|
||||
|
||||
const tasksWithoutShare = await db
|
||||
.prepare(
|
||||
@@ -555,8 +565,8 @@ export async function seedIfEmpty() {
|
||||
db
|
||||
.prepare(
|
||||
`INSERT INTO tasks
|
||||
(id, name, brand, quantity, claimed_quantity, due_at, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
(id, name, brand, quantity, claimed_quantity, due_at, status, join_qr_code)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, '')`,
|
||||
)
|
||||
.bind(
|
||||
"task-summer",
|
||||
@@ -570,8 +580,8 @@ export async function seedIfEmpty() {
|
||||
db
|
||||
.prepare(
|
||||
`INSERT INTO tasks
|
||||
(id, name, brand, quantity, claimed_quantity, due_at, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
(id, name, brand, quantity, claimed_quantity, due_at, status, join_qr_code)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, '')`,
|
||||
)
|
||||
.bind(
|
||||
"task-living",
|
||||
@@ -853,11 +863,24 @@ export async function getDashboardData(ownerUserid?: string) {
|
||||
});
|
||||
const tasksBase = `SELECT t.*,
|
||||
(SELECT c.title FROM contents c WHERE c.task_id = t.id ORDER BY c.source_row LIMIT 1) AS keyword,
|
||||
(SELECT c.body FROM contents c WHERE c.task_id = t.id ORDER BY c.source_row LIMIT 1) AS instructions
|
||||
FROM tasks t`;
|
||||
(SELECT c.body FROM contents c WHERE c.task_id = t.id ORDER BY c.source_row LIMIT 1) AS instructions,
|
||||
(SELECT COUNT(*) FROM distributions d WHERE d.task_id = t.id) AS dist_total,
|
||||
(SELECT COUNT(*) FROM distributions d WHERE d.task_id = t.id AND d.status = 'complete') AS dist_complete,
|
||||
(SELECT COUNT(*) FROM distributions d WHERE d.task_id = t.id AND d.result_submitted_at IS NOT NULL) AS dist_submitted,
|
||||
(SELECT rt.name FROM tasks rt WHERE rt.id = t.related_task_id) AS related_task_name,
|
||||
(SELECT COUNT(*) FROM distributions d WHERE d.task_id = t.related_task_id) AS related_dist_total,
|
||||
(SELECT COUNT(*) FROM distributions d WHERE d.task_id = t.related_task_id AND d.status = 'complete') AS related_dist_complete,
|
||||
(SELECT rt2.id FROM tasks rt2 WHERE rt2.related_task_id = t.id LIMIT 1) AS reverse_related_task_id,
|
||||
(SELECT rt2.name FROM tasks rt2 WHERE rt2.related_task_id = t.id LIMIT 1) AS reverse_related_task_name,
|
||||
(SELECT COUNT(*) FROM distributions d WHERE d.task_id = (SELECT rt2.id FROM tasks rt2 WHERE rt2.related_task_id = t.id LIMIT 1)) AS reverse_dist_total,
|
||||
(SELECT COUNT(*) FROM distributions d WHERE d.task_id = (SELECT rt2.id FROM tasks rt2 WHERE rt2.related_task_id = t.id LIMIT 1) AND d.result_submitted_at IS NOT NULL) AS reverse_dist_submitted
|
||||
FROM tasks t
|
||||
LEFT JOIN projects pj ON pj.id = t.project_id`;
|
||||
const distributionsBase = `SELECT
|
||||
d.*,
|
||||
c.title AS content_title,
|
||||
c.related_content_id AS related_content_id,
|
||||
rc.title AS related_content_title,
|
||||
p.name AS partner_name,
|
||||
cl.claimant_name AS claimant_name,
|
||||
a.nickname AS account_nickname,
|
||||
@@ -870,9 +893,11 @@ export async function getDashboardData(ownerUserid?: string) {
|
||||
t.due_at AS due_at
|
||||
FROM distributions d
|
||||
JOIN contents c ON c.id = d.content_id
|
||||
LEFT JOIN contents rc ON rc.id = c.related_content_id
|
||||
JOIN partners p ON p.id = d.partner_id
|
||||
LEFT JOIN claims cl ON cl.id = d.claim_id
|
||||
JOIN tasks t ON t.id = d.task_id
|
||||
LEFT JOIN projects pj ON pj.id = t.project_id
|
||||
LEFT JOIN accounts a ON a.id = d.account_id`;
|
||||
const [partnersResult, tasksResult, accountsResult, distributionsResult] =
|
||||
await Promise.all([
|
||||
@@ -882,9 +907,9 @@ export async function getDashboardData(ownerUserid?: string) {
|
||||
ownerFilter
|
||||
? db
|
||||
.prepare(
|
||||
`${tasksBase} WHERE t.owner_userid = ? ORDER BY t.created_at DESC`,
|
||||
`${tasksBase} WHERE (t.owner_userid = ? OR (t.project_id != '' AND pj.owner_userid = ?)) ORDER BY t.created_at DESC`,
|
||||
)
|
||||
.bind(ownerFilter)
|
||||
.bind(ownerFilter, ownerFilter)
|
||||
.all()
|
||||
: ownerFilter === null
|
||||
? db.prepare(`${tasksBase} ORDER BY t.created_at DESC`).all()
|
||||
@@ -913,9 +938,9 @@ export async function getDashboardData(ownerUserid?: string) {
|
||||
ownerFilter
|
||||
? db
|
||||
.prepare(
|
||||
`${distributionsBase} WHERE t.owner_userid = ? ORDER BY d.updated_at DESC, d.claimed_at DESC`,
|
||||
`${distributionsBase} WHERE (t.owner_userid = ? OR (t.project_id != '' AND pj.owner_userid = ?)) ORDER BY d.updated_at DESC, d.claimed_at DESC`,
|
||||
)
|
||||
.bind(ownerFilter)
|
||||
.bind(ownerFilter, ownerFilter)
|
||||
.all()
|
||||
: ownerFilter === null
|
||||
? db
|
||||
@@ -964,6 +989,11 @@ export type TaskReportRow = {
|
||||
latest_comments: number | null;
|
||||
latest_collects: number | null;
|
||||
latest_shares: number | null;
|
||||
related_task_id: string | null;
|
||||
related_task_name: string | null;
|
||||
reverse_related_task_id: string | null;
|
||||
reverse_related_task_name: string | null;
|
||||
submitted_count: number;
|
||||
};
|
||||
|
||||
export async function getTaskReportData(db: DatabaseClient, ownerUserid?: string) {
|
||||
@@ -982,10 +1012,15 @@ export async function getTaskReportData(db: DatabaseClient, ownerUserid?: string
|
||||
t.due_at,
|
||||
t.created_at,
|
||||
t.quantity,
|
||||
t.related_task_id,
|
||||
rt.name AS related_task_name,
|
||||
(SELECT rrt.id FROM tasks rrt WHERE rrt.related_task_id = t.id LIMIT 1) AS reverse_related_task_id,
|
||||
(SELECT rrt.name FROM tasks rrt WHERE rrt.related_task_id = t.id LIMIT 1) AS reverse_related_task_name,
|
||||
p.name AS project_name,
|
||||
COUNT(d.id) AS claimed_count,
|
||||
SUM(CASE WHEN TRIM(COALESCE(d.publish_url, '')) != '' THEN 1 ELSE 0 END) AS published_count,
|
||||
SUM(CASE WHEN d.status = 'complete' THEN 1 ELSE 0 END) AS completed_count,
|
||||
SUM(CASE WHEN d.result_submitted_at IS NOT NULL THEN 1 ELSE 0 END) AS submitted_count,
|
||||
SUM(d.exposure) AS exposure_total,
|
||||
SUM(d.views) AS views_total,
|
||||
SUM(d.d2_likes) AS d2_likes,
|
||||
@@ -1003,13 +1038,14 @@ export async function getTaskReportData(db: DatabaseClient, ownerUserid?: string
|
||||
SUM(d.latest_shares) AS latest_shares
|
||||
FROM tasks t
|
||||
LEFT JOIN distributions d ON d.task_id = t.id
|
||||
LEFT JOIN projects p ON p.id = t.project_id`;
|
||||
LEFT JOIN projects p ON p.id = t.project_id
|
||||
LEFT JOIN tasks rt ON rt.id = t.related_task_id`;
|
||||
const statement = ownerFilter
|
||||
? db
|
||||
.prepare(
|
||||
`${base} WHERE t.owner_userid = ? GROUP BY t.id ORDER BY t.created_at DESC`,
|
||||
`${base} WHERE (t.owner_userid = ? OR p.owner_userid = ?) GROUP BY t.id ORDER BY t.created_at DESC`,
|
||||
)
|
||||
.bind(ownerFilter)
|
||||
.bind(ownerFilter, ownerFilter)
|
||||
: db.prepare(`${base} GROUP BY t.id ORDER BY t.created_at DESC`);
|
||||
const result = await statement.all<TaskReportRow>();
|
||||
return result.results;
|
||||
|
||||
Reference in New Issue
Block a user