nolo-cli 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-runtime/agentRecordConfig.ts +4 -0
- package/agent-runtime/hostAdapter.ts +2 -0
- package/agent-runtime/index.ts +7 -0
- package/agent-runtime/localLoop.ts +2 -0
- package/agent-runtime/platformChatProvider.ts +3 -0
- package/agent-runtime/runtimeToolPolicy.ts +92 -0
- package/agent-runtime/types.ts +42 -0
- package/agentRunCommand.ts +74 -1
- package/agentRuntimeCommands.ts +17 -89
- package/ai/agent/streamAgentChatTurn.ts +104 -20
- package/ai/chat/fetchUtils.native.ts +2 -0
- package/ai/chat/fetchUtils.ts +2 -0
- package/ai/chat/sendOpenAICompletionsRequest.ts +56 -0
- package/ai/chat/sendOpenAIResponseRequest.ts +64 -0
- package/ai/llm/kimi.ts +1 -1
- package/ai/llm/providers.ts +3 -0
- package/ai/llm/reasoningModels.ts +1 -0
- package/ai/skills/skillDocProtocol.ts +95 -3
- package/ai/taskRun/taskRunProtocol.ts +1 -0
- package/ai/tools/agent/agentTools.ts +17 -0
- package/ai/tools/agent/startAgentDialogTool.ts +53 -0
- package/ai/tools/modelUsageTools.ts +5 -0
- package/client/agentRun.test.ts +257 -7
- package/client/agentRun.ts +133 -34
- package/client/localRuntimeAdapter.test.ts +2 -0
- package/client/localRuntimeAdapter.ts +15 -2
- package/database/actions/common.ts +4 -3
- package/database/config.ts +19 -0
- package/machineCommands.ts +400 -45
- package/package.json +4 -2
- package/render/canvas/canvasEditContext.ts +127 -0
- package/render/canvas/canvasRuntime.ts +57 -0
- package/render/canvas/canvasSnapshotParser.ts +76 -0
- package/render/canvas/canvasTree.ts +308 -0
- package/render/canvas/types.ts +46 -0
- package/render/layout/deleteBehavior.ts +52 -0
- package/render/layout/mainLayoutSidebar.ts +17 -0
- package/render/layout/mainLayoutViewMode.ts +56 -0
- package/render/layout/topbarUtils.ts +87 -0
- package/render/layout/useDevReloadPending.ts +30 -0
- package/render/page/createPageAction.ts +183 -0
- package/render/page/docSlice.ts +468 -0
- package/render/page/server/createPage.ts +174 -0
- package/render/page/server/handleCreatePage.ts +91 -0
- package/render/page/server/index.ts +4 -0
- package/render/page/types.ts +17 -0
- package/render/page/useKeyboardSave.ts +48 -0
- package/render/styles/zIndex.ts +12 -0
- package/render/surf/WeatherIconStyles.ts +17 -0
- package/render/surf/color.ts +9 -0
- package/render/surf/config.ts +46 -0
- package/render/surf/screens/style.ts +1 -0
- package/render/surf/styles/ToggleButtonStyles.ts +8 -0
- package/render/surf/utils/groupedWeatherData.ts +32 -0
- package/render/surf/weatherUtils.ts +50 -0
- package/render/table/activityColumns.ts +6 -0
- package/render/table/createTableAction.ts +270 -0
- package/render/table/deleteTableAction.ts +129 -0
- package/render/table/fetchAndCacheTableRows.ts +174 -0
- package/render/table/tableSlice.ts +1106 -0
- package/render/table/tableView.ts +289 -0
- package/render/table/toolValueUtils.ts +363 -0
- package/render/table/types.ts +252 -0
- package/render/table/useCreateTable.ts +72 -0
- package/render/table/useTable.ts +61 -0
- package/render/table/utils/tableSerialization.ts +50 -0
- package/render/web/elements/artifactPreviewCode.ts +43 -0
- package/render/web/elements/artifactRuntimePreload.ts +52 -0
- package/render/web/elements/codeBlockAutoPreview.ts +10 -0
- package/render/web/elements/mermaidPreview.ts +21 -0
- package/render/web/ui/useInlineEdit.ts +135 -0
- package/tableCommands.ts +42 -5
|
@@ -0,0 +1,1106 @@
|
|
|
1
|
+
// 文件: render/table/tableSlice.ts
|
|
2
|
+
|
|
3
|
+
import { formatISO } from "date-fns";
|
|
4
|
+
import {
|
|
5
|
+
asyncThunkCreator,
|
|
6
|
+
buildCreateSlice,
|
|
7
|
+
PayloadAction,
|
|
8
|
+
createSelector,
|
|
9
|
+
} from "@reduxjs/toolkit";
|
|
10
|
+
import { ulid } from "ulid";
|
|
11
|
+
import type { RootState } from "app/store";
|
|
12
|
+
import { getRuntimeServerContext } from "database/runtimeServerContext";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
write,
|
|
16
|
+
readAndWait,
|
|
17
|
+
patch,
|
|
18
|
+
selectAll as selectAllDb,
|
|
19
|
+
upsertSSREntity,
|
|
20
|
+
} from "database/dbSlice";
|
|
21
|
+
import { metaKey, rowKey } from "database/keys";
|
|
22
|
+
import { DataType } from "create/types";
|
|
23
|
+
import { resolveReplicationServers, scheduleWriteReplication } from "database/actions/replication";
|
|
24
|
+
import { fetchAndCacheTableRows } from "./fetchAndCacheTableRows";
|
|
25
|
+
|
|
26
|
+
import type { CreateTableArgs } from "./createTableAction";
|
|
27
|
+
import { createTableAction } from "./createTableAction";
|
|
28
|
+
import { deleteTableAction, type DeleteTableArgs } from "./deleteTableAction";
|
|
29
|
+
import type { TableMeta, TableColumn } from "./types";
|
|
30
|
+
|
|
31
|
+
/* --------------------------------------------------------------------------
|
|
32
|
+
* Slice 状态
|
|
33
|
+
* ------------------------------------------------------------------------*/
|
|
34
|
+
|
|
35
|
+
export interface TableSliceState {
|
|
36
|
+
currentTable: TableMeta | null;
|
|
37
|
+
isLoading: boolean;
|
|
38
|
+
isInitialized: boolean;
|
|
39
|
+
error: string | null;
|
|
40
|
+
|
|
41
|
+
// 当前表的所有行(通过 loadTableRows 填充)
|
|
42
|
+
rows: any[];
|
|
43
|
+
focusContext: {
|
|
44
|
+
rowDbKey: string;
|
|
45
|
+
columnName: string;
|
|
46
|
+
rowIndex: number | null;
|
|
47
|
+
colIndex: number | null;
|
|
48
|
+
rowTitle: string | null;
|
|
49
|
+
cellPreview: string | null;
|
|
50
|
+
isEditing: boolean;
|
|
51
|
+
} | null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* --------------------------------------------------------------------------
|
|
55
|
+
* 初始状态
|
|
56
|
+
* ------------------------------------------------------------------------*/
|
|
57
|
+
|
|
58
|
+
const initialState: TableSliceState = {
|
|
59
|
+
currentTable: null,
|
|
60
|
+
isLoading: false,
|
|
61
|
+
isInitialized: false,
|
|
62
|
+
error: null,
|
|
63
|
+
rows: [],
|
|
64
|
+
focusContext: null,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/* --------------------------------------------------------------------------
|
|
68
|
+
* 工具方法
|
|
69
|
+
* ------------------------------------------------------------------------*/
|
|
70
|
+
|
|
71
|
+
const reorderList = <T,>(list: T[], from: number, to: number): T[] => {
|
|
72
|
+
const next = [...list];
|
|
73
|
+
const [moved] = next.splice(from, 1);
|
|
74
|
+
next.splice(to, 0, moved);
|
|
75
|
+
return next;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/* --------------------------------------------------------------------------
|
|
79
|
+
* thunk-capable createSlice
|
|
80
|
+
* ------------------------------------------------------------------------*/
|
|
81
|
+
|
|
82
|
+
const createSliceWithThunks = buildCreateSlice({
|
|
83
|
+
creators: { asyncThunk: asyncThunkCreator },
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
/* --------------------------------------------------------------------------
|
|
87
|
+
* 其它 thunk 参数类型
|
|
88
|
+
* ------------------------------------------------------------------------*/
|
|
89
|
+
|
|
90
|
+
interface InitTableArgs {
|
|
91
|
+
tenantId: string;
|
|
92
|
+
tableId: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface AddRowArgs {
|
|
96
|
+
tenantId: string;
|
|
97
|
+
tableId: string;
|
|
98
|
+
values: Record<string, any>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface AddColumnArgs {
|
|
102
|
+
tenantId: string;
|
|
103
|
+
tableId: string;
|
|
104
|
+
columnName: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
interface DeleteColumnArgs {
|
|
108
|
+
tenantId: string;
|
|
109
|
+
tableId: string;
|
|
110
|
+
columnName: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
interface RenameColumnArgs {
|
|
114
|
+
tenantId: string;
|
|
115
|
+
tableId: string;
|
|
116
|
+
oldName: string;
|
|
117
|
+
newName: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface RenameColumnLabelArgs {
|
|
121
|
+
tenantId: string;
|
|
122
|
+
tableId: string;
|
|
123
|
+
columnId: string;
|
|
124
|
+
newLabel: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface RenameTableArgs {
|
|
128
|
+
tenantId: string;
|
|
129
|
+
tableId: string; // 路由 / metaKey 中的 tableId,不做变更
|
|
130
|
+
newName: string; // 新的显示名称 displayName
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
interface LoadTableRowsArgs {
|
|
134
|
+
tenantId: string;
|
|
135
|
+
tableId: string;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface UpdateCellArgs {
|
|
139
|
+
dbKey: string;
|
|
140
|
+
columnName: string;
|
|
141
|
+
value: any;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
interface ReorderColumnArgs {
|
|
145
|
+
tenantId: string;
|
|
146
|
+
tableId: string;
|
|
147
|
+
fromIndex: number;
|
|
148
|
+
toIndex: number;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
interface UpdateColumnWidthArgs {
|
|
152
|
+
tenantId: string;
|
|
153
|
+
tableId: string;
|
|
154
|
+
columnId: string;
|
|
155
|
+
width: number;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* --------------------------------------------------------------------------
|
|
159
|
+
* Slice 定义
|
|
160
|
+
* ------------------------------------------------------------------------*/
|
|
161
|
+
|
|
162
|
+
export const tableSlice = createSliceWithThunks({
|
|
163
|
+
name: "table",
|
|
164
|
+
initialState,
|
|
165
|
+
reducers: (create) => ({
|
|
166
|
+
/* --------------------------------------
|
|
167
|
+
* 1. 创建表:createTable
|
|
168
|
+
* ------------------------------------*/
|
|
169
|
+
createTable: create.asyncThunk<string, CreateTableArgs | undefined>(
|
|
170
|
+
createTableAction
|
|
171
|
+
),
|
|
172
|
+
|
|
173
|
+
/* --------------------------------------
|
|
174
|
+
* 2. 加载已有表定义:initTable
|
|
175
|
+
* ------------------------------------*/
|
|
176
|
+
initTable: create.asyncThunk(
|
|
177
|
+
async (args: InitTableArgs, { dispatch, rejectWithValue }) => {
|
|
178
|
+
const { tenantId, tableId } = args;
|
|
179
|
+
const dbKey = metaKey(tenantId, tableId);
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
const readAction = await dispatch(readAndWait(dbKey));
|
|
183
|
+
|
|
184
|
+
if (readAndWait.fulfilled.match(readAction) && readAction.payload) {
|
|
185
|
+
const meta = readAction.payload as TableMeta;
|
|
186
|
+
return meta;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const msg =
|
|
190
|
+
(readAction.payload as any)?.message || `无法加载表 ${tableId}`;
|
|
191
|
+
return rejectWithValue(msg);
|
|
192
|
+
} catch (e: any) {
|
|
193
|
+
return rejectWithValue(e.message || `初始化表 ${tableId} 时出错`);
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
pending: (state) => {
|
|
198
|
+
state.isLoading = true;
|
|
199
|
+
state.error = null;
|
|
200
|
+
state.isInitialized = false;
|
|
201
|
+
state.currentTable = null;
|
|
202
|
+
state.rows = []; // 切换/重载表时,行数据一起清空
|
|
203
|
+
},
|
|
204
|
+
fulfilled: (state, action: PayloadAction<TableMeta>) => {
|
|
205
|
+
state.isLoading = false;
|
|
206
|
+
state.isInitialized = true;
|
|
207
|
+
state.currentTable = action.payload;
|
|
208
|
+
state.error = null;
|
|
209
|
+
},
|
|
210
|
+
rejected: (state, action) => {
|
|
211
|
+
state.isLoading = false;
|
|
212
|
+
state.isInitialized = true;
|
|
213
|
+
state.currentTable = null;
|
|
214
|
+
state.error =
|
|
215
|
+
(action.payload as string) ||
|
|
216
|
+
action.error.message ||
|
|
217
|
+
"初始化表时发生未知错误";
|
|
218
|
+
state.rows = [];
|
|
219
|
+
},
|
|
220
|
+
}
|
|
221
|
+
),
|
|
222
|
+
|
|
223
|
+
/* --------------------------------------
|
|
224
|
+
* 2.1 加载某表的所有行:loadTableRows
|
|
225
|
+
* ------------------------------------*/
|
|
226
|
+
loadTableRows: create.asyncThunk(
|
|
227
|
+
async (args: LoadTableRowsArgs, { getState, rejectWithValue, extra }) => {
|
|
228
|
+
const { tenantId, tableId } = args;
|
|
229
|
+
const { db } = extra as { db: any };
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
const state = getState() as RootState;
|
|
233
|
+
const { currentToken: token, remoteServers } =
|
|
234
|
+
getRuntimeServerContext(state);
|
|
235
|
+
return await fetchAndCacheTableRows({
|
|
236
|
+
db,
|
|
237
|
+
tenantId,
|
|
238
|
+
tableId,
|
|
239
|
+
token,
|
|
240
|
+
remoteServers,
|
|
241
|
+
});
|
|
242
|
+
} catch (e: any) {
|
|
243
|
+
return rejectWithValue(e.message || "加载表行失败");
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
pending: (state) => {
|
|
248
|
+
state.error = null;
|
|
249
|
+
},
|
|
250
|
+
fulfilled: (state, action: PayloadAction<any[]>) => {
|
|
251
|
+
state.rows = action.payload;
|
|
252
|
+
},
|
|
253
|
+
rejected: (state, action) => {
|
|
254
|
+
state.error =
|
|
255
|
+
(action.payload as string) ||
|
|
256
|
+
action.error.message ||
|
|
257
|
+
"加载表行时发生未知错误";
|
|
258
|
+
state.rows = [];
|
|
259
|
+
},
|
|
260
|
+
}
|
|
261
|
+
),
|
|
262
|
+
|
|
263
|
+
/* --------------------------------------
|
|
264
|
+
* 3. 新增一行:addRow
|
|
265
|
+
* ------------------------------------*/
|
|
266
|
+
addRow: create.asyncThunk(
|
|
267
|
+
async (args: AddRowArgs, { dispatch, rejectWithValue }) => {
|
|
268
|
+
const { tenantId, tableId, values } = args;
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
const { dbKey, rowId } = rowKey.create(tenantId, tableId);
|
|
272
|
+
const nowIso = formatISO(new Date());
|
|
273
|
+
|
|
274
|
+
const row = {
|
|
275
|
+
dbKey,
|
|
276
|
+
tenantId,
|
|
277
|
+
tableId,
|
|
278
|
+
rowId,
|
|
279
|
+
createdAt: nowIso,
|
|
280
|
+
updatedAt: nowIso,
|
|
281
|
+
type: DataType.TABLE_ROW as const,
|
|
282
|
+
...values,
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
await dispatch(
|
|
286
|
+
write({
|
|
287
|
+
data: row,
|
|
288
|
+
customKey: dbKey,
|
|
289
|
+
})
|
|
290
|
+
).unwrap();
|
|
291
|
+
|
|
292
|
+
return row;
|
|
293
|
+
} catch (e: any) {
|
|
294
|
+
return rejectWithValue(e.message || "新增表行失败");
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
pending: (state) => {
|
|
299
|
+
state.error = null;
|
|
300
|
+
},
|
|
301
|
+
fulfilled: (state, action: PayloadAction<any>) => {
|
|
302
|
+
const row = action.payload as any;
|
|
303
|
+
const meta = state.currentTable;
|
|
304
|
+
|
|
305
|
+
if (
|
|
306
|
+
meta &&
|
|
307
|
+
row?.tenantId === meta.tenantId &&
|
|
308
|
+
row?.tableId === meta.tableId
|
|
309
|
+
) {
|
|
310
|
+
state.rows.push(row);
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
rejected: (state, action) => {
|
|
314
|
+
state.error =
|
|
315
|
+
(action.payload as string) ||
|
|
316
|
+
action.error.message ||
|
|
317
|
+
"新增表行时发生未知错误";
|
|
318
|
+
},
|
|
319
|
+
}
|
|
320
|
+
),
|
|
321
|
+
|
|
322
|
+
/* --------------------------------------
|
|
323
|
+
* 3.1 删除一行:deleteRow
|
|
324
|
+
* ------------------------------------*/
|
|
325
|
+
deleteRow: create.asyncThunk(
|
|
326
|
+
async (dbKey: string, { dispatch, getState, rejectWithValue, extra }) => {
|
|
327
|
+
try {
|
|
328
|
+
const state = getState() as RootState;
|
|
329
|
+
const row = state.table.rows.find((item) => item?.dbKey === dbKey);
|
|
330
|
+
|
|
331
|
+
if (!row) {
|
|
332
|
+
return rejectWithValue(`当前表中找不到要删除的行:${dbKey}`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const nowIso = formatISO(new Date());
|
|
336
|
+
const tombstoneRow = {
|
|
337
|
+
...row,
|
|
338
|
+
deletedAt: nowIso,
|
|
339
|
+
updatedAt: nowIso,
|
|
340
|
+
type: DataType.TABLE_ROW as const,
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
if (extra?.db && typeof extra.db.put === "function") {
|
|
344
|
+
await extra.db.put(dbKey, tombstoneRow);
|
|
345
|
+
}
|
|
346
|
+
dispatch(upsertSSREntity(tombstoneRow));
|
|
347
|
+
|
|
348
|
+
const { currentServer, syncServers } = getRuntimeServerContext(state);
|
|
349
|
+
const servers = resolveReplicationServers(currentServer, syncServers);
|
|
350
|
+
scheduleWriteReplication(
|
|
351
|
+
servers,
|
|
352
|
+
{
|
|
353
|
+
data: tombstoneRow,
|
|
354
|
+
customKey: dbKey,
|
|
355
|
+
},
|
|
356
|
+
state
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
return dbKey;
|
|
360
|
+
} catch (e: any) {
|
|
361
|
+
return rejectWithValue(e.message || "删除表行失败");
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
pending: (state) => {
|
|
366
|
+
state.error = null;
|
|
367
|
+
},
|
|
368
|
+
fulfilled: (state, action: PayloadAction<string>) => {
|
|
369
|
+
const dbKey = action.payload;
|
|
370
|
+
state.rows = state.rows.filter((row) => row.dbKey !== dbKey);
|
|
371
|
+
},
|
|
372
|
+
rejected: (state, action) => {
|
|
373
|
+
state.error =
|
|
374
|
+
(action.payload as string) ||
|
|
375
|
+
action.error.message ||
|
|
376
|
+
"删除表行时发生未知错误";
|
|
377
|
+
},
|
|
378
|
+
}
|
|
379
|
+
),
|
|
380
|
+
|
|
381
|
+
/* --------------------------------------
|
|
382
|
+
* 3.2 删除整张表:deleteTable
|
|
383
|
+
* ------------------------------------*/
|
|
384
|
+
deleteTable: create.asyncThunk<string, DeleteTableArgs>(
|
|
385
|
+
deleteTableAction,
|
|
386
|
+
{
|
|
387
|
+
pending: (state) => {
|
|
388
|
+
state.error = null;
|
|
389
|
+
},
|
|
390
|
+
fulfilled: (state, action: PayloadAction<string>) => {
|
|
391
|
+
const deletedKey = action.payload;
|
|
392
|
+
|
|
393
|
+
if (state.currentTable?.dbKey === deletedKey) {
|
|
394
|
+
state.currentTable = null;
|
|
395
|
+
state.rows = [];
|
|
396
|
+
state.isInitialized = false;
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
rejected: (state, action) => {
|
|
400
|
+
state.error =
|
|
401
|
+
(action.payload as string) ||
|
|
402
|
+
action.error.message ||
|
|
403
|
+
"删除表时发生未知错误";
|
|
404
|
+
},
|
|
405
|
+
}
|
|
406
|
+
),
|
|
407
|
+
|
|
408
|
+
/* --------------------------------------
|
|
409
|
+
* 4. 新增字段:addColumn
|
|
410
|
+
* ------------------------------------*/
|
|
411
|
+
addColumn: create.asyncThunk(
|
|
412
|
+
async (args: AddColumnArgs, { dispatch, getState, rejectWithValue }) => {
|
|
413
|
+
const { tenantId, tableId, columnName } = args;
|
|
414
|
+
|
|
415
|
+
if (!columnName.trim()) {
|
|
416
|
+
return rejectWithValue("字段名不能为空");
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const state = (getState() as RootState).table;
|
|
420
|
+
const meta = state.currentTable;
|
|
421
|
+
|
|
422
|
+
if (!meta || meta.tenantId !== tenantId || meta.tableId !== tableId) {
|
|
423
|
+
return rejectWithValue("当前没有加载对应的表定义");
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (meta.columns.some((c) => c.name === columnName)) {
|
|
427
|
+
return rejectWithValue(`字段 ${columnName} 已存在`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
try {
|
|
431
|
+
const nowIso = formatISO(new Date());
|
|
432
|
+
|
|
433
|
+
const newColumn: TableColumn = {
|
|
434
|
+
id: ulid(),
|
|
435
|
+
name: columnName,
|
|
436
|
+
// 默认显示名与机器名一致;之后用户可以单独改 label
|
|
437
|
+
label: columnName,
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
const newColumns: TableColumn[] = [...meta.columns, newColumn];
|
|
441
|
+
|
|
442
|
+
await dispatch(
|
|
443
|
+
patch({
|
|
444
|
+
dbKey: meta.dbKey,
|
|
445
|
+
changes: {
|
|
446
|
+
columns: newColumns,
|
|
447
|
+
updatedAt: nowIso,
|
|
448
|
+
},
|
|
449
|
+
})
|
|
450
|
+
).unwrap();
|
|
451
|
+
|
|
452
|
+
const nextMeta: TableMeta = {
|
|
453
|
+
...meta,
|
|
454
|
+
columns: newColumns,
|
|
455
|
+
updatedAt: nowIso,
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
return nextMeta;
|
|
459
|
+
} catch (e: any) {
|
|
460
|
+
return rejectWithValue(e.message || "添加字段失败");
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
pending: (state) => {
|
|
465
|
+
state.error = null;
|
|
466
|
+
},
|
|
467
|
+
fulfilled: (state, action: PayloadAction<TableMeta>) => {
|
|
468
|
+
state.currentTable = action.payload;
|
|
469
|
+
state.isInitialized = true;
|
|
470
|
+
},
|
|
471
|
+
rejected: (state, action) => {
|
|
472
|
+
state.error =
|
|
473
|
+
(action.payload as string) ||
|
|
474
|
+
action.error.message ||
|
|
475
|
+
"为表新增字段时发生未知错误";
|
|
476
|
+
},
|
|
477
|
+
}
|
|
478
|
+
),
|
|
479
|
+
|
|
480
|
+
/* --------------------------------------
|
|
481
|
+
* 4.1 删除字段:deleteColumn
|
|
482
|
+
* ------------------------------------*/
|
|
483
|
+
deleteColumn: create.asyncThunk(
|
|
484
|
+
async (
|
|
485
|
+
args: DeleteColumnArgs,
|
|
486
|
+
{ dispatch, getState, rejectWithValue }
|
|
487
|
+
) => {
|
|
488
|
+
const { tenantId, tableId, columnName } = args;
|
|
489
|
+
|
|
490
|
+
const state = (getState() as RootState).table;
|
|
491
|
+
const meta = state.currentTable;
|
|
492
|
+
|
|
493
|
+
if (!meta || meta.tenantId !== tenantId || meta.tableId !== tableId) {
|
|
494
|
+
return rejectWithValue("当前没有加载对应的表定义");
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (!meta.columns.some((c) => c.name === columnName)) {
|
|
498
|
+
return rejectWithValue(`字段 ${columnName} 不存在`);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const nowIso = formatISO(new Date());
|
|
502
|
+
const newColumns: TableColumn[] = meta.columns.filter(
|
|
503
|
+
(c) => c.name !== columnName
|
|
504
|
+
);
|
|
505
|
+
const rows = state.rows;
|
|
506
|
+
|
|
507
|
+
try {
|
|
508
|
+
// 1) 删除所有行上的该字段(利用 patch 的 null -> 删除语义)
|
|
509
|
+
const rowsWithField = rows.filter((row) =>
|
|
510
|
+
Object.prototype.hasOwnProperty.call(row, columnName)
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
await Promise.all(
|
|
514
|
+
rowsWithField.map((row) =>
|
|
515
|
+
dispatch(
|
|
516
|
+
patch({
|
|
517
|
+
dbKey: row.dbKey,
|
|
518
|
+
changes: {
|
|
519
|
+
[columnName]: null,
|
|
520
|
+
updatedAt: nowIso,
|
|
521
|
+
},
|
|
522
|
+
})
|
|
523
|
+
).unwrap()
|
|
524
|
+
)
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
// 2) 更新 meta.columns
|
|
528
|
+
await dispatch(
|
|
529
|
+
patch({
|
|
530
|
+
dbKey: meta.dbKey,
|
|
531
|
+
changes: {
|
|
532
|
+
columns: newColumns,
|
|
533
|
+
updatedAt: nowIso,
|
|
534
|
+
},
|
|
535
|
+
})
|
|
536
|
+
).unwrap();
|
|
537
|
+
|
|
538
|
+
// 3) 生成新的 rows(内存态)
|
|
539
|
+
const newRows = rows.map((row) => {
|
|
540
|
+
if (!Object.prototype.hasOwnProperty.call(row, columnName)) {
|
|
541
|
+
return row;
|
|
542
|
+
}
|
|
543
|
+
const { [columnName]: _removed, ...rest } = row;
|
|
544
|
+
return {
|
|
545
|
+
...rest,
|
|
546
|
+
updatedAt: nowIso,
|
|
547
|
+
};
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
return {
|
|
551
|
+
meta: {
|
|
552
|
+
...meta,
|
|
553
|
+
columns: newColumns,
|
|
554
|
+
updatedAt: nowIso,
|
|
555
|
+
} as TableMeta,
|
|
556
|
+
rows: newRows,
|
|
557
|
+
};
|
|
558
|
+
} catch (e: any) {
|
|
559
|
+
return rejectWithValue(e.message || "删除字段失败");
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
pending: (state) => {
|
|
564
|
+
state.error = null;
|
|
565
|
+
},
|
|
566
|
+
fulfilled: (
|
|
567
|
+
state,
|
|
568
|
+
action: PayloadAction<{ meta: TableMeta; rows: any[] }>
|
|
569
|
+
) => {
|
|
570
|
+
state.currentTable = action.payload.meta;
|
|
571
|
+
state.rows = action.payload.rows;
|
|
572
|
+
state.isInitialized = true;
|
|
573
|
+
},
|
|
574
|
+
rejected: (state, action) => {
|
|
575
|
+
state.error =
|
|
576
|
+
(action.payload as string) ||
|
|
577
|
+
action.error.message ||
|
|
578
|
+
"删除字段时发生未知错误";
|
|
579
|
+
},
|
|
580
|
+
}
|
|
581
|
+
),
|
|
582
|
+
|
|
583
|
+
/* --------------------------------------
|
|
584
|
+
* 4.1-bis 调整字段顺序:reorderColumn
|
|
585
|
+
* ------------------------------------*/
|
|
586
|
+
reorderColumn: create.asyncThunk(
|
|
587
|
+
async (
|
|
588
|
+
args: ReorderColumnArgs,
|
|
589
|
+
{ dispatch, getState, rejectWithValue }
|
|
590
|
+
) => {
|
|
591
|
+
const { tenantId, tableId, fromIndex, toIndex } = args;
|
|
592
|
+
const state = (getState() as RootState).table;
|
|
593
|
+
const meta = state.currentTable;
|
|
594
|
+
|
|
595
|
+
if (!meta || meta.tenantId !== tenantId || meta.tableId !== tableId) {
|
|
596
|
+
return rejectWithValue("当前没有加载对应的表定义");
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const columnCount = meta.columns.length;
|
|
600
|
+
if (
|
|
601
|
+
fromIndex < 0 ||
|
|
602
|
+
fromIndex >= columnCount ||
|
|
603
|
+
toIndex < 0 ||
|
|
604
|
+
toIndex >= columnCount
|
|
605
|
+
) {
|
|
606
|
+
return rejectWithValue("列索引超出范围");
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (fromIndex === toIndex) {
|
|
610
|
+
// 不需要改动,直接返回原 meta,避免无意义写库
|
|
611
|
+
return meta;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const nowIso = formatISO(new Date());
|
|
615
|
+
const newColumns = reorderList(meta.columns, fromIndex, toIndex);
|
|
616
|
+
|
|
617
|
+
try {
|
|
618
|
+
await dispatch(
|
|
619
|
+
patch({
|
|
620
|
+
dbKey: meta.dbKey,
|
|
621
|
+
changes: {
|
|
622
|
+
columns: newColumns,
|
|
623
|
+
updatedAt: nowIso,
|
|
624
|
+
},
|
|
625
|
+
})
|
|
626
|
+
).unwrap();
|
|
627
|
+
|
|
628
|
+
const nextMeta: TableMeta = {
|
|
629
|
+
...meta,
|
|
630
|
+
columns: newColumns,
|
|
631
|
+
updatedAt: nowIso,
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
return nextMeta;
|
|
635
|
+
} catch (e: any) {
|
|
636
|
+
return rejectWithValue(e.message || "调整列顺序失败");
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
pending: (state) => {
|
|
641
|
+
state.error = null;
|
|
642
|
+
},
|
|
643
|
+
fulfilled: (state, action: PayloadAction<TableMeta>) => {
|
|
644
|
+
state.currentTable = action.payload;
|
|
645
|
+
state.isInitialized = true;
|
|
646
|
+
},
|
|
647
|
+
rejected: (state, action) => {
|
|
648
|
+
state.error =
|
|
649
|
+
(action.payload as string) ||
|
|
650
|
+
action.error.message ||
|
|
651
|
+
"调整列顺序时发生未知错误";
|
|
652
|
+
},
|
|
653
|
+
}
|
|
654
|
+
),
|
|
655
|
+
|
|
656
|
+
/* --------------------------------------
|
|
657
|
+
* 4.2 重命名字段(机器名):renameColumn
|
|
658
|
+
* 说明:这是“改字段 key 并迁移所有行数据”的重操作,
|
|
659
|
+
* 目前 UI 不直接调用,保留给将来高级设置用。
|
|
660
|
+
* ------------------------------------*/
|
|
661
|
+
renameColumn: create.asyncThunk(
|
|
662
|
+
async (
|
|
663
|
+
args: RenameColumnArgs,
|
|
664
|
+
{ dispatch, getState, rejectWithValue }
|
|
665
|
+
) => {
|
|
666
|
+
const { tenantId, tableId, oldName, newName } = args;
|
|
667
|
+
const trimmedNewName = newName.trim();
|
|
668
|
+
|
|
669
|
+
if (!trimmedNewName) {
|
|
670
|
+
return rejectWithValue("新的字段名不能为空");
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const state = (getState() as RootState).table;
|
|
674
|
+
const meta = state.currentTable;
|
|
675
|
+
|
|
676
|
+
if (!meta || meta.tenantId !== tenantId || meta.tableId !== tableId) {
|
|
677
|
+
return rejectWithValue("当前没有加载对应的表定义");
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (!meta.columns.some((c) => c.name === oldName)) {
|
|
681
|
+
return rejectWithValue(`字段 ${oldName} 不存在`);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (meta.columns.some((c) => c.name === trimmedNewName)) {
|
|
685
|
+
return rejectWithValue(`字段 ${trimmedNewName} 已存在`);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const nowIso = formatISO(new Date());
|
|
689
|
+
const rows = state.rows;
|
|
690
|
+
|
|
691
|
+
const newColumns: TableColumn[] = meta.columns.map((c) =>
|
|
692
|
+
c.name === oldName ? { ...c, name: trimmedNewName } : c
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
try {
|
|
696
|
+
// 1) 遍历所有行,把 oldName -> newName,并删除 oldName
|
|
697
|
+
const rowsWithOldField = rows.filter((row) =>
|
|
698
|
+
Object.prototype.hasOwnProperty.call(row, oldName)
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
await Promise.all(
|
|
702
|
+
rowsWithOldField.map((row) => {
|
|
703
|
+
const changes: Record<string, any> = {
|
|
704
|
+
[trimmedNewName]: row[oldName],
|
|
705
|
+
[oldName]: null,
|
|
706
|
+
updatedAt: nowIso,
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
return dispatch(
|
|
710
|
+
patch({
|
|
711
|
+
dbKey: row.dbKey,
|
|
712
|
+
changes,
|
|
713
|
+
})
|
|
714
|
+
).unwrap();
|
|
715
|
+
})
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
// 2) 更新 meta.columns
|
|
719
|
+
await dispatch(
|
|
720
|
+
patch({
|
|
721
|
+
dbKey: meta.dbKey,
|
|
722
|
+
changes: {
|
|
723
|
+
columns: newColumns,
|
|
724
|
+
updatedAt: nowIso,
|
|
725
|
+
},
|
|
726
|
+
})
|
|
727
|
+
).unwrap();
|
|
728
|
+
|
|
729
|
+
// 3) 内存态 rows 同步字段名
|
|
730
|
+
const newRows = rows.map((row) => {
|
|
731
|
+
if (!Object.prototype.hasOwnProperty.call(row, oldName)) {
|
|
732
|
+
return row;
|
|
733
|
+
}
|
|
734
|
+
const { [oldName]: oldValue, ...rest } = row;
|
|
735
|
+
return {
|
|
736
|
+
...rest,
|
|
737
|
+
[trimmedNewName]: oldValue,
|
|
738
|
+
updatedAt: nowIso,
|
|
739
|
+
};
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
return {
|
|
743
|
+
meta: {
|
|
744
|
+
...meta,
|
|
745
|
+
columns: newColumns,
|
|
746
|
+
updatedAt: nowIso,
|
|
747
|
+
} as TableMeta,
|
|
748
|
+
rows: newRows,
|
|
749
|
+
};
|
|
750
|
+
} catch (e: any) {
|
|
751
|
+
return rejectWithValue(e.message || "重命名字段失败");
|
|
752
|
+
}
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
pending: (state) => {
|
|
756
|
+
state.error = null;
|
|
757
|
+
},
|
|
758
|
+
fulfilled: (
|
|
759
|
+
state,
|
|
760
|
+
action: PayloadAction<{ meta: TableMeta; rows: any[] }>
|
|
761
|
+
) => {
|
|
762
|
+
state.currentTable = action.payload.meta;
|
|
763
|
+
state.rows = action.payload.rows;
|
|
764
|
+
state.isInitialized = true;
|
|
765
|
+
},
|
|
766
|
+
rejected: (state, action) => {
|
|
767
|
+
state.error =
|
|
768
|
+
(action.payload as string) ||
|
|
769
|
+
action.error.message ||
|
|
770
|
+
"重命名字段时发生未知错误";
|
|
771
|
+
},
|
|
772
|
+
}
|
|
773
|
+
),
|
|
774
|
+
|
|
775
|
+
/* --------------------------------------
|
|
776
|
+
* 4.2-bis 重命名字段显示名:renameColumnLabel
|
|
777
|
+
* 说明:只改 columns[].label,不动 name / 行数据
|
|
778
|
+
* UI 表头双击使用这一条。
|
|
779
|
+
* ------------------------------------*/
|
|
780
|
+
renameColumnLabel: create.asyncThunk(
|
|
781
|
+
async (
|
|
782
|
+
args: RenameColumnLabelArgs,
|
|
783
|
+
{ dispatch, getState, rejectWithValue }
|
|
784
|
+
) => {
|
|
785
|
+
const { tenantId, tableId, columnId, newLabel } = args;
|
|
786
|
+
const trimmedLabel = newLabel.trim();
|
|
787
|
+
|
|
788
|
+
if (!trimmedLabel) {
|
|
789
|
+
return rejectWithValue("字段显示名不能为空");
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const state = (getState() as RootState).table;
|
|
793
|
+
const meta = state.currentTable;
|
|
794
|
+
|
|
795
|
+
if (!meta || meta.tenantId !== tenantId || meta.tableId !== tableId) {
|
|
796
|
+
return rejectWithValue("当前没有加载对应的表定义");
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const column = meta.columns.find((c) => c.id === columnId);
|
|
800
|
+
if (!column) {
|
|
801
|
+
return rejectWithValue("要重命名的字段不存在");
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const nowIso = formatISO(new Date());
|
|
805
|
+
|
|
806
|
+
const newColumns: TableColumn[] = meta.columns.map((c) =>
|
|
807
|
+
c.id === columnId ? { ...c, label: trimmedLabel } : c
|
|
808
|
+
);
|
|
809
|
+
|
|
810
|
+
try {
|
|
811
|
+
await dispatch(
|
|
812
|
+
patch({
|
|
813
|
+
dbKey: meta.dbKey,
|
|
814
|
+
changes: {
|
|
815
|
+
columns: newColumns,
|
|
816
|
+
updatedAt: nowIso,
|
|
817
|
+
},
|
|
818
|
+
})
|
|
819
|
+
).unwrap();
|
|
820
|
+
|
|
821
|
+
const nextMeta: TableMeta = {
|
|
822
|
+
...meta,
|
|
823
|
+
columns: newColumns,
|
|
824
|
+
updatedAt: nowIso,
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
return nextMeta;
|
|
828
|
+
} catch (e: any) {
|
|
829
|
+
return rejectWithValue(e.message || "重命名字段显示名失败");
|
|
830
|
+
}
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
pending: (state) => {
|
|
834
|
+
state.error = null;
|
|
835
|
+
},
|
|
836
|
+
fulfilled: (state, action: PayloadAction<TableMeta>) => {
|
|
837
|
+
state.currentTable = action.payload;
|
|
838
|
+
state.isInitialized = true;
|
|
839
|
+
},
|
|
840
|
+
rejected: (state, action) => {
|
|
841
|
+
state.error =
|
|
842
|
+
(action.payload as string) ||
|
|
843
|
+
action.error.message ||
|
|
844
|
+
"重命名字段显示名时发生未知错误";
|
|
845
|
+
},
|
|
846
|
+
}
|
|
847
|
+
),
|
|
848
|
+
|
|
849
|
+
/* --------------------------------------
|
|
850
|
+
* 4.2-ter 更新字段宽度:updateColumnWidth
|
|
851
|
+
* 说明:只改 columns[].width,用于持久化列宽
|
|
852
|
+
* ------------------------------------*/
|
|
853
|
+
updateColumnWidth: create.asyncThunk(
|
|
854
|
+
async (
|
|
855
|
+
args: UpdateColumnWidthArgs,
|
|
856
|
+
{ dispatch, getState, rejectWithValue }
|
|
857
|
+
) => {
|
|
858
|
+
const { tenantId, tableId, columnId, width } = args;
|
|
859
|
+
|
|
860
|
+
const state = (getState() as RootState).table;
|
|
861
|
+
const meta = state.currentTable;
|
|
862
|
+
|
|
863
|
+
if (!meta || meta.tenantId !== tenantId || meta.tableId !== tableId) {
|
|
864
|
+
return rejectWithValue("当前没有加载对应的表定义");
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
const column = meta.columns.find((c) => c.id === columnId);
|
|
868
|
+
if (!column) {
|
|
869
|
+
return rejectWithValue("要调整宽度的字段不存在");
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
const normalizedWidth =
|
|
873
|
+
typeof width === "number" && width > 0 ? Math.round(width) : undefined;
|
|
874
|
+
|
|
875
|
+
const nowIso = formatISO(new Date());
|
|
876
|
+
|
|
877
|
+
const newColumns: TableColumn[] = meta.columns.map((c) =>
|
|
878
|
+
c.id === columnId ? { ...c, width: normalizedWidth } : c
|
|
879
|
+
);
|
|
880
|
+
|
|
881
|
+
try {
|
|
882
|
+
await dispatch(
|
|
883
|
+
patch({
|
|
884
|
+
dbKey: meta.dbKey,
|
|
885
|
+
changes: {
|
|
886
|
+
columns: newColumns,
|
|
887
|
+
updatedAt: nowIso,
|
|
888
|
+
},
|
|
889
|
+
})
|
|
890
|
+
).unwrap();
|
|
891
|
+
|
|
892
|
+
const nextMeta: TableMeta = {
|
|
893
|
+
...meta,
|
|
894
|
+
columns: newColumns,
|
|
895
|
+
updatedAt: nowIso,
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
return nextMeta;
|
|
899
|
+
} catch (e: any) {
|
|
900
|
+
return rejectWithValue(e.message || "更新字段宽度失败");
|
|
901
|
+
}
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
pending: (state) => {
|
|
905
|
+
state.error = null;
|
|
906
|
+
},
|
|
907
|
+
fulfilled: (state, action: PayloadAction<TableMeta>) => {
|
|
908
|
+
state.currentTable = action.payload;
|
|
909
|
+
state.isInitialized = true;
|
|
910
|
+
},
|
|
911
|
+
rejected: (state, action) => {
|
|
912
|
+
state.error =
|
|
913
|
+
(action.payload as string) ||
|
|
914
|
+
action.error.message ||
|
|
915
|
+
"更新字段宽度时发生未知错误";
|
|
916
|
+
},
|
|
917
|
+
}
|
|
918
|
+
),
|
|
919
|
+
|
|
920
|
+
/* --------------------------------------
|
|
921
|
+
* 4.3 重命名表:renameTable(仅修改显示名称)
|
|
922
|
+
* ------------------------------------*/
|
|
923
|
+
renameTable: create.asyncThunk(
|
|
924
|
+
async (
|
|
925
|
+
args: RenameTableArgs,
|
|
926
|
+
{ dispatch, getState, rejectWithValue }
|
|
927
|
+
) => {
|
|
928
|
+
const { tenantId, tableId, newName } = args;
|
|
929
|
+
const trimmedName = newName.trim();
|
|
930
|
+
|
|
931
|
+
if (!trimmedName) {
|
|
932
|
+
return rejectWithValue("表名不能为空");
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const state = (getState() as RootState).table;
|
|
936
|
+
const meta = state.currentTable;
|
|
937
|
+
|
|
938
|
+
if (!meta || meta.tenantId !== tenantId || meta.tableId !== tableId) {
|
|
939
|
+
return rejectWithValue("当前没有加载对应的表定义");
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const nowIso = formatISO(new Date());
|
|
943
|
+
|
|
944
|
+
try {
|
|
945
|
+
await dispatch(
|
|
946
|
+
patch({
|
|
947
|
+
dbKey: meta.dbKey,
|
|
948
|
+
changes: {
|
|
949
|
+
displayName: trimmedName,
|
|
950
|
+
updatedAt: nowIso,
|
|
951
|
+
},
|
|
952
|
+
})
|
|
953
|
+
).unwrap();
|
|
954
|
+
|
|
955
|
+
const nextMeta: TableMeta = {
|
|
956
|
+
...meta,
|
|
957
|
+
displayName: trimmedName,
|
|
958
|
+
updatedAt: nowIso,
|
|
959
|
+
};
|
|
960
|
+
|
|
961
|
+
return nextMeta;
|
|
962
|
+
} catch (e: any) {
|
|
963
|
+
return rejectWithValue(e.message || "重命名表失败");
|
|
964
|
+
}
|
|
965
|
+
},
|
|
966
|
+
{
|
|
967
|
+
pending: (state) => {
|
|
968
|
+
state.error = null;
|
|
969
|
+
},
|
|
970
|
+
fulfilled: (state, action: PayloadAction<TableMeta>) => {
|
|
971
|
+
state.currentTable = action.payload;
|
|
972
|
+
},
|
|
973
|
+
rejected: (state, action) => {
|
|
974
|
+
state.error =
|
|
975
|
+
(action.payload as string) ||
|
|
976
|
+
action.error.message ||
|
|
977
|
+
"重命名表时发生未知错误";
|
|
978
|
+
},
|
|
979
|
+
}
|
|
980
|
+
),
|
|
981
|
+
|
|
982
|
+
/* --------------------------------------
|
|
983
|
+
* 4.4 更新单元格:updateCell
|
|
984
|
+
* ------------------------------------*/
|
|
985
|
+
updateCell: create.asyncThunk(
|
|
986
|
+
async (args: UpdateCellArgs, { dispatch, rejectWithValue }) => {
|
|
987
|
+
const { dbKey, columnName, value } = args;
|
|
988
|
+
|
|
989
|
+
try {
|
|
990
|
+
const nowIso = formatISO(new Date());
|
|
991
|
+
|
|
992
|
+
await dispatch(
|
|
993
|
+
patch({
|
|
994
|
+
dbKey,
|
|
995
|
+
changes: {
|
|
996
|
+
[columnName]: value,
|
|
997
|
+
updatedAt: nowIso,
|
|
998
|
+
},
|
|
999
|
+
})
|
|
1000
|
+
).unwrap();
|
|
1001
|
+
|
|
1002
|
+
return { dbKey, columnName, value, updatedAt: nowIso };
|
|
1003
|
+
} catch (e: any) {
|
|
1004
|
+
return rejectWithValue(e.message || "更新单元格失败");
|
|
1005
|
+
}
|
|
1006
|
+
},
|
|
1007
|
+
{
|
|
1008
|
+
pending: (state) => {
|
|
1009
|
+
state.error = null;
|
|
1010
|
+
},
|
|
1011
|
+
fulfilled: (
|
|
1012
|
+
state,
|
|
1013
|
+
action: PayloadAction<{
|
|
1014
|
+
dbKey: string;
|
|
1015
|
+
columnName: string;
|
|
1016
|
+
value: any;
|
|
1017
|
+
updatedAt: string;
|
|
1018
|
+
}>
|
|
1019
|
+
) => {
|
|
1020
|
+
const { dbKey, columnName, value, updatedAt } = action.payload;
|
|
1021
|
+
const row = state.rows.find((r) => r.dbKey === dbKey);
|
|
1022
|
+
if (row) {
|
|
1023
|
+
row[columnName] = value;
|
|
1024
|
+
row.updatedAt = updatedAt;
|
|
1025
|
+
}
|
|
1026
|
+
},
|
|
1027
|
+
rejected: (state, action) => {
|
|
1028
|
+
state.error =
|
|
1029
|
+
(action.payload as string) ||
|
|
1030
|
+
action.error.message ||
|
|
1031
|
+
"更新单元格时发生未知错误";
|
|
1032
|
+
},
|
|
1033
|
+
}
|
|
1034
|
+
),
|
|
1035
|
+
|
|
1036
|
+
/* --------------------------------------
|
|
1037
|
+
* 5. 重置当前表状态
|
|
1038
|
+
* ------------------------------------*/
|
|
1039
|
+
setTableFocusContext: create.reducer(
|
|
1040
|
+
(state, action: PayloadAction<TableSliceState["focusContext"]>) => {
|
|
1041
|
+
state.focusContext = action.payload;
|
|
1042
|
+
}
|
|
1043
|
+
),
|
|
1044
|
+
|
|
1045
|
+
resetTable: create.reducer((state) => {
|
|
1046
|
+
Object.assign(state, initialState);
|
|
1047
|
+
}),
|
|
1048
|
+
}),
|
|
1049
|
+
|
|
1050
|
+
selectors: {
|
|
1051
|
+
selectCurrentTable: (s: TableSliceState) => s.currentTable,
|
|
1052
|
+
selectTableIsLoading: (s: TableSliceState) => s.isLoading,
|
|
1053
|
+
selectTableIsInitialized: (s: TableSliceState) => s.isInitialized,
|
|
1054
|
+
selectTableError: (s: TableSliceState) => s.error,
|
|
1055
|
+
selectTableColumns: (s: TableSliceState) =>
|
|
1056
|
+
s.currentTable ? s.currentTable.columns : [],
|
|
1057
|
+
selectTableRows: (s: TableSliceState) => s.rows,
|
|
1058
|
+
selectTableFocusContext: (s: TableSliceState) => s.focusContext,
|
|
1059
|
+
},
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
/* --------------------------------------------------------------------------
|
|
1063
|
+
* 老接口:按表取所有行(基于 dbSlice),目前页面用不到了
|
|
1064
|
+
* ------------------------------------------------------------------------*/
|
|
1065
|
+
|
|
1066
|
+
export const makeSelectRowsByTable = (tenantId: string, tableId: string) =>
|
|
1067
|
+
createSelector(
|
|
1068
|
+
(state: RootState) => selectAllDb(state),
|
|
1069
|
+
(entities) =>
|
|
1070
|
+
entities.filter((e) => e.tableId === tableId && e.tenantId === tenantId)
|
|
1071
|
+
);
|
|
1072
|
+
|
|
1073
|
+
/* --------------------------------------------------------------------------
|
|
1074
|
+
* 导出
|
|
1075
|
+
* ------------------------------------------------------------------------*/
|
|
1076
|
+
|
|
1077
|
+
export const {
|
|
1078
|
+
createTable,
|
|
1079
|
+
initTable,
|
|
1080
|
+
addRow,
|
|
1081
|
+
deleteRow,
|
|
1082
|
+
deleteTable,
|
|
1083
|
+
addColumn,
|
|
1084
|
+
deleteColumn,
|
|
1085
|
+
renameColumn,
|
|
1086
|
+
renameColumnLabel,
|
|
1087
|
+
renameTable,
|
|
1088
|
+
resetTable,
|
|
1089
|
+
loadTableRows,
|
|
1090
|
+
updateCell,
|
|
1091
|
+
reorderColumn,
|
|
1092
|
+
updateColumnWidth,
|
|
1093
|
+
setTableFocusContext,
|
|
1094
|
+
} = tableSlice.actions;
|
|
1095
|
+
|
|
1096
|
+
export const {
|
|
1097
|
+
selectCurrentTable,
|
|
1098
|
+
selectTableIsLoading,
|
|
1099
|
+
selectTableIsInitialized,
|
|
1100
|
+
selectTableError,
|
|
1101
|
+
selectTableColumns,
|
|
1102
|
+
selectTableRows,
|
|
1103
|
+
selectTableFocusContext,
|
|
1104
|
+
} = tableSlice.selectors;
|
|
1105
|
+
|
|
1106
|
+
export default tableSlice.reducer;
|