fix some bugs

This commit is contained in:
2026-06-27 13:49:00 +03:30
parent c3b47e33e1
commit 4d004c3515
13 changed files with 331 additions and 57 deletions
+29 -5
View File
@@ -354,7 +354,8 @@
var CONFIG_COOKIE_NAME = 'APP_CONFIG_CACHE'
var CONFIG_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 7
var TOKEN_STORAGE_KEY = 'liman_user_token'
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'
@@ -575,14 +576,37 @@
})
}
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 token = localStorage.getItem(TOKEN_STORAGE_KEY)
var preserved = collectAuthTokensForPreserve()
localStorage.clear()
if (token) {
localStorage.setItem(TOKEN_STORAGE_KEY, token)
}
Object.keys(preserved).forEach(function (key) {
localStorage.setItem(key, preserved[key])
})
}
async function clearServiceWorkersAndCaches() {
+1
View File
@@ -9,6 +9,7 @@ import { applyProjectTheme } from '@/utils/config'
import { useSettingsStore } from '@/store/settingsStore'
import { useUserStore } from '@/store/userStore'
import { getToken } from '@/utils'
import '@/utils/useDateTime'
import { initPriceRealtimeChannel } from '@/utils/priceSocket'
type BootWindow = Window & {
+8 -9
View File
@@ -1,7 +1,7 @@
import type { AppConfig } from '@/config/types'
import { resetVersionCheckCache } from '@/config/load-version'
import { clearCachedPwaManifest } from '@/modules/pwa/manifest-cache'
import { getTokenName } from '@/themeConfig'
import { collectAuthTokensForPreserve } from '@/utils/authToken'
const CONFIG_COOKIE_NAME = 'APP_CONFIG_CACHE'
const CONFIG_SUCCESS_URL_KEY = 'CONFIG_Success_URL'
@@ -201,13 +201,12 @@ export async function clearBrowserRuntimeCaches() {
}
function clearLocalStorageExceptToken() {
const tokenKey = getTokenName()
const token = localStorage.getItem(tokenKey)
const preserved = collectAuthTokensForPreserve()
localStorage.clear()
if (token) {
localStorage.setItem(tokenKey, token)
for (const [key, value] of Object.entries(preserved)) {
localStorage.setItem(key, value)
}
}
@@ -242,13 +241,13 @@ export async function refreshConfigCacheForVersion(
startVersionUpdateLock(normalizedVersion)
const freshConfig = await fetchConfig()
clearCachedAppConfig()
clearAppStorageExceptToken()
clearAllBootstrapFetchCaches()
await clearBrowserRuntimeCaches()
const freshConfig = await fetchConfig()
writeCachedAppConfig(freshConfig)
finishVersionUpdateBeforeReload(normalizedVersion)
@@ -261,13 +260,13 @@ export async function resetAppAndRefreshConfig(
) {
const normalizedVersion = getNormalizedVersion(version)
const freshConfig = await fetchConfig()
clearCachedAppConfig()
clearAppStorageExceptToken()
clearAllBootstrapFetchCaches()
await clearBrowserRuntimeCaches()
const freshConfig = await fetchConfig()
writeCachedAppConfig(freshConfig)
if (normalizedVersion) {
@@ -73,6 +73,8 @@ async function checkRemoteVersion() {
}
async function startUpdate(version: string) {
const configBackup = window.__APP_CONFIG__
try {
isUpdating.value = true
hasError.value = false
@@ -95,6 +97,11 @@ async function startUpdate(version: string) {
} catch (error) {
console.error('Version update failed:', error)
if (configBackup) {
window.__APP_CONFIG__ = configBackup
setAppConfig(configBackup)
}
cancelVersionUpdateLock()
isUpdating.value = false
+7 -2
View File
@@ -31,9 +31,14 @@ export function getAvatarUploadURL(): string {
return base ? `${base}/customer/avatar` : '/customer/avatar'
}
/** کلید ثابت — وقتی config پاک می‌شود (به‌روزرسانی نسخه) توکن گم نشود */
export const SESSION_AUTH_KEY = 'customer_session_token'
export const LEGACY_TOKEN_SUFFIX = '_app_token'
export function getTokenName(): string {
const name = getConfigApp('name') || readCachedTenantField('name') || 'app'
return `${name}_app_token`
const name = readCachedTenantField('name') || getConfigApp('name') || 'app'
return `${name}${LEGACY_TOKEN_SUFFIX}`
}
/** @deprecated از getTokenName() استفاده کن */
+72
View File
@@ -0,0 +1,72 @@
import {
getTokenName,
LEGACY_TOKEN_SUFFIX,
SESSION_AUTH_KEY,
} from '@/themeConfig'
function legacyTokenKeys(): string[] {
const keys: string[] = []
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i)
if (key?.endsWith(LEGACY_TOKEN_SUFFIX)) {
keys.push(key)
}
}
return keys
}
/** خواندن توکن — اول session ثابت، بعد کلید tenant، بعد هر *_app_token */
export function findStoredAuthToken(): string | null {
const session = localStorage.getItem(SESSION_AUTH_KEY)
if (session) {
return session
}
const named = localStorage.getItem(getTokenName())
if (named) {
localStorage.setItem(SESSION_AUTH_KEY, named)
return named
}
for (const key of legacyTokenKeys()) {
const value = localStorage.getItem(key)
if (value) {
localStorage.setItem(SESSION_AUTH_KEY, value)
return value
}
}
return null
}
export function persistAuthToken(token: string | undefined | null) {
if (!token) {
return
}
localStorage.setItem(SESSION_AUTH_KEY, token)
localStorage.setItem(getTokenName(), token)
}
export function clearStoredAuthTokens() {
localStorage.removeItem(SESSION_AUTH_KEY)
for (const key of legacyTokenKeys()) {
localStorage.removeItem(key)
}
}
export function collectAuthTokensForPreserve(): Record<string, string> {
const preserved: Record<string, string> = {}
for (const key of [SESSION_AUTH_KEY, getTokenName(), ...legacyTokenKeys()]) {
const value = localStorage.getItem(key)
if (value) {
preserved[key] = value
}
}
return preserved
}
+12 -5
View File
@@ -1,4 +1,8 @@
import { getTokenName } from '@/themeConfig'
import {
clearStoredAuthTokens,
findStoredAuthToken,
persistAuthToken,
} from '@/utils/authToken'
import { ref } from 'vue'
import {useConfigStore} from "@/store/configStore";
@@ -11,7 +15,10 @@ const nameProject= getConfigApp('name')
function setToken(token: string | undefined) {
return localStorage.setItem(getTokenName(), token)
if (!token) {
return
}
return persistAuthToken(token)
}
function setFingerPrint(val: string | undefined) {
@@ -19,7 +26,7 @@ function setFingerPrint(val: string | undefined) {
}
function getToken() {
return localStorage.getItem(getTokenName())
return findStoredAuthToken()
}
function getIsActiveFingerprint() {
@@ -56,7 +63,7 @@ export async function authenticateWithFingerprint() {
const credentialId = btoa(String.fromCharCode(...new Uint8Array(credential.rawId)))
// بازیابی توکن از localStorage
const token = localStorage.getItem(`${nameProject}_app_token`)
const token = findStoredAuthToken()
if (!token) {
console.warn('توکن پیدا نشد، عملیات متوقف شد.')
@@ -78,7 +85,7 @@ export async function authenticateWithFingerprint() {
function removeToken() {
return localStorage.removeItem(getTokenName())
return clearStoredAuthTokens()
}
function removeFingerPrint() {
+69 -16
View File
@@ -3,6 +3,7 @@ import { baseURL as fallbackBaseURL } from '@/themeConfig'
import type {AxiosRequestConfig, AxiosResponse} from 'axios'
import axios from 'axios'
import {getToken, removeToken} from "@/utils";
import { findStoredAuthToken } from '@/utils/authToken';
import router from "@/router";
import {get} from 'lodash'
import {message} from "ant-design-vue";
@@ -25,6 +26,58 @@ const _axios = axios.create({
headers,
})
let sessionVerifyTimer: ReturnType<typeof setTimeout> | null = null
let sessionVerifyInFlight = false
function requestPath(config: { url?: string; baseURL?: string } | undefined): string {
const raw = String(config?.url ?? '')
if (raw.startsWith('http')) {
try {
return new URL(raw).pathname
} catch {
return raw
}
}
return raw
}
async function logoutToLogin() {
removeToken()
if (router.currentRoute.value.name !== 'login') {
await router.push({ name: 'login' })
}
}
function scheduleSessionVerify() {
if (sessionVerifyTimer) {
return
}
sessionVerifyTimer = setTimeout(async () => {
sessionVerifyTimer = null
if (sessionVerifyInFlight) {
return
}
const token = findStoredAuthToken()
if (!token) {
return
}
sessionVerifyInFlight = true
try {
await _axios.get('/customer/me', {
headers: { Authorization: `Bearer ${token}` },
meta: { errorMessage: 'none' },
})
} catch {
await logoutToLogin()
} finally {
sessionVerifyInFlight = false
}
}, 2000)
}
_axios.interceptors.request.use(
(config) => {
const runtimeBaseURL = getConfigApp('node_server')
@@ -54,24 +107,24 @@ _axios.interceptors.response.use(
return response.data
},
async function (error) {
console.log('this is axios error', error)
if (error.response.data && error.response.data.message) {
checkStatus(error.response.data.message, error.response.status, error.config)
const response = error.response
const config = error.config
if (response?.data?.message && response.status !== 401) {
checkStatus(response.data.message, response.status, config)
}
//console.log('this is error on interceptors.response', error.config.meta)
/* if (error.response && (error.response.status == 401 || error.response.status == 400)) {
//debugger
removeToken()
router.push({ name: 'login' })
}*/
if (error.response && error.response.status == 401) {
///removeToken()
console.log('error')
removeToken()
// router.push({ name: 'login' })
await router.push({name: 'login'})
} else if (error.response && error.response.status == 403) {
if (response?.status === 401) {
const hadAuth = Boolean(config?.headers?.Authorization)
if (hadAuth) {
const path = requestPath(config)
if (path.includes('/customer/me')) {
await logoutToLogin()
} else {
scheduleSessionVerify()
}
}
} else if (response?.status === 403) {
message.error('شما دسترسی لازم برای انجام این عملیات را ندارید')
} else if (error.response && error.response.status == 422) {
// message.error(error.response.data?.message)
+93 -16
View File
@@ -2,7 +2,7 @@
<div
ref="headerRef"
class="app-header px-4 py-2 top-0 z-30 wrap-fix background-color-transition"
:class="{ 'app-header--scrolled': scrollY > 50 }"
:class="{ 'app-header--scrolled': isHeaderScrolled }"
>
<div class="flex justify-between my-2 items-center">
<div v-if="isBoursecuProject()" class="flex items-center gap-2">
@@ -40,47 +40,126 @@
</template>
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
import { useUserStore } from '@/store/userStore'
import router from '@/router'
import Icon from '@/components/BasicPacks/BasicIcon.vue'
import { getProjectMobileNavLogo, isBoursecuProject } from '@/utils/config'
const headerRef = ref<HTMLElement | null>(null)
const scrollY = ref(0)
const isHeaderScrolled = ref(false)
const userStore = useUserStore()
const projectLogo = computed(() => getProjectMobileNavLogo())
let intersectionObserver: IntersectionObserver | null = null
let scrollTarget: HTMLElement | Window | null = null
function isScrollableOverflow(value: string) {
return value === 'auto' || value === 'scroll' || value === 'overlay'
}
function findScrollParent(el: HTMLElement | null): HTMLElement | null {
let node = el?.parentElement ?? null
while (node) {
const { overflowY } = getComputedStyle(node)
if (overflowY === 'auto' || overflowY === 'scroll') {
const style = getComputedStyle(node)
const overflowY = style.overflowY
const overflow = style.overflow
if (isScrollableOverflow(overflowY) || isScrollableOverflow(overflow)) {
return node
}
node = node.parentElement
}
return null
}
function updateScrollY() {
if (!scrollTarget) return
scrollY.value = scrollTarget instanceof Window ? scrollTarget.scrollY : scrollTarget.scrollTop
function resolveScrollRoot(): HTMLElement | null {
const header = headerRef.value
if (!header) return null
const chromeInner = header.closest('.main-with-bottom-chrome-inner')
if (chromeInner instanceof HTMLElement) {
return chromeInner
}
const fromParent = findScrollParent(header)
if (fromParent) {
return fromParent
}
const global = document.querySelector('.main-with-bottom-chrome-inner')
return global instanceof HTMLElement ? global : null
}
onMounted(() => {
scrollTarget = findScrollParent(headerRef.value) ?? window
scrollTarget.addEventListener('scroll', updateScrollY, { passive: true })
updateScrollY()
function resolveScrollSentinel(scrollRoot: HTMLElement | null): HTMLElement | null {
if (!scrollRoot) return null
const local = scrollRoot.querySelector('.app-header-scroll-sentinel')
if (local instanceof HTMLElement) {
return local
}
return null
}
function updateScrollStateFromPosition() {
if (!scrollTarget) return
const next =
scrollTarget instanceof Window
? scrollTarget.scrollY > 50
: scrollTarget.scrollTop > 50
isHeaderScrolled.value = next
}
function teardownScrollTracking() {
intersectionObserver?.disconnect()
intersectionObserver = null
scrollTarget?.removeEventListener('scroll', updateScrollStateFromPosition)
scrollTarget = null
}
function setupScrollTracking() {
teardownScrollTracking()
const scrollRoot = resolveScrollRoot()
const sentinel = resolveScrollSentinel(scrollRoot)
if (scrollRoot && sentinel && typeof IntersectionObserver !== 'undefined') {
intersectionObserver = new IntersectionObserver(
([entry]) => {
isHeaderScrolled.value = !entry?.isIntersecting
},
{
root: scrollRoot,
threshold: 0,
rootMargin: '-1px 0px 0px 0px',
},
)
intersectionObserver.observe(sentinel)
return
}
scrollTarget = scrollRoot ?? window
scrollTarget.addEventListener('scroll', updateScrollStateFromPosition, { passive: true })
updateScrollStateFromPosition()
}
onMounted(async () => {
setupScrollTracking()
await nextTick()
setupScrollTracking()
requestAnimationFrame(() => setupScrollTracking())
})
onUnmounted(() => {
scrollTarget?.removeEventListener('scroll', updateScrollY)
teardownScrollTracking()
})
defineEmits<{
@@ -93,10 +172,8 @@ defineEmits<{
background-color: transparent;
}
/* عین bg-white shadow opacity-95 قبلی */
.app-header--scrolled {
background-color: #fff;
opacity: 0.95;
background-color: rgba(255, 255, 255, 0.92);
box-shadow:
0 1px 3px 0 rgb(0 0 0 / 0.1),
0 1px 2px -1px rgb(0 0 0 / 0.1);
@@ -3,7 +3,7 @@
class="w-full flex justify-evenly rounded-lg py-2 home_appear_fade_in"
:class="[
isBoursecuLayout ? 'mt-8 pt-2' : 'mt-6',
variant === 'mobile' ? 'pb-45' : 'mt-0 pb-0',
variant === 'mobile' ? 'pb-20' : 'mt-0 pb-0',
]"
>
<div class="text-center w-full">
+1 -1
View File
@@ -356,7 +356,7 @@
</div>
</div>
</div>
<div class=" w-full flex justify-evenly rounded-lg py-2 home_appear_fade_in mt-6 pb-45">
<div class=" w-full flex justify-evenly rounded-lg py-2 home_appear_fade_in mt-6 pb-20">
<div class="text-center w-full">
<div>بالاترین قیمت امروز</div>
<div v-if="lastPrice" class="flex flex-col items-center mt-3">
+14 -1
View File
@@ -1,5 +1,6 @@
<template>
<div class="mt-10 p-5">
<div class="mt-10 p-5 home-mobile-root">
<div class="app-header-scroll-sentinel" aria-hidden="true" />
<component :is="layoutComponent" :layout-config="layoutConfig" />
<AppHeader @logout="$emit('logout')" />
</div>
@@ -21,3 +22,15 @@ defineEmits<{
const layoutComponent = computed(() => resolveHomeLayoutComponent())
const layoutConfig = computed(() => getHomeLayoutConfig(getHomeLayoutId()))
</script>
<style scoped>
.home-mobile-root {
position: relative;
}
.app-header-scroll-sentinel {
height: 1px;
width: 100%;
pointer-events: none;
}
</style>
+17 -1
View File
@@ -23,7 +23,7 @@ export default defineConfig({
enabled: true
},
workbox: {
cleanupOutdatedCaches: false,
cleanupOutdatedCaches: true,
clientsClaim: true,
skipWaiting: true
},
@@ -93,6 +93,22 @@ export default defineConfig({
server: {
port: 3100,
},
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (
id.includes('node_modules/moment-jalaali')
|| id.includes('node_modules/jalaali-js')
|| id.includes('node_modules/moment/')
|| id.includes('/src/utils/useDateTime')
) {
return 'datetime'
}
},
},
},
},
// base: `/single`,
// base: `/customer`,
css: {