diff --git a/DESIGN.md b/DESIGN.md index 729887c..2a1f62c 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -27,7 +27,7 @@ - 全局搜索 API 只返回公开浏览所需的最小结果字段:结果类型、ID、展示标题、目标 URL、可选摘要和可选图片;用户搜索结果只使用公开 Profile 所需的 `id`、`displayName` 和目标 URL,不返回邮箱、角色、权限、Referral、编辑审计、审核原因、token/hash、内部字段或调试信息。 - 用户界面只展示业务数据和设计内的文案,不展示提示词、计划、调试信息、字段内部名或修改说明。 - 可编辑 Wiki 内容必须记录创建者、最后编辑者、创建时间、最后编辑时间和编辑历史。 -- 列表顺序由 `sort_order` 控制,默认按创建时间旧到新初始化,排序值按 10 递增以便后续插入和拖拽排序。 +- 除 Pokemon 外,列表顺序由 `sort_order` 控制,默认按创建时间旧到新初始化,排序值按 10 递增以便后续插入和拖拽排序;Pokemon 列表按内部 `id` 升序展示,不提供手动排序。 ## 国际化 @@ -358,7 +358,7 @@ - `created_at` - 详情页展示最后编辑者、最后编辑时间和编辑历史面板。 - 编辑历史中的用户信息只展示必要署名,不暴露邮箱、token、hash 或内部元数据。 -- 排序操作仍更新列表顺序、最后编辑者和最后编辑时间,但 `sort_order` / Sort order 字段变更不写入或展示在详情页编辑历史面板中。 +- 非 Pokemon 列表排序操作仍更新列表顺序、最后编辑者和最后编辑时间,但 `sort_order` / Sort order 字段变更不写入或展示在详情页编辑历史面板中。 - 编辑署名、编辑历史署名、Life 作者和讨论作者可链接到对应公开 Profile。 ## Wiki 图片上传 @@ -529,7 +529,6 @@ Pokemon 可配置: - Speed - 出现的栖息地:由栖息地出现配置反向展示 - 翻译 -- 排序 普通 Pokemon 与 Event Pokemon 分开展示: @@ -586,7 +585,7 @@ Pokemon 列表功能: - 按喜欢的东西筛选: - 满足任意条件 - 满足全部条件 -- 按自定义排序展示 +- 按 Pokemon 内部 `id` 升序展示 - 列表首屏只读取一页数据;滚动到列表底部时继续读取下一页,不一次性加载全部 Pokemon。 - Pokemon 列表卡片只展示 Pokemon 图片和下方的 `#ID 名称`;不展示喜欢的环境、属性、特长、喜欢的东西或编辑元信息。 - Pokemon 卡片在已配置图片时展示所选图片缩略图;未配置图片时保留默认 Poké Ball 标记。 @@ -1005,7 +1004,7 @@ API 暴露边界: - 全局主导航使用 `AppShell` 侧边栏;移动端通过导航按钮打开侧边栏抽屉。 - 管理入口在全局侧边栏中保持单一 Admin 入口,`/admin` 内部使用页面内二级菜单分组组织管理模块: - 配置:System config。 - - 内容:Daily CheckList、Pokemon、物品、材料单、栖息地的维护、排序或删除入口,以及 Data Tools。 + - 内容:Daily CheckList、Pokemon、物品、材料单、栖息地的维护、排序或删除入口,以及 Data Tools;Pokemon 在 Admin 中可删除但不提供手动排序。 - 内容管理包含 Items、Event Items 与 Ancient Artifacts;Items / Event Items 使用同一物品数据模型,通过 `is_event_item` 拆分入口。 - 本地化:Languages、System wordings。 - 访问权限:Users、Roles、Permissions、Rate limits。 @@ -1190,7 +1189,7 @@ API 暴露边界: - `GET /api/admin/ai-moderation` - `PUT /api/admin/ai-moderation` - `PUT /api/admin/system-wordings/:key` -- Pokemon、物品、材料单、栖息地的列表排序需要对应实体的 `order` 权限。 +- 物品、材料单、栖息地的列表排序需要对应实体的 `order` 权限;Pokemon 按内部 `id` 排序,不提供列表排序 API 或 Admin 手动排序入口。 ## 开发与验证 diff --git a/backend/db/schema.sql b/backend/db/schema.sql index 15d30b1..2c7fa69 100644 --- a/backend/db/schema.sql +++ b/backend/db/schema.sql @@ -231,7 +231,6 @@ VALUES ('pokemon.create', 'Create Pokemon', 'Create Pokemon records.', 'Pokemon', true), ('pokemon.update', 'Update Pokemon', 'Edit Pokemon records.', 'Pokemon', true), ('pokemon.delete', 'Delete Pokemon', 'Delete Pokemon records.', 'Pokemon', true), - ('pokemon.order', 'Order Pokemon', 'Reorder Pokemon records.', 'Pokemon', true), ('pokemon.fetch', 'Fetch Pokemon data', 'Fetch Pokemon data and sprite candidates.', 'Pokemon', true), ('pokemon.upload', 'Upload Pokemon images', 'Upload Pokemon images.', 'Pokemon', true), ('habitats.create', 'Create habitats', 'Create habitat records.', 'Habitats', true), @@ -275,6 +274,9 @@ VALUES ('discussions.comments.like', 'Like discussion comments', 'Like and unlike entity discussion comments.', 'Discussions', true) ON CONFLICT (key) DO NOTHING; +DELETE FROM permissions +WHERE key = 'pokemon.order'; + INSERT INTO roles (key, name, description, level, enabled, system_role) VALUES ('owner', 'Owner', 'Highest-level system owner with all permissions.', 1000, true, true), @@ -329,7 +331,6 @@ JOIN permissions p ON p.key = ANY (ARRAY[ 'pokemon.create', 'pokemon.update', 'pokemon.delete', - 'pokemon.order', 'pokemon.fetch', 'pokemon.upload', 'habitats.create', @@ -411,7 +412,6 @@ JOIN permissions p ON p.key = ANY (ARRAY[ 'checklist.order', 'pokemon.create', 'pokemon.update', - 'pokemon.order', 'pokemon.fetch', 'pokemon.upload', 'habitats.create', diff --git a/backend/src/queries.ts b/backend/src/queries.ts index 327c897..14a53ff 100644 --- a/backend/src/queries.ts +++ b/backend/src/queries.ts @@ -108,7 +108,7 @@ type ConfigDefinition = { hasRateable?: boolean; hasChangeLog?: boolean; }; -type SortableContentType = 'pokemon' | 'items' | 'ancient-artifacts' | 'recipes' | 'habitats'; +type SortableContentType = 'items' | 'ancient-artifacts' | 'recipes' | 'habitats'; type SortableContentDefinition = { table: string; entityType: SortableContentType; @@ -691,7 +691,6 @@ const configDefinitions: Record = { }; const sortableContentDefinitions: Record = { - pokemon: { table: 'pokemon', entityType: 'pokemon' }, items: { table: 'items', entityType: 'items' }, 'ancient-artifacts': { table: 'items', entityType: 'ancient-artifacts' }, recipes: { table: 'recipes', entityType: 'recipes' }, @@ -2809,7 +2808,7 @@ export async function globalSearch(paramsQuery: QueryParams = {}, locale = defau ${pokemonImageJson('p')} AS image FROM pokemon p WHERE ${pokemonName} ILIKE $1 - ORDER BY ${orderByEntity('p')} + ORDER BY p.id LIMIT $2 `, [pattern, limit] @@ -5746,11 +5745,6 @@ async function reorderContent(type: SortableContentType, payload: Record, userId: number, locale = defaultLocale) { - await reorderContent('pokemon', payload, userId); - return listPokemon({}, locale); -} - export async function reorderItems(payload: Record, userId: number, locale = defaultLocale) { await reorderContent('items', payload, userId); return listItems({}, locale); @@ -5822,7 +5816,7 @@ export async function listPokemon(paramsQuery: QueryParams, locale = defaultLoca } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; - return queryMaybePaged(`${pokemonProjection(locale)} ${whereClause} ORDER BY ${orderByEntity('p')}`, params, paramsQuery); + return queryMaybePaged(`${pokemonProjection(locale)} ${whereClause} ORDER BY p.id`, params, paramsQuery); } export async function getPokemon(id: number, locale = defaultLocale) { @@ -5927,7 +5921,6 @@ export async function getPokemon(id: number, locale = defaultLocale) { scored_pokemon AS ( SELECT related_pokemon.id, - related_pokemon.sort_order, (related_pokemon.environment_id = current_pokemon.environment_id) AS "environmentMatches", COUNT(current_favourites.favorite_thing_id)::integer AS "favoriteThingMatchCount" FROM current_pokemon @@ -5936,7 +5929,7 @@ export async function getPokemon(id: number, locale = defaultLocale) { ON related_pokemon_favourite.pokemon_id = related_pokemon.id LEFT JOIN current_favourites ON current_favourites.favorite_thing_id = related_pokemon_favourite.favorite_thing_id - GROUP BY related_pokemon.id, related_pokemon.sort_order, related_pokemon.environment_id, current_pokemon.environment_id + GROUP BY related_pokemon.id, related_pokemon.environment_id, current_pokemon.environment_id HAVING related_pokemon.environment_id = current_pokemon.environment_id OR COUNT(current_favourites.favorite_thing_id) > 0 ) @@ -5981,7 +5974,7 @@ export async function getPokemon(id: number, locale = defaultLocale) { FROM scored_pokemon JOIN pokemon related_pokemon ON related_pokemon.id = scored_pokemon.id JOIN environments related_environment ON related_environment.id = related_pokemon.environment_id - ORDER BY scored_pokemon."environmentMatches" DESC, scored_pokemon."favoriteThingMatchCount" DESC, scored_pokemon.sort_order, related_pokemon.id + ORDER BY scored_pokemon."environmentMatches" DESC, scored_pokemon."favoriteThingMatchCount" DESC, related_pokemon.id `, [id] ), @@ -6369,10 +6362,10 @@ export async function listHabitats(paramsQuery: QueryParams = {}, locale = defau 'name', pokemon_rows.name, 'isEventItem', pokemon_rows.is_event_item ) - ORDER BY pokemon_rows.sort_order, pokemon_rows.id + ORDER BY pokemon_rows.id ) FROM ( - SELECT DISTINCT p.id, p.display_id, ${pokemonName} AS name, p.is_event_item, p.sort_order + SELECT DISTINCT p.id, p.display_id, ${pokemonName} AS name, p.is_event_item FROM habitat_pokemon hp JOIN pokemon p ON p.id = hp.pokemon_id WHERE hp.habitat_id = h.id @@ -6443,7 +6436,7 @@ export async function getHabitat(id: number, locale = defaultLocale) { JOIN pokemon p ON p.id = hp.pokemon_id JOIN maps m ON m.id = hp.map_id WHERE hp.habitat_id = $1 - ORDER BY hp.rarity, ${orderByEntity('p')}, ${orderByEntity('m')} + ORDER BY hp.rarity, p.id, ${orderByEntity('m')} `, [id] ), @@ -6855,7 +6848,7 @@ export async function getItem(id: number, locale = defaultLocale) { JOIN skills s ON s.id = psid.skill_id WHERE psid.item_id = $1 AND s.has_item_drop = true - ORDER BY ${orderByEntity('p')}, ${orderByEntity('s')} + ORDER BY p.id, ${orderByEntity('s')} `, [id] ), @@ -6893,7 +6886,7 @@ export async function getItem(id: number, locale = defaultLocale) { WHERE ps.pokemon_id = p.id AND trading_skill.has_trading = true ) - ORDER BY pti.preference DESC, ${orderByEntity('p')} + ORDER BY pti.preference DESC, p.id `, [id] ), @@ -8493,7 +8486,7 @@ async function exportGenericScopeData(client: DbClient, entityType: string, incl async function exportScopeData(client: DbClient, scope: DataToolScope): Promise { if (scope === 'pokemon') { return { - pokemon: await tableRows(client, 'SELECT * FROM pokemon ORDER BY sort_order, id'), + pokemon: await tableRows(client, 'SELECT * FROM pokemon ORDER BY id'), pokemonTypeLinks: await tableRows(client, 'SELECT * FROM pokemon_pokemon_types ORDER BY pokemon_id, slot_order'), pokemonSkills: await tableRows(client, 'SELECT * FROM pokemon_skills ORDER BY pokemon_id, skill_id'), pokemonFavoriteThings: await tableRows(client, 'SELECT * FROM pokemon_favorite_things ORDER BY pokemon_id, favorite_thing_id'), diff --git a/backend/src/server.ts b/backend/src/server.ts index 9a56408..e28b503 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -111,7 +111,6 @@ import { reorderHabitats, reorderItems, reorderLanguages, - reorderPokemon, reorderRecipes, retryEntityDiscussionCommentModeration, retryLifeCommentModeration, @@ -2092,11 +2091,6 @@ app.delete('/api/admin/daily-checklist/:id', async (request, reply) => { return deleted ? reply.code(204).send() : notFound(reply, request); }); -app.put('/api/admin/pokemon/order', async (request, reply) => { - const user = await requirePermissionWithRateLimits(request, reply, 'pokemon.order', 'wikiWrite'); - return user ? reorderPokemon(request.body as Record, user.id, requestLocale(request)) : undefined; -}); - app.put('/api/admin/items/order', async (request, reply) => { const user = await requirePermissionWithRateLimits(request, reply, 'items.order', 'wikiWrite'); return user ? reorderItems(request.body as Record, user.id, requestLocale(request)) : undefined; diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 73b441e..32f4f93 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -1460,7 +1460,6 @@ export const api = { updatePokemon: (id: string | number, payload: PokemonPayload) => sendJson(`/api/pokemon/${id}`, 'PUT', payload), deletePokemon: (id: string | number) => deleteJson(`/api/pokemon/${id}`), - reorderPokemon: (ids: number[]) => sendJson('/api/admin/pokemon/order', 'PUT', { ids }), habitats: (params: Record = {}) => getJson(`/api/habitats${buildQuery(params)}`), habitatsPage: (params: PublicListQueryParams = {}) => diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue index 105da35..af58826 100644 --- a/frontend/src/views/AdminView.vue +++ b/frontend/src/views/AdminView.vue @@ -156,7 +156,7 @@ const adminNavigationGroups = computed(() => { label: t('pages.admin.contentGroup'), items: [ { key: 'checklist', label: t('pages.admin.checklist'), permission: ['checklist.create', 'checklist.update', 'checklist.delete', 'checklist.order'] }, - { key: 'pokemon', label: t('pages.admin.pokemonList'), permission: ['pokemon.order', 'pokemon.delete'] }, + { key: 'pokemon', label: t('pages.admin.pokemonList'), permission: 'pokemon.delete' }, { key: 'items', label: t('pages.admin.itemList'), permission: ['items.order', 'items.delete'] }, { key: 'ancientArtifacts', @@ -502,8 +502,6 @@ const languageKey = (item: Language) => item.code; const languageLabel = (item: Language) => item.name; const configKey = (item: EditableConfig) => item.id; const configLabel = (item: EditableConfig) => item.name; -const pokemonKey = (item: Pokemon) => item.id; -const pokemonLabel = (item: Pokemon) => `#${item.displayId} ${item.name}`; const itemKey = (item: Item) => item.id; const itemLabel = (item: Item) => item.name; const ancientArtifactKey = (item: AncientArtifact) => item.id; @@ -932,10 +930,6 @@ function previewConfigOrder(rows: EditableConfig[]) { configRows.value = rows; } -function previewPokemonOrder(rows: Pokemon[]) { - pokemonRows.value = rows; -} - function previewItemOrder(rows: Item[]) { itemRows.value = rows; } @@ -1004,18 +998,6 @@ async function persistConfigOrder(nextRows: EditableConfig[], fallbackRows: Edit }); } -async function persistPokemonOrder(nextRows: Pokemon[], fallbackRows: Pokemon[]) { - pokemonRows.value = nextRows; - await run(async () => { - try { - pokemonRows.value = await api.reorderPokemon(nextRows.map((item) => item.id)); - } catch (error) { - pokemonRows.value = fallbackRows; - throw error; - } - }); -} - async function persistItemOrder(nextRows: Item[], fallbackRows: Item[]) { itemRows.value = nextRows; await run(async () => { @@ -2319,20 +2301,8 @@ onMounted(() => {

{{ t('pages.admin.pokemonList') }}

- - - + +

{{ t('common.noRecords') }}