feat(ui): adopt Tailwind v4 design system and archive change
- Add tailwindcss v4 + @tailwindcss/vite to mall, shop-admin, admin - Add @vmall/shared/theme.css tokens with html[data-accent] presets - Add @vmall/ui kit (VBtn/VBadge/VField/VInput/VCard/VPanel/VTable/VPage, VAccentSwatch, useAccent) as a Nuxt module - Convert all three apps to kit + utilities; delete ui.css/mall.css and every <style scoped>; consoles get accent presets, mall locked to red - Fix VCard boolean prop default (padding) and PDP/store stale useAsyncData keys on param navigation - Archive adopt-tailwind-design-system; new frontend-ui capability spec
This commit is contained in:
@@ -135,13 +135,13 @@ async function save(): Promise<void> {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="mpanel addresses-panel">
|
||||
<h1 class="mpanel-title">
|
||||
{{ t("user.addresses") }}
|
||||
<button class="mbtn red" type="button" @click="openAdd">{{ t("user.addAddress") }}</button>
|
||||
</h1>
|
||||
<VCard class="min-h-[560px]">
|
||||
<div class="mb-4 flex items-center justify-between gap-3">
|
||||
<h1 class="m-0 text-xl font-bold text-text">{{ t("user.addresses") }}</h1>
|
||||
<VBtn variant="primary" size="sm" type="button" @click="openAdd">{{ t("user.addAddress") }}</VBtn>
|
||||
</div>
|
||||
<UiEmptyState v-if="!loading && rows.length === 0" :text="t('user.noAddresses')" />
|
||||
<table v-else class="mtable">
|
||||
<VTable v-else>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ t("user.recipient") }}</th>
|
||||
@@ -165,99 +165,34 @@ async function save(): Promise<void> {
|
||||
<td>{{ row.line1 }}</td>
|
||||
<td>{{ row.postal_code }}</td>
|
||||
<td>
|
||||
<span v-if="row.is_default" class="default-tag">{{ t("user.defaultAddress") }}</span>
|
||||
<button v-else class="mbtn" type="button" @click="setDefault(row.id)">{{ t("user.setDefault") }}</button>
|
||||
<span v-if="row.is_default" class="text-xs text-primary">{{ t("user.defaultAddress") }}</span>
|
||||
<VBtn v-else size="sm" type="button" @click="setDefault(row.id)">{{ t("user.setDefault") }}</VBtn>
|
||||
</td>
|
||||
<td>
|
||||
<button class="mbtn" type="button" @click="openEdit(row)">{{ t("common.edit") }}</button>
|
||||
<button class="mbtn" type="button" @click="remove(row)">{{ t("common.delete") }}</button>
|
||||
<td class="space-x-2 whitespace-nowrap">
|
||||
<VBtn size="sm" type="button" @click="openEdit(row)">{{ t("common.edit") }}</VBtn>
|
||||
<VBtn size="sm" variant="danger" type="button" @click="remove(row)">{{ t("common.delete") }}</VBtn>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</VTable>
|
||||
|
||||
<UiModal :open="open" :title="modalTitle" @close="open = false">
|
||||
<div class="form-grid">
|
||||
<label>
|
||||
<span>{{ t("user.recipient") }}</span>
|
||||
<input v-model.trim="form.recipient" class="minput" type="text" />
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t("user.phone") }}</span>
|
||||
<input v-model.trim="form.phone" class="minput" type="text" />
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t("user.country") }}</span>
|
||||
<input v-model.trim="form.country" class="minput" type="text" />
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t("user.region") }}</span>
|
||||
<input v-model.trim="form.region" class="minput" type="text" />
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t("user.city") }}</span>
|
||||
<input v-model.trim="form.city" class="minput" type="text" />
|
||||
</label>
|
||||
<label class="full-line">
|
||||
<span>{{ t("user.line1") }}</span>
|
||||
<input v-model.trim="form.line1" class="minput" type="text" />
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t("user.postalCode") }}</span>
|
||||
<input v-model.trim="form.postal_code" class="minput" type="text" />
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input v-model="form.is_default" type="checkbox" />
|
||||
<span>{{ t("user.defaultAddress") }}</span>
|
||||
</label>
|
||||
<div class="grid gap-3 sm:grid-cols-2">
|
||||
<VField :label="t('user.recipient')"><VInput v-model="form.recipient" /></VField>
|
||||
<VField :label="t('user.phone')"><VInput v-model="form.phone" /></VField>
|
||||
<VField :label="t('user.country')"><VInput v-model="form.country" /></VField>
|
||||
<VField :label="t('user.region')"><VInput v-model="form.region" /></VField>
|
||||
<VField :label="t('user.city')"><VInput v-model="form.city" /></VField>
|
||||
<VField class="sm:col-span-2" :label="t('user.line1')"><VInput v-model="form.line1" /></VField>
|
||||
<VField :label="t('user.postalCode')"><VInput v-model="form.postal_code" /></VField>
|
||||
<label class="flex items-center gap-2 self-end pb-3 text-sm text-text"><input v-model="form.is_default" class="h-4 w-4 accent-primary" type="checkbox" /> <span>{{ t("user.defaultAddress") }}</span></label>
|
||||
</div>
|
||||
<p v-if="errorKey" class="error">{{ t(errorKey) }}</p>
|
||||
<p v-if="errorKey" class="mt-2 text-sm text-danger">{{ t(errorKey) }}</p>
|
||||
<template #footer>
|
||||
<button class="mbtn" type="button" @click="open = false">{{ t("common.cancel") }}</button>
|
||||
<button class="mbtn red" type="button" :disabled="saving" @click="save">{{ t("user.saveAddress") }}</button>
|
||||
<VBtn type="button" @click="open = false">{{ t("common.cancel") }}</VBtn>
|
||||
<VBtn variant="primary" type="button" :disabled="saving" @click="save">{{ t("user.saveAddress") }}</VBtn>
|
||||
</template>
|
||||
</UiModal>
|
||||
</section>
|
||||
</VCard>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.addresses-panel {
|
||||
min-height: 560px;
|
||||
}
|
||||
.default-tag {
|
||||
color: var(--mall-red);
|
||||
font-size: 12px;
|
||||
}
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.form-grid label > span {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
color: var(--mall-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
.full-line {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.checkbox > span {
|
||||
margin: 0;
|
||||
}
|
||||
.error {
|
||||
margin-top: 8px;
|
||||
color: var(--mall-red);
|
||||
font-size: 12px;
|
||||
}
|
||||
@media (max-width: 760px) {
|
||||
.form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user