feat(items): add base price and support usage in creation defaults
Add `base_price` to items schema, API, and edit history Display and edit base price in item details and forms Add `clearable` prop to TagsSelect for optional single selections Include usage in item creation session defaults
This commit is contained in:
@@ -596,6 +596,7 @@ Pokemon 详情页展示:
|
||||
|
||||
- 名称
|
||||
- 介绍
|
||||
- Base Price:可为空
|
||||
- 是否为 Event Item:`is_event_item`
|
||||
- 分类:必填,使用系统固定列表,不在管理端配置:
|
||||
- Furniture
|
||||
@@ -640,7 +641,7 @@ Items 与 Event Items 使用相同数据模型:
|
||||
- 按标签筛选
|
||||
- 按自定义排序展示
|
||||
- All 视图在满足写入权限时支持对 Grid Item 右键插入新物品到前/后,并支持直接拖曳 Item 调整排序;插入与拖曳只作用于当前展示的 Items 列表,不影响 Event Items 入口。
|
||||
- 新增物品入口支持当前浏览器 Session 的默认值菜单;用户可为新建物品预设分类、客制化勾选项和入手方式。默认值只影响 `/items/new` 与 `/event-items/new` 的新建表单初始值,不影响编辑已有物品,不改变 API、数据库模型、权限或审计行为;Event Items 仍由 `/event-items/new` 入口决定 `is_event_item`。
|
||||
- 新增物品入口支持当前浏览器 Session 的默认值菜单;用户可为新建物品预设分类、用途、客制化勾选项和入手方式。默认值只影响 `/items/new` 与 `/event-items/new` 的新建表单初始值,不影响编辑已有物品,不改变 API、数据库模型、权限或审计行为;Event Items 仍由 `/event-items/new` 入口决定 `is_event_item`。
|
||||
- 物品列表桌面端使用 12 列紧凑 Grid,每个格子只展示物品图标;有用途的物品在卡片左上角以斜 Ribbon 展示用途名称;物品名称通过 hover / focus Tooltip 展示。
|
||||
- 物品列表移动端保持常规卡片布局,展示物品图标、名称和分类。
|
||||
- 物品列表不展示标签、入手方式或编辑元信息。
|
||||
@@ -652,6 +653,7 @@ Items 与 Event Items 使用相同数据模型:
|
||||
- 当前图标图片;未配置图标时展示默认物品标记占位符
|
||||
- 顶部按图标 / 占位符与核心信息概览并排展示,移动端改为单列;顶部概览卡片不显示 `Image` / `Details` 通用区块标题,也不展示图片历史缩略图
|
||||
- 介绍
|
||||
- Base Price
|
||||
- 分类
|
||||
- 用途
|
||||
- 入手方式
|
||||
|
||||
@@ -976,6 +976,7 @@ CREATE TABLE IF NOT EXISTS items (
|
||||
id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name text NOT NULL UNIQUE,
|
||||
details text NOT NULL DEFAULT '',
|
||||
base_price integer,
|
||||
category_key text NOT NULL DEFAULT 'other',
|
||||
usage_key text,
|
||||
category_id integer REFERENCES item_categories(id),
|
||||
@@ -1220,9 +1221,14 @@ ALTER TABLE life_tags
|
||||
|
||||
ALTER TABLE items
|
||||
ADD COLUMN IF NOT EXISTS details text NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS base_price integer,
|
||||
ADD COLUMN IF NOT EXISTS category_key text,
|
||||
ADD COLUMN IF NOT EXISTS usage_key text;
|
||||
|
||||
UPDATE items
|
||||
SET base_price = NULL
|
||||
WHERE base_price < 0;
|
||||
|
||||
ALTER TABLE ancient_artifacts
|
||||
ADD COLUMN IF NOT EXISTS image_path text NOT NULL DEFAULT '';
|
||||
|
||||
@@ -1301,10 +1307,12 @@ ALTER TABLE items
|
||||
|
||||
ALTER TABLE items
|
||||
DROP CONSTRAINT IF EXISTS items_display_id_positive,
|
||||
DROP CONSTRAINT IF EXISTS items_base_price_check,
|
||||
DROP CONSTRAINT IF EXISTS items_category_key_check,
|
||||
DROP CONSTRAINT IF EXISTS items_usage_key_check;
|
||||
|
||||
ALTER TABLE items
|
||||
ADD CONSTRAINT items_base_price_check CHECK (base_price IS NULL OR base_price >= 0),
|
||||
ADD CONSTRAINT items_category_key_check CHECK (category_key IN (
|
||||
'furniture',
|
||||
'misc',
|
||||
|
||||
@@ -203,6 +203,7 @@ type PokemonCsvData = {
|
||||
type ItemPayload = {
|
||||
name: string;
|
||||
details: string;
|
||||
basePrice: number | null;
|
||||
translations: TranslationInput;
|
||||
categoryId: number;
|
||||
categoryKey: string;
|
||||
@@ -543,6 +544,7 @@ type PokemonChangeSource = {
|
||||
type ItemChangeSource = {
|
||||
name: string;
|
||||
details: string;
|
||||
basePrice: number | null;
|
||||
isEventItem: boolean;
|
||||
image: EntityImageValue | null;
|
||||
category: { name: string };
|
||||
@@ -1077,6 +1079,20 @@ function cleanNonNegativeNumber(value: unknown, message: string): number {
|
||||
return numberValue;
|
||||
}
|
||||
|
||||
function cleanOptionalNonNegativeInteger(value: unknown, message: string): number | null {
|
||||
const rawValue = typeof value === 'string' ? value.trim() : value;
|
||||
if (rawValue === undefined || rawValue === null || rawValue === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const numberValue = Number(rawValue);
|
||||
if (!Number.isInteger(numberValue) || numberValue < 0) {
|
||||
throw validationError(message);
|
||||
}
|
||||
|
||||
return numberValue;
|
||||
}
|
||||
|
||||
function cleanQuantities(value: unknown): IdQuantity[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
@@ -2246,6 +2262,12 @@ async function itemEditChanges(
|
||||
|
||||
pushChange(changes, 'Name', before.name, after.name);
|
||||
pushChange(changes, 'Description', before.details, after.details);
|
||||
pushChange(
|
||||
changes,
|
||||
'Base Price',
|
||||
before.basePrice === null ? null : String(before.basePrice),
|
||||
after.basePrice === null ? null : String(after.basePrice)
|
||||
);
|
||||
pushTranslationChanges(changes, before.translations, after.translations, ['name', 'details']);
|
||||
pushChange(changes, 'Event item', boolValue(before.isEventItem), boolValue(after.isEventItem));
|
||||
pushChange(changes, 'Image', imagePathLabel(before.image?.path), imagePathLabel(after.imagePath));
|
||||
@@ -6216,6 +6238,7 @@ function itemProjection(locale: string): string {
|
||||
i.name AS "baseName",
|
||||
${itemDetails} AS details,
|
||||
i.details AS "baseDetails",
|
||||
i.base_price AS "basePrice",
|
||||
i.is_event_item AS "isEventItem",
|
||||
${translationsSelect('items', 'i.id')} AS translations,
|
||||
${auditSelect('i', 'item_created_user', 'item_updated_user')},
|
||||
@@ -6484,6 +6507,7 @@ function cleanItemPayload(payload: Record<string, unknown>): ItemPayload {
|
||||
return {
|
||||
name: cleanName(payload.name, 'server.validation.itemNameRequired'),
|
||||
details: cleanOptionalText(payload.details),
|
||||
basePrice: cleanOptionalNonNegativeInteger(payload.basePrice, 'server.validation.invalidField'),
|
||||
translations: cleanTranslations(payload.translations, ['name', 'details']),
|
||||
categoryId,
|
||||
categoryKey: category.key,
|
||||
@@ -6558,6 +6582,7 @@ export async function createItem(payload: Record<string, unknown>, userId: numbe
|
||||
INSERT INTO items (
|
||||
name,
|
||||
details,
|
||||
base_price,
|
||||
category_key,
|
||||
usage_key,
|
||||
dyeable,
|
||||
@@ -6570,12 +6595,13 @@ export async function createItem(payload: Record<string, unknown>, userId: numbe
|
||||
created_by_user_id,
|
||||
updated_by_user_id
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $12)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $13)
|
||||
RETURNING id
|
||||
`,
|
||||
[
|
||||
cleanPayload.name,
|
||||
cleanPayload.details,
|
||||
cleanPayload.basePrice,
|
||||
cleanPayload.categoryKey,
|
||||
cleanPayload.usageKey,
|
||||
cleanPayload.dyeable,
|
||||
@@ -6631,21 +6657,23 @@ export async function updateItem(id: number, payload: Record<string, unknown>, u
|
||||
UPDATE items
|
||||
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,
|
||||
base_price = $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,
|
||||
updated_at = now()
|
||||
WHERE id = $12
|
||||
WHERE id = $13
|
||||
`,
|
||||
[
|
||||
cleanPayload.name,
|
||||
cleanPayload.details,
|
||||
cleanPayload.basePrice,
|
||||
cleanPayload.categoryKey,
|
||||
cleanPayload.usageKey,
|
||||
cleanPayload.dyeable,
|
||||
@@ -7586,6 +7614,7 @@ const dataToolColumns = {
|
||||
'id',
|
||||
'name',
|
||||
'details',
|
||||
'base_price',
|
||||
'category_key',
|
||||
'usage_key',
|
||||
'dyeable',
|
||||
|
||||
@@ -49,6 +49,9 @@ const changeLabelKeys: Record<string, string> = {
|
||||
分类: 'pages.items.category',
|
||||
Usage: 'pages.items.usage',
|
||||
用途: 'pages.items.usage',
|
||||
'Base Price': 'pages.items.basePrice',
|
||||
'Base price': 'pages.items.basePrice',
|
||||
基础价格: 'pages.items.basePrice',
|
||||
Dyeable: 'pages.items.dyeable',
|
||||
可染色: 'pages.items.dyeable',
|
||||
'Dual dyeable': 'pages.items.dualDyeable',
|
||||
|
||||
@@ -33,12 +33,14 @@ const props = withDefaults(
|
||||
creating?: boolean;
|
||||
createLabel?: string;
|
||||
dropdownStrategy?: DropdownStrategy;
|
||||
clearable?: boolean;
|
||||
}>(),
|
||||
{
|
||||
multiple: true,
|
||||
max: 0,
|
||||
allowCreate: false,
|
||||
creating: false
|
||||
creating: false,
|
||||
clearable: false
|
||||
}
|
||||
);
|
||||
|
||||
@@ -167,6 +169,12 @@ function updateValue(values: string[]) {
|
||||
|
||||
function selectOption(value: string) {
|
||||
if (!props.multiple) {
|
||||
if (props.clearable && selectedValues.value.has(value)) {
|
||||
updateValue([]);
|
||||
closeDropdown();
|
||||
return;
|
||||
}
|
||||
|
||||
updateValue([value]);
|
||||
closeDropdown();
|
||||
return;
|
||||
|
||||
@@ -257,6 +257,7 @@ export interface Item extends EditInfo {
|
||||
baseName?: string;
|
||||
details: string;
|
||||
baseDetails?: string;
|
||||
basePrice: number | null;
|
||||
isEventItem: boolean;
|
||||
translations?: TranslationMap;
|
||||
image: EntityImage | null;
|
||||
@@ -789,6 +790,7 @@ export interface PokemonImageOptionsResult {
|
||||
export interface ItemPayload {
|
||||
name: string;
|
||||
details: string;
|
||||
basePrice: number | null;
|
||||
translations?: TranslationMap;
|
||||
categoryId: number;
|
||||
usageId: number | null;
|
||||
|
||||
@@ -17,7 +17,7 @@ import { api, getAuthToken, type AuthUser, type ItemDetail } from '../services/a
|
||||
import ItemEdit from './ItemEdit.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const { locale, t } = useI18n();
|
||||
const item = ref<ItemDetail | null>(null);
|
||||
const currentUser = ref<AuthUser | null>(null);
|
||||
const detailTab = ref('details');
|
||||
@@ -38,6 +38,10 @@ const itemSubtitle = computed(() => {
|
||||
});
|
||||
const detailKicker = computed(() => (item.value?.isEventItem ? t('pages.eventItems.detailKicker') : t('pages.items.detailKicker')));
|
||||
const listTarget = computed(() => (item.value?.isEventItem ? '/event-items' : '/items'));
|
||||
const basePriceDisplay = computed(() => {
|
||||
const price = item.value?.basePrice;
|
||||
return price === null || price === undefined ? t('common.none') : new Intl.NumberFormat(locale.value).format(price);
|
||||
});
|
||||
|
||||
const customization = computed(() => {
|
||||
if (!item.value) {
|
||||
@@ -190,6 +194,10 @@ watch(
|
||||
<dt>{{ t('pages.items.usage') }}</dt>
|
||||
<dd>{{ item.usage?.name ?? t('common.none') }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('pages.items.basePrice') }}</dt>
|
||||
<dd>{{ basePriceDisplay }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('pages.items.recipeInfo') }}</dt>
|
||||
<dd>{{ item.noRecipe ? t('pages.items.noRecipe') : item.recipe ? item.recipe.name : t('common.none') }}</dd>
|
||||
|
||||
@@ -38,6 +38,7 @@ const creatingSelect = ref('');
|
||||
const itemForm = ref({
|
||||
name: '',
|
||||
details: '',
|
||||
basePrice: '',
|
||||
translations: {} as TranslationMap,
|
||||
categoryId: '',
|
||||
usageId: '',
|
||||
@@ -53,6 +54,7 @@ const itemForm = ref({
|
||||
|
||||
type ItemCreateDefaults = {
|
||||
categoryId: string;
|
||||
usageId: string;
|
||||
dyeable: boolean;
|
||||
dualDyeable: boolean;
|
||||
patternEditable: boolean;
|
||||
@@ -98,6 +100,7 @@ function readItemCreateDefaults(): ItemCreateDefaults {
|
||||
if (typeof sessionStorage === 'undefined') {
|
||||
return {
|
||||
categoryId: '',
|
||||
usageId: '',
|
||||
dyeable: false,
|
||||
dualDyeable: false,
|
||||
patternEditable: false,
|
||||
@@ -111,6 +114,7 @@ function readItemCreateDefaults(): ItemCreateDefaults {
|
||||
if (!rawValue) {
|
||||
return {
|
||||
categoryId: '',
|
||||
usageId: '',
|
||||
dyeable: false,
|
||||
dualDyeable: false,
|
||||
patternEditable: false,
|
||||
@@ -122,6 +126,7 @@ function readItemCreateDefaults(): ItemCreateDefaults {
|
||||
const parsedValue = JSON.parse(rawValue) as Partial<ItemCreateDefaults>;
|
||||
return {
|
||||
categoryId: typeof parsedValue.categoryId === 'string' ? parsedValue.categoryId : '',
|
||||
usageId: typeof parsedValue.usageId === 'string' ? parsedValue.usageId : '',
|
||||
dyeable: parsedValue.dyeable === true,
|
||||
dualDyeable: parsedValue.dualDyeable === true,
|
||||
patternEditable: parsedValue.patternEditable === true,
|
||||
@@ -133,6 +138,7 @@ function readItemCreateDefaults(): ItemCreateDefaults {
|
||||
} catch {
|
||||
return {
|
||||
categoryId: '',
|
||||
usageId: '',
|
||||
dyeable: false,
|
||||
dualDyeable: false,
|
||||
patternEditable: false,
|
||||
@@ -151,10 +157,12 @@ function applyItemCreateDefaults(isEventItem: boolean) {
|
||||
|
||||
const defaults = readItemCreateDefaults();
|
||||
const categoryIds = new Set(loadedOptions.itemCategories.map((item) => String(item.id)));
|
||||
const usageIds = new Set(loadedOptions.itemUsages.map((item) => String(item.id)));
|
||||
const methodIds = new Set(loadedOptions.acquisitionMethods.map((item) => String(item.id)));
|
||||
itemForm.value = {
|
||||
...itemForm.value,
|
||||
categoryId: categoryIds.has(defaults.categoryId) ? defaults.categoryId : '',
|
||||
usageId: usageIds.has(defaults.usageId) ? defaults.usageId : '',
|
||||
dyeable: defaults.dyeable,
|
||||
dualDyeable: defaults.dualDyeable,
|
||||
patternEditable: defaults.patternEditable,
|
||||
@@ -207,6 +215,7 @@ async function loadEditor() {
|
||||
itemForm.value = {
|
||||
name: item.baseName ?? item.name,
|
||||
details: item.baseDetails ?? item.details,
|
||||
basePrice: item.basePrice === null || item.basePrice === undefined ? '' : String(item.basePrice),
|
||||
translations: item.translations ?? {},
|
||||
categoryId: String(item.category.id),
|
||||
usageId: item.usage ? String(item.usage.id) : '',
|
||||
@@ -260,6 +269,7 @@ async function saveItem() {
|
||||
const payload: ItemPayload = {
|
||||
name: itemNameForSave(),
|
||||
details: itemForm.value.details,
|
||||
basePrice: itemForm.value.basePrice.trim() === '' ? null : Number(itemForm.value.basePrice),
|
||||
translations: itemForm.value.translations,
|
||||
categoryId: Number(itemForm.value.categoryId),
|
||||
usageId: itemForm.value.usageId ? Number(itemForm.value.usageId) : null,
|
||||
@@ -342,6 +352,11 @@ onMounted(() => {
|
||||
@error="message = $event"
|
||||
/>
|
||||
|
||||
<div class="field">
|
||||
<label for="item-base-price">{{ t('pages.items.basePrice') }}</label>
|
||||
<input id="item-base-price" v-model="itemForm.basePrice" type="number" min="0" step="1" inputmode="numeric" />
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="item-category">{{ t('pages.items.category') }}</label>
|
||||
<TagsSelect
|
||||
@@ -361,6 +376,7 @@ onMounted(() => {
|
||||
v-model="itemForm.usageId"
|
||||
:options="options.itemUsages"
|
||||
:multiple="false"
|
||||
clearable
|
||||
:placeholder="t('common.none')"
|
||||
:search-placeholder="t('pages.items.searchUsage')"
|
||||
/>
|
||||
|
||||
@@ -46,6 +46,7 @@ const dropCommitted = ref(false);
|
||||
|
||||
type ItemCreateDefaults = {
|
||||
categoryId: string;
|
||||
usageId: string;
|
||||
dyeable: boolean;
|
||||
dualDyeable: boolean;
|
||||
patternEditable: boolean;
|
||||
@@ -57,6 +58,7 @@ const itemCreateDefaultsStorageKey = 'pokopia_item_create_defaults';
|
||||
|
||||
const emptyItemCreateDefaults = (): ItemCreateDefaults => ({
|
||||
categoryId: '',
|
||||
usageId: '',
|
||||
dyeable: false,
|
||||
dualDyeable: false,
|
||||
patternEditable: false,
|
||||
@@ -101,6 +103,7 @@ const canCreateItem = computed(() => currentUser.value?.permissions.includes('it
|
||||
const hasItemCreateDefaults = computed(
|
||||
() =>
|
||||
itemCreateDefaults.value.categoryId !== '' ||
|
||||
itemCreateDefaults.value.usageId !== '' ||
|
||||
itemCreateDefaults.value.dyeable ||
|
||||
itemCreateDefaults.value.dualDyeable ||
|
||||
itemCreateDefaults.value.patternEditable ||
|
||||
@@ -177,6 +180,7 @@ function readItemCreateDefaults(): ItemCreateDefaults {
|
||||
const parsedValue = JSON.parse(rawValue) as Partial<ItemCreateDefaults>;
|
||||
return {
|
||||
categoryId: typeof parsedValue.categoryId === 'string' ? parsedValue.categoryId : '',
|
||||
usageId: typeof parsedValue.usageId === 'string' ? parsedValue.usageId : '',
|
||||
dyeable: parsedValue.dyeable === true,
|
||||
dualDyeable: parsedValue.dualDyeable === true,
|
||||
patternEditable: parsedValue.patternEditable === true,
|
||||
@@ -209,10 +213,12 @@ function sanitizeItemCreateDefaults() {
|
||||
}
|
||||
|
||||
const categoryIds = new Set(options.value.itemCategories.map((item) => String(item.id)));
|
||||
const usageIds = new Set(options.value.itemUsages.map((item) => String(item.id)));
|
||||
const methodIds = new Set(options.value.acquisitionMethods.map((item) => String(item.id)));
|
||||
const nextDefaults = {
|
||||
...itemCreateDefaults.value,
|
||||
categoryId: categoryIds.has(itemCreateDefaults.value.categoryId) ? itemCreateDefaults.value.categoryId : '',
|
||||
usageId: usageIds.has(itemCreateDefaults.value.usageId) ? itemCreateDefaults.value.usageId : '',
|
||||
acquisitionMethodIds: itemCreateDefaults.value.acquisitionMethodIds.filter((item) => methodIds.has(item))
|
||||
};
|
||||
|
||||
@@ -509,6 +515,19 @@ watch(itemSortingAllowed, (allowed) => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="item-default-usage">{{ t('pages.items.usage') }}</label>
|
||||
<TagsSelect
|
||||
id="item-default-usage"
|
||||
v-model="itemCreateDefaults.usageId"
|
||||
:options="options.itemUsages"
|
||||
:multiple="false"
|
||||
clearable
|
||||
:placeholder="t('common.none')"
|
||||
:search-placeholder="t('pages.items.searchUsage')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="check-row item-create-defaults-menu__checks">
|
||||
<label><input v-model="itemCreateDefaults.dyeable" type="checkbox" /> {{ t('pages.items.dyeable') }}</label>
|
||||
<label><input v-model="itemCreateDefaults.dualDyeable" type="checkbox" /> {{ t('pages.items.dualDyeable') }}</label>
|
||||
@@ -557,6 +576,7 @@ watch(itemSortingAllowed, (allowed) => {
|
||||
v-model="usageId"
|
||||
:options="options.itemUsages"
|
||||
:multiple="false"
|
||||
clearable
|
||||
:placeholder="t('common.all')"
|
||||
:search-placeholder="t('pages.items.searchUsage')"
|
||||
/>
|
||||
|
||||
@@ -134,7 +134,7 @@ export const systemWordingMessages = {
|
||||
pokemonDetailDescription:
|
||||
'Read {name} details in Pokopia Wiki, including habitat, types, specialities, favourites, stats, related items, discussions, and edit history.',
|
||||
itemDetailDescription:
|
||||
'Browse {name} item details in Pokopia Wiki, including category, usage, acquisition methods, customization, related recipes, habitats, and Pokemon drops.',
|
||||
'Browse {name} item details in Pokopia Wiki, including base price, category, usage, acquisition methods, customization, related recipes, habitats, and Pokemon drops.',
|
||||
ancientArtifactDetailDescription:
|
||||
'Browse {name} Ancient Artifact details in Pokopia Wiki, including category, tags, description, discussions, and edit history.',
|
||||
habitatDetailDescription:
|
||||
@@ -680,7 +680,7 @@ export const systemWordingMessages = {
|
||||
detailKicker: 'Item Detail',
|
||||
detailSubtitle: 'Item detail',
|
||||
editKicker: 'Item Edit',
|
||||
editSubtitle: 'Maintain item category, usage, acquisition methods, customization, and tags.',
|
||||
editSubtitle: 'Maintain item base price, category, usage, acquisition methods, customization, and tags.',
|
||||
newTitle: 'New item',
|
||||
editTitle: 'Edit {name}',
|
||||
fallbackName: 'Item',
|
||||
@@ -688,6 +688,7 @@ export const systemWordingMessages = {
|
||||
loadingDetail: 'Loading item detail',
|
||||
loadingEdit: 'Loading item editor',
|
||||
description: 'Description',
|
||||
basePrice: 'Base Price',
|
||||
category: 'Category',
|
||||
usage: 'Usage',
|
||||
tags: 'Tags',
|
||||
@@ -720,7 +721,7 @@ export const systemWordingMessages = {
|
||||
subtitle: 'Browse event items by category, usage, and tags.',
|
||||
kicker: 'Event Items',
|
||||
detailKicker: 'Event Item Detail',
|
||||
editSubtitle: 'Maintain event item category, usage, acquisition methods, customization, and tags.',
|
||||
editSubtitle: 'Maintain event item base price, category, usage, acquisition methods, customization, and tags.',
|
||||
newTitle: 'New event item'
|
||||
},
|
||||
ancientArtifacts: {
|
||||
@@ -1495,7 +1496,7 @@ export const systemWordingMessages = {
|
||||
seo: {
|
||||
siteDescription: '浏览 Pokopia Wiki 的 Pokemon、Event Pokemon、栖息地、Event Habitats、物品、Event Items、Ancient Artifacts、材料单、每日清单和 Life 社区动态。',
|
||||
pokemonDetailDescription: '查看 {name} 在 Pokopia Wiki 中的栖息地、属性、特长、喜欢的东西、六维、相关物品、讨论和编辑历史。',
|
||||
itemDetailDescription: '查看 {name} 在 Pokopia Wiki 中的分类、用途、入手方式、自定义、相关材料单、栖息地和 Pokemon 掉落。',
|
||||
itemDetailDescription: '查看 {name} 在 Pokopia Wiki 中的基础价格、分类、用途、入手方式、自定义、相关材料单、栖息地和 Pokemon 掉落。',
|
||||
ancientArtifactDetailDescription: '查看 {name} 在 Pokopia Wiki 中的分类、标签、介绍、讨论和编辑历史。',
|
||||
habitatDetailDescription: '查看 {name} 在 Pokopia Wiki 中的配方、可能出现的 Pokemon、地图、时间、天气、讨论和编辑历史。',
|
||||
recipeDetailDescription: '查看 {name} 材料单在 Pokopia Wiki 中的结果物品、入手方式、需要材料、讨论和编辑历史。'
|
||||
@@ -2018,7 +2019,7 @@ export const systemWordingMessages = {
|
||||
detailKicker: 'Item Detail',
|
||||
detailSubtitle: '物品详情',
|
||||
editKicker: 'Item Edit',
|
||||
editSubtitle: '维护物品分类、用途、入手方式、自定义和标签。',
|
||||
editSubtitle: '维护物品基础价格、分类、用途、入手方式、自定义和标签。',
|
||||
newTitle: '新增物品',
|
||||
editTitle: '编辑 {name}',
|
||||
fallbackName: '物品',
|
||||
@@ -2026,6 +2027,7 @@ export const systemWordingMessages = {
|
||||
loadingDetail: '正在加载物品详情',
|
||||
loadingEdit: '正在加载物品编辑内容',
|
||||
description: '介绍',
|
||||
basePrice: '基础价格',
|
||||
category: '分类',
|
||||
usage: '用途',
|
||||
tags: '标签',
|
||||
@@ -2058,7 +2060,7 @@ export const systemWordingMessages = {
|
||||
subtitle: '按分类、用途、标签查看活动物品。',
|
||||
kicker: 'Event Items',
|
||||
detailKicker: 'Event Item Detail',
|
||||
editSubtitle: '维护 Event Item 分类、用途、入手方式、自定义和标签。',
|
||||
editSubtitle: '维护 Event Item 基础价格、分类、用途、入手方式、自定义和标签。',
|
||||
newTitle: '新增 Event Item'
|
||||
},
|
||||
ancientArtifacts: {
|
||||
|
||||
Reference in New Issue
Block a user