create landing page for boursemes
@@ -0,0 +1 @@
|
||||
VITE_LOGIN_APP_URL=https://boursemes.com/home
|
||||
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
dist
|
||||
.DS_Store
|
||||
*.local
|
||||
.env
|
||||
@@ -0,0 +1,16 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
BasicButton: typeof import('./src/components/BasicPacks/BasicButton.vue')['default']
|
||||
BasicIcon: typeof import('./src/components/BasicPacks/BasicIcon.vue')['default']
|
||||
BasicModal: typeof import('./src/components/BasicPacks/BasicModal.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_LOGIN_APP_URL: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
@@ -0,0 +1,751 @@
|
||||
<!doctype html>
|
||||
<html lang="fa" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="enamad" content="44889195" />
|
||||
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content"
|
||||
/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
|
||||
<title>در حال بارگذاری...</title>
|
||||
<!---->
|
||||
<style>
|
||||
:root {
|
||||
--boot-bg: #133050;
|
||||
--boot-bg-soft: #133050;
|
||||
--boot-card: rgba(255, 255, 255, 0.88);
|
||||
--boot-border: rgba(15, 23, 42, 0.08);
|
||||
--boot-text: #111827;
|
||||
--boot-muted: rgba(15, 23, 42, 0.62);
|
||||
--boot-accent: #2563eb;
|
||||
--boot-orb: rgba(37, 99, 235, 0.10);
|
||||
--boot-spinner-track: rgba(15, 23, 42, 0.10);
|
||||
--boot-shadow: rgba(15, 23, 42, 0.10);
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
background: var(--boot-bg);
|
||||
}
|
||||
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.boot-loader {
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
direction: rtl;
|
||||
font-family:
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
sans-serif;
|
||||
background: var(--boot-bg);
|
||||
color: var(--boot-text);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.boot-loader__logo {
|
||||
width: min(72vw, 280px);
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
animation: boot-logo-fade-in 0.45s ease-out both;
|
||||
}
|
||||
|
||||
@keyframes boot-logo-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.96);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Critical: full-viewport splash before Vue/CSS hydrate */
|
||||
@media (min-width: 768px) {
|
||||
html, body, #app {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
body {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
#app,
|
||||
.boot-loader {
|
||||
width: 100% !important;
|
||||
max-width: none !important;
|
||||
}
|
||||
.app-splash-screen {
|
||||
position: fixed !important;
|
||||
inset: 0 !important;
|
||||
width: 100vw !important;
|
||||
min-width: 100vw !important;
|
||||
max-width: none !important;
|
||||
min-height: 100vh !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
margin: 0 !important;
|
||||
transform: none !important;
|
||||
z-index: 2147483646;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
if (window.matchMedia('(min-width: 768px)').matches) {
|
||||
document.documentElement.classList.add('app-layout--desktop')
|
||||
}
|
||||
})()
|
||||
</script>
|
||||
|
||||
<script>
|
||||
;(function () {
|
||||
var CONFIG_COOKIE_NAME = 'APP_CONFIG_CACHE'
|
||||
|
||||
function getCookie(name) {
|
||||
var cookie = document.cookie.split('; ').find(function (row) {
|
||||
return row.indexOf(name + '=') === 0
|
||||
})
|
||||
|
||||
if (!cookie) return null
|
||||
|
||||
return cookie.substring(name.length + 1)
|
||||
}
|
||||
|
||||
function readCachedConfig() {
|
||||
try {
|
||||
var rawCookie = getCookie(CONFIG_COOKIE_NAME)
|
||||
|
||||
if (!rawCookie) return null
|
||||
|
||||
var decoded = decodeURIComponent(rawCookie)
|
||||
var payload = JSON.parse(decoded)
|
||||
|
||||
return payload && payload.config ? payload.config : null
|
||||
} catch (error) {
|
||||
console.warn('Boot theme: failed to read cached config', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeHex(hex) {
|
||||
if (!hex || typeof hex !== 'string') return null
|
||||
|
||||
var value = hex.trim()
|
||||
|
||||
if (!value) return null
|
||||
|
||||
if (value[0] !== '#') {
|
||||
value = '#' + value
|
||||
}
|
||||
|
||||
if (/^#[0-9a-fA-F]{3}$/.test(value)) {
|
||||
value =
|
||||
'#' +
|
||||
value[1] +
|
||||
value[1] +
|
||||
value[2] +
|
||||
value[2] +
|
||||
value[3] +
|
||||
value[3]
|
||||
}
|
||||
|
||||
if (!/^#[0-9a-fA-F]{6}$/.test(value)) return null
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
function hexToRgb(hex) {
|
||||
var normalized = normalizeHex(hex)
|
||||
|
||||
if (!normalized) return null
|
||||
|
||||
var num = parseInt(normalized.slice(1), 16)
|
||||
|
||||
return {
|
||||
r: (num >> 16) & 255,
|
||||
g: (num >> 8) & 255,
|
||||
b: num & 255
|
||||
}
|
||||
}
|
||||
|
||||
function rgbToHex(r, g, b) {
|
||||
function toHex(value) {
|
||||
var hex = Math.max(0, Math.min(255, Math.round(value))).toString(16)
|
||||
return hex.length === 1 ? '0' + hex : hex
|
||||
}
|
||||
|
||||
return '#' + toHex(r) + toHex(g) + toHex(b)
|
||||
}
|
||||
|
||||
function mix(hexA, hexB, weight) {
|
||||
var a = hexToRgb(hexA)
|
||||
var b = hexToRgb(hexB)
|
||||
|
||||
if (!a || !b) return hexA
|
||||
|
||||
return rgbToHex(
|
||||
a.r * (1 - weight) + b.r * weight,
|
||||
a.g * (1 - weight) + b.g * weight,
|
||||
a.b * (1 - weight) + b.b * weight
|
||||
)
|
||||
}
|
||||
|
||||
function brightness(hex) {
|
||||
var rgb = hexToRgb(hex)
|
||||
|
||||
if (!rgb) return 0
|
||||
|
||||
return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000
|
||||
}
|
||||
|
||||
function hexToRgba(hex, alpha) {
|
||||
var rgb = hexToRgb(hex)
|
||||
|
||||
if (!rgb) return 'rgba(96, 165, 250, ' + alpha + ')'
|
||||
|
||||
return 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + alpha + ')'
|
||||
}
|
||||
|
||||
function setVar(name, value) {
|
||||
document.documentElement.style.setProperty(name, value)
|
||||
}
|
||||
|
||||
function applyBootTheme() {
|
||||
var config = readCachedConfig()
|
||||
var user = config && config.user ? config.user : null
|
||||
|
||||
if (!user) return
|
||||
|
||||
var tenantColor = normalizeHex(user.color)
|
||||
var tenantSecondary = normalizeHex(user.secondary_color)
|
||||
var tenantBackground = normalizeHex(user.background_color)
|
||||
|
||||
/**
|
||||
* مهم:
|
||||
* background_color بکگراند اپ است.
|
||||
* اگر نبود، لودینگ باید روشن بماند، نه اینکه color را بکگراند کنیم.
|
||||
*/
|
||||
var bg = tenantBackground || '#ffffff'
|
||||
|
||||
/**
|
||||
* color و secondary_color فقط برای accent استفاده میشوند.
|
||||
*/
|
||||
var accent = tenantSecondary || tenantColor || '#2563eb'
|
||||
|
||||
var isLight = brightness(bg) > 155
|
||||
|
||||
var bgSoft = isLight ? mix(bg, accent, 0.045) : mix(bg, '#ffffff', 0.055)
|
||||
|
||||
var text = isLight ? '#111827' : '#ffffff'
|
||||
var muted = isLight ? 'rgba(15, 23, 42, 0.62)' : 'rgba(255, 255, 255, 0.70)'
|
||||
|
||||
var card = isLight ? 'rgba(255, 255, 255, 0.88)' : 'rgba(255, 255, 255, 0.07)'
|
||||
var border = isLight ? 'rgba(15, 23, 42, 0.08)' : 'rgba(255, 255, 255, 0.12)'
|
||||
|
||||
var orb = hexToRgba(accent, isLight ? 0.10 : 0.18)
|
||||
var spinnerTrack = isLight ? 'rgba(15, 23, 42, 0.10)' : 'rgba(255, 255, 255, 0.14)'
|
||||
var shadow = isLight ? 'rgba(15, 23, 42, 0.10)' : 'rgba(0, 0, 0, 0.24)'
|
||||
|
||||
setVar('--boot-bg', bg)
|
||||
setVar('--boot-bg-soft', bgSoft)
|
||||
setVar('--boot-accent', accent)
|
||||
setVar('--boot-text', text)
|
||||
setVar('--boot-muted', muted)
|
||||
setVar('--boot-card', card)
|
||||
setVar('--boot-border', border)
|
||||
setVar('--boot-orb', orb)
|
||||
setVar('--boot-spinner-track', spinnerTrack)
|
||||
setVar('--boot-shadow', shadow)
|
||||
|
||||
if (tenantColor) {
|
||||
setVar('--App-primary-color', tenantColor)
|
||||
}
|
||||
if (tenantSecondary) {
|
||||
setVar('--App-secondary-color', tenantSecondary)
|
||||
}
|
||||
if (tenantBackground) {
|
||||
setVar('--App-background-color', tenantBackground)
|
||||
}
|
||||
|
||||
var logoEl = document.getElementById('boot-logo')
|
||||
if (logoEl) {
|
||||
if (user.name === 'boursecu') {
|
||||
logoEl.src = '/boursecu-splash.png'
|
||||
} else if (user.avatar) {
|
||||
logoEl.src = user.avatar
|
||||
}
|
||||
}
|
||||
|
||||
if (user.name === 'boursecu') {
|
||||
setVar('--boot-bg', '#133050')
|
||||
setVar('--boot-bg-soft', '#133050')
|
||||
}
|
||||
|
||||
document.title = user.name_fa || user.name || 'در حال بارگذاری...'
|
||||
}
|
||||
|
||||
applyBootTheme()
|
||||
})()
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="boot-loader">
|
||||
<img
|
||||
id="boot-logo"
|
||||
class="boot-loader__logo"
|
||||
src="/boursecu-splash.png"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
;(function () {
|
||||
var BOOT_TIMEOUT_MS = 90000
|
||||
|
||||
var RETRY_KEY = '__APP_BOOT_RETRY_CLICKED__'
|
||||
var RECOVERY_KEY = '__APP_BOOT_RECOVERY_DONE__'
|
||||
|
||||
var CONFIG_COOKIE_NAME = 'APP_CONFIG_CACHE'
|
||||
var CONFIG_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 7
|
||||
|
||||
var TOKEN_STORAGE_KEY = 'customer_session_token'
|
||||
var LEGACY_TOKEN_SUFFIX = '_app_token'
|
||||
var VERSION_SUCCESS_URL_KEY = 'VERSION_Success_URL'
|
||||
var VERSION_STORAGE_KEY = 'version'
|
||||
var CONFIG_SUCCESS_URL_KEY = 'CONFIG_Success_URL'
|
||||
|
||||
function getProtocol() {
|
||||
return window.location.protocol === 'https:' ? 'https:' : 'http:'
|
||||
}
|
||||
|
||||
function getVersionUrls() {
|
||||
var protocol = getProtocol()
|
||||
|
||||
return [
|
||||
'https://nerkh.gold/get_version',
|
||||
'https://tablo.talasevin.ir/get_version',
|
||||
'https://abshode-service.montgit.ir/server/get_version',
|
||||
protocol + '//212.23.201.173:8381/get_version',
|
||||
protocol + '//91.212.174.137:4040/server/get_version'
|
||||
]
|
||||
}
|
||||
|
||||
function getConfigUrls() {
|
||||
var protocol = getProtocol()
|
||||
|
||||
return [
|
||||
protocol + '//nerkh.gold/get_user',
|
||||
protocol + '//tablo.talasevin.ir/get_user',
|
||||
protocol + '//abshode-service.montgit.ir/server/get_user',
|
||||
protocol + '//212.23.201.173:8381/get_user',
|
||||
protocol + '//91.212.174.137:4040/server/get_user'
|
||||
]
|
||||
}
|
||||
|
||||
function sortVersionUrls(urls) {
|
||||
var lastSuccessUrl = localStorage.getItem(VERSION_SUCCESS_URL_KEY)
|
||||
|
||||
if (!lastSuccessUrl) {
|
||||
return urls.slice()
|
||||
}
|
||||
|
||||
return [lastSuccessUrl].concat(urls.filter(function (url) {
|
||||
return url !== lastSuccessUrl
|
||||
}))
|
||||
}
|
||||
|
||||
function sortByLastSuccess(urls) {
|
||||
var lastSuccessUrl = localStorage.getItem(CONFIG_SUCCESS_URL_KEY)
|
||||
|
||||
return urls.slice().sort(function (a, b) {
|
||||
if (a === lastSuccessUrl) return -1
|
||||
if (b === lastSuccessUrl) return 1
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
function normalizeVersionResponse(data) {
|
||||
if (typeof data === 'string' || typeof data === 'number') {
|
||||
return String(data).trim()
|
||||
}
|
||||
|
||||
if (!data) return ''
|
||||
|
||||
if (typeof data.data === 'string' || typeof data.data === 'number') {
|
||||
return String(data.data).trim()
|
||||
}
|
||||
|
||||
var version =
|
||||
data.version ||
|
||||
data.value ||
|
||||
(data.data && typeof data.data === 'object' ? data.data.version : '')
|
||||
|
||||
return version ? String(version).trim() : ''
|
||||
}
|
||||
|
||||
async function fetchJsonOrText(url, timeout) {
|
||||
var controller = new AbortController()
|
||||
|
||||
var timeoutId = window.setTimeout(function () {
|
||||
controller.abort()
|
||||
}, timeout)
|
||||
|
||||
try {
|
||||
var response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json'
|
||||
},
|
||||
cache: 'no-store',
|
||||
signal: controller.signal
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Request failed: ' + response.status)
|
||||
}
|
||||
|
||||
var contentType = response.headers.get('content-type') || ''
|
||||
|
||||
if (contentType.indexOf('application/json') !== -1) {
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
return await response.text()
|
||||
} finally {
|
||||
window.clearTimeout(timeoutId)
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchVersionFromUrl(url, timeout) {
|
||||
var controller = new AbortController()
|
||||
var timeoutId = window.setTimeout(function () {
|
||||
controller.abort()
|
||||
}, timeout)
|
||||
|
||||
try {
|
||||
var response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json'
|
||||
},
|
||||
cache: 'no-store',
|
||||
signal: controller.signal
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Version request failed: ' + response.status)
|
||||
}
|
||||
|
||||
var contentType = response.headers.get('content-type') || ''
|
||||
var data
|
||||
|
||||
if (contentType.indexOf('application/json') !== -1) {
|
||||
data = await response.json()
|
||||
} else {
|
||||
data = await response.text()
|
||||
}
|
||||
|
||||
var version = normalizeVersionResponse(data)
|
||||
|
||||
if (!version) {
|
||||
throw new Error('Version response is empty')
|
||||
}
|
||||
|
||||
localStorage.setItem(VERSION_SUCCESS_URL_KEY, url)
|
||||
return version
|
||||
} catch (error) {
|
||||
if (localStorage.getItem(VERSION_SUCCESS_URL_KEY) === url) {
|
||||
localStorage.removeItem(VERSION_SUCCESS_URL_KEY)
|
||||
}
|
||||
console.warn('Version failed:', url, error)
|
||||
return undefined
|
||||
} finally {
|
||||
window.clearTimeout(timeoutId)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRemoteVersion(rounds) {
|
||||
var urls = sortVersionUrls(getVersionUrls())
|
||||
|
||||
for (var round = 1; round <= rounds; round++) {
|
||||
for (var i = 0; i < urls.length; i++) {
|
||||
var timeout = i === urls.length - 1 ? 6000 : 3500
|
||||
var result = await fetchVersionFromUrl(urls[i], timeout)
|
||||
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
async function fetchFreshConfig(rounds) {
|
||||
var urls = sortByLastSuccess(getConfigUrls())
|
||||
|
||||
for (var round = 1; round <= rounds; round++) {
|
||||
for (var i = 0; i < urls.length; i++) {
|
||||
var url = urls[i]
|
||||
|
||||
try {
|
||||
var config = await fetchJsonOrText(url, i === urls.length - 1 ? 7000 : 4000)
|
||||
|
||||
if (!config || typeof config !== 'object') {
|
||||
throw new Error('Config response is invalid')
|
||||
}
|
||||
|
||||
return {
|
||||
config: config,
|
||||
url: url
|
||||
}
|
||||
} catch (error) {
|
||||
if (localStorage.getItem(CONFIG_SUCCESS_URL_KEY) === url) {
|
||||
localStorage.removeItem(CONFIG_SUCCESS_URL_KEY)
|
||||
}
|
||||
console.warn('Config failed:', url, 'round:', round, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Cannot fetch fresh config')
|
||||
}
|
||||
|
||||
function writeConfigCookie(config) {
|
||||
try {
|
||||
var payload = {
|
||||
cached_at: Date.now(),
|
||||
config: config
|
||||
}
|
||||
|
||||
var encoded = encodeURIComponent(JSON.stringify(payload))
|
||||
var secure = window.location.protocol === 'https:' ? '; Secure' : ''
|
||||
|
||||
document.cookie =
|
||||
CONFIG_COOKIE_NAME +
|
||||
'=' +
|
||||
encoded +
|
||||
'; Path=/; Max-Age=' +
|
||||
CONFIG_COOKIE_MAX_AGE_SECONDS +
|
||||
'; SameSite=Lax' +
|
||||
secure
|
||||
} catch (error) {
|
||||
console.warn('Failed to write config cookie:', error)
|
||||
}
|
||||
}
|
||||
|
||||
function clearReadableCookies() {
|
||||
document.cookie.split(';').forEach(function (cookie) {
|
||||
var name = cookie.split('=')[0] && cookie.split('=')[0].trim()
|
||||
|
||||
if (!name) return
|
||||
|
||||
document.cookie = name + '=; Path=/; Max-Age=0; SameSite=Lax'
|
||||
})
|
||||
}
|
||||
|
||||
function collectAuthTokensForPreserve() {
|
||||
var preserved = {}
|
||||
|
||||
var session = localStorage.getItem(TOKEN_STORAGE_KEY)
|
||||
if (session) {
|
||||
preserved[TOKEN_STORAGE_KEY] = session
|
||||
}
|
||||
|
||||
for (var i = 0; i < localStorage.length; i++) {
|
||||
var key = localStorage.key(i)
|
||||
if (!key) continue
|
||||
if (key.length >= LEGACY_TOKEN_SUFFIX.length &&
|
||||
key.slice(-LEGACY_TOKEN_SUFFIX.length) === LEGACY_TOKEN_SUFFIX) {
|
||||
var value = localStorage.getItem(key)
|
||||
if (value) {
|
||||
preserved[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return preserved
|
||||
}
|
||||
|
||||
function clearLocalStorageExceptToken() {
|
||||
var preserved = collectAuthTokensForPreserve()
|
||||
|
||||
localStorage.clear()
|
||||
|
||||
Object.keys(preserved).forEach(function (key) {
|
||||
localStorage.setItem(key, preserved[key])
|
||||
})
|
||||
}
|
||||
|
||||
async function clearServiceWorkersAndCaches() {
|
||||
try {
|
||||
if ('serviceWorker' in navigator) {
|
||||
var registrations = await navigator.serviceWorker.getRegistrations()
|
||||
|
||||
await Promise.all(
|
||||
registrations.map(function (registration) {
|
||||
return registration.unregister()
|
||||
})
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to unregister service workers:', error)
|
||||
}
|
||||
|
||||
try {
|
||||
if ('caches' in window) {
|
||||
var keys = await caches.keys()
|
||||
|
||||
await Promise.all(
|
||||
keys.map(function (key) {
|
||||
return caches.delete(key)
|
||||
})
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to clear caches:', error)
|
||||
}
|
||||
}
|
||||
|
||||
function reloadWithCacheBust() {
|
||||
var url = new URL(window.location.href)
|
||||
url.searchParams.set('__boot_recover', String(Date.now()))
|
||||
window.location.replace(url.toString())
|
||||
}
|
||||
|
||||
function renderRecovering(text) {
|
||||
var app = document.getElementById('app')
|
||||
if (!app) return
|
||||
|
||||
app.innerHTML =
|
||||
'<div style="min-height: 100vh; display: flex; align-items: center; justify-content: center; direction: rtl; font-family: sans-serif; padding: 24px; background: var(--boot-bg); color: var(--boot-text);">' +
|
||||
'<div style="width: min(420px, 100%); text-align: center; background: var(--boot-card); border: 1px solid var(--boot-border); border-radius: 24px; padding: 28px 22px;">' +
|
||||
'<div style="width: 54px; height: 54px; margin: 0 auto 18px; border-radius: 999px; border: 4px solid rgba(255,255,255,0.18); border-top-color: var(--boot-accent); animation: bootSpin 0.9s linear infinite;"></div>' +
|
||||
'<style>@keyframes bootSpin { to { transform: rotate(360deg); } }</style>' +
|
||||
'<h2 style="margin: 0 0 12px; font-size: 20px;">در حال بازیابی برنامه</h2>' +
|
||||
'<p style="margin: 0; color: var(--boot-muted); line-height: 2; font-size: 14px;">' +
|
||||
(text || 'لطفاً چند لحظه صبر کنید.') +
|
||||
'</p>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
}
|
||||
|
||||
function renderBootError() {
|
||||
var app = document.getElementById('app')
|
||||
if (!app) return
|
||||
|
||||
app.innerHTML =
|
||||
'<div style="min-height: 100vh; display: flex; align-items: center; justify-content: center; direction: rtl; font-family: sans-serif; padding: 24px; background: var(--boot-bg); color: var(--boot-text);">' +
|
||||
'<div style="width: min(420px, 100%); text-align: center; background: var(--boot-card); border: 1px solid var(--boot-border); border-radius: 24px; padding: 28px 22px;">' +
|
||||
'<div style="font-size: 42px; margin-bottom: 12px;">⚠️</div>' +
|
||||
'<h2 style="margin: 0 0 12px; font-size: 20px;">خطا در بارگذاری برنامه</h2>' +
|
||||
'<p style="margin: 0 0 20px; color: var(--boot-muted); line-height: 2; font-size: 14px;">' +
|
||||
'ارتباط با سرور برقرار نشد یا فایلهای برنامه بهدرستی دریافت نشدند.' +
|
||||
'</p>' +
|
||||
'<button onclick="window.__APP_BOOT_SIMPLE_RETRY__()" style="padding: 11px 22px; border: 0; border-radius: 12px; background: var(--boot-accent); color: white; cursor: pointer; font-weight: 700;">' +
|
||||
'تلاش مجدد' +
|
||||
'</button>' +
|
||||
'<button onclick="window.__APP_BOOT_CLEAN_RELOAD__()" style="margin-right: 8px; padding: 11px 18px; border: 1px solid var(--boot-border); border-radius: 12px; background: transparent; color: var(--boot-text); cursor: pointer; font-weight: 700;">' +
|
||||
'پاکسازی و تلاش مجدد' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
}
|
||||
|
||||
async function cleanFetchAndReload() {
|
||||
renderRecovering('در حال دریافت تنظیمات جدید برنامه...')
|
||||
|
||||
var version = await loadRemoteVersion(5)
|
||||
var freshConfigResult = await fetchFreshConfig(5)
|
||||
|
||||
clearReadableCookies()
|
||||
// clearLocalStorageExceptToken()
|
||||
await clearServiceWorkersAndCaches()
|
||||
|
||||
writeConfigCookie(freshConfigResult.config)
|
||||
localStorage.setItem(CONFIG_SUCCESS_URL_KEY, freshConfigResult.url)
|
||||
|
||||
if (version) {
|
||||
localStorage.setItem(VERSION_STORAGE_KEY, version)
|
||||
}
|
||||
|
||||
sessionStorage.removeItem(RETRY_KEY)
|
||||
sessionStorage.removeItem(RECOVERY_KEY)
|
||||
|
||||
reloadWithCacheBust()
|
||||
}
|
||||
|
||||
window.__APP_BOOT_SIMPLE_RETRY__ = function () {
|
||||
sessionStorage.setItem(RETRY_KEY, '1')
|
||||
reloadWithCacheBust()
|
||||
}
|
||||
|
||||
window.__APP_BOOT_CLEAN_RELOAD__ = async function () {
|
||||
try {
|
||||
sessionStorage.setItem(RECOVERY_KEY, '1')
|
||||
await cleanFetchAndReload()
|
||||
} catch (error) {
|
||||
console.error('Manual boot recovery failed:', error)
|
||||
renderBootError()
|
||||
}
|
||||
}
|
||||
|
||||
window.__BOOT_TIMEOUT__ = window.setTimeout(async function () {
|
||||
if (window.__APP_BOOTSTRAPPED__) return
|
||||
|
||||
var app = document.getElementById('app')
|
||||
var stillBooting = app && app.querySelector('.boot-loader')
|
||||
|
||||
if (!stillBooting) return
|
||||
|
||||
if (!sessionStorage.getItem(RETRY_KEY)) {
|
||||
renderBootError()
|
||||
return
|
||||
}
|
||||
|
||||
if (!sessionStorage.getItem(RECOVERY_KEY)) {
|
||||
sessionStorage.setItem(RECOVERY_KEY, '1')
|
||||
|
||||
try {
|
||||
await cleanFetchAndReload()
|
||||
} catch (error) {
|
||||
console.error('Auto boot recovery after retry failed:', error)
|
||||
renderBootError()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
renderBootError()
|
||||
}, BOOT_TIMEOUT_MS)
|
||||
})()
|
||||
</script>
|
||||
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "landing",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"ant-design-vue": "^3.2.20",
|
||||
"axios": "^1.6.2",
|
||||
"lodash": "^4.17.21",
|
||||
"pinia": "^2.1.4",
|
||||
"unocss": "^0.55.1",
|
||||
"unplugin-vue-components": "^0.25.1",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.3.2",
|
||||
"@tsconfig/node18": "^18.2.0",
|
||||
"@types/lodash": "^4.17.7",
|
||||
"@types/node": "^18.17.0",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vue/eslint-config-prettier": "^8.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"@vue/tsconfig": "^0.4.0",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"less": "^4.2.0",
|
||||
"prettier": "^3.0.0",
|
||||
"typescript": "~5.1.6",
|
||||
"vite": "^4.4.6",
|
||||
"vue-tsc": "^2.1.10"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
@@ -0,0 +1,5 @@
|
||||
import useAxios from "@/utils/useAxios";
|
||||
|
||||
export async function getSettings() {
|
||||
return await useAxios.get(`api/get_settings`)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import useAxios from "@/utils/useAxios";
|
||||
import type {Price} from "@/models/home";
|
||||
|
||||
export const getChart = async () => await useAxios.get<Array<Array<any>>>(`/api/candle`)
|
||||
|
||||
/** @deprecated قیمت up داخل /api/price/last در فیلد up برمیگردد */
|
||||
export const getPrice = async () => await useAxios.get<Price>(`/api/price/up`)
|
||||
|
||||
export const getLastPrice = async () => await useAxios.get<Price>(`/api/price/last`)
|
||||
|
||||
export const getAllPrices = async () => await useAxios.post('/customer/prices/all')
|
||||
@@ -0,0 +1,14 @@
|
||||
import useAxios from "@/utils/useAxios";
|
||||
import type {PInventory, Product} from "@/models/products";
|
||||
import type { PWallet, Wallet } from '@/models/wallet'
|
||||
|
||||
|
||||
export const getProducts = async (): Promise<any> => {
|
||||
return await useAxios.get<Product[]>(`/customer/product/list`)
|
||||
}
|
||||
export const getProductInventory = async (): Promise<any> => {
|
||||
return await useAxios.post<PInventory[]>(`/customer/wallet/product/table`)
|
||||
}
|
||||
export const getOneProductWallet = async (id: number): Promise<any> => {
|
||||
return await useAxios.get<PWallet>(`/customer/wallet/product/${id}`)
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
@import (less) './var.less';
|
||||
|
||||
/**
|
||||
* Dana Pro (FaNum) — اعداد فارسی
|
||||
* منبع: DanaPro Webfonts / Farsi numerals
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: dana;
|
||||
font-style: normal;
|
||||
font-weight: 10;
|
||||
src: url('../fonts/Dana/woff2/DanaFaNum-Hairline.woff2') format('woff2'),
|
||||
url('../fonts/Dana/woff/DanaFaNum-Hairline.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: dana;
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src: url('../fonts/Dana/woff2/DanaFaNum-Thin.woff2') format('woff2'),
|
||||
url('../fonts/Dana/woff/DanaFaNum-Thin.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: dana;
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url('../fonts/Dana/woff2/DanaFaNum-UltraLight.woff2') format('woff2'),
|
||||
url('../fonts/Dana/woff/DanaFaNum-UltraLight.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: dana;
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url('../fonts/Dana/woff2/DanaFaNum-Light.woff2') format('woff2'),
|
||||
url('../fonts/Dana/woff/DanaFaNum-Light.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: dana;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('../fonts/Dana/woff2/DanaFaNum-Regular.woff2') format('woff2'),
|
||||
url('../fonts/Dana/woff/DanaFaNum-Regular.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: dana;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url('../fonts/Dana/woff2/DanaFaNum-Medium.woff2') format('woff2'),
|
||||
url('../fonts/Dana/woff/DanaFaNum-Medium.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: dana;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url('../fonts/Dana/woff2/DanaFaNum-DemiBold.woff2') format('woff2'),
|
||||
url('../fonts/Dana/woff/DanaFaNum-DemiBold.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: dana;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('../fonts/Dana/woff2/DanaFaNum-Bold.woff2') format('woff2'),
|
||||
url('../fonts/Dana/woff/DanaFaNum-Bold.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: dana;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url('../fonts/Dana/woff2/DanaFaNum-ExtraBold.woff2') format('woff2'),
|
||||
url('../fonts/Dana/woff/DanaFaNum-ExtraBold.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: dana;
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url('../fonts/Dana/woff2/DanaFaNum-Black.woff2') format('woff2'),
|
||||
url('../fonts/Dana/woff/DanaFaNum-Black.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: dana;
|
||||
font-style: normal;
|
||||
font-weight: 930;
|
||||
src: url('../fonts/Dana/woff2/DanaFaNum-ExtraBlack.woff2') format('woff2'),
|
||||
url('../fonts/Dana/woff/DanaFaNum-ExtraBlack.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: dana;
|
||||
font-style: normal;
|
||||
font-weight: 960;
|
||||
src: url('../fonts/Dana/woff2/DanaFaNum-Heavy.woff2') format('woff2'),
|
||||
url('../fonts/Dana/woff/DanaFaNum-Heavy.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: dana;
|
||||
font-style: normal;
|
||||
font-weight: 990;
|
||||
src: url('../fonts/Dana/woff2/DanaFaNum-fat.woff2') format('woff2'),
|
||||
url('../fonts/Dana/woff/DanaFaNum-fat.woff') format('woff');
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
@import (less) './var.less';
|
||||
@import (less) './fonts.less';
|
||||
@import (less) '../../views/base/aboutUs/desktop/styles/desktop-about-us.less';
|
||||
@import (less) '../../views/login/desktop/styles/desktop-auth.less';
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--App-font-family) !important;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
@prefixCls: 'noname';
|
||||
|
||||
@App-primary-color: var(--App-primary-color);
|
||||
@secondary-color: var(--App-secondary-color);
|
||||
@prefixCls-font-family: 'dana';
|
||||
@prefixCls-background-color: var(--App-background-color);
|
||||
|
||||
@border-radius-base: 8px;
|
||||
:root {
|
||||
--App-primary-color: #0A1B37;
|
||||
--App-secondary-color: #e0be7f;
|
||||
--App-background-color: #ffffff;
|
||||
--App-font-family: 'dana', sans-serif;
|
||||
}
|
||||
|
After Width: | Height: | Size: 607 B |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 64 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 3.5V14.5" stroke="#1a2b47" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.25 11L12 14.75L15.75 11" stroke="#1a2b47" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 16.5V18.25C5 19.22 5.78 20 6.75 20H17.25C18.22 20 19 19.22 19 18.25V16.5" stroke="#1a2b47" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 510 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="19" height="10" viewBox="0 0 19 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.5002 0.833496L10.4168 7.91683L6.25016 3.75016L0.833496 9.16683" stroke="#0E9F6E" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 269 B |
|
After Width: | Height: | Size: 294 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 508 KiB |
|
After Width: | Height: | Size: 37 KiB |