1405/02/07-add authentication card for garoosi

This commit is contained in:
2026-04-27 18:18:05 +03:30
parent e96ef77e8f
commit 688c356e7d
7 changed files with 327 additions and 2 deletions
+4
View File
@@ -1,6 +1,7 @@
import useAxios from '@core/utils/useAxios'
import type { AddId, Customer } from '@/model/customer'
import type { TableInput } from '@core/type/baseTable'
import type {CheckCardRequest, CheckCardResponse} from "@/model/user";
export const createCustomer = async (obj: Customer) =>
await useAxios.post<AddId>(`/admin/customer`, obj)
@@ -34,3 +35,6 @@ export const getSingleTable = async (params: TableInput) =>
export const getSingleArchiveTable = async (params: TableInput) =>
await useAxios.post<Array<Customer>>('/admin/customer/single/archive/table', params)
export const checkCard = async (data: CheckCardRequest) => {
return await useAxios.post<CheckCardResponse>('/admin/check_card', data)
}
@@ -1,6 +1,6 @@
<template>
<div>
<DatePicker :locale="locale" v-model:value="_date" :size="props.size" v-bind="_attrs" inputReadOnly/>
<DatePicker :locale="locale" v-model:value="_date" :size="props.size" v-bind="_attrs" :inputReadOnly="inputReadOnly" />
</div>
</template>
@@ -14,6 +14,7 @@ interface Props {
value?: string | Dayjs
size?: string
format?: string
inputReadOnly?:boolean
}
const attrs = useAttrs()
@@ -25,7 +26,8 @@ const _attrs = computed(() => {
const props = withDefaults(defineProps<Props>(), {
value: '',
format: 'YYYY/MM/DD'
format: 'YYYY/MM/DD',
inputReadOnly:true
})
const emit = defineEmits<{
+1
View File
@@ -69,6 +69,7 @@ export function setupRouter(app: App) {
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: routerStore.routes,
/*scrollBehavior(to, from, savedPosition) {
/!*if (savedPosition) {
return savedPosition
+11
View File
@@ -21,3 +21,14 @@ export interface User {
export interface AddId {
id: number
}
export interface CheckCardRequest {
birth_date: string
national_code: string
cart_number: string
}
export interface CheckCardResponse {
success: boolean
message?: string
data?: any
}
@@ -0,0 +1,293 @@
<template>
<div class="flex items-center justify-center p-4">
<div class="w-full max-w-md">
<a-card class="shadow-2xl rounded-2xl border-0" :bordered="false">
<!-- Header -->
<div class="text-center mb-8">
<div class="w-20 h-20 mx-auto mb-4 rounded-full bg-gradient-to-r from-blue-500 to-indigo-600 flex items-center justify-center">
<svg class="w-10 h-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
</div>
<h2 class="text-3xl font-bold text-gray-800 mb-2">احراز هویت</h2>
<p class="text-gray-500 text-sm">لطفاً اطلاعات کارت بانکی فرد مورد نظر را وارد کنید</p>
</div>
<!-- Form -->
<a-form
:model="formState"
:rules="rules"
:label-col="{ span: 24 }"
:wrapper-col="{ span: 24 }"
@finish="handleSubmit"
class="rtl-form"
>
<a-form-item label="تاریخ تولد" name="birth_date" required>
<BaseDatePicker
v-model:value="formState.birth_date"
placeholder="انتخاب تاریخ تولد"
format="YYYY-MM-DD"
class="w-full"
size="large"
:disabled-date="disabledDate"
/>
</a-form-item>
<a-form-item label="کد ملی" name="national_code" required>
<a-input
v-model:value="formState.national_code"
placeholder="کد ملی خود را وارد کنید"
size="large"
maxlength="10"
class=" text-lg"
>
<template #prefix>
<span class="text-gray-400 ml-2">🇮🇷</span>
</template>
</a-input>
</a-form-item>
<a-form-item label="شماره کارت" name="cart_number" required>
<a-input
v-model:value="formState.cart_number"
placeholder="شماره ۱۶ رقمی کارت را وارد کنید"
size="large"
maxlength="16"
class=" text-lg tracking-wider"
>
<template #prefix>
<svg class="w-5 h-5 text-gray-400 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
</svg>
</template>
</a-input>
</a-form-item>
<a-form-item class="mt-3">
<a-button
type="primary"
html-type="submit"
size="large"
:loading="loading"
block
class="h-12 text-base font-semibold border-0"
>
<template #icon>
<svg class="w-5 h-5 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
</template>
تأیید و ادامه
</a-button>
</a-form-item>
</a-form>
</a-card>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, h } from 'vue'
import { message, Modal } from 'ant-design-vue'
import type { FormRules } from 'ant-design-vue/es/form'
import type { CheckCardRequest } from "@/model/user"
import { checkCard } from "@/api/customer"
import DatePicker from "@core/components/_sample/components/datePicker.vue";
const formState = reactive<CheckCardRequest>({
birth_date: '',
national_code: '',
cart_number: ''
})
const loading = ref(false)
// Validation rules
const rules: FormRules = {
birth_date: [
{ required: true, message: 'لطفاً تاریخ تولد را انتخاب کنید', trigger: 'change' }
],
national_code: [
{ required: true, message: 'لطفاً کد ملی را وارد کنید', trigger: 'blur' },
{ len: 10, message: 'کد ملی باید ۱۰ رقم باشد', trigger: 'blur' },
{ pattern: /^[0-9]+$/, message: 'کد ملی فقط می‌تواند شامل اعداد باشد', trigger: 'blur' },
{ validator: validateNationalCode, trigger: 'blur' }
],
cart_number: [
{ required: true, message: 'لطفاً شماره کارت را وارد کنید', trigger: 'blur' },
{ len: 16, message: 'شماره کارت باید ۱۶ رقم باشد', trigger: 'blur' },
{ pattern: /^[0-9]+$/, message: 'شماره کارت فقط می‌تواند شامل اعداد باشد', trigger: 'blur' },
{ validator: validateLuhn, trigger: 'blur' }
]
}
// Validate Iranian National Code
function validateNationalCode(_rule: any, value: string) {
if (!value) return Promise.resolve()
const code = value.toString()
if (code.length !== 10 || !/^\d{10}$/.test(code)) {
return Promise.reject('کد ملی نامعتبر است')
}
const check = parseInt(code[9])
let sum = 0
for (let i = 0; i < 9; i++) {
sum += parseInt(code[i]) * (10 - i)
}
const remainder = sum % 11
const isValid = (remainder < 2 && check === remainder) || (remainder >= 2 && check === 11 - remainder)
return isValid ? Promise.resolve() : Promise.reject('کد ملی نامعتبر است')
}
// Validate Luhn algorithm for card number
function validateLuhn(_rule: any, value: string) {
if (!value) return Promise.resolve()
if (!/^\d{16}$/.test(value)) {
return Promise.reject('شماره کارت نامعتبر است')
}
let sum = 0
let isEven = false
for (let i = value.length - 1; i >= 0; i--) {
let digit = parseInt(value[i])
if (isEven) {
digit *= 2
if (digit > 9) digit -= 9
}
sum += digit
isEven = !isEven
}
return sum % 10 === 0 ? Promise.resolve() : Promise.reject('شماره کارت نامعتبر است')
}
// Disable future dates
function disabledDate(current: any) {
return current && current > new Date()
}
// Format card number with spaces (XXXX XXXX XXXX XXXX)
function formatCardNumber(cardNumber: string): string {
return cardNumber.replace(/(\d{4})/g, '$1 ').trim()
}
// Get Persian bank name
function getBankPersianName(bankCode: string): string {
const banks: Record<string, string> = {
'SAMAN': 'بانک سامان',
'MELLI': 'بانک ملی ایران',
'MELAT': 'بانک ملت',
'SADERAT': 'بانک صادرات ایران',
'TEJARAT': 'بانک تجارت',
'PASARGAD': 'بانک پاسارگاد',
'KARAFARIN': 'بانک کارآفرین',
'EGHTESAD_NOVIN': 'بانک اقتصاد نوین',
'PARSIAN': 'بانک پارسیان',
'SINAA': 'بانک سینا',
'MELLAT': 'بانک ملت'
}
return banks[bankCode] || bankCode
}
// Display success modal with user information
function showSuccessModal(owners: any[], cartNumber: string, bankName: string) {
if (!owners || owners.length === 0) {
message.success({
content: 'احراز هویت با موفقیت انجام شد',
duration: 3
})
return
}
const owner = owners[0]
const fullName = `${owner.firstName} ${owner.lastName}`
const formattedCardNumber = formatCardNumber(cartNumber)
const persianBankName = getBankPersianName(bankName)
Modal.success({
title: 'تأیید هویت',
content: h('div', { style: 'direction: rtl; text-align: right;' }, [
h('p', {
style: 'margin-bottom: 16px; font-size: 14px; line-height: 1.6; color: #1f2937; font-weight: 500;'
},
`احراز هویت آقای / سرکار خانم ${fullName} با موفقیت انجام گردید.`
),
h('div', {
style: 'margin-top: 20px; padding: 12px; background-color: #f3f4f6; border-radius: 8px;'
}, [
h('p', { style: 'margin-bottom: 8px; font-size: 13px; color: #4B5563;' }, [
h('span', { style: 'font-weight: 600;' }, 'شماره کارت: '),
h('span', { style: 'font-family: monospace; direction: ltr; display: inline-block;' }, formattedCardNumber)
]),
h('p', { style: 'font-size: 13px; color: #4B5563; margin: 0;' }, [
h('span', { style: 'font-weight: 600;' }, 'بانک صادرکننده: '),
h('span', {}, persianBankName)
])
])
]),
okText: 'تأیید',
width: 420,
maskClosable: true
})
}
// Submit form
async function handleSubmit() {
if (!formState.birth_date) {
message.warning('لطفاً تاریخ تولد را انتخاب کنید')
return
}
loading.value = true
try {
const response = await checkCard(formState)
// Check if the response is successful based on the provided structure
if (response?.cartMatching?.matched === true && response?.cartData) {
const { cartData } = response
const owners = cartData.ibanInfo?.owners || []
const bankName = cartData.ibanInfo?.bank || ''
// Show success message with user information
showSuccessModal(owners, cartData.number, bankName)
// Optional: Redirect to another page after modal is closed
// You can add a callback here for redirection
} else {
message.error({
content: 'اطلاعات وارد شده با داده‌های بانکی مطابقت ندارد. لطفاً مجدداً تلاش نمایید.',
duration: 4
})
}
} catch (error: any) {
console.error('Error:', error)
// Handle different error scenarios
if (error.response?.status === 404) {
message.error('کاربری با این اطلاعات یافت نشد. لطفاً اطلاعات را بررسی نمایید.')
} else if (error.response?.status === 400) {
message.error('درخواست نامعتبر. لطفاً اطلاعات را به درستی وارد نمایید.')
} else if (error.response?.status === 500) {
message.error('خطای داخلی سرور. لطفاً چند دقیقه دیگر مجدداً تلاش نمایید.')
} else {
message.error(error.response?.data?.message || 'خطا در ارتباط با سرور. لطفاً مجدداً تلاش نمایید.')
}
} finally {
loading.value = false
}
}
</script>
<style scoped lang="less">
</style>
+3
View File
@@ -47,8 +47,11 @@ export function setupRoutes(permissionsKeys: string[], router?: Router) {
function filterRoutesByApp(routes: RouteRecordRaw) {
if (!routes.children) return
routes.children = routes.children.filter((item) => {
if (item.name === 'discounts' && appName !== 'goldsun') return false
if (item.name === 'banners' && appName !== 'garoosi') return false
if (item.name === 'authentication' && appName !== 'garoosi') return false
return true
})
routes.children.forEach((child) => filterRoutesByApp(child))
+11
View File
@@ -47,6 +47,17 @@ const Customers: RouteRecordRaw = {
icon: 'vuesax-linear:save-2',
title: 'بایگانی'
}
},
{
name: 'authentication',
path: 'authentication',
component: () => import('@/pages/menuPages/customers/authentication/index.vue'),
meta: {
// permissionsKey: 'user',
icon: 'vuesax-linear:card-tick',
title: 'احراز هویت',
}
}
]
}