feat(history): exclude sort order changes from edit history

Stop recording sort order changes in the backend edit log
Filter out existing sort order changes from the frontend edit history panel
This commit is contained in:
2026-05-05 07:15:18 +08:00
parent 357dc061d6
commit 8ee29e9549
3 changed files with 22 additions and 24 deletions

View File

@@ -344,6 +344,7 @@
- `created_at` - `created_at`
- 详情页展示最后编辑者、最后编辑时间和编辑历史面板。 - 详情页展示最后编辑者、最后编辑时间和编辑历史面板。
- 编辑历史中的用户信息只展示必要署名不暴露邮箱、token、hash 或内部元数据。 - 编辑历史中的用户信息只展示必要署名不暴露邮箱、token、hash 或内部元数据。
- 排序操作仍更新列表顺序、最后编辑者和最后编辑时间,但 `sort_order` / Sort order 字段变更不写入或展示在详情页编辑历史面板中。
- 编辑署名、编辑历史署名、Life 作者和讨论作者可链接到对应公开 Profile。 - 编辑署名、编辑历史署名、Life 作者和讨论作者可链接到对应公开 Profile。
## Wiki 图片上传 ## Wiki 图片上传

View File

@@ -1142,7 +1142,6 @@ async function nextPokemonInternalId(
async function reorderTableRows( async function reorderTableRows(
client: DbClient, client: DbClient,
tableName: string, tableName: string,
entityType: string,
ids: number[], ids: number[],
userId: number userId: number
): Promise<void> { ): Promise<void> {
@@ -1171,9 +1170,6 @@ async function reorderTableRows(
`, `,
[nextSortOrder, userId, id] [nextSortOrder, userId, id]
); );
const changes: EditChange[] = [];
pushChange(changes, 'Sort order', String(previousSortOrder), String(nextSortOrder));
await recordEditLog(client, entityType, id, 'update', userId, changes);
} }
} }
@@ -2798,9 +2794,6 @@ export async function reorderDailyChecklistItems(payload: Record<string, unknown
`, `,
[nextSortOrder, userId, id] [nextSortOrder, userId, id]
); );
const changes: EditChange[] = [];
pushChange(changes, 'Sort order', String(previousSortOrder), String(nextSortOrder));
await recordEditLog(client, 'daily-checklist-items', id, 'update', userId, changes);
} }
}); });
@@ -5339,7 +5332,7 @@ export async function reorderConfig(type: ConfigType, payload: Record<string, un
} }
await withTransaction(async (client) => { await withTransaction(async (client) => {
await reorderTableRows(client, definition.table, type, ids, userId); await reorderTableRows(client, definition.table, ids, userId);
}); });
return listConfig(type, locale); return listConfig(type, locale);
@@ -5440,7 +5433,7 @@ async function reorderContent(type: SortableContentType, payload: Record<string,
} }
await withTransaction(async (client) => { await withTransaction(async (client) => {
await reorderTableRows(client, definition.table, definition.entityType, ids, userId); await reorderTableRows(client, definition.table, ids, userId);
}); });
} }
@@ -6618,7 +6611,7 @@ export async function createItem(payload: Record<string, unknown>, userId: numbe
} }
orderedIds.splice(targetIndex + (cleanPayload.insertAfterItemId !== null ? 1 : 0), 0, itemId); orderedIds.splice(targetIndex + (cleanPayload.insertAfterItemId !== null ? 1 : 0), 0, itemId);
await reorderTableRows(client, 'items', 'items', orderedIds, userId); await reorderTableRows(client, 'items', orderedIds, userId);
} }
await recordEditLog(client, 'items', itemId, 'create', userId); await recordEditLog(client, 'items', itemId, 'create', userId);
@@ -7526,7 +7519,7 @@ export async function reorderDishCategories(payload: Record<string, unknown>, us
throw validationError('server.validation.selectRecord'); throw validationError('server.validation.selectRecord');
} }
await withTransaction(async (client) => { await withTransaction(async (client) => {
await reorderTableRows(client, 'dish_categories', 'dish-categories', ids, userId); await reorderTableRows(client, 'dish_categories', ids, userId);
}); });
return listDish(locale); return listDish(locale);
} }
@@ -7537,7 +7530,7 @@ export async function reorderDishes(payload: Record<string, unknown>, userId: nu
throw validationError('server.validation.selectRecord'); throw validationError('server.validation.selectRecord');
} }
await withTransaction(async (client) => { await withTransaction(async (client) => {
await reorderTableRows(client, 'dishes', 'dishes', ids, userId); await reorderTableRows(client, 'dishes', ids, userId);
}); });
return listDish(locale); return listDish(locale);
} }

View File

@@ -2,7 +2,7 @@
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import type { EditHistoryAction, EditHistoryEntry, EditInfo, UserSummary } from '../services/api'; import type { EditHistoryAction, EditHistoryEntry, EditInfo, UserSummary } from '../services/api';
defineProps<{ const props = defineProps<{
entity: EditInfo; entity: EditInfo;
history: EditHistoryEntry[]; history: EditHistoryEntry[];
}>(); }>();
@@ -118,7 +118,11 @@ function changeValue(value: string): string {
} }
function visibleChanges(entry: EditHistoryEntry) { function visibleChanges(entry: EditHistoryEntry) {
return entry.changes.filter((change) => change.label !== 'Display ID'); return entry.changes.filter((change) => change.label !== 'Display ID' && change.label !== 'Sort order' && change.label !== '排序');
}
function visibleHistoryEntries() {
return props.history.filter((entry) => entry.action !== 'update' || visibleChanges(entry).length > 0);
} }
function historySummary(entry: EditHistoryEntry): string { function historySummary(entry: EditHistoryEntry): string {
@@ -148,29 +152,29 @@ function formatDateTime(value: string): string {
<div> <div>
<dt>{{ t('history.createdBy') }}</dt> <dt>{{ t('history.createdBy') }}</dt>
<dd> <dd>
<RouterLink v-if="entity.createdBy" class="user-profile-link" :to="`/profile/${entity.createdBy.id}`"> <RouterLink v-if="props.entity.createdBy" class="user-profile-link" :to="`/profile/${props.entity.createdBy.id}`">
{{ entity.createdBy.displayName }} {{ props.entity.createdBy.displayName }}
</RouterLink> </RouterLink>
<strong v-else>{{ displayName(entity.createdBy) }}</strong> <strong v-else>{{ displayName(props.entity.createdBy) }}</strong>
<time :datetime="entity.createdAt">{{ formatDateTime(entity.createdAt) }}</time> <time :datetime="props.entity.createdAt">{{ formatDateTime(props.entity.createdAt) }}</time>
</dd> </dd>
</div> </div>
<div> <div>
<dt>{{ t('history.lastEdited') }}</dt> <dt>{{ t('history.lastEdited') }}</dt>
<dd> <dd>
<RouterLink v-if="entity.updatedBy" class="user-profile-link" :to="`/profile/${entity.updatedBy.id}`"> <RouterLink v-if="props.entity.updatedBy" class="user-profile-link" :to="`/profile/${props.entity.updatedBy.id}`">
{{ entity.updatedBy.displayName }} {{ props.entity.updatedBy.displayName }}
</RouterLink> </RouterLink>
<strong v-else>{{ displayName(entity.updatedBy) }}</strong> <strong v-else>{{ displayName(props.entity.updatedBy) }}</strong>
<time :datetime="entity.updatedAt">{{ formatDateTime(entity.updatedAt) }}</time> <time :datetime="props.entity.updatedAt">{{ formatDateTime(props.entity.updatedAt) }}</time>
</dd> </dd>
</div> </div>
</dl> </dl>
<section class="edit-history-list" aria-labelledby="edit-history-list-title"> <section class="edit-history-list" aria-labelledby="edit-history-list-title">
<h3 id="edit-history-list-title">{{ t('history.editHistory') }}</h3> <h3 id="edit-history-list-title">{{ t('history.editHistory') }}</h3>
<ol v-if="history.length" class="edit-timeline"> <ol v-if="visibleHistoryEntries().length" class="edit-timeline">
<li v-for="entry in history" :key="`${entry.action}-${entry.createdAt}-${entry.user?.id ?? 'system'}`"> <li v-for="entry in visibleHistoryEntries()" :key="`${entry.action}-${entry.createdAt}-${entry.user?.id ?? 'system'}`">
<span class="edit-timeline__avatar" aria-hidden="true">{{ actionMark(entry.action) }}</span> <span class="edit-timeline__avatar" aria-hidden="true">{{ actionMark(entry.action) }}</span>
<div class="edit-timeline__body"> <div class="edit-timeline__body">
<details class="edit-history-entry"> <details class="edit-history-entry">