feat: align piggy, support chat and numeric input with modular app
Persian keyboards send ٫ ، and / as decimal or thousand separators, so amount and weight fields now normalize input before parsing. Support chat reopens an existing thread instead of starting a new one, and axios surfaces the server message on failure. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -77,6 +77,17 @@ export function setupRouterGuards() {
|
||||
}
|
||||
}
|
||||
|
||||
if (to.name === 'giftCard' && getToken()) {
|
||||
const { useSettingsStore } = await import('@/store/settingsStore')
|
||||
const settingsStore = useSettingsStore()
|
||||
if (!settingsStore.loaded) {
|
||||
await settingsStore.fetchSettings()
|
||||
}
|
||||
if (!settingsStore.isSettingTruthy('has_page_gift_card')) {
|
||||
return next({ name: (from.name as string) || 'user' })
|
||||
}
|
||||
}
|
||||
|
||||
if (isFirst) {
|
||||
isFirst = false
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
:type="props.type"
|
||||
class=""
|
||||
@change="handlerChange"
|
||||
@input="handlerChange"
|
||||
/>
|
||||
<a-input
|
||||
v-else
|
||||
@@ -28,6 +29,7 @@
|
||||
:pattern="props.pattern"
|
||||
class=""
|
||||
@change="handlerChange"
|
||||
@input="handlerChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -36,7 +38,12 @@
|
||||
import {usePrefix} from "@/utils/usePrefix";
|
||||
import {computed} from "vue";
|
||||
import {usePrice} from "@/components/BasicPacks/BasicPriceInput/src/utils/usePrice";
|
||||
import {persianToEnglishDigits, shouldNormalizeInputDigits} from '@/utils/persianDigits';
|
||||
import {
|
||||
normalizeDecimalInput,
|
||||
persianToEnglishDigits,
|
||||
shouldNormalizeDecimalSeparators,
|
||||
shouldNormalizeInputDigits,
|
||||
} from '@/utils/persianDigits';
|
||||
|
||||
//Props
|
||||
interface Prop {
|
||||
@@ -79,18 +86,27 @@ function normalizeValue(value: unknown) {
|
||||
return value
|
||||
}
|
||||
|
||||
if (shouldNormalizeDecimalSeparators(props)) {
|
||||
const isPrice = String(props.type ?? '').trim().toLowerCase() === 'price'
|
||||
return normalizeDecimalInput(value as string | number, {
|
||||
commaAsThousand: isPrice,
|
||||
})
|
||||
}
|
||||
|
||||
let normalized = String(value)
|
||||
if (shouldNormalizeInputDigits(props)) {
|
||||
normalized = persianToEnglishDigits(normalized)
|
||||
}
|
||||
if (props.type === 'price') {
|
||||
normalized = normalized.replaceAll(',', '')
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
const handlerChange = (e: any) => {
|
||||
const normalized = normalizeValue(e.target.value)
|
||||
const handlerChange = (e: Event | string) => {
|
||||
const target = typeof e === 'string' ? null : (e.target as HTMLInputElement | null)
|
||||
const raw = typeof e === 'string' ? e : target?.value
|
||||
const normalized = normalizeValue(raw)
|
||||
if (target && typeof normalized === 'string' && target.value !== normalized) {
|
||||
target.value = normalized
|
||||
}
|
||||
emits('update:value', normalized)
|
||||
emits('onchange', normalized)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import {usePrice} from './utils/usePrice'
|
||||
import {computed} from "vue";
|
||||
import BasicInput from "../../BasicInput.vue";
|
||||
import {persianToEnglishDigits} from '@/utils/persianDigits'
|
||||
import {normalizeDecimalInput} from '@/utils/persianDigits'
|
||||
|
||||
interface Props {
|
||||
value?: string | number
|
||||
@@ -24,7 +24,7 @@ const props = withDefaults(defineProps<Props>(), {})
|
||||
|
||||
const _val = computed({
|
||||
set(val: string) {
|
||||
emit('update:value', persianToEnglishDigits(val).replaceAll(',', ''))
|
||||
emit('update:value', normalizeDecimalInput(val))
|
||||
},
|
||||
get() {
|
||||
return props.value ? usePrice(props.value as number, true, '').trim() : ''
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
loadSupportMessengerFromSettings,
|
||||
messengerNeedsClipboardCopy,
|
||||
openSupportMessengerEntry,
|
||||
openSupportMessengerWithMessage,
|
||||
resolveDepositGuideText,
|
||||
resolveDepositSupportMessage,
|
||||
resolveSupportMessengerEntries,
|
||||
@@ -138,23 +137,22 @@ export function useSupportMessenger() {
|
||||
const messengerEntries = supportMessengerEntries.value
|
||||
const entries = pickerEntries.value
|
||||
|
||||
// لیست خالی + چت خاموش → هیچکار
|
||||
if (entries.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// فقط چت (لیست پیامرسان خالی) → مستقیم چت
|
||||
if (isChatEnabled.value && messengerEntries.length === 0) {
|
||||
return goToSupportChat()
|
||||
}
|
||||
|
||||
if (entries.length === 0) {
|
||||
return openSupportMessengerWithMessage(
|
||||
supportMessengerSettings.value.messenger,
|
||||
supportMessengerSettings.value.messengerId,
|
||||
draft,
|
||||
legacySupportLink.value,
|
||||
)
|
||||
}
|
||||
|
||||
// فقط یک گزینه (چت یا یک پیامرسان) → مستقیم همان
|
||||
if (entries.length === 1) {
|
||||
return openMessengerEntry(entries[0], draft)
|
||||
}
|
||||
|
||||
// چند گزینه (چت + لینکها یا چند پیامرسان) → منوی انتخاب
|
||||
const now = Date.now()
|
||||
if (supportPickerOpen.value) {
|
||||
if (now - supportPickerOpenedAt < 450) return true
|
||||
@@ -249,7 +247,7 @@ export function useSupportMessenger() {
|
||||
}
|
||||
|
||||
const hasMessengerLink = computed(
|
||||
() => isChatEnabled.value || supportMessengerEntries.value.length > 0 || !!messengerLink.value,
|
||||
() => isChatEnabled.value || supportMessengerEntries.value.length > 0,
|
||||
)
|
||||
const hasMultipleMessengers = computed(() => pickerEntries.value.length > 1)
|
||||
const hasSupportPhoneLink = computed(() => !!supportPhoneLink.value)
|
||||
|
||||
@@ -64,6 +64,9 @@ const secondaryItems = computed(() =>
|
||||
if (item.id === 'notifyTrigger') {
|
||||
return settingsStore.isSettingTruthy('has_page_price_trigger')
|
||||
}
|
||||
if (item.id === 'giftCard') {
|
||||
return settingsStore.isSettingTruthy('has_page_gift_card')
|
||||
}
|
||||
return true
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
<!-- </div>-->
|
||||
<!-- </BasicButton>-->
|
||||
<BasicButton
|
||||
v-if="authenticated"
|
||||
v-if="authenticated && giftCardEnabled"
|
||||
icon-size="16"
|
||||
class="!font-bold w-15rem"
|
||||
@click="go('giftCard')"
|
||||
@@ -138,6 +138,9 @@ const settingsStore = useSettingsStore()
|
||||
const priceTriggerEnabled = computed(() =>
|
||||
settingsStore.isSettingTruthy('has_page_price_trigger'),
|
||||
)
|
||||
const giftCardEnabled = computed(() =>
|
||||
settingsStore.isSettingTruthy('has_page_gift_card'),
|
||||
)
|
||||
|
||||
function go(routeName: string) {
|
||||
emit('navigate', routeName)
|
||||
|
||||
@@ -13,6 +13,49 @@ export function persianToEnglishDigits(value: string | number | null | undefined
|
||||
.replace(/٫/g, '.')
|
||||
}
|
||||
|
||||
/**
|
||||
* اعداد فارسی/عربی + جداکنندههای اعشار رایج ماشینحساب فارسی.
|
||||
* - `commaAsThousand` true (مبلغ): `,` / `،` حذف میشوند
|
||||
* - `commaAsThousand` false (وزن/اعشار): `,` / `،` هم → `.`
|
||||
*/
|
||||
export function normalizeDecimalInput(
|
||||
val: string | number | null | undefined,
|
||||
options?: { commaAsThousand?: boolean }
|
||||
): string {
|
||||
if (val == null || val === '') return ''
|
||||
|
||||
const commaAsThousand = options?.commaAsThousand ?? true
|
||||
let normalized = persianToEnglishDigits(String(val))
|
||||
|
||||
if (commaAsThousand) {
|
||||
normalized = normalized.replace(/[,،]/g, '')
|
||||
} else {
|
||||
normalized = normalized.replace(/[,،]/g, '.')
|
||||
}
|
||||
|
||||
normalized = normalized
|
||||
.replace(/\//g, '.')
|
||||
.replace(/[^\d.]/g, '')
|
||||
|
||||
const firstDot = normalized.indexOf('.')
|
||||
if (firstDot !== -1) {
|
||||
normalized =
|
||||
normalized.slice(0, firstDot + 1) +
|
||||
normalized.slice(firstDot + 1).replace(/\./g, '')
|
||||
}
|
||||
|
||||
return normalized
|
||||
}
|
||||
|
||||
export function parseDecimalInput(
|
||||
val: string | number | null | undefined,
|
||||
options?: { commaAsThousand?: boolean }
|
||||
): number {
|
||||
const normalized = normalizeDecimalInput(val, options)
|
||||
if (!normalized) return NaN
|
||||
return Number(normalized)
|
||||
}
|
||||
|
||||
export function shouldNormalizeInputDigits(options: {
|
||||
isPassword?: boolean
|
||||
type?: string
|
||||
@@ -27,3 +70,16 @@ export function shouldNormalizeInputDigits(options: {
|
||||
const mode = String(options.inputmode ?? '').trim().toLowerCase()
|
||||
return mode === 'numeric' || mode === 'decimal' || mode === 'number'
|
||||
}
|
||||
|
||||
/** فیلدهای وزن/مبلغ/عدد اعشاری که باید `/` را ممیز بگیرند */
|
||||
export function shouldNormalizeDecimalSeparators(options: {
|
||||
isPassword?: boolean
|
||||
type?: string
|
||||
inputmode?: string
|
||||
}): boolean {
|
||||
if (options.isPassword) return false
|
||||
const type = String(options.type ?? '').trim().toLowerCase()
|
||||
if (type === 'price' || type === 'number') return true
|
||||
const mode = String(options.inputmode ?? '').trim().toLowerCase()
|
||||
return mode === 'numeric' || mode === 'decimal' || mode === 'number'
|
||||
}
|
||||
|
||||
@@ -87,8 +87,11 @@ export function parseSupportMessengersJson(raw?: string | null): SupportMessenge
|
||||
export function resolveSupportMessengersFromSettings(
|
||||
settings: Array<{ key?: string; value?: unknown }>,
|
||||
): SupportMessengerEntry[] {
|
||||
const fromJson = parseSupportMessengersJson(getSupportSettingValue(settings, 'support_messengers'))
|
||||
if (fromJson.length > 0) return fromJson
|
||||
// اگر کلید جدید در تنظیمات هست (حتی خالی/null)، فقط همان لیست معتبر است — legacy بله را برنگردان
|
||||
const hasMessengersKey = settings.some((row) => row?.key === 'support_messengers')
|
||||
if (hasMessengersKey) {
|
||||
return parseSupportMessengersJson(getSupportSettingValue(settings, 'support_messengers'))
|
||||
}
|
||||
|
||||
const legacyMessenger = getSupportSettingValue(settings, 'support_messenger').toLowerCase()
|
||||
const legacyId = getSupportSettingValue(settings, 'support_messenger_id')
|
||||
@@ -107,9 +110,8 @@ export function resolveSupportMessengerEntries(
|
||||
): ResolvedSupportMessengerEntry[] {
|
||||
const resolved = settings ?? supportMessengerSettings.value
|
||||
const text = (messageText ?? '').trim()
|
||||
const source = resolved.messengers?.length
|
||||
? resolved.messengers
|
||||
: resolveLegacySingleMessenger(resolved)
|
||||
// فقط لیست resolveشده؛ قبل از load خالی است تا legacy دوباره دکمه نسازد
|
||||
const source = Array.isArray(resolved.messengers) ? resolved.messengers : []
|
||||
|
||||
return source
|
||||
.map((entry) => {
|
||||
@@ -125,14 +127,6 @@ export function resolveSupportMessengerEntries(
|
||||
.filter((entry): entry is ResolvedSupportMessengerEntry => entry !== null)
|
||||
}
|
||||
|
||||
function resolveLegacySingleMessenger(settings: SupportMessengerSettings): SupportMessengerEntry[] {
|
||||
const messenger = (settings.messenger ?? '').trim().toLowerCase()
|
||||
const id = (settings.messengerId ?? '').trim()
|
||||
if (!messenger || !id) return []
|
||||
if (!VALID_SUPPORT_MESSENGERS.has(messenger)) return []
|
||||
return [{ messenger, id }]
|
||||
}
|
||||
|
||||
/** @deprecated از depositGuideText استفاده کنید */
|
||||
export const DEFAULT_SUPPORT_MESSENGER_TEXT = DEFAULT_DEPOSIT_GUIDE_TEXT
|
||||
|
||||
|
||||
+21
-4
@@ -71,29 +71,46 @@ _axios.interceptors.response.use(
|
||||
}
|
||||
|
||||
const apiMessage = extractApiErrorMessage(response?.data)
|
||||
const status = Number(response?.status ?? 0)
|
||||
|
||||
if (response?.status === 403) {
|
||||
showApiError(apiMessage || 'شما دسترسی لازم برای انجام این عملیات را ندارید', config)
|
||||
showApiError(
|
||||
withHttpStatus(apiMessage || 'شما دسترسی لازم برای انجام این عملیات را ندارید', 403),
|
||||
config,
|
||||
)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
if (response?.status === 422) {
|
||||
if (apiMessage) {
|
||||
showApiError(apiMessage, config)
|
||||
showApiError(withHttpStatus(apiMessage, 422), config)
|
||||
} else {
|
||||
showApiError(withHttpStatus('خطا در اعتبارسنجی', 422), config)
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
if (response) {
|
||||
showApiError(apiMessage || DEFAULT_LOAD_ERROR, config)
|
||||
if (status >= 500) {
|
||||
showApiError(withHttpStatus(SERVER_ERROR, status), config)
|
||||
} else {
|
||||
showApiError(withHttpStatus(apiMessage || DEFAULT_LOAD_ERROR, status), config)
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
showApiError(DEFAULT_LOAD_ERROR, config)
|
||||
// بدون status (نه 4xx نه 5xx): قطع شبکه، timeout، DNS، …
|
||||
showApiError(NETWORK_ERROR, config)
|
||||
return Promise.reject(error)
|
||||
})
|
||||
|
||||
const DEFAULT_LOAD_ERROR = 'خطا در بارگذاری اطلاعات'
|
||||
const SERVER_ERROR = 'خطا در برقراری ارتباط با سرور'
|
||||
const NETWORK_ERROR = 'لطفا اینترنت خود را بررسی کنید و مجددا تلاش کنید'
|
||||
|
||||
function withHttpStatus(message: string, status: number): string {
|
||||
return `${message} (${status})`
|
||||
}
|
||||
|
||||
function extractApiErrorMessage(data: unknown): string {
|
||||
if (!data || typeof data !== 'object') {
|
||||
|
||||
@@ -477,7 +477,7 @@
|
||||
|
||||
.dhome-default-actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,87 +67,68 @@
|
||||
<!-- <div class="bg-gradient-to-l mt-3 p-1 shadow-lg from-primary rounded-lg p-px to-transparent ">-->
|
||||
<div class="mt-3 p-1 shadow-lg from-primary rounded-lg p-px to-transparent border-solid border border-primary">
|
||||
<div class=" p-1 py-6 rounded-lg bg-white">
|
||||
<div class="flex justify-between items-start gap-1">
|
||||
<div class="home-quick-actions">
|
||||
<div
|
||||
class="text-center min-w-0 flex-1 home_appear_fade_in_up"
|
||||
class="home-quick-action home_appear_fade_in_up"
|
||||
style="text-align: -webkit-center"
|
||||
@click="router.push({ name: 'gold' })"
|
||||
>
|
||||
<div
|
||||
class="rounded-full w-[44px] h-[44px] mx-auto bg-primary flex items-center justify-center"
|
||||
style="border: solid 1px #a72b77"
|
||||
>
|
||||
<Icon class="text-white" icon="vuesax-linear:money-receive" />
|
||||
<div class="home-quick-action__icon">
|
||||
<Icon class="text-white" icon="vuesax-linear:money-receive" size="18" />
|
||||
</div>
|
||||
<div class="text-primary text-[11px] leading-tight mt-1">خرید و فروش</div>
|
||||
<div class="home-quick-action__label">خرید و فروش</div>
|
||||
</div>
|
||||
<div
|
||||
class="text-center min-w-0 flex-1 home_appear_fade_in_down"
|
||||
class="home-quick-action home_appear_fade_in_down"
|
||||
style="text-align: -webkit-center"
|
||||
@click="router.push({ name: 'wallet', query: { state: 'deposit' } })"
|
||||
>
|
||||
<div
|
||||
class="rounded-full w-[44px] h-[44px] mx-auto bg-primary flex items-center justify-center"
|
||||
style="border: solid 1px #a72b77"
|
||||
>
|
||||
<Icon class="text-white" icon="vuesax-linear:card-add" />
|
||||
<div class="home-quick-action__icon">
|
||||
<Icon class="text-white" icon="vuesax-linear:card-add" size="18" />
|
||||
</div>
|
||||
<div class="text-primary text-[11px] leading-tight mt-1">واریز نقدی</div>
|
||||
<div class="home-quick-action__label">واریز نقدی</div>
|
||||
</div>
|
||||
<div
|
||||
class="text-center min-w-0 flex-1 home_appear_fade_in_up"
|
||||
class="home-quick-action home_appear_fade_in_up"
|
||||
style="text-align: -webkit-center"
|
||||
@click="openWholeBalanceVisible()"
|
||||
>
|
||||
<div
|
||||
class="rounded-full w-[44px] h-[44px] mx-auto bg-primary flex items-center justify-center"
|
||||
style="border: solid 1px #a72b77"
|
||||
>
|
||||
<Icon class="text-white" icon="vuesax-linear:receipt-item" />
|
||||
<div class="home-quick-action__icon">
|
||||
<Icon class="text-white" icon="vuesax-linear:receipt-item" size="18" />
|
||||
</div>
|
||||
<div class="text-primary text-[11px] leading-tight mt-1">موجودی</div>
|
||||
<div class="home-quick-action__label">موجودی</div>
|
||||
</div>
|
||||
<div
|
||||
class="text-center min-w-0 flex-1 home_appear_fade_in_down"
|
||||
class="home-quick-action home_appear_fade_in_down"
|
||||
style="text-align: -webkit-center"
|
||||
@click="router.push({ name: 'fund' })"
|
||||
>
|
||||
<div
|
||||
class="rounded-full w-[44px] h-[44px] mx-auto bg-primary flex items-center justify-center"
|
||||
style=" border: solid 1px #a72b77"
|
||||
>
|
||||
<Icon class="text-white font-bold" icon="vuesax-linear:archive-1" />
|
||||
<div class="home-quick-action__icon">
|
||||
<Icon class="text-white font-bold" icon="vuesax-linear:archive-1" size="18" />
|
||||
</div>
|
||||
<div class="text-primary text-[11px] leading-tight mt-1">دارایی ها</div>
|
||||
<div class="home-quick-action__label">دارایی ها</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="productFundsEnabled"
|
||||
class="text-center min-w-0 flex-1 home_appear_fade_in_down"
|
||||
class="home-quick-action home_appear_fade_in_down"
|
||||
style="text-align: -webkit-center"
|
||||
@click="router.push({ name: 'piggy' })"
|
||||
>
|
||||
<div
|
||||
class="rounded-full w-[44px] h-[44px] mx-auto bg-primary flex items-center justify-center"
|
||||
style=" border: solid 1px #a72b77"
|
||||
>
|
||||
<Icon class="text-white font-bold" icon="vuesax-linear:safe-home" />
|
||||
<div class="home-quick-action__icon">
|
||||
<Icon class="text-white font-bold" icon="vuesax-linear:safe-home" size="18" />
|
||||
</div>
|
||||
<div class="text-primary text-[11px] leading-tight mt-1">قلک</div>
|
||||
<div class="home-quick-action__label">قلک</div>
|
||||
</div>
|
||||
<div
|
||||
class="text-center min-w-0 flex-1 home_appear_fade_in_down"
|
||||
class="home-quick-action home_appear_fade_in_down"
|
||||
style="text-align: -webkit-center"
|
||||
@click="router.push({ name: 'earnMoney' })"
|
||||
>
|
||||
<div
|
||||
class="rounded-full w-[44px] h-[44px] mx-auto bg-primary flex items-center justify-center"
|
||||
style=" border: solid 1px #a72b77"
|
||||
>
|
||||
<Icon class="text-white font-bold" icon="vuesax-linear:dollar-circle" />
|
||||
<div class="home-quick-action__icon">
|
||||
<Icon class="text-white font-bold" icon="vuesax-linear:dollar-circle" size="18" />
|
||||
</div>
|
||||
<div class="text-primary text-[11px] leading-tight mt-1">کسب درآمد</div>
|
||||
<div class="home-quick-action__label">کسب درآمد</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- <ADivider></ADivider>-->
|
||||
@@ -957,6 +938,60 @@ function copyCode() {
|
||||
}
|
||||
}
|
||||
|
||||
.home-quick-actions {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.home-quick-action {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.home-quick-action__icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 9999px;
|
||||
background: var(--App-primary, #a72b77);
|
||||
border: solid 1px #a72b77;
|
||||
}
|
||||
|
||||
.home-quick-action__label {
|
||||
margin-top: 4px;
|
||||
padding: 0 2px;
|
||||
color: var(--App-primary, #a72b77);
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
line-height: 1.25;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.home-quick-actions {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.home-quick-action__icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.home-quick-action__label {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
.odometer {
|
||||
font-size: 60px;
|
||||
text-align: center;
|
||||
|
||||
+118
-94
@@ -1,95 +1,97 @@
|
||||
<template>
|
||||
<div class="piggy-page">
|
||||
<header class="piggy-bar">
|
||||
<button type="button" class="piggy-bar__btn" aria-label="بازگشت" @click="goBack">
|
||||
<BasicIcon icon="vuesax-linear:arrow-right-1" size="22" />
|
||||
</button>
|
||||
<h1 class="piggy-bar__title">قلکها</h1>
|
||||
<button type="button" class="piggy-bar__btn piggy-bar__btn--primary" aria-label="قلک جدید" @click="goAdd">
|
||||
<BasicIcon icon="vuesax-linear:add" size="20" />
|
||||
</button>
|
||||
</header>
|
||||
<div class="piggy-page__inner">
|
||||
<header class="piggy-bar">
|
||||
<button type="button" class="piggy-bar__btn" aria-label="بازگشت" @click="goBack">
|
||||
<BasicIcon icon="vuesax-linear:arrow-right-1" size="22" />
|
||||
</button>
|
||||
<h1 class="piggy-bar__title">قلکها</h1>
|
||||
<button type="button" class="piggy-bar__btn piggy-bar__btn--primary" aria-label="قلک جدید" @click="goAdd">
|
||||
<BasicIcon icon="vuesax-linear:add" size="20" />
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<a-spin :spinning="busy">
|
||||
<section class="piggy-summary">
|
||||
<div class="piggy-summary__card">
|
||||
<div class="piggy-summary__row">
|
||||
<div>
|
||||
<p class="piggy-summary__label">جمع موجودی داخل قلکها</p>
|
||||
<p class="piggy-summary__value" dir="ltr">
|
||||
{{ formatPiggyAmount(totalGold) }}
|
||||
<span class="piggy-summary__unit">گرم</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="piggy-summary__badge">
|
||||
<BasicIcon icon="vuesax-linear:safe-home" size="22" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="piggy-summary__meta">
|
||||
<span>{{ rows.length }} قلک</span>
|
||||
<span class="piggy-summary__dot" />
|
||||
<span>{{ groups.length }} محصول</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BasicButton class="!w-full mt-3" type="primary" size="middle" block @click="goAdd">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<BasicIcon icon="vuesax-linear:add-circle" size="18" />
|
||||
<span>{{ rows.length ? 'افزودن قلک' : 'ساخت اولین قلک' }}</span>
|
||||
</div>
|
||||
</BasicButton>
|
||||
</section>
|
||||
|
||||
<section class="piggy-body">
|
||||
<div v-if="!rows.length" class="piggy-empty">
|
||||
<div class="piggy-empty__icon">
|
||||
<BasicIcon icon="vuesax-linear:safe-home" size="36" />
|
||||
</div>
|
||||
<p class="piggy-empty__title">قلکی ندارید</p>
|
||||
<p class="piggy-empty__desc">
|
||||
برای جدا کردن بخشی از موجودی هر محصول (مثلاً آبشده یا سکه) یک قلک بسازید.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="piggy-groups">
|
||||
<div v-for="group in groups" :key="group.productId" class="piggy-group">
|
||||
<div class="piggy-group__head">
|
||||
<a-spin :spinning="busy">
|
||||
<section class="piggy-summary">
|
||||
<div class="piggy-summary__card">
|
||||
<div class="piggy-summary__row">
|
||||
<div class="min-w-0">
|
||||
<p class="piggy-group__name">{{ group.productTitle }}</p>
|
||||
<p class="piggy-group__count">{{ group.funds.length }} قلک</p>
|
||||
<p class="piggy-summary__label">جمع موجودی داخل قلکها</p>
|
||||
<p class="piggy-summary__value" dir="ltr">
|
||||
{{ formatPiggyAmount(totalGold) }}
|
||||
<span class="piggy-summary__unit">گرم</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="piggy-summary__badge">
|
||||
<BasicIcon icon="vuesax-linear:safe-home" size="22" />
|
||||
</div>
|
||||
<p class="piggy-group__total" dir="ltr">
|
||||
{{ formatPiggyAmount(group.totalGold) }}
|
||||
<span>گرم</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="piggy-group__list">
|
||||
<button
|
||||
v-for="item in group.funds"
|
||||
:key="item.id"
|
||||
type="button"
|
||||
class="piggy-row"
|
||||
@click="openInfo(item.id!)"
|
||||
>
|
||||
<div class="piggy-row__icon">
|
||||
<BasicIcon :icon="piggyIconName(item.title)" size="20" />
|
||||
</div>
|
||||
<div class="piggy-row__main">
|
||||
<div class="piggy-row__title">{{ item.title || 'بدون عنوان' }}</div>
|
||||
<div class="piggy-row__sub">کد {{ item.id }}</div>
|
||||
</div>
|
||||
<div class="piggy-row__amount" dir="ltr">
|
||||
<strong>{{ formatPiggyAmount(fundWalletGold(item)) }}</strong>
|
||||
<span>گرم</span>
|
||||
</div>
|
||||
<BasicIcon class="piggy-row__chevron" icon="vuesax-linear:arrow-left-1" size="16" />
|
||||
</button>
|
||||
<div class="piggy-summary__meta">
|
||||
<span>{{ rows.length }} قلک</span>
|
||||
<span class="piggy-summary__dot" />
|
||||
<span>{{ groups.length }} محصول</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</a-spin>
|
||||
|
||||
<BasicButton class="!w-full mt-3" type="primary" size="middle" block @click="goAdd">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<BasicIcon icon="vuesax-linear:add-circle" size="18" />
|
||||
<span>{{ rows.length ? 'افزودن قلک' : 'ساخت اولین قلک' }}</span>
|
||||
</div>
|
||||
</BasicButton>
|
||||
</section>
|
||||
|
||||
<section class="piggy-body">
|
||||
<div v-if="!rows.length" class="piggy-empty">
|
||||
<div class="piggy-empty__icon">
|
||||
<BasicIcon icon="vuesax-linear:safe-home" size="36" />
|
||||
</div>
|
||||
<p class="piggy-empty__title">قلکی ندارید</p>
|
||||
<p class="piggy-empty__desc">
|
||||
برای جدا کردن بخشی از موجودی هر محصول (مثلاً آبشده یا سکه) یک قلک بسازید.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="piggy-groups">
|
||||
<div v-for="group in groups" :key="group.productId" class="piggy-group">
|
||||
<div class="piggy-group__head">
|
||||
<div class="min-w-0">
|
||||
<p class="piggy-group__name">{{ group.productTitle }}</p>
|
||||
<p class="piggy-group__count">{{ group.funds.length }} قلک</p>
|
||||
</div>
|
||||
<p class="piggy-group__total" dir="ltr">
|
||||
{{ formatPiggyAmount(group.totalGold) }}
|
||||
<span>گرم</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="piggy-group__list">
|
||||
<button
|
||||
v-for="item in group.funds"
|
||||
:key="item.id"
|
||||
type="button"
|
||||
class="piggy-row"
|
||||
@click="openInfo(item.id!)"
|
||||
>
|
||||
<div class="piggy-row__icon">
|
||||
<BasicIcon :icon="piggyIconName(item.title)" size="20" />
|
||||
</div>
|
||||
<div class="piggy-row__main">
|
||||
<div class="piggy-row__title">{{ item.title || 'بدون عنوان' }}</div>
|
||||
<div class="piggy-row__sub">کد {{ item.id }}</div>
|
||||
</div>
|
||||
<div class="piggy-row__amount" dir="ltr">
|
||||
<strong>{{ formatPiggyAmount(fundWalletGold(item)) }}</strong>
|
||||
<span>گرم</span>
|
||||
</div>
|
||||
<BasicIcon class="piggy-row__chevron" icon="vuesax-linear:arrow-left-1" size="16" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</a-spin>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -143,23 +145,33 @@ onMounted(async () => {
|
||||
|
||||
<style scoped>
|
||||
.piggy-page {
|
||||
@apply relative min-h-full bg-gray-50 pb-24;
|
||||
box-sizing: border-box;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
background: #f9fafb;
|
||||
padding: 0 12px calc(var(--app-bottom-chrome-height, 108px) + 1rem);
|
||||
}
|
||||
.piggy-page__inner {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-width: 26rem;
|
||||
margin-inline: auto;
|
||||
}
|
||||
.piggy-bar {
|
||||
@apply sticky top-0 z-20 flex items-center justify-between bg-white px-3 py-3;
|
||||
box-shadow: 0 1px 0 rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
.piggy-bar__title {
|
||||
@apply m-0 text-base font-bold text-gray-900;
|
||||
@apply m-0 truncate px-2 text-base font-bold text-gray-900;
|
||||
}
|
||||
.piggy-bar__btn {
|
||||
@apply flex h-9 w-9 items-center justify-center rounded-full border-none bg-gray-100 text-gray-800;
|
||||
@apply flex h-9 w-9 shrink-0 items-center justify-center rounded-full border-none bg-gray-100 text-gray-800;
|
||||
}
|
||||
.piggy-bar__btn--primary {
|
||||
@apply bg-primary text-white;
|
||||
}
|
||||
.piggy-summary {
|
||||
@apply px-4 pt-4;
|
||||
@apply px-3 pt-4 sm:px-4;
|
||||
}
|
||||
.piggy-summary__card {
|
||||
@apply rounded-2xl bg-primary p-4 text-white;
|
||||
@@ -171,7 +183,7 @@ onMounted(async () => {
|
||||
@apply m-0 text-sm text-white/80;
|
||||
}
|
||||
.piggy-summary__value {
|
||||
@apply m-0 mt-1 flex items-baseline gap-2 text-3xl font-bold;
|
||||
@apply m-0 mt-1 flex flex-wrap items-baseline gap-2 text-2xl font-bold sm:text-3xl;
|
||||
}
|
||||
.piggy-summary__unit {
|
||||
@apply text-sm font-medium text-white/75;
|
||||
@@ -180,13 +192,13 @@ onMounted(async () => {
|
||||
@apply flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-white/15;
|
||||
}
|
||||
.piggy-summary__meta {
|
||||
@apply mt-4 flex items-center gap-2 text-xs text-white/75;
|
||||
@apply mt-4 flex flex-wrap items-center gap-2 text-xs text-white/75;
|
||||
}
|
||||
.piggy-summary__dot {
|
||||
@apply h-1 w-1 rounded-full bg-white/50;
|
||||
}
|
||||
.piggy-body {
|
||||
@apply px-4 pt-4;
|
||||
@apply px-3 pt-4 sm:px-4;
|
||||
}
|
||||
.piggy-groups {
|
||||
@apply flex flex-col gap-4;
|
||||
@@ -196,7 +208,7 @@ onMounted(async () => {
|
||||
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
.piggy-group__head {
|
||||
@apply flex items-center justify-between gap-3 border-0 border-b border-solid border-gray-100 px-4 py-3;
|
||||
@apply flex items-center justify-between gap-3 border-0 border-b border-solid border-gray-100 px-3 py-3 sm:px-4;
|
||||
}
|
||||
.piggy-group__name {
|
||||
@apply m-0 truncate text-sm font-bold text-gray-900;
|
||||
@@ -214,10 +226,10 @@ onMounted(async () => {
|
||||
@apply flex flex-col;
|
||||
}
|
||||
.piggy-row {
|
||||
@apply flex w-full items-center gap-3 border-0 border-b border-solid border-gray-50 bg-transparent px-3 py-3 text-right last:border-b-0;
|
||||
@apply flex w-full min-w-0 items-center gap-2 border-0 border-b border-solid border-gray-50 bg-transparent px-2.5 py-3 text-right last:border-b-0 sm:gap-3 sm:px-3;
|
||||
}
|
||||
.piggy-row__icon {
|
||||
@apply flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-gray-50 text-primary;
|
||||
@apply flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-gray-50 text-primary sm:h-10 sm:w-10;
|
||||
}
|
||||
.piggy-row__main {
|
||||
@apply min-w-0 flex-1;
|
||||
@@ -253,4 +265,16 @@ onMounted(async () => {
|
||||
.piggy-empty__desc {
|
||||
@apply m-0 mt-2 text-sm leading-6 text-gray-500;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.piggy-page {
|
||||
padding: 16px 16px 2rem;
|
||||
}
|
||||
.piggy-page__inner {
|
||||
max-width: 28rem;
|
||||
}
|
||||
.piggy-bar {
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+104
-69
@@ -1,75 +1,77 @@
|
||||
<template>
|
||||
<div class="piggy-info">
|
||||
<header class="piggy-info__hero">
|
||||
<div class="piggy-info__top">
|
||||
<button type="button" class="piggy-info__btn" aria-label="بازگشت" @click="goBack">
|
||||
<BasicIcon icon="vuesax-linear:arrow-right-1" size="22" />
|
||||
</button>
|
||||
<h1 class="piggy-info__title">{{ fund?.title || 'قلک' }}</h1>
|
||||
<button type="button" class="piggy-info__btn" aria-label="حذف" @click="onDelete">
|
||||
<BasicIcon icon="vuesax-linear:trash" size="18" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<a-spin :spinning="busy">
|
||||
<div class="piggy-info__balance-block">
|
||||
<p class="piggy-info__product">{{ fund?.product?.title || '—' }}</p>
|
||||
<p class="piggy-info__label">موجودی قلک</p>
|
||||
<p class="piggy-info__value" dir="ltr">
|
||||
{{ formatPiggyAmount(potGold) }}
|
||||
<span>گرم</span>
|
||||
</p>
|
||||
<p class="piggy-info__free">
|
||||
آزاد قابل واریز:
|
||||
<strong dir="ltr">{{ formatPiggyAmount(freeGold) }}</strong>
|
||||
گرم
|
||||
</p>
|
||||
<div class="piggy-info__inner">
|
||||
<header class="piggy-info__hero">
|
||||
<div class="piggy-info__top">
|
||||
<button type="button" class="piggy-info__btn" aria-label="بازگشت" @click="goBack">
|
||||
<BasicIcon icon="vuesax-linear:arrow-right-1" size="22" />
|
||||
</button>
|
||||
<h1 class="piggy-info__title">{{ fund?.title || 'قلک' }}</h1>
|
||||
<button type="button" class="piggy-info__btn" aria-label="حذف" @click="onDelete">
|
||||
<BasicIcon icon="vuesax-linear:trash" size="18" />
|
||||
</button>
|
||||
</div>
|
||||
</a-spin>
|
||||
</header>
|
||||
|
||||
<div class="piggy-info__sheet">
|
||||
<div class="piggy-info__actions">
|
||||
<BasicButton class="!flex-1" size="middle" @click="openTx(false)">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<BasicIcon icon="vuesax-linear:export" size="18" />
|
||||
<span>برداشت به آزاد</span>
|
||||
<a-spin :spinning="busy">
|
||||
<div class="piggy-info__balance-block">
|
||||
<p class="piggy-info__product">{{ fund?.product?.title || '—' }}</p>
|
||||
<p class="piggy-info__label">موجودی قلک</p>
|
||||
<p class="piggy-info__value" dir="ltr">
|
||||
{{ formatPiggyAmount(potGold) }}
|
||||
<span>گرم</span>
|
||||
</p>
|
||||
<p class="piggy-info__free">
|
||||
آزاد قابل واریز:
|
||||
<strong dir="ltr">{{ formatPiggyAmount(freeGold) }}</strong>
|
||||
گرم
|
||||
</p>
|
||||
</div>
|
||||
</BasicButton>
|
||||
<BasicButton class="!flex-1" type="primary" size="middle" @click="openTx(true)">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<BasicIcon icon="vuesax-linear:import" size="18" />
|
||||
<span>واریز از آزاد</span>
|
||||
</div>
|
||||
</BasicButton>
|
||||
</div>
|
||||
</a-spin>
|
||||
</header>
|
||||
|
||||
<section class="piggy-info__tx">
|
||||
<h2 class="piggy-info__tx-title">تراکنشهای این قلک</h2>
|
||||
<div v-if="!transactions.length" class="piggy-info__tx-empty">
|
||||
<BasicIcon icon="vuesax-linear:receipt-2" size="28" />
|
||||
<p class="m-0 mt-2 text-sm text-gray-500">هنوز جابهجایی ثبت نشده</p>
|
||||
<div class="piggy-info__sheet">
|
||||
<div class="piggy-info__actions">
|
||||
<BasicButton class="piggy-info__action" size="middle" @click="openTx(false)">
|
||||
<div class="piggy-info__action-inner">
|
||||
<BasicIcon icon="vuesax-linear:export" size="18" />
|
||||
<span>برداشت به آزاد</span>
|
||||
</div>
|
||||
</BasicButton>
|
||||
<BasicButton class="piggy-info__action" type="primary" size="middle" @click="openTx(true)">
|
||||
<div class="piggy-info__action-inner">
|
||||
<BasicIcon icon="vuesax-linear:import" size="18" />
|
||||
<span>واریز از آزاد</span>
|
||||
</div>
|
||||
</BasicButton>
|
||||
</div>
|
||||
<div v-else class="piggy-info__tx-list">
|
||||
<div v-for="tx in transactions" :key="tx.id" class="piggy-info__tx-row">
|
||||
<div class="piggy-info__tx-dot" />
|
||||
<div class="min-w-0 flex-1 text-right">
|
||||
<div class="truncate text-sm font-bold text-gray-900">
|
||||
{{ tx.type_str || tx.type || 'تراکنش' }}
|
||||
|
||||
<section class="piggy-info__tx">
|
||||
<h2 class="piggy-info__tx-title">تراکنشهای این قلک</h2>
|
||||
<div v-if="!transactions.length" class="piggy-info__tx-empty">
|
||||
<BasicIcon icon="vuesax-linear:receipt-2" size="28" />
|
||||
<p class="m-0 mt-2 text-sm text-gray-500">هنوز جابهجایی ثبت نشده</p>
|
||||
</div>
|
||||
<div v-else class="piggy-info__tx-list">
|
||||
<div v-for="tx in transactions" :key="tx.id" class="piggy-info__tx-row">
|
||||
<div class="piggy-info__tx-dot" />
|
||||
<div class="min-w-0 flex-1 text-right">
|
||||
<div class="truncate text-sm font-bold text-gray-900">
|
||||
{{ tx.type_str || tx.type || 'تراکنش' }}
|
||||
</div>
|
||||
<div class="mt-0.5 text-[11px] text-gray-400">{{ tx.created_at || '' }}</div>
|
||||
</div>
|
||||
<div class="shrink-0 font-bold text-primary" dir="ltr">
|
||||
{{ formatPiggyAmount(Number(tx.gold ?? 0)) }}
|
||||
</div>
|
||||
<div class="mt-0.5 text-[11px] text-gray-400">{{ tx.created_at || '' }}</div>
|
||||
</div>
|
||||
<div class="shrink-0 font-bold text-primary" dir="ltr">
|
||||
{{ formatPiggyAmount(Number(tx.gold ?? 0)) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BasicModal v-model:visible="txVisible" :title="isDeposit ? 'واریز به قلک' : 'برداشت از قلک'">
|
||||
<div class="flex flex-col gap-3 p-1">
|
||||
<p class="m-0 text-xs text-gray-500">
|
||||
<p class="m-0 text-xs leading-5 text-gray-500">
|
||||
{{
|
||||
isDeposit
|
||||
? `از موجودی آزاد «${fund?.product?.title || 'محصول'}» — حداکثر ${formatPiggyAmount(freeGold)} گرم`
|
||||
@@ -218,46 +220,62 @@ onMounted(async () => {
|
||||
|
||||
<style scoped>
|
||||
.piggy-info {
|
||||
@apply min-h-full bg-gray-50 pb-28;
|
||||
box-sizing: border-box;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
background: #f9fafb;
|
||||
padding: 0 12px calc(var(--app-bottom-chrome-height, 108px) + 1rem);
|
||||
}
|
||||
.piggy-info__inner {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-width: 26rem;
|
||||
margin-inline: auto;
|
||||
}
|
||||
.piggy-info__hero {
|
||||
@apply bg-primary px-3 pb-8 pt-3 text-white;
|
||||
}
|
||||
.piggy-info__top {
|
||||
@apply flex items-center justify-between;
|
||||
@apply flex items-center justify-between gap-2;
|
||||
}
|
||||
.piggy-info__btn {
|
||||
@apply flex h-9 w-9 items-center justify-center rounded-full border-none bg-white/15 text-white;
|
||||
@apply flex h-9 w-9 shrink-0 items-center justify-center rounded-full border-none bg-white/15 text-white;
|
||||
}
|
||||
.piggy-info__title {
|
||||
@apply m-0 max-w-[55%] truncate text-base font-bold text-white;
|
||||
@apply m-0 min-w-0 flex-1 truncate text-center text-base font-bold text-white;
|
||||
}
|
||||
.piggy-info__balance-block {
|
||||
@apply mt-6 text-center;
|
||||
@apply mt-6 px-2 text-center;
|
||||
}
|
||||
.piggy-info__product {
|
||||
@apply m-0 inline-flex rounded-full bg-white/15 px-3 py-1 text-xs text-white/90;
|
||||
@apply m-0 inline-flex max-w-full truncate rounded-full bg-white/15 px-3 py-1 text-xs text-white/90;
|
||||
}
|
||||
.piggy-info__label {
|
||||
@apply m-0 mt-4 text-sm text-white/75;
|
||||
}
|
||||
.piggy-info__value {
|
||||
@apply m-0 mt-1 flex items-baseline justify-center gap-2 text-4xl font-bold;
|
||||
@apply m-0 mt-1 flex flex-wrap items-baseline justify-center gap-2 text-3xl font-bold sm:text-4xl;
|
||||
}
|
||||
.piggy-info__value span {
|
||||
@apply text-sm font-medium text-white/75;
|
||||
}
|
||||
.piggy-info__free {
|
||||
@apply m-0 mt-3 text-xs text-white/70;
|
||||
@apply m-0 mt-3 text-xs leading-5 text-white/70;
|
||||
}
|
||||
.piggy-info__free strong {
|
||||
@apply text-white;
|
||||
}
|
||||
.piggy-info__sheet {
|
||||
@apply relative z-10 -mt-4 rounded-t-3xl bg-gray-50 px-4 pt-5;
|
||||
@apply relative z-10 -mt-4 rounded-t-3xl bg-gray-50 px-3 pt-5 sm:px-4;
|
||||
}
|
||||
.piggy-info__actions {
|
||||
@apply flex gap-2;
|
||||
@apply flex flex-col gap-2 sm:flex-row;
|
||||
}
|
||||
.piggy-info__action {
|
||||
@apply !w-full sm:!flex-1;
|
||||
}
|
||||
.piggy-info__action-inner {
|
||||
@apply flex items-center justify-center gap-2 text-sm;
|
||||
}
|
||||
.piggy-info__tx {
|
||||
@apply mt-5;
|
||||
@@ -273,10 +291,27 @@ onMounted(async () => {
|
||||
@apply flex flex-col gap-2;
|
||||
}
|
||||
.piggy-info__tx-row {
|
||||
@apply flex items-center gap-3 rounded-2xl bg-white p-3;
|
||||
@apply flex min-w-0 items-center gap-3 rounded-2xl bg-white p-3;
|
||||
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
.piggy-info__tx-dot {
|
||||
@apply h-2.5 w-2.5 shrink-0 rounded-full bg-primary;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.piggy-info {
|
||||
padding: 16px 16px 2rem;
|
||||
}
|
||||
.piggy-info__inner {
|
||||
max-width: 26rem;
|
||||
overflow: hidden;
|
||||
border-radius: 16px;
|
||||
}
|
||||
.piggy-info__hero {
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
.piggy-info__actions {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+218
-84
@@ -1,78 +1,80 @@
|
||||
<template>
|
||||
<div class="piggy-create">
|
||||
<header class="piggy-bar">
|
||||
<button type="button" class="piggy-bar__btn" aria-label="بازگشت" @click="goBack">
|
||||
<BasicIcon icon="vuesax-linear:arrow-right-1" size="22" />
|
||||
</button>
|
||||
<h1 class="piggy-bar__title">{{ editId ? 'ویرایش قلک' : 'ساخت قلک' }}</h1>
|
||||
<span class="w-9" />
|
||||
</header>
|
||||
<div class="piggy-create__inner">
|
||||
<header class="piggy-bar">
|
||||
<button type="button" class="piggy-bar__btn" aria-label="بازگشت" @click="goBack">
|
||||
<BasicIcon icon="vuesax-linear:arrow-right-1" size="22" />
|
||||
</button>
|
||||
<h1 class="piggy-bar__title">{{ editId ? 'ویرایش قلک' : 'ساخت قلک' }}</h1>
|
||||
<span class="w-9 shrink-0" />
|
||||
</header>
|
||||
|
||||
<a-spin :spinning="busy">
|
||||
<div class="piggy-create__body">
|
||||
<section class="piggy-create__step">
|
||||
<div class="piggy-create__step-head">
|
||||
<span class="piggy-create__step-num">۱</span>
|
||||
<div>
|
||||
<h2 class="piggy-create__step-title">محصول</h2>
|
||||
<p class="piggy-create__step-desc">این قلک فقط برای همین محصول پر میشود</p>
|
||||
<a-spin :spinning="busy">
|
||||
<div class="piggy-create__body">
|
||||
<section class="piggy-create__step">
|
||||
<div class="piggy-create__step-head">
|
||||
<span class="piggy-create__step-num">۱</span>
|
||||
<div class="min-w-0">
|
||||
<h2 class="piggy-create__step-title">محصول</h2>
|
||||
<p class="piggy-create__step-desc">این قلک فقط برای همین محصول پر میشود</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="editId" class="piggy-create__locked">
|
||||
<BasicIcon icon="vuesax-linear:box-1" size="18" />
|
||||
<span>{{ lockedProductTitle || 'محصول ثابت' }}</span>
|
||||
</div>
|
||||
<div v-else class="piggy-create__products">
|
||||
<button
|
||||
v-for="p in products"
|
||||
:key="p.id"
|
||||
type="button"
|
||||
class="piggy-create__product"
|
||||
:class="{ 'piggy-create__product--on': productId === p.id }"
|
||||
@click="productId = p.id"
|
||||
>
|
||||
<div v-if="editId" class="piggy-create__locked">
|
||||
<BasicIcon icon="vuesax-linear:box-1" size="18" />
|
||||
<span class="truncate">{{ p.title || `محصول ${p.id}` }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="piggy-create__step">
|
||||
<div class="piggy-create__step-head">
|
||||
<span class="piggy-create__step-num">۲</span>
|
||||
<div>
|
||||
<h2 class="piggy-create__step-title">نام قلک</h2>
|
||||
<p class="piggy-create__step-desc">مثلاً پسانداز، هدیه یا ازدواج</p>
|
||||
<span class="truncate">{{ lockedProductTitle || 'محصول ثابت' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="piggy-create__title-row">
|
||||
<div class="piggy-create__title-icon">
|
||||
<BasicIcon :icon="piggyIconName(title)" size="22" />
|
||||
<div v-else class="piggy-create__products">
|
||||
<button
|
||||
v-for="p in products"
|
||||
:key="p.id"
|
||||
type="button"
|
||||
class="piggy-create__product"
|
||||
:class="{ 'piggy-create__product--on': productId === p.id }"
|
||||
@click="productId = p.id"
|
||||
>
|
||||
<BasicIcon icon="vuesax-linear:box-1" size="18" />
|
||||
<span class="min-w-0 truncate">{{ p.title || `محصول ${p.id}` }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<BasicInput v-model:value="title" class="!flex-1" placeholder="عنوان قلک" maxlength="25" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="piggy-create__presets">
|
||||
<button
|
||||
v-for="preset in presets"
|
||||
:key="preset"
|
||||
type="button"
|
||||
class="piggy-create__chip"
|
||||
:class="{ 'piggy-create__chip--on': title === preset }"
|
||||
@click="title = preset"
|
||||
>
|
||||
{{ preset }}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<section class="piggy-create__step">
|
||||
<div class="piggy-create__step-head">
|
||||
<span class="piggy-create__step-num">۲</span>
|
||||
<div class="min-w-0">
|
||||
<h2 class="piggy-create__step-title">نام قلک</h2>
|
||||
<p class="piggy-create__step-desc">مثلاً پسانداز، هدیه یا ازدواج</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BasicButton class="!w-full" type="primary" size="middle" block :disabled="busy" @click="submit">
|
||||
{{ editId ? 'ذخیره' : 'ساخت قلک' }}
|
||||
</BasicButton>
|
||||
</div>
|
||||
</a-spin>
|
||||
<div class="piggy-create__title-row">
|
||||
<div class="piggy-create__title-icon">
|
||||
<BasicIcon :icon="piggyIconName(title)" size="22" />
|
||||
</div>
|
||||
<BasicInput v-model:value="title" class="!min-w-0 !flex-1" placeholder="عنوان قلک" maxlength="25" />
|
||||
</div>
|
||||
|
||||
<div class="piggy-create__presets">
|
||||
<button
|
||||
v-for="preset in presets"
|
||||
:key="preset"
|
||||
type="button"
|
||||
class="piggy-create__chip"
|
||||
:class="{ 'piggy-create__chip--on': title === preset }"
|
||||
@click="title = preset"
|
||||
>
|
||||
{{ preset }}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<BasicButton class="!w-full" type="primary" size="middle" block :disabled="busy" @click="submit">
|
||||
{{ editId ? 'ذخیره' : 'ساخت قلک' }}
|
||||
</BasicButton>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -161,62 +163,194 @@ onMounted(async () => {
|
||||
|
||||
<style scoped>
|
||||
.piggy-create {
|
||||
@apply min-h-full bg-gray-50 pb-24;
|
||||
box-sizing: border-box;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
background: #f9fafb;
|
||||
padding: 0 12px calc(var(--app-bottom-chrome-height, 108px) + 1rem);
|
||||
}
|
||||
|
||||
.piggy-create__inner {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-width: 26rem;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.piggy-bar {
|
||||
@apply sticky top-0 z-20 flex items-center justify-between bg-white px-3 py-3;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
padding: 12px;
|
||||
box-shadow: 0 1px 0 rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.piggy-bar__title {
|
||||
@apply m-0 text-base font-bold text-gray-900;
|
||||
margin: 0;
|
||||
padding: 0 8px;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.piggy-bar__btn {
|
||||
@apply flex h-9 w-9 items-center justify-center rounded-full border-none bg-gray-100 text-gray-800;
|
||||
display: flex;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
border-radius: 9999px;
|
||||
background: #f3f4f6;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.piggy-create__body {
|
||||
@apply flex flex-col gap-4 px-4 pt-4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.piggy-create__step {
|
||||
@apply rounded-2xl bg-white p-4;
|
||||
border-radius: 16px;
|
||||
background: #fff;
|
||||
padding: 14px;
|
||||
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
|
||||
.piggy-create__step-head {
|
||||
@apply mb-4 flex items-start gap-3;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.piggy-create__step-num {
|
||||
@apply flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary text-sm font-bold text-white;
|
||||
display: flex;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 9999px;
|
||||
background: var(--App-primary, #0a1b37);
|
||||
color: #fff;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.piggy-create__step-title {
|
||||
@apply m-0 text-sm font-bold text-gray-900;
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.piggy-create__step-desc {
|
||||
@apply m-0 mt-1 text-xs text-gray-500;
|
||||
margin: 4px 0 0;
|
||||
font-size: 0.75rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.piggy-create__products {
|
||||
@apply grid grid-cols-2 gap-2;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.piggy-create__product {
|
||||
@apply flex items-center gap-2 rounded-xl border-none bg-gray-50 px-3 py-3 text-right text-sm text-gray-700;
|
||||
display: inline-flex;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: #f9fafb;
|
||||
padding: 10px 14px;
|
||||
text-align: right;
|
||||
font-size: 0.875rem;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.piggy-create__product--on {
|
||||
@apply bg-primary text-white;
|
||||
background: var(--App-primary, #0a1b37);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.piggy-create__locked {
|
||||
@apply flex items-center gap-2 rounded-xl bg-gray-50 px-3 py-3 text-sm font-bold text-gray-800;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border-radius: 12px;
|
||||
background: #f9fafb;
|
||||
padding: 12px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.piggy-create__title-row {
|
||||
@apply flex items-center gap-2;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.piggy-create__title-icon {
|
||||
@apply flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-gray-50 text-primary;
|
||||
display: flex;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 12px;
|
||||
background: #f9fafb;
|
||||
color: var(--App-primary, #0a1b37);
|
||||
}
|
||||
|
||||
.piggy-create__presets {
|
||||
@apply mt-3 flex flex-wrap gap-2;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.piggy-create__chip {
|
||||
@apply rounded-full border-none bg-gray-100 px-3 py-2 text-xs text-gray-600;
|
||||
border: none;
|
||||
border-radius: 9999px;
|
||||
background: #f3f4f6;
|
||||
padding: 8px 12px;
|
||||
font-size: 0.75rem;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.piggy-create__chip--on {
|
||||
@apply bg-primary text-white;
|
||||
background: var(--App-primary, #0a1b37);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.piggy-create {
|
||||
padding: 16px 16px 2rem;
|
||||
}
|
||||
|
||||
.piggy-create__inner {
|
||||
max-width: 26rem;
|
||||
}
|
||||
|
||||
.piggy-bar {
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -125,6 +125,13 @@ export function useUserPage() {
|
||||
onClick: () => router.push({ name: 'about' }),
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
id: 'giftCard',
|
||||
label: 'ثبت کارت هدیه',
|
||||
icon: 'vuesax-linear:gift',
|
||||
onClick: () => router.push({ name: 'giftCard' }),
|
||||
visible: settingsStore.isSettingTruthy('has_page_gift_card'),
|
||||
},
|
||||
{
|
||||
id: 'support',
|
||||
label: 'پشتیبانی',
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
"
|
||||
:style="{ opacity: getSwipeOpacity(message.id) }"
|
||||
>
|
||||
↩
|
||||
<BasicIcon icon="vuesax-linear:undo" size="16" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -281,10 +281,13 @@
|
||||
: 'support-reply-button--support'
|
||||
"
|
||||
title="پاسخ"
|
||||
@pointerdown.stop
|
||||
@pointerup.stop
|
||||
@pointercancel.stop
|
||||
@pointermove.stop
|
||||
@click.stop="emit('reply', message)"
|
||||
>
|
||||
<!-- <BasicIcon icon="vuesax-linear:message-text" size="17" />-->
|
||||
<div class="w-1rem h-1rem flex justify-center">↩</div>
|
||||
<BasicIcon icon="vuesax-linear:undo" size="16" />
|
||||
</BasicButton>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1129,7 +1132,8 @@ defineExpose({
|
||||
}
|
||||
|
||||
.support-day-group {
|
||||
display: contents;
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.support-day-sticky {
|
||||
@@ -1148,12 +1152,12 @@ defineExpose({
|
||||
justify-content: center;
|
||||
padding: 7px 14px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--bg, #fff) 72%, transparent);
|
||||
background: var(--bg, #fff);
|
||||
border: 1px solid rgb(var(--colorPrimaryRGB) / 0.18);
|
||||
color: var(--colorText, #111827);
|
||||
backdrop-filter: blur(8px);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 6px 20px rgb(var(--colorPrimaryRGB) / 0.08);
|
||||
}
|
||||
|
||||
@@ -1221,6 +1225,10 @@ defineExpose({
|
||||
transform 0.15s ease;
|
||||
}
|
||||
|
||||
.support-reply-button :deep(svg) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.support-row:hover .support-reply-button,
|
||||
.support-row:focus-within .support-reply-button {
|
||||
opacity: 1;
|
||||
@@ -1316,27 +1324,36 @@ defineExpose({
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 9px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 14px;
|
||||
margin-bottom: 10px;
|
||||
padding: 9px 11px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
min-width: 10rem;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.support-bubble__reply--user {
|
||||
background: rgba(255, 255, 255, 0.14);
|
||||
background: rgba(255, 255, 255, 0.22);
|
||||
border-color: rgba(255, 255, 255, 0.28);
|
||||
}
|
||||
|
||||
.support-bubble__reply--support {
|
||||
background: rgb(var(--colorPrimaryRGB) / 0.08);
|
||||
background: #f4f4f5;
|
||||
border-color: #e4e4e7;
|
||||
}
|
||||
|
||||
.support-bubble__reply-line {
|
||||
width: 3px;
|
||||
border-radius: 999px;
|
||||
flex: 0 0 3px;
|
||||
background: currentColor;
|
||||
opacity: 0.75;
|
||||
align-self: stretch;
|
||||
min-height: 28px;
|
||||
background: var(--colorPrimary, #2563eb);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.support-bubble__reply--user .support-bubble__reply-line {
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
}
|
||||
|
||||
.support-bubble__reply-content {
|
||||
@@ -1362,7 +1379,7 @@ defineExpose({
|
||||
unicode-bidi: plaintext;
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
opacity: 0.86;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.support-bubble--user .support-bubble__reply-text,
|
||||
@@ -1375,7 +1392,7 @@ defineExpose({
|
||||
}
|
||||
|
||||
.support-bubble--support .support-bubble__reply-text {
|
||||
color: color-mix(in srgb, var(--colorText, #111827) 72%, transparent);
|
||||
color: #52525b;
|
||||
}
|
||||
|
||||
.support-bubble__attachments {
|
||||
|
||||
Reference in New Issue
Block a user