новый дизайн
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
<script setup>
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
|
||||
const props = defineProps({
|
||||
category: { type: Object, default: null },
|
||||
action: { type: String, required: true },
|
||||
submitLabel: { type: String, required: true },
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
name: props.category?.name || '',
|
||||
icon: props.category?.icon || '',
|
||||
requiredRank: props.category?.required_rank ?? 0,
|
||||
flags: props.category?.flags ?? 0,
|
||||
enabled: props.category?.enabled ?? true,
|
||||
});
|
||||
|
||||
function submit() {
|
||||
const options = {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
if (!props.category) form.reset();
|
||||
},
|
||||
};
|
||||
|
||||
if (props.category) {
|
||||
form.patch(props.action, options);
|
||||
} else {
|
||||
form.post(props.action, options);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="admin-form" @submit.prevent="submit">
|
||||
<div class="admin-form-grid">
|
||||
<label class="admin-field admin-field--wide">
|
||||
<span>Название</span>
|
||||
<input v-model="form.name" maxlength="120" required />
|
||||
<small v-if="form.errors.name">{{ form.errors.name }}</small>
|
||||
</label>
|
||||
<label class="admin-field admin-field--wide">
|
||||
<span>Иконка клиента</span>
|
||||
<input v-model="form.icon" maxlength="255" placeholder="inv_helmet_96" />
|
||||
<small v-if="form.errors.icon">{{ form.errors.icon }}</small>
|
||||
</label>
|
||||
<label class="admin-field">
|
||||
<span>Требуемый ранг</span>
|
||||
<input v-model="form.requiredRank" type="number" min="0" max="255" required />
|
||||
<small v-if="form.errors.requiredRank">{{ form.errors.requiredRank }}</small>
|
||||
</label>
|
||||
<label class="admin-field">
|
||||
<span>Флаги</span>
|
||||
<input v-model="form.flags" type="number" min="0" required />
|
||||
<small v-if="form.errors.flags">{{ form.errors.flags }}</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label class="admin-check">
|
||||
<input v-model="form.enabled" type="checkbox" />
|
||||
<span class="admin-check__box"></span>
|
||||
<span>Категория включена</span>
|
||||
</label>
|
||||
|
||||
<button class="btn btn-primary admin-submit" type="submit" :disabled="form.processing">
|
||||
{{ form.processing ? 'Сохраняем…' : submitLabel }}
|
||||
</button>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,90 @@
|
||||
<script setup>
|
||||
import { router, useForm } from '@inertiajs/vue3';
|
||||
|
||||
const props = defineProps({
|
||||
item: { type: Object, default: null },
|
||||
action: { type: String, required: true },
|
||||
destroyUrl: { type: String, default: null },
|
||||
allowImage: { type: Boolean, default: false },
|
||||
labels: { type: Object, required: true },
|
||||
initial: { type: Object, default: () => ({}) },
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
title: props.item?.title ?? props.initial.title ?? '',
|
||||
sort_order: props.item?.sort_order ?? props.initial.sort_order ?? 0,
|
||||
is_published: props.item?.is_published ?? props.initial.is_published ?? true,
|
||||
body: props.item?.body ?? props.initial.body ?? '',
|
||||
...(props.allowImage ? { image: null } : {}),
|
||||
});
|
||||
|
||||
function submit() {
|
||||
const options = {
|
||||
preserveScroll: true,
|
||||
forceFormData: props.allowImage,
|
||||
onSuccess: () => {
|
||||
if (!props.item) form.reset();
|
||||
},
|
||||
};
|
||||
|
||||
if (props.item && props.allowImage) {
|
||||
form.transform((data) => ({ ...data, _method: 'patch' })).post(props.action, options);
|
||||
} else if (props.item) {
|
||||
form.patch(props.action, options);
|
||||
} else {
|
||||
form.post(props.action, options);
|
||||
}
|
||||
}
|
||||
|
||||
function destroyItem() {
|
||||
if (props.destroyUrl && window.confirm(props.labels.deleteConfirm)) {
|
||||
router.delete(props.destroyUrl, { preserveScroll: true });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="admin-form" @submit.prevent="submit">
|
||||
<div class="admin-form-grid">
|
||||
<label class="admin-field admin-field--wide">
|
||||
<span>{{ labels.title }}</span>
|
||||
<input v-model="form.title" maxlength="120" required />
|
||||
<small v-if="form.errors.title">{{ form.errors.title }}</small>
|
||||
</label>
|
||||
<label class="admin-field">
|
||||
<span>Порядок вывода</span>
|
||||
<input v-model="form.sort_order" type="number" min="0" max="9999" required />
|
||||
<small v-if="form.errors.sort_order">{{ form.errors.sort_order }}</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label class="admin-check">
|
||||
<input v-model="form.is_published" type="checkbox" />
|
||||
<span class="admin-check__box"></span>
|
||||
<span>{{ labels.published }}</span>
|
||||
</label>
|
||||
|
||||
<label class="admin-field">
|
||||
<span>{{ labels.body }}</span>
|
||||
<textarea v-model="form.body" rows="10" required></textarea>
|
||||
<em v-if="labels.bodyHint">{{ labels.bodyHint }}</em>
|
||||
<small v-if="form.errors.body">{{ form.errors.body }}</small>
|
||||
</label>
|
||||
|
||||
<label v-if="allowImage" class="admin-field admin-upload">
|
||||
<span>{{ item ? 'Заменить картинку' : 'Картинка' }}</span>
|
||||
<input type="file" accept="image/*" @input="form.image = $event.target.files[0]" />
|
||||
<em>{{ item ? 'Оставь пустым, чтобы сохранить текущую.' : 'Максимум 2 МБ. Необязательно.' }}</em>
|
||||
<small v-if="form.errors.image">{{ form.errors.image }}</small>
|
||||
</label>
|
||||
|
||||
<div class="admin-form-actions">
|
||||
<button class="btn btn-primary admin-submit" type="submit" :disabled="form.processing">
|
||||
{{ form.processing ? 'Сохраняем…' : labels.submit }}
|
||||
</button>
|
||||
<button v-if="destroyUrl" class="btn btn-ghost admin-danger-button" type="button" @click="destroyItem">
|
||||
{{ labels.delete }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,149 @@
|
||||
<script setup>
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
|
||||
const props = defineProps({
|
||||
product: { type: Object, default: null },
|
||||
categories: { type: Array, required: true },
|
||||
productTypes: { type: Array, required: true },
|
||||
action: { type: String, required: true },
|
||||
submitLabel: { type: String, required: true },
|
||||
});
|
||||
|
||||
const initialRewards = Object.fromEntries(
|
||||
Array.from({ length: 8 }, (_, index) => {
|
||||
const slot = index + 1;
|
||||
const reward = props.product?.rewards?.find((item) => item.slot === slot);
|
||||
return [
|
||||
[`reward_${slot}`, reward?.id ?? 0],
|
||||
[`rewardcount_${slot}`, reward?.count ?? 0],
|
||||
];
|
||||
}).flat(),
|
||||
);
|
||||
|
||||
const form = useForm({
|
||||
category_ids: props.product?.category_ids || [],
|
||||
type: props.product?.type ?? 1,
|
||||
name: props.product?.name || '',
|
||||
tooltipName: props.product?.tooltip_name || '',
|
||||
tooltipType: props.product?.tooltip_type || '',
|
||||
tooltipText: props.product?.tooltip_text || '',
|
||||
icon: props.product?.icon || '',
|
||||
price: props.product?.price ?? 1,
|
||||
hyperlinkId: props.product?.hyperlink_id ?? 0,
|
||||
creatureEntry: props.product?.creature_entry ?? 0,
|
||||
discountAmount: props.product?.discount_amount ?? 0,
|
||||
flags: props.product?.flags ?? 0,
|
||||
...initialRewards,
|
||||
new: props.product?.is_new ?? false,
|
||||
enabled: props.product?.enabled ?? true,
|
||||
});
|
||||
|
||||
function submit() {
|
||||
const options = { preserveScroll: true };
|
||||
if (props.product) {
|
||||
form.patch(props.action, options);
|
||||
} else {
|
||||
form.post(props.action, {
|
||||
...options,
|
||||
onSuccess: () => form.reset(),
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="admin-form" @submit.prevent="submit">
|
||||
<div class="admin-form-grid">
|
||||
<label class="admin-field admin-field--wide">
|
||||
<span>Категории</span>
|
||||
<select v-model="form.category_ids" multiple :size="Math.min(Math.max(categories.length, 2), 8)" required>
|
||||
<option v-for="category in categories" :key="category.id" :value="category.id">{{ category.name }}</option>
|
||||
</select>
|
||||
<em>Можно выбрать несколько с Ctrl/Cmd.</em>
|
||||
<small v-if="form.errors.category_ids">{{ form.errors.category_ids }}</small>
|
||||
</label>
|
||||
<label class="admin-field">
|
||||
<span>Тип</span>
|
||||
<select v-model="form.type" required>
|
||||
<option v-for="type in productTypes" :key="type.value" :value="type.value">{{ type.label }} ({{ type.value }})</option>
|
||||
</select>
|
||||
<small v-if="form.errors.type">{{ form.errors.type }}</small>
|
||||
</label>
|
||||
<label class="admin-field admin-field--wide">
|
||||
<span>Название</span>
|
||||
<textarea v-model="form.name" rows="3" required></textarea>
|
||||
<small v-if="form.errors.name">{{ form.errors.name }}</small>
|
||||
</label>
|
||||
<label class="admin-field">
|
||||
<span>Название подсказки</span>
|
||||
<input v-model="form.tooltipName" />
|
||||
</label>
|
||||
<label class="admin-field">
|
||||
<span>Тип подсказки</span>
|
||||
<input v-model="form.tooltipType" placeholder="item или spell" />
|
||||
</label>
|
||||
<label class="admin-field admin-field--wide">
|
||||
<span>Текст подсказки</span>
|
||||
<textarea v-model="form.tooltipText" rows="5"></textarea>
|
||||
</label>
|
||||
<label class="admin-field admin-field--wide">
|
||||
<span>Иконка клиента</span>
|
||||
<input v-model="form.icon" placeholder="inv_sword_22" />
|
||||
</label>
|
||||
<label class="admin-field">
|
||||
<span>Цена, Слёзы Элуны</span>
|
||||
<input v-model="form.price" type="number" min="1" required />
|
||||
<small v-if="form.errors.price">{{ form.errors.price }}</small>
|
||||
</label>
|
||||
<label class="admin-field">
|
||||
<span>Скидка, %</span>
|
||||
<input v-model="form.discountAmount" type="number" min="0" max="100" />
|
||||
</label>
|
||||
<label class="admin-field">
|
||||
<span>Hyperlink ID</span>
|
||||
<input v-model="form.hyperlinkId" type="number" min="0" />
|
||||
</label>
|
||||
<label class="admin-field">
|
||||
<span>Creature Entry</span>
|
||||
<input v-model="form.creatureEntry" type="number" min="0" />
|
||||
</label>
|
||||
<label class="admin-field">
|
||||
<span>Флаги</span>
|
||||
<input v-model="form.flags" type="number" min="0" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<details class="admin-subform">
|
||||
<summary>Награды · 8 слотов</summary>
|
||||
<div class="admin-rewards">
|
||||
<div v-for="slot in 8" :key="slot">
|
||||
<label class="admin-field">
|
||||
<span>Награда {{ slot }}</span>
|
||||
<input v-model="form[`reward_${slot}`]" type="number" min="0" />
|
||||
</label>
|
||||
<label class="admin-field">
|
||||
<span>Количество {{ slot }}</span>
|
||||
<input v-model="form[`rewardcount_${slot}`]" type="number" min="0" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<div class="admin-check-row">
|
||||
<label class="admin-check">
|
||||
<input v-model="form.new" type="checkbox" />
|
||||
<span class="admin-check__box"></span>
|
||||
<span>Пометка «Новинка»</span>
|
||||
</label>
|
||||
<label class="admin-check">
|
||||
<input v-model="form.enabled" type="checkbox" />
|
||||
<span class="admin-check__box"></span>
|
||||
<span>Позиция включена</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary admin-submit" type="submit" :disabled="form.processing">
|
||||
{{ form.processing ? 'Сохраняем…' : submitLabel }}
|
||||
</button>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
items: { type: Array, required: true },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="admin-stat-grid">
|
||||
<article v-for="item in items" :key="item.label" class="portal-card admin-stat">
|
||||
<span>{{ item.label }}</span>
|
||||
<strong>{{ item.value }}</strong>
|
||||
<small v-if="item.note">{{ item.note }}</small>
|
||||
</article>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user