feat(editor): 历史记录支持展示操作人

在历史列表分组头部和子步骤中展示 operator 信息,并补充步骤类型字段以承载操作人与扩展数据,同时收敛相关类型定义与插件方法声明,提升历史记录渲染与扩展能力。
This commit is contained in:
roymondchen 2026-06-18 17:24:25 +08:00
parent 9f2fa1a9c8
commit 1ade61d62e
6 changed files with 83 additions and 28 deletions

View File

@ -49,6 +49,13 @@
>{{ sourceLabel(group.source) }}</span
>
<span
v-if="!merged && group.operator"
class="m-editor-history-list-item-operator"
:title="`操作人:${group.operator}`"
>{{ group.operator }}</span
>
<span
v-if="!merged && group.time"
class="m-editor-history-list-item-time"
@ -103,6 +110,9 @@
:title="`操作途径:${sourceLabel(s.source)}`"
>{{ sourceLabel(s.source) }}</span
>
<span v-if="s.operator" class="m-editor-history-list-item-operator" :title="`操作人:${s.operator}`">{{
s.operator
}}</span>
<span v-if="s.time" class="m-editor-history-list-item-time" :title="s.timeTitle || s.time">{{ s.time }}</span>
</li>
</ul>

View File

@ -25,51 +25,49 @@ export interface HistoryBucketGroup<T extends BaseStepValue = BaseStepValue> {
steps: { index: number; applied: boolean; isCurrent?: boolean; step: T }[];
}
/** GroupRow 渲染所需的单个子步视图模型(已由 {@link toRowGroup} 预先派生,组件内部不再触碰原始 step。 */
export interface HistoryRowStep {
/** 该子步在所属栈中的稳定索引。 */
index: number;
/**
* GroupRow {@link toRowGroup}
* {@link HistoryRowStep} / {@link HistoryRowGroup}
* 便 /
*/
export interface HistoryRowDisplay {
/** 是否已应用false 表示已被 undoUI 灰态)。 */
applied: boolean;
/** 是否为当前所在步骤。 */
isCurrent?: boolean;
/** 是否为最近一次保存的记录。 */
saved?: boolean;
/** 子步描述文案。 */
/** 是否为当前所在步骤 / 分组。 */
isCurrent: boolean;
/** 描述文案。 */
desc: string;
/** 是否可查看差异。 */
diffable?: boolean;
/** 是否可回滚。 */
revertable?: boolean;
/** 操作途径。 */
source?: HistoryOpSource;
/** 操作人。 */
operator?: string;
/** 时间文案。 */
time?: string;
/** 时间的完整 title 提示。 */
timeTitle?: string;
}
/** GroupRow 渲染所需的单个子步视图模型(已由 {@link toRowGroup} 预先派生,组件内部不再触碰原始 step。 */
export interface HistoryRowStep extends HistoryRowDisplay {
/** 该子步在所属栈中的稳定索引。 */
index: number;
/** 是否为最近一次保存的记录。 */
saved?: boolean;
/** 是否可查看差异。 */
diffable?: boolean;
/** 是否可回滚。 */
revertable?: boolean;
}
/**
* GroupRow {@link toRowGroup}
* GroupRow props header
*/
export interface HistoryRowGroup {
export interface HistoryRowGroup extends HistoryRowDisplay {
/** 分组的稳定 key作为 toggle 事件 payload 与折叠状态的索引。 */
key: string;
/** 组内最后一步是否已应用。 */
applied: boolean;
/** 是否为当前所在分组。 */
isCurrent: boolean;
/** 操作类型,用于徽标颜色与文案。 */
opType: HistoryOpType;
/** 组整体描述文案。 */
desc: string;
/** 组的操作途径(取组内最近一步)。 */
source?: HistoryOpSource;
/** 组头部时间文案(取组内最近一步)。 */
time?: string;
/** 组头部时间的完整 title 提示。 */
timeTitle?: string;
/** 子步列表时间正序其长度即合并步数length > 1 即为合并组。 */
subSteps: HistoryRowStep[];
}
@ -144,6 +142,10 @@ export const sourceLabel = (source: HistoryOpSource = 'unknown'): string => {
export const groupSource = (group: { steps: { step: { source?: HistoryOpSource } }[] }): HistoryOpSource | undefined =>
group.steps[group.steps.length - 1]?.step.source;
/** 取一组历史步骤里最后一步(最近一次)的操作人,用于组头部展示。 */
export const groupOperator = (group: { steps: { step: { operator?: string } }[] }): string | undefined =>
group.steps[group.steps.length - 1]?.step.operator;
/** {@link toRowGroup} 接受的最小分组结构PageHistoryGroup 与 HistoryBucketGroup 均满足。 */
interface RowGroupInput<T extends BaseStepValue = BaseStepValue> {
applied: boolean;
@ -173,17 +175,19 @@ export const toRowGroup = <T extends BaseStepValue = BaseStepValue>(
opType: group.opType,
desc: describeGroup ? describeGroup(group) : describeStep(lastStep),
source: groupSource(group),
operator: groupOperator(group),
time: formatHistoryTime(timestamp),
timeTitle: formatHistoryFullTime(timestamp),
subSteps: group.steps.map((s) => ({
index: s.index,
applied: s.applied,
isCurrent: s.isCurrent,
isCurrent: Boolean(s.isCurrent),
saved: s.step.saved,
desc: describeStep(s.step),
diffable: isStepDiffable ? isStepDiffable(s.step) : false,
revertable: s.applied && (isStepRevertable ? isStepRevertable(s.step) : true),
source: s.step.source,
operator: s.step.operator,
time: formatHistoryTime(s.step.timestamp),
timeTitle: formatHistoryFullTime(s.step.timestamp),
})),

View File

@ -17,6 +17,7 @@
*/
import { reactive } from 'vue';
import type { Writable } from 'type-fest';
import type { CodeBlockContent, DataSourceSchema, Id, MPage, MPageFragment } from '@tmagic/core';
import type { ChangeRecord } from '@tmagic/form';
@ -33,6 +34,7 @@ import type {
PersistedHistoryState,
StackHistoryGroup,
StepValue,
SyncHookPlugin,
} from '@editor/type';
import { getEditorConfig } from '@editor/utils/config';
import {
@ -51,6 +53,22 @@ import { UndoRedo } from '@editor/utils/undo-redo';
import BaseService from './BaseService';
import editorService from './editor';
const canUsePluginMethods = {
sync: [
'push',
'pushCodeBlock',
'pushDataSource',
'undoCodeBlock',
'redoCodeBlock',
'undoDataSource',
'redoDataSource',
'undo',
'redo',
] as const,
};
type SyncMethodName = Writable<(typeof canUsePluginMethods)['sync']>;
/** 历史记录持久化快照的默认存储位置与结构版本。 */
const DEFAULT_DB_NAME = 'tmagic-editor';
const DEFAULT_STORE_NAME = 'history';
@ -69,7 +87,7 @@ class History extends BaseService {
});
constructor() {
super([]);
super([...canUsePluginMethods.sync.map((methodName) => ({ name: methodName, isAsync: false }))]);
this.on('change', this.setCanUndoRedo);
}
@ -630,6 +648,10 @@ class History extends BaseService {
return groups;
}
public usePlugin(options: SyncHookPlugin<SyncMethodName, History>): void {
super.usePlugin(options);
}
/**
* pageId
*

View File

@ -330,6 +330,20 @@
font-weight: 400; // 防止被合并组头部的粗体继承
}
// 操作人徽标浅蓝描边胶囊弱化展示操作人仅在 step.operator 有值时渲染
.m-editor-history-list-item-operator {
flex: 0 0 auto;
padding: 0 6px;
border: 1px solid #c6e2ff;
border-radius: 8px;
font-size: 10px;
line-height: 14px;
color: #409eff;
background-color: #ecf5ff;
white-space: nowrap;
font-weight: 400; // 防止被合并组头部的粗体继承
}
// 已保存徽标绿色实心胶囊标记最近一次保存对应的历史记录 historyService.markSaved 对应
.m-editor-history-list-item-saved {
flex: 0 0 auto;

View File

@ -820,6 +820,10 @@ export interface BaseStepValue<T = unknown> {
* / DSL root
*/
rootStep?: boolean;
/** 操作人 */
operator?: string;
/** 扩展信息 */
extra?: Record<string, any>;
}
// #endregion BaseStepValue

View File

@ -23,6 +23,7 @@ const makeGroup = (overrides: Partial<HistoryRowGroup> = {}): HistoryRowGroup =>
/** 构造单个子步,缺省值贴近真实派生结果。 */
const makeStep = (overrides: Partial<HistoryRowStep> & Pick<HistoryRowStep, 'index'>): HistoryRowStep => ({
applied: true,
isCurrent: false,
desc: '',
...overrides,
});