wip(freight): migration 0017 + model/column-list groundwork for add-freight-templates
This commit is contained in:
+103
-27
@@ -107,7 +107,12 @@ function validate(kind: ContentKind): boolean {
|
||||
for (const [index, row] of list.entries()) {
|
||||
if (kind === "quick-links") {
|
||||
const link = row as QuickLinkRow;
|
||||
if (!link.label.en.trim() || !link.label.zh.trim() || !link.url.trim() || !link.glyph.trim()) {
|
||||
if (
|
||||
!link.label.en.trim() ||
|
||||
!link.label.zh.trim() ||
|
||||
!link.url.trim() ||
|
||||
!link.glyph.trim()
|
||||
) {
|
||||
errors[index] = t("admin.contentQuickLinkInvalid");
|
||||
}
|
||||
} else if (kind === "floor-adverts") {
|
||||
@@ -150,7 +155,6 @@ async function saveKind(kind: ContentKind): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
void loadContent();
|
||||
});
|
||||
@@ -158,14 +162,23 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<VPage :title="$t('nav.content')">
|
||||
<p v-if="loading" class="text-sm text-muted">{{ $t("common.loading") }}</p>
|
||||
<p v-else-if="loadError" class="my-2 text-sm text-danger" role="alert">{{ loadError }}</p>
|
||||
<p v-if="loading" class="text-muted text-sm">{{ $t("common.loading") }}</p>
|
||||
<p v-else-if="loadError" class="text-danger my-2 text-sm" role="alert">{{ loadError }}</p>
|
||||
<template v-else>
|
||||
<VCard v-for="kind in (['banners', 'promos'] as ContentKind[])" :key="kind" class="mb-5">
|
||||
<VCard v-for="kind in ['banners', 'promos'] as ContentKind[]" :key="kind" class="mb-5">
|
||||
<h2 class="mb-4 text-base font-semibold">{{ $t(kindLabelKey[kind]) }}</h2>
|
||||
<div v-for="(row, index) in rowsOf(kind)" :key="index" class="mb-3 rounded-md border border-border p-3">
|
||||
<div
|
||||
v-for="(row, index) in rowsOf(kind)"
|
||||
:key="index"
|
||||
class="border-border mb-3 rounded-md border p-3"
|
||||
>
|
||||
<div class="grid items-end gap-3 md:grid-cols-[auto_1fr_1fr_auto_auto]">
|
||||
<img v-if="row.image" :src="row.image" :alt="$t(kindLabelKey[kind])" class="h-12 w-20 rounded object-cover" />
|
||||
<img
|
||||
v-if="row.image"
|
||||
:src="row.image"
|
||||
:alt="$t(kindLabelKey[kind])"
|
||||
class="h-12 w-20 rounded object-cover"
|
||||
/>
|
||||
<VField :label="$t('admin.contentImage')" :error="rowErrors[kind][index]">
|
||||
<VInput v-model="row.image" />
|
||||
</VField>
|
||||
@@ -173,12 +186,17 @@ onMounted(() => {
|
||||
<VInput v-model="row.url" />
|
||||
</VField>
|
||||
<label class="mb-3.5 flex items-center gap-2 text-sm">
|
||||
<input v-model="row.active" type="checkbox" class="h-4 w-4 accent-primary" />
|
||||
<input v-model="row.active" type="checkbox" class="accent-primary h-4 w-4" />
|
||||
{{ $t("admin.enabled") }}
|
||||
</label>
|
||||
<div class="mb-3.5 flex gap-1">
|
||||
<VBtn size="sm" :disabled="index === 0" @click="moveRow(kind, index, -1)">↑</VBtn>
|
||||
<VBtn size="sm" :disabled="index === rowsOf(kind).length - 1" @click="moveRow(kind, index, 1)">↓</VBtn>
|
||||
<VBtn
|
||||
size="sm"
|
||||
:disabled="index === rowsOf(kind).length - 1"
|
||||
@click="moveRow(kind, index, 1)"
|
||||
>↓</VBtn
|
||||
>
|
||||
<VBtn size="sm" @click="removeRow(kind, index)">{{ $t("common.delete") }}</VBtn>
|
||||
</div>
|
||||
</div>
|
||||
@@ -188,7 +206,12 @@ onMounted(() => {
|
||||
<VBtn variant="primary" :disabled="saving === kind" @click="saveKind(kind)">
|
||||
{{ saving === kind ? $t("common.loading") : $t("common.save") }}
|
||||
</VBtn>
|
||||
<p v-if="feedback[kind]" role="status" class="text-sm" :class="feedback[kind]!.ok ? 'text-success' : 'text-danger'">
|
||||
<p
|
||||
v-if="feedback[kind]"
|
||||
role="status"
|
||||
class="text-sm"
|
||||
:class="feedback[kind]!.ok ? 'text-success' : 'text-danger'"
|
||||
>
|
||||
{{ feedback[kind]!.message }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -196,9 +219,17 @@ onMounted(() => {
|
||||
|
||||
<VCard class="mb-5">
|
||||
<h2 class="mb-4 text-base font-semibold">{{ $t("admin.contentQuickLinks") }}</h2>
|
||||
<div v-for="(row, index) in quickLinks" :key="index" class="mb-3 rounded-md border border-border p-3">
|
||||
<div
|
||||
v-for="(row, index) in quickLinks"
|
||||
:key="index"
|
||||
class="border-border mb-3 rounded-md border p-3"
|
||||
>
|
||||
<div class="grid items-end gap-3 md:grid-cols-[auto_1fr_1fr_1fr_1fr_auto_auto]">
|
||||
<svg viewBox="0 0 24 24" class="mb-3.5 h-8 w-8 fill-current text-primary" aria-hidden="true">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
class="text-primary mb-3.5 h-8 w-8 fill-current"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path :d="row.glyph" />
|
||||
</svg>
|
||||
<VField :label="$t('admin.contentLabelEn')" :error="rowErrors['quick-links'][index]">
|
||||
@@ -214,22 +245,40 @@ onMounted(() => {
|
||||
<VInput v-model="row.glyph" placeholder="M12 2L2 22h20z" />
|
||||
</VField>
|
||||
<label class="mb-3.5 flex items-center gap-2 text-sm">
|
||||
<input v-model="row.active" type="checkbox" class="h-4 w-4 accent-primary" />
|
||||
<input v-model="row.active" type="checkbox" class="accent-primary h-4 w-4" />
|
||||
{{ $t("admin.enabled") }}
|
||||
</label>
|
||||
<div class="mb-3.5 flex gap-1">
|
||||
<VBtn size="sm" :disabled="index === 0" @click="moveRow('quick-links', index, -1)">↑</VBtn>
|
||||
<VBtn size="sm" :disabled="index === quickLinks.length - 1" @click="moveRow('quick-links', index, 1)">↓</VBtn>
|
||||
<VBtn size="sm" @click="removeRow('quick-links', index)">{{ $t("common.delete") }}</VBtn>
|
||||
<VBtn size="sm" :disabled="index === 0" @click="moveRow('quick-links', index, -1)"
|
||||
>↑</VBtn
|
||||
>
|
||||
<VBtn
|
||||
size="sm"
|
||||
:disabled="index === quickLinks.length - 1"
|
||||
@click="moveRow('quick-links', index, 1)"
|
||||
>↓</VBtn
|
||||
>
|
||||
<VBtn size="sm" @click="removeRow('quick-links', index)">{{
|
||||
$t("common.delete")
|
||||
}}</VBtn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<VBtn size="sm" @click="addRow('quick-links')">{{ $t("admin.contentAddRow") }}</VBtn>
|
||||
<VBtn variant="primary" :disabled="saving === 'quick-links'" @click="saveKind('quick-links')">
|
||||
<VBtn
|
||||
variant="primary"
|
||||
:disabled="saving === 'quick-links'"
|
||||
@click="saveKind('quick-links')"
|
||||
>
|
||||
{{ saving === "quick-links" ? $t("common.loading") : $t("common.save") }}
|
||||
</VBtn>
|
||||
<p v-if="feedback['quick-links']" role="status" class="text-sm" :class="feedback['quick-links']!.ok ? 'text-success' : 'text-danger'">
|
||||
<p
|
||||
v-if="feedback['quick-links']"
|
||||
role="status"
|
||||
class="text-sm"
|
||||
:class="feedback['quick-links']!.ok ? 'text-success' : 'text-danger'"
|
||||
>
|
||||
{{ feedback["quick-links"]!.message }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -237,33 +286,60 @@ onMounted(() => {
|
||||
|
||||
<VCard class="mb-5">
|
||||
<h2 class="mb-4 text-base font-semibold">{{ $t("admin.contentFloorAdverts") }}</h2>
|
||||
<div v-for="(row, index) in floorAdverts" :key="index" class="mb-3 rounded-md border border-border p-3">
|
||||
<div
|
||||
v-for="(row, index) in floorAdverts"
|
||||
:key="index"
|
||||
class="border-border mb-3 rounded-md border p-3"
|
||||
>
|
||||
<div class="grid items-end gap-3 md:grid-cols-[auto_1fr_auto_auto]">
|
||||
<img v-if="row.image" :src="row.image" :alt="$t('admin.contentFloorAdverts')" class="h-12 w-20 rounded object-cover" />
|
||||
<img
|
||||
v-if="row.image"
|
||||
:src="row.image"
|
||||
:alt="$t('admin.contentFloorAdverts')"
|
||||
class="h-12 w-20 rounded object-cover"
|
||||
/>
|
||||
<VField :label="$t('admin.contentImage')" :error="rowErrors['floor-adverts'][index]">
|
||||
<VInput v-model="row.image" />
|
||||
</VField>
|
||||
<label class="mb-3.5 flex items-center gap-2 text-sm">
|
||||
<input v-model="row.active" type="checkbox" class="h-4 w-4 accent-primary" />
|
||||
<input v-model="row.active" type="checkbox" class="accent-primary h-4 w-4" />
|
||||
{{ $t("admin.enabled") }}
|
||||
</label>
|
||||
<div class="mb-3.5 flex gap-1">
|
||||
<VBtn size="sm" :disabled="index === 0" @click="moveRow('floor-adverts', index, -1)">↑</VBtn>
|
||||
<VBtn size="sm" :disabled="index === floorAdverts.length - 1" @click="moveRow('floor-adverts', index, 1)">↓</VBtn>
|
||||
<VBtn size="sm" @click="removeRow('floor-adverts', index)">{{ $t("common.delete") }}</VBtn>
|
||||
<VBtn size="sm" :disabled="index === 0" @click="moveRow('floor-adverts', index, -1)"
|
||||
>↑</VBtn
|
||||
>
|
||||
<VBtn
|
||||
size="sm"
|
||||
:disabled="index === floorAdverts.length - 1"
|
||||
@click="moveRow('floor-adverts', index, 1)"
|
||||
>↓</VBtn
|
||||
>
|
||||
<VBtn size="sm" @click="removeRow('floor-adverts', index)">{{
|
||||
$t("common.delete")
|
||||
}}</VBtn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<VBtn size="sm" @click="addRow('floor-adverts')">{{ $t("admin.contentAddRow") }}</VBtn>
|
||||
<VBtn variant="primary" :disabled="saving === 'floor-adverts'" @click="saveKind('floor-adverts')">
|
||||
<VBtn
|
||||
variant="primary"
|
||||
:disabled="saving === 'floor-adverts'"
|
||||
@click="saveKind('floor-adverts')"
|
||||
>
|
||||
{{ saving === "floor-adverts" ? $t("common.loading") : $t("common.save") }}
|
||||
</VBtn>
|
||||
<p v-if="feedback['floor-adverts']" role="status" class="text-sm" :class="feedback['floor-adverts']!.ok ? 'text-success' : 'text-danger'">
|
||||
<p
|
||||
v-if="feedback['floor-adverts']"
|
||||
role="status"
|
||||
class="text-sm"
|
||||
:class="feedback['floor-adverts']!.ok ? 'text-success' : 'text-danger'"
|
||||
>
|
||||
{{ feedback["floor-adverts"]!.message }}
|
||||
</p>
|
||||
</div>
|
||||
</VCard>
|
||||
</template>
|
||||
</VPage>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user