Files
mnote/wolai-frontend/convex/tables.ts
T
2026-01-18 05:13:53 +08:00

290 lines
7.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { mutation, query } from "./_generated/server";
import { v } from "convex/values";
import { nowIso } from "./_utils/time";
import { generateId } from "./_utils/id";
// Query: 获取单个表格
export const get = query({
args: { userId: v.string(), tableId: v.string() },
handler: async (ctx, args) => {
const table = await ctx.db
.query("document_tables")
.withIndex("by_table_id", (q) => q.eq("id", args.tableId))
.first();
if (!table) return null;
if (table.workspace_id) {
// 验证用户是否在工作区中(简化版:假设 userId 有效)
// TODO: 添加 workspace_members 验证
}
return {
id: table.id,
workspace_id: table.workspace_id,
document_id: table.document_id,
grid_key: table.grid_key,
title: table.title,
schema: table.schema,
view_preferences: table.view_preferences,
snapshot: table.snapshot ?? null,
is_archived: table.is_archived,
last_synced_at: table.last_synced_at ?? null,
created_by: table.created_by,
updated_by: table.updated_by ?? null,
created_at: table.created_at,
updated_at: table.updated_at,
};
},
});
// Query: 通过 gridKey 获取表格(用于 Luckysheet get-workerbook
export const getByGridKey = query({
args: { gridKey: v.string() },
handler: async (ctx, args) => {
const table = await ctx.db
.query("document_tables")
.withIndex("by_grid_key", (q) => q.eq("grid_key", args.gridKey))
.first();
if (!table) return null;
// 返回 Luckysheet 需要的格式
return {
title: table.title,
gridKey: table.grid_key,
lang: "zh",
};
},
});
// Query: 通过 gridKey 获取完整表格数据(用于 Luckysheet load
export const getByGridKeyFull = query({
args: { gridKey: v.string() },
handler: async (ctx, args) => {
const table = await ctx.db
.query("document_tables")
.withIndex("by_grid_key", (q) => q.eq("grid_key", args.gridKey))
.first();
if (!table) return null;
// 返回完整的表格数据,包括 schema 和 snapshot
return {
id: table.id,
grid_key: table.grid_key,
title: table.title,
schema: table.schema,
snapshot: table.snapshot ?? null,
};
},
});
// Query: 列出文档的所有表格
export const listByDocument = query({
args: { userId: v.string(), documentId: v.string() },
handler: async (ctx, args) => {
const tables = await ctx.db
.query("document_tables")
.withIndex("by_document", (q) => q.eq("document_id", args.documentId))
.collect();
return tables
.filter((t) => !t.is_archived)
.map((t) => ({
id: t.id,
document_id: t.document_id,
grid_key: t.grid_key,
title: t.title,
schema: t.schema,
view_preferences: t.view_preferences,
snapshot: t.snapshot ?? null,
is_archived: t.is_archived,
last_synced_at: t.last_synced_at ?? null,
created_at: t.created_at,
updated_at: t.updated_at,
}));
},
});
// Mutation: 创建表格
export const create = mutation({
args: {
userId: v.string(),
workspaceId: v.string(),
documentId: v.string(),
title: v.optional(v.string()),
schema: v.optional(v.any()),
snapshot: v.optional(v.any()),
},
handler: async (ctx, args) => {
const now = nowIso();
const tableId = generateId();
const gridKey = generateId(); // 用于 WebSocket 协同标识
const table = {
id: tableId,
workspace_id: args.workspaceId,
document_id: args.documentId,
grid_key: gridKey,
title: args.title ?? "未命名表格",
schema: args.schema ?? {},
view_preferences: {},
snapshot: args.snapshot ?? null,
is_archived: false,
last_synced_at: now,
created_by: args.userId,
updated_by: args.userId,
created_at: now,
updated_at: now,
};
await ctx.db.insert("document_tables", table);
return {
id: table.id,
grid_key: table.grid_key,
title: table.title,
schema: table.schema,
snapshot: table.snapshot,
};
},
});
// Mutation: 更新表格
export const update = mutation({
args: {
userId: v.string(),
tableId: v.string(),
title: v.optional(v.string()),
schema: v.optional(v.any()),
view_preferences: v.optional(v.any()),
snapshot: v.optional(v.any()),
rows: v.optional(v.any()),
},
handler: async (ctx, args) => {
const table = await ctx.db
.query("document_tables")
.withIndex("by_table_id", (q) => q.eq("id", args.tableId))
.first();
if (!table) {
throw new Error("Table not found");
}
const now = nowIso();
const updates: any = {
updated_at: now,
updated_by: args.userId,
last_synced_at: now,
};
if (args.title !== undefined) updates.title = args.title;
if (args.schema !== undefined) updates.schema = args.schema;
if (args.view_preferences !== undefined) updates.view_preferences = args.view_preferences;
if (args.snapshot !== undefined) updates.snapshot = args.snapshot;
await ctx.db.patch(table._id, updates);
// 如果提供了 rows,更新行数据
if (args.rows && Array.isArray(args.rows)) {
// 先删除旧行
const existingRows = await ctx.db
.query("document_table_rows")
.withIndex("by_table", (q) => q.eq("table_id", args.tableId))
.collect();
for (const row of existingRows) {
await ctx.db.delete(row._id);
}
// 插入新行
for (const [index, rowData] of args.rows.entries()) {
await ctx.db.insert("document_table_rows", {
id: generateId(),
workspace_id: table.workspace_id,
document_id: table.document_id,
table_id: args.tableId,
row_index: index,
row_data: rowData,
row_hash: JSON.stringify(rowData),
is_deleted: false,
updated_by: args.userId,
created_at: now,
updated_at: now,
});
}
}
return {
id: table.id,
title: updates.title ?? table.title,
schema: updates.schema ?? table.schema,
view_preferences: updates.view_preferences ?? table.view_preferences,
snapshot: updates.snapshot ?? table.snapshot,
updated_at: now,
};
},
});
// Mutation: 删除表格(软删除)
export const remove = mutation({
args: { userId: v.string(), tableId: v.string() },
handler: async (ctx, args) => {
const table = await ctx.db
.query("document_tables")
.withIndex("by_table_id", (q) => q.eq("id", args.tableId))
.first();
if (!table) {
throw new Error("Table not found");
}
await ctx.db.patch(table._id, {
is_archived: true,
updated_at: nowIso(),
updated_by: args.userId,
});
return { success: true };
},
});
// Mutation: 永久删除表格
export const purge = mutation({
args: { userId: v.string(), tableId: v.string() },
handler: async (ctx, args) => {
const table = await ctx.db
.query("document_tables")
.withIndex("by_table_id", (q) => q.eq("id", args.tableId))
.first();
if (!table) {
throw new Error("Table not found");
}
// 删除关联的行
const rows = await ctx.db
.query("document_table_rows")
.withIndex("by_table", (q) => q.eq("table_id", args.tableId))
.collect();
for (const row of rows) {
await ctx.db.delete(row._id);
}
// 删除表格
await ctx.db.delete(table._id);
return { success: true };
},
});
// Query: 获取表格行数据
export const getRows = query({
args: { tableId: v.string() },
handler: async (ctx, args) => {
const rows = await ctx.db
.query("document_table_rows")
.withIndex("by_table", (q) => q.eq("table_id", args.tableId))
.collect();
return rows
.filter((r) => !r.is_deleted)
.sort((a, b) => a.row_index - b.row_index)
.map((r) => r.row_data);
},
});