- enforce localhost:5174 for wheel campaign client URL in local development - lock vite dev server ports using strictPort on admin (5173) and client (5174) - sanitize .env.example and remove hardcoded staging domain - configure root dev orchestration and docker multi-stage build context
155 lines
2.9 KiB
Vue
155 lines
2.9 KiB
Vue
<script setup lang="ts">
|
|
import { ArrowRightOutlined } from '@ant-design/icons-vue';
|
|
import { computed } from 'vue';
|
|
import { useRouter } from 'vue-router';
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
title?: string;
|
|
showBack?: boolean;
|
|
transparent?: boolean;
|
|
}>(),
|
|
{
|
|
title: '',
|
|
showBack: false,
|
|
transparent: false,
|
|
},
|
|
);
|
|
|
|
const emit = defineEmits<{
|
|
back: [];
|
|
}>();
|
|
|
|
const router = useRouter();
|
|
|
|
const canGoBack = computed(() => props.showBack);
|
|
|
|
function handleBack() {
|
|
emit('back');
|
|
if (window.history.length > 1) {
|
|
router.back();
|
|
} else {
|
|
router.push('/m/dashboard');
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<header class="m-topbar" :class="{ 'm-topbar--transparent': transparent }">
|
|
<div class="m-topbar__safe" />
|
|
<div class="m-topbar__inner">
|
|
<div class="m-topbar__side m-topbar__side--start">
|
|
<button
|
|
v-if="canGoBack"
|
|
type="button"
|
|
class="m-topbar__btn"
|
|
aria-label="بازگشت"
|
|
@click="handleBack"
|
|
>
|
|
<ArrowRightOutlined />
|
|
</button>
|
|
<slot name="start" />
|
|
</div>
|
|
|
|
<h1 class="m-topbar__title">{{ title }}</h1>
|
|
|
|
<div class="m-topbar__side m-topbar__side--end">
|
|
<slot name="actions" />
|
|
</div>
|
|
</div>
|
|
</header>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.m-topbar {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
background: linear-gradient(135deg, #4f46e5 0%, #6366f1 55%, #7c3aed 100%);
|
|
color: #fff;
|
|
box-shadow: 0 4px 20px rgba(79, 70, 229, 0.28);
|
|
}
|
|
|
|
.m-topbar--transparent {
|
|
background: transparent;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.m-topbar__safe {
|
|
height: env(safe-area-inset-top, 0px);
|
|
}
|
|
|
|
.m-topbar__inner {
|
|
height: 56px;
|
|
display: grid;
|
|
grid-template-columns: 72px 1fr 72px;
|
|
align-items: center;
|
|
padding: 0 8px;
|
|
}
|
|
|
|
.m-topbar__title {
|
|
margin: 0;
|
|
text-align: center;
|
|
font-size: 16px;
|
|
font-weight: 700;
|
|
letter-spacing: -0.01em;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
color: #fff;
|
|
}
|
|
|
|
.m-topbar__side {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
min-height: 48px;
|
|
}
|
|
|
|
.m-topbar__side--start {
|
|
justify-content: flex-start;
|
|
}
|
|
|
|
.m-topbar__side--end {
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.m-topbar__btn {
|
|
width: 44px;
|
|
height: 44px;
|
|
border: none;
|
|
border-radius: 12px;
|
|
background: rgba(255, 255, 255, 0.14);
|
|
color: #fff;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 16px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.m-topbar__btn:active {
|
|
background: rgba(255, 255, 255, 0.28);
|
|
transform: scale(0.96);
|
|
}
|
|
|
|
:deep(.m-topbar-action) {
|
|
width: 44px;
|
|
height: 44px;
|
|
border: none;
|
|
border-radius: 12px;
|
|
background: rgba(255, 255, 255, 0.14);
|
|
color: #fff;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 16px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
:deep(.m-topbar-action:active) {
|
|
background: rgba(255, 255, 255, 0.28);
|
|
transform: scale(0.96);
|
|
}
|
|
</style>
|