152 lines
4.9 KiB
Vue
152 lines
4.9 KiB
Vue
<script setup lang="ts">
|
|
import type { ShopProfileSelfInput } from "@vmall/shared";
|
|
|
|
definePageMeta({ middleware: "auth" });
|
|
|
|
const { $api } = useNuxtApp();
|
|
const { t } = useI18n();
|
|
|
|
const form = reactive({
|
|
logo: "",
|
|
banner: "",
|
|
company: "",
|
|
region: "",
|
|
addressEn: "",
|
|
addressZh: "",
|
|
noticeEn: "",
|
|
noticeZh: "",
|
|
afterSaleEn: "",
|
|
afterSaleZh: "",
|
|
});
|
|
const loading = ref(true);
|
|
const saving = ref(false);
|
|
const loadError = ref("");
|
|
const formError = ref("");
|
|
const feedback = ref<{ ok: boolean; message: string } | null>(null);
|
|
|
|
async function loadProfile(): Promise<void> {
|
|
loading.value = true;
|
|
loadError.value = "";
|
|
try {
|
|
const shop = await $api.shop.getMyShop();
|
|
const profile = await $api.getShop(shop.slug);
|
|
form.logo = profile.logo ?? "";
|
|
form.banner = profile.banner ?? "";
|
|
form.company = profile.company ?? "";
|
|
form.region = profile.region ?? "";
|
|
form.addressEn = profile.address?.en ?? "";
|
|
form.addressZh = profile.address?.zh ?? "";
|
|
form.noticeEn = profile.notice?.en ?? "";
|
|
form.noticeZh = profile.notice?.zh ?? "";
|
|
form.afterSaleEn = profile.after_sale?.en ?? "";
|
|
form.afterSaleZh = profile.after_sale?.zh ?? "";
|
|
} catch (error: unknown) {
|
|
loadError.value = error instanceof Error ? error.message : t("common.error");
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
function bilingualField(en: string, zh: string): { en: string; zh: string } | null {
|
|
const enText = en.trim();
|
|
const zhText = zh.trim();
|
|
if (!enText && !zhText) return null;
|
|
if (!enText || !zhText) throw new Error(t("shop.bilingualRequired"));
|
|
return { en: enText, zh: zhText };
|
|
}
|
|
|
|
async function save(): Promise<void> {
|
|
feedback.value = null;
|
|
formError.value = "";
|
|
let body: ShopProfileSelfInput;
|
|
try {
|
|
body = {
|
|
logo: form.logo.trim() || null,
|
|
banner: form.banner.trim() || null,
|
|
company: form.company.trim() || null,
|
|
region: form.region.trim() || null,
|
|
address: bilingualField(form.addressEn, form.addressZh),
|
|
notice: bilingualField(form.noticeEn, form.noticeZh),
|
|
after_sale: bilingualField(form.afterSaleEn, form.afterSaleZh),
|
|
};
|
|
} catch (error: unknown) {
|
|
formError.value = error instanceof Error ? error.message : t("common.error");
|
|
return;
|
|
}
|
|
saving.value = true;
|
|
try {
|
|
await $api.shop.updateMyProfile(body);
|
|
feedback.value = { ok: true, message: t("shop.profileSaved") };
|
|
} catch (error: unknown) {
|
|
feedback.value = {
|
|
ok: false,
|
|
message: error instanceof Error ? error.message : t("common.error"),
|
|
};
|
|
} finally {
|
|
saving.value = false;
|
|
}
|
|
}
|
|
|
|
const inputClass =
|
|
"w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-text focus:border-primary focus:outline-2 focus:outline-primary/30 disabled:opacity-50";
|
|
|
|
onMounted(() => {
|
|
void loadProfile();
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<VPage :title="$t('nav.shopProfile')">
|
|
<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>
|
|
<VCard v-else>
|
|
<div class="grid gap-3 md:grid-cols-2">
|
|
<VField :label="$t('shop.profileLogo')">
|
|
<VInput v-model="form.logo" placeholder="https://…" />
|
|
</VField>
|
|
<VField :label="$t('shop.profileBanner')">
|
|
<VInput v-model="form.banner" placeholder="https://…" />
|
|
</VField>
|
|
<VField :label="$t('shop.profileCompany')">
|
|
<VInput v-model="form.company" />
|
|
</VField>
|
|
<VField :label="$t('shop.profileRegion')">
|
|
<VInput v-model="form.region" />
|
|
</VField>
|
|
<VField :label="$t('shop.profileAddressEn')">
|
|
<VInput v-model="form.addressEn" />
|
|
</VField>
|
|
<VField :label="$t('shop.profileAddressZh')">
|
|
<VInput v-model="form.addressZh" />
|
|
</VField>
|
|
<VField :label="$t('shop.profileNoticeEn')">
|
|
<textarea v-model="form.noticeEn" :class="inputClass" rows="3" />
|
|
</VField>
|
|
<VField :label="$t('shop.profileNoticeZh')">
|
|
<textarea v-model="form.noticeZh" :class="inputClass" rows="3" />
|
|
</VField>
|
|
<VField :label="$t('shop.profileAfterSaleEn')">
|
|
<textarea v-model="form.afterSaleEn" :class="inputClass" rows="3" />
|
|
</VField>
|
|
<VField :label="$t('shop.profileAfterSaleZh')">
|
|
<textarea v-model="form.afterSaleZh" :class="inputClass" rows="3" />
|
|
</VField>
|
|
</div>
|
|
<p v-if="formError" class="text-danger my-2 text-sm" role="alert">{{ formError }}</p>
|
|
<div class="flex items-center gap-3">
|
|
<VBtn variant="primary" :disabled="saving" @click="save">
|
|
{{ saving ? $t("common.loading") : $t("common.save") }}
|
|
</VBtn>
|
|
<p
|
|
v-if="feedback"
|
|
role="status"
|
|
class="text-sm"
|
|
:class="feedback.ok ? 'text-success' : 'text-danger'"
|
|
>
|
|
{{ feedback.message }}
|
|
</p>
|
|
</div>
|
|
</VCard>
|
|
</VPage>
|
|
</template>
|