feat: complete admin panel and backend admin api migration

This commit is contained in:
s.esmaeili
2026-09-13 21:02:25 +03:30
parent 03119cd655
commit 267fc7b7d1
239 changed files with 25431 additions and 935 deletions
+161
View File
@@ -0,0 +1,161 @@
<template>
<!--
Ported from legacy henza_admin/src/views/auth/Login.vue.
Same two fields, same Farsi copy, same logo, same submit gate. Vuesax's
vs-input/vs-button and VeeValidate become ant-design-vue equivalents, and
the loading overlay becomes the button's own loading state.
-->
<div id="page-login">
<a-card class="login-card" :bordered="false">
<div class="login-grid">
<div class="login-form">
<div class="vx-card__title">
<h4>ورود به محیط مدیریت</h4>
<p>لطفا شماره تلفن همراه خود به همراه رمز عبور وارد کنید.</p>
</div>
<a-form layout="vertical" class="mt-8" @submit.prevent="loginJWT">
<a-form-item
label="موبایل"
:validate-status="mobileError ? 'error' : ''"
:help="mobileError"
>
<a-input
v-model:value="mobile"
autocomplete="off"
size="large"
@blur="touchedMobile = true"
/>
</a-form-item>
<a-form-item
label="رمز عبور"
:validate-status="passwordError ? 'error' : ''"
:help="passwordError"
>
<a-input-password
v-model:value="password"
size="large"
@blur="touchedPassword = true"
@pressEnter="loginJWT"
/>
</a-form-item>
<a-button
type="primary"
size="large"
:disabled="!validateForm"
:loading="loading"
@click="loginJWT"
>
ورود
</a-button>
</a-form>
</div>
<div class="login-art">
<img src="../../assets/images/logo/henza-icon.svg" alt="login" />
</div>
</div>
</a-card>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';
import { message } from 'ant-design-vue';
import { useAuthStore } from '../../stores/auth';
const router = useRouter();
const auth = useAuthStore();
const mobile = ref('');
const password = ref('');
const loading = ref(false);
const touchedMobile = ref(false);
const touchedPassword = ref(false);
// legacy VeeValidate rules: mobile 'required|min:8', password 'required|min:5',
// both validated on blur.
const mobileError = computed(() => {
if (!touchedMobile.value) return '';
if (!mobile.value) return 'وارد کردن موبایل الزامی است';
if (mobile.value.length < 8) return 'مقدار ورودی صحیح نیست';
return '';
});
const passwordError = computed(() => {
if (!touchedPassword.value) return '';
if (!password.value) return 'وارد کردن رمز عبور الزامی است';
if (password.value.length < 5) return 'مقدار ورودی صحیح نیست';
return '';
});
// legacy: !errors.any() && mobile !== '' && password !== ''
const validateForm = computed(
() => !mobileError.value && !passwordError.value && mobile.value !== '' && password.value !== '',
);
async function loginJWT() {
if (!validateForm.value || loading.value) return;
loading.value = true;
try {
// legacy also sent token_mobile from localStorage for Firebase push, which is
// inactive in this port; the field is omitted rather than sent as null.
await auth.login({ phone: mobile.value, password: password.value });
// legacy did a hard `window.location.href = '/admin/my-data'`; a router push
// reaches the same route without discarding the freshly-populated store.
await router.push('/my-data');
} catch (e) {
console.log(e);
message.error('خطا در ورود');
} finally {
loading.value = false;
}
}
</script>
<style scoped>
#page-login {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
}
.login-card {
width: 100%;
max-width: 900px;
}
.login-grid {
display: grid;
grid-template-columns: 1fr;
align-items: center;
}
@media (min-width: 992px) {
.login-grid {
grid-template-columns: 1fr 1fr;
}
}
.login-form {
background-color: #eff2f7; /* legacy inline style */
padding: 2rem;
min-height: 505px; /* legacy .login-tabs-container */
}
.login-art {
display: none;
text-align: center;
}
@media (min-width: 992px) {
.login-art {
display: block;
}
}
.login-art img {
max-width: 70%;
margin: 0 auto;
}
.mt-8 {
margin-top: 2rem;
}
</style>