From 28f4e6032c2d2a462c9249976eed4cb0862ccfcc Mon Sep 17 00:00:00 2001 From: xiaomai Date: Mon, 4 May 2026 21:32:00 +0800 Subject: [PATCH] refactor: remove display ID from items and ancient artifacts Drop display_id column from items and ancient_artifacts tables Remove display ID inputs, labels, and sorting logic across the stack BREAKING CHANGE: behavior is not backward compatible. --- DESIGN.md | 16 ++-- backend/db/schema.sql | 26 +++--- backend/src/queries.ts | 83 +++++++------------- frontend/src/components/EditHistoryPanel.vue | 14 ++-- frontend/src/services/api.ts | 6 -- frontend/src/views/AdminView.vue | 22 +++--- frontend/src/views/AncientArtifactDetail.vue | 6 +- frontend/src/views/AncientArtifactEdit.vue | 8 -- frontend/src/views/AncientArtifactList.vue | 2 +- frontend/src/views/DishView.vue | 8 +- frontend/src/views/ItemDetail.vue | 6 +- frontend/src/views/ItemEdit.vue | 8 -- frontend/src/views/ItemsList.vue | 2 +- frontend/src/views/RecipeDetail.vue | 4 +- frontend/src/views/RecipeList.vue | 2 +- system-wordings.ts | 28 +++---- 16 files changed, 89 insertions(+), 152 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index f512623..0041ad7 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -593,7 +593,6 @@ Pokemon 详情页展示: 物品可配置: -- Display ID:用于物品和 Event Items 各自列表内展示与排序;`display_id` 与 `is_event_item` 组合唯一 - 名称 - 介绍 - 是否为 Event Item:`is_event_item` @@ -638,8 +637,8 @@ Items 与 Event Items 使用相同数据模型: - 按分类展示为标签页 - 按用途筛选 - 按标签筛选 -- 按 Display ID 和自定义排序展示 -- 物品列表卡片使用与 Pokemon 列表一致的居中图鉴式布局,只展示物品图标、`#Display ID 名称` 和分类;不展示标签、入手方式或编辑元信息。 +- 按自定义排序展示 +- 物品列表卡片使用与 Pokemon 列表一致的居中图鉴式布局,只展示物品图标、名称和分类;不展示标签、入手方式或编辑元信息。 - 有用途的物品在卡片左上角以斜 Ribbon 展示用途名称。 - 已配置图标时,物品卡片展示图标缩略图;未配置图标时保留默认物品标记。 @@ -648,7 +647,6 @@ Items 与 Event Items 使用相同数据模型: - 基本信息 - 当前图标图片;未配置图标时展示默认物品标记占位符 - 顶部按图标 / 占位符与核心信息概览并排展示,移动端改为单列;顶部概览卡片不显示 `Image` / `Details` 通用区块标题,也不展示图片历史缩略图 -- Display ID - 介绍 - 分类 - 用途 @@ -667,7 +665,6 @@ Items 与 Event Items 使用相同数据模型: Ancient Artifacts 是独立 Wiki 内容类型,可配置: -- Display ID:用于展示与排序 - 名称 - 介绍 - 图片:使用 Ancient Artifacts 上传目录,支持图片历史 @@ -684,12 +681,11 @@ Ancient Artifacts 列表功能: - 搜索 - 按分类展示为标签页 - 按标签筛选 -- 按 Display ID 和自定义排序展示 -- 列表卡片使用与 Pokemon 列表一致的居中图鉴式布局,展示图片 / 默认 Ancient Artifact 标记、`#Display ID 名称` 和分类;不展示编辑元信息。 +- 按自定义排序展示 +- 列表卡片使用与 Pokemon 列表一致的居中图鉴式布局,展示图片 / 默认 Ancient Artifact 标记、名称和分类;不展示编辑元信息。 Ancient Artifacts 详情页展示: -- Display ID - 名称 - 图片;未配置图片时展示默认 Ancient Artifact 标记 - 介绍 @@ -719,8 +715,8 @@ Ancient Artifacts 详情页展示: - 独立于物品列表展示 - 按结果物品分类展示 -- 按结果物品 Display ID 和自定义排序展示 -- 材料单列表卡片使用与 Pokemon 列表一致的居中图鉴式布局,按结果物品展示图标、`#Display ID 名称` 和分类;不展示编辑元信息。 +- 按自定义排序展示 +- 材料单列表卡片使用与 Pokemon 列表一致的居中图鉴式布局,按结果物品展示图标、名称和分类;不展示编辑元信息。 - 有用途的结果物品在卡片左上角以斜 Ribbon 展示用途名称。 - Create Recipe 按钮展示在结果物品名称下方;已有材料单的卡片保留同等按钮空间但不显示按钮;标记为无材料单的物品展示禁用按钮;可创建材料单的物品展示可点击按钮并进入创建流程。 diff --git a/backend/db/schema.sql b/backend/db/schema.sql index 3a73139..c9a7287 100644 --- a/backend/db/schema.sql +++ b/backend/db/schema.sql @@ -974,7 +974,6 @@ CREATE TABLE IF NOT EXISTS acquisition_methods ( CREATE TABLE IF NOT EXISTS items ( id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - display_id integer NOT NULL CHECK (display_id > 0), name text NOT NULL UNIQUE, details text NOT NULL DEFAULT '', category_key text NOT NULL DEFAULT 'other', @@ -1011,7 +1010,6 @@ CREATE TABLE IF NOT EXISTS items ( CREATE TABLE IF NOT EXISTS ancient_artifacts ( id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - display_id integer NOT NULL UNIQUE CHECK (display_id > 0), name text NOT NULL UNIQUE, details text NOT NULL DEFAULT '', category_key text NOT NULL CHECK (category_key IN ('lost-relics-l', 'lost-relics-s', 'fossils')), @@ -1221,7 +1219,6 @@ ALTER TABLE life_tags ADD COLUMN IF NOT EXISTS is_default boolean NOT NULL DEFAULT false; ALTER TABLE items - ADD COLUMN IF NOT EXISTS display_id integer, ADD COLUMN IF NOT EXISTS details text NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS category_key text, ADD COLUMN IF NOT EXISTS usage_key text; @@ -1242,10 +1239,6 @@ BEGIN END IF; END $$; -UPDATE items -SET display_id = id -WHERE display_id IS NULL; - UPDATE items i SET category_key = CASE lower(trim(c.name)) WHEN 'furniture' THEN 'furniture' @@ -1303,7 +1296,6 @@ WHERE usage_key IS NOT NULL AND usage_key NOT IN ('decoration', 'relaxation', 'toy', 'road'); ALTER TABLE items - ALTER COLUMN display_id SET NOT NULL, ALTER COLUMN category_key SET NOT NULL, ALTER COLUMN category_key SET DEFAULT 'other'; @@ -1313,7 +1305,6 @@ ALTER TABLE items DROP CONSTRAINT IF EXISTS items_usage_key_check; ALTER TABLE items - ADD CONSTRAINT items_display_id_positive CHECK (display_id > 0), ADD CONSTRAINT items_category_key_check CHECK (category_key IN ( 'furniture', 'misc', @@ -1330,6 +1321,20 @@ ALTER TABLE items )), ADD CONSTRAINT items_usage_key_check CHECK (usage_key IS NULL OR usage_key IN ('decoration', 'relaxation', 'toy', 'road')); +DROP INDEX IF EXISTS items_display_event_item_key; +DROP INDEX IF EXISTS items_display_order_idx; +DROP INDEX IF EXISTS ancient_artifacts_display_order_idx; + +ALTER TABLE ancient_artifacts + DROP CONSTRAINT IF EXISTS ancient_artifacts_display_id_key, + DROP CONSTRAINT IF EXISTS ancient_artifacts_display_id_check; + +ALTER TABLE items + DROP COLUMN IF EXISTS display_id; + +ALTER TABLE ancient_artifacts + DROP COLUMN IF EXISTS display_id; + CREATE INDEX IF NOT EXISTS environments_sort_order_idx ON environments(sort_order, id); CREATE INDEX IF NOT EXISTS skills_sort_order_idx ON skills(sort_order, id); CREATE INDEX IF NOT EXISTS favorite_things_sort_order_idx ON favorite_things(sort_order, id); @@ -1342,10 +1347,7 @@ CREATE INDEX IF NOT EXISTS item_categories_sort_order_idx ON item_categories(sor CREATE INDEX IF NOT EXISTS item_usages_sort_order_idx ON item_usages(sort_order, id); CREATE INDEX IF NOT EXISTS acquisition_methods_sort_order_idx ON acquisition_methods(sort_order, id); CREATE INDEX IF NOT EXISTS items_sort_order_idx ON items(sort_order, id); -CREATE UNIQUE INDEX IF NOT EXISTS items_display_event_item_key ON items(display_id, is_event_item); -CREATE INDEX IF NOT EXISTS items_display_order_idx ON items(is_event_item, display_id, sort_order, id); CREATE INDEX IF NOT EXISTS ancient_artifacts_sort_order_idx ON ancient_artifacts(sort_order, id); -CREATE INDEX IF NOT EXISTS ancient_artifacts_display_order_idx ON ancient_artifacts(display_id, sort_order, id); CREATE INDEX IF NOT EXISTS recipes_sort_order_idx ON recipes(sort_order, id); CREATE INDEX IF NOT EXISTS dish_categories_sort_order_idx ON dish_categories(sort_order, id); CREATE INDEX IF NOT EXISTS dish_flavors_sort_order_idx ON dish_flavors(sort_order, id); diff --git a/backend/src/queries.ts b/backend/src/queries.ts index de8e7f5..4178e8f 100644 --- a/backend/src/queries.ts +++ b/backend/src/queries.ts @@ -201,7 +201,6 @@ type PokemonCsvData = { }; type ItemPayload = { - displayId: number; name: string; details: string; translations: TranslationInput; @@ -220,7 +219,6 @@ type ItemPayload = { }; type AncientArtifactPayload = { - displayId: number; name: string; details: string; translations: TranslationInput; @@ -541,7 +539,6 @@ type PokemonChangeSource = { favorite_things: Array<{ name: string }>; } & TranslationChangeSource; type ItemChangeSource = { - displayId: number; name: string; details: string; isEventItem: boolean; @@ -554,7 +551,6 @@ type ItemChangeSource = { tags: Array<{ name: string }>; } & TranslationChangeSource; type AncientArtifactChangeSource = { - displayId: number; name: string; details: string; image: EntityImageValue | null; @@ -2251,7 +2247,6 @@ async function itemEditChanges( const tagNames = await entityNameMap(client, 'favorite_things', after.tagIds); pushChange(changes, 'Name', before.name, after.name); - pushChange(changes, 'Display ID', String(before.displayId), String(after.displayId)); pushChange(changes, 'Description', before.details, after.details); pushTranslationChanges(changes, before.translations, after.translations, ['name', 'details']); pushChange(changes, 'Event item', boolValue(before.isEventItem), boolValue(after.isEventItem)); @@ -2277,7 +2272,6 @@ async function ancientArtifactEditChanges( const tagNames = await entityNameMap(client, 'favorite_things', after.tagIds); pushChange(changes, 'Name', before.name, after.name); - pushChange(changes, 'Display ID', String(before.displayId), String(after.displayId)); pushChange(changes, 'Description', before.details, after.details); pushTranslationChanges(changes, before.translations, after.translations, ['name', 'details']); pushChange(changes, 'Image', imagePathLabel(before.image?.path), imagePathLabel(after.imagePath)); @@ -2574,7 +2568,7 @@ export async function globalSearch(paramsQuery: QueryParams = {}, locale = defau ${uploadedImageJson('i.image_path')} AS image FROM items i WHERE ${itemName} ILIKE $1 - ORDER BY i.display_id, ${orderByEntity('i')} + ORDER BY ${orderByEntity('i')} LIMIT $2 `, [pattern, limit] @@ -2591,7 +2585,7 @@ export async function globalSearch(paramsQuery: QueryParams = {}, locale = defau ${uploadedImageJson('a.image_path')} AS image FROM ancient_artifacts a WHERE ${artifactName} ILIKE $1 - ORDER BY a.display_id, ${orderByEntity('a')} + ORDER BY ${orderByEntity('a')} LIMIT $2 `, [pattern, limit] @@ -6223,7 +6217,6 @@ function itemProjection(locale: string): string { return ` SELECT i.id, - i.display_id AS "displayId", ${itemName} AS name, i.name AS "baseName", ${itemDetails} AS details, @@ -6324,8 +6317,8 @@ export async function listItems(paramsQuery: QueryParams, locale = defaultLocale const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; const orderClause = recipeOrder - ? `ORDER BY CASE WHEN item_recipe.id IS NULL THEN 1 ELSE 0 END, item_recipe.sort_order, item_recipe.id, i.display_id, ${orderByEntity('i')}` - : `ORDER BY i.display_id, ${orderByEntity('i')}`; + ? `ORDER BY CASE WHEN item_recipe.id IS NULL THEN 1 ELSE 0 END, item_recipe.sort_order, item_recipe.id, ${orderByEntity('i')}` + : `ORDER BY ${orderByEntity('i')}`; return query(`${itemProjection(locale)} ${whereClause} ${orderClause}`, params); } @@ -6382,7 +6375,6 @@ export async function getItem(id: number, locale = defaultLocale) { ), '[]'::json) AS materials, json_build_object( 'id', result_item.id, - 'displayId', result_item.display_id, 'name', ${resultItemName}, 'image', ${uploadedImageJson('result_item.image_path')}, 'category', ${systemListJsonSql('result_item.category_key', itemCategoryOptions, locale)}, @@ -6489,7 +6481,6 @@ function cleanItemPayload(payload: Record): ItemPayload { const usage = usageId === null ? null : systemListOptionById(itemUsageOptions, usageId, 'server.validation.usageRequired'); return { - displayId: requirePositiveInteger(payload.displayId, 'server.validation.itemDisplayIdRequired'), name: cleanName(payload.name, 'server.validation.itemNameRequired'), details: cleanOptionalText(payload.details), translations: cleanTranslations(payload.translations, ['name', 'details']), @@ -6546,7 +6537,6 @@ export async function createItem(payload: Record, userId: numbe const result = await client.query<{ id: number }>( ` INSERT INTO items ( - display_id, name, details, category_key, @@ -6561,11 +6551,10 @@ export async function createItem(payload: Record, userId: numbe created_by_user_id, updated_by_user_id ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $13) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $12) RETURNING id `, [ - cleanPayload.displayId, cleanPayload.name, cleanPayload.details, cleanPayload.categoryKey, @@ -6599,23 +6588,21 @@ export async function updateItem(id: number, payload: Record, u const result = await client.query( ` UPDATE items - SET display_id = $1, - name = $2, - details = $3, - category_key = $4, - usage_key = $5, - dyeable = $6, - dual_dyeable = $7, - pattern_editable = $8, - no_recipe = $9, - is_event_item = $10, - image_path = $11, - updated_by_user_id = $12, + SET name = $1, + details = $2, + category_key = $3, + usage_key = $4, + dyeable = $5, + dual_dyeable = $6, + pattern_editable = $7, + no_recipe = $8, + is_event_item = $9, + image_path = $10, + updated_by_user_id = $11, updated_at = now() - WHERE id = $13 + WHERE id = $12 `, [ - cleanPayload.displayId, cleanPayload.name, cleanPayload.details, cleanPayload.categoryKey, @@ -6665,7 +6652,6 @@ function ancientArtifactProjection(locale: string): string { return ` SELECT a.id, - a.display_id AS "displayId", ${artifactName} AS name, a.name AS "baseName", ${artifactDetails} AS details, @@ -6719,7 +6705,7 @@ export async function listAncientArtifacts(paramsQuery: QueryParams = {}, locale } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; - return query(`${ancientArtifactProjection(locale)} ${whereClause} ORDER BY a.display_id, ${orderByEntity('a')}`, params); + return query(`${ancientArtifactProjection(locale)} ${whereClause} ORDER BY ${orderByEntity('a')}`, params); } export async function getAncientArtifact(id: number, locale = defaultLocale) { @@ -6738,7 +6724,6 @@ function cleanAncientArtifactPayload(payload: Record): AncientA const category = systemListOptionById(ancientArtifactCategoryOptions, categoryId, 'server.validation.categoryRequired'); return { - displayId: requirePositiveInteger(payload.displayId, 'server.validation.artifactDisplayIdRequired'), name: cleanName(payload.name, 'server.validation.artifactNameRequired'), details: cleanOptionalText(payload.details), translations: cleanTranslations(payload.translations, ['name', 'details']), @@ -6768,7 +6753,6 @@ export async function createAncientArtifact(payload: Record, us const result = await client.query<{ id: number }>( ` INSERT INTO ancient_artifacts ( - display_id, name, details, category_key, @@ -6777,11 +6761,10 @@ export async function createAncientArtifact(payload: Record, us created_by_user_id, updated_by_user_id ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $7) + VALUES ($1, $2, $3, $4, $5, $6, $6) RETURNING id `, [ - cleanPayload.displayId, cleanPayload.name, cleanPayload.details, cleanPayload.categoryKey, @@ -6809,16 +6792,15 @@ export async function updateAncientArtifact(id: number, payload: Record = { 标题: 'pages.checklist.task', 'Pokemon ID': 'pages.pokemon.id', 'Pokopia ID': 'pages.pokemon.id', - 'Display ID': 'pages.items.displayId', 'Event item': 'common.eventItem', 'Event Pokemon': 'pages.pokemon.eventItem', 'Event Habitat': 'pages.habitats.eventItem', @@ -118,12 +117,17 @@ function changeValue(value: string): string { return values[value] ?? value; } +function visibleChanges(entry: EditHistoryEntry) { + return entry.changes.filter((change) => change.label !== 'Display ID'); +} + function historySummary(entry: EditHistoryEntry): string { - if (!entry.changes.length) { + const changes = visibleChanges(entry); + if (!changes.length) { return actionLabel(entry.action); } - return entry.changes.map((change) => changeLabel(change.label)).join(locale.value === 'zh-CN' ? '、' : ', '); + return changes.map((change) => changeLabel(change.label)).join(locale.value === 'zh-CN' ? '、' : ', '); } function formatDateTime(value: string): string { @@ -175,8 +179,8 @@ function formatDateTime(value: string): string {
-
-
+
+
{{ changeLabel(change.label) }}
{{ t('history.before') }} diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 3f90e6d..9c9c7fb 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -246,7 +246,6 @@ export interface HabitatUsage { } export interface RecipeResultItem extends NamedEntity { - displayId: number; image?: EntityImage | null; category?: NamedEntity; usage?: NamedEntity | null; @@ -254,7 +253,6 @@ export interface RecipeResultItem extends NamedEntity { export interface Item extends EditInfo { id: number; - displayId: number; name: string; baseName?: string; details: string; @@ -276,7 +274,6 @@ export interface Item extends EditInfo { export interface AncientArtifact extends EditInfo { id: number; - displayId: number; name: string; baseName?: string; details: string; @@ -312,7 +309,6 @@ export interface Recipe extends EditInfo { } export interface ItemLink extends NamedEntity { - displayId: number; image?: EntityImage | null; category?: NamedEntity; } @@ -791,7 +787,6 @@ export interface PokemonImageOptionsResult { } export interface ItemPayload { - displayId: number; name: string; details: string; translations?: TranslationMap; @@ -808,7 +803,6 @@ export interface ItemPayload { } export interface AncientArtifactPayload { - displayId: number; name: string; details: string; translations?: TranslationMap; diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue index fff43ac..0129236 100644 --- a/frontend/src/views/AdminView.vue +++ b/frontend/src/views/AdminView.vue @@ -448,15 +448,15 @@ 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.displayId} ${item.name}`; +const itemLabel = (item: Item) => item.name; const ancientArtifactKey = (item: AncientArtifact) => item.id; -const ancientArtifactLabel = (item: AncientArtifact) => `#${item.displayId} ${item.name}`; +const ancientArtifactLabel = (item: AncientArtifact) => item.name; const recipeKey = (item: Recipe) => item.id; const recipeLabel = (item: Recipe) => item.name; const dishCategoryKey = (item: DishCategory) => item.id; const dishCategoryLabel = (item: DishCategory) => item.name; const dishKey = (item: Dish) => item.id; -const dishLabel = (item: Dish) => `#${item.item.displayId} ${item.item.name}`; +const dishLabel = (item: Dish) => item.item.name; const habitatKey = (item: Habitat) => item.id; const habitatLabel = (item: Habitat) => item.name; @@ -2260,7 +2260,7 @@ onMounted(() => { @reorder="persistItemOrder" >