added some text handling 3

This commit is contained in:
2026-07-08 18:24:55 +03:30
parent 89e2643dff
commit 01437afa84
19 changed files with 683 additions and 168 deletions
@@ -1,16 +1,18 @@
<template>
<a-tooltip
placement="top"
:title="tooltipText"
overlay-class-name="dashboard-help-tooltip"
:overlay-style="{ zIndex: TOOLTIP_ABOVE_MODAL_Z_INDEX }"
:overlay-inner-style="{ maxWidth: '26rem', direction: 'rtl', textAlign: 'right', whiteSpace: 'pre-line' }"
:overlay-inner-style="tooltipInnerStyle"
:mouse-enter-delay="0.15"
>
<template #title>
<AdminHelpTooltipContent :content="parsed" />
</template>
<span
class="dashboard-help-trigger inline-flex shrink-0 items-center justify-center"
role="img"
:aria-label="tooltipText"
:aria-label="plainText"
tabindex="0"
>
<IosInfoBadge :size="badgeSize" />
@@ -21,7 +23,9 @@
<script setup lang="ts">
import type { DashboardHelp } from '@/pages/dashboard/dashboardSectionCopy'
import IosInfoBadge from '@/pages/dashboard/components/IosInfoBadge.vue'
import AdminHelpTooltipContent from '@/components/AdminHelpTooltipContent.vue'
import { TOOLTIP_ABOVE_MODAL_Z_INDEX } from '@/constants/overlayZIndex'
import { adminHelpPlainText } from '@/utils/adminHelpFormat'
const props = withDefaults(
defineProps<{
@@ -35,13 +39,18 @@ const props = withDefaults(
const badgeSize = computed(() => props.size)
const tooltipText = computed(() => {
const blocks = [props.help.description?.trim()].filter(Boolean)
if (props.help.formula?.trim()) {
blocks.push(props.help.formula.trim())
}
return blocks.join('\n\n')
})
const parsed = computed(() => ({
description: props.help.description?.trim() ?? '',
formula: props.help.formula?.trim() || undefined,
}))
const plainText = computed(() => adminHelpPlainText(parsed.value))
const tooltipInnerStyle = {
maxWidth: '26rem',
direction: 'rtl',
textAlign: 'right',
}
</script>
<style scoped>
@@ -56,4 +65,9 @@ const tooltipText = computed(() => {
z-index: 1250 !important;
pointer-events: none;
}
.ant-tooltip.dashboard-help-tooltip .ant-tooltip-inner {
direction: rtl;
text-align: right;
}
</style>
@@ -1,10 +1,6 @@
<template>
<section class="dashboard-income-summary">
<DashboardSectionHeader title="خلاصه درآمد معاملات" :help="help" />
<p class="dashboard-income-summary__intro">
سود اختلاف قیمت و کارمزد جدا محاسبه می‌شوند. جمع = هر دو. همه مبالغ
<strong>ریال</strong> هستند (نه تومان).
</p>
<div class="dashboard-income-summary__grid">
<article
@@ -95,13 +91,6 @@ const rows = computed(() => [
color: var(--admin-text);
}
.dashboard-income-summary__intro {
margin: 0 0 0.5rem;
font-size: 0.6875rem;
line-height: 1.55;
color: var(--admin-text-secondary);
}
.dashboard-income-summary__grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
+1 -1
View File
@@ -58,7 +58,7 @@ export const dashboardKpi = {
} as const satisfies Record<string, DashboardHelp>
export const dashboardTradingIncomeSummary = help(
'تفکیک درآمد از معاملات در بازه انتخابی: سود اختلاف قیمت و کارمزد ریالی جدا، و جمع هر دو.',
'در بازهٔ انتخابی، درآمد معاملات را به دو بخش می‌بینید: سود اختلاف قیمت و کارمزد ریالی. همهٔ مبالغ به ریال هستند (نه تومان).',
'جمع سود معاملات = سود اختلاف قیمت + کارمزد معاملات'
)
@@ -400,7 +400,13 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
support_messenger_text:
'متن راهنمای واریز در مودال شارژ حساب. متغیرها: ٪نام یا %نام = نام صاحب حساب؛ ٪کارت یا %کارت = شماره کارت؛ ٪شبا یا %شبا = شبا. فاصله بعد از ٪ اختیاری است. اگر خالی باشد از متن پیش‌فرض اپ استفاده می‌شود.',
deposit_support_message_text:
'متن پیام هنگام کلیک روی «پیام به پشتیبانی» و «پیام به پشتیبانی (sms)» در مودال واریز. همان متغیرهای ٪نام، ٪کارت و ٪شبا. پیش‌فرض: «شماره حساب».',
`متن پیام هنگام کلیک «پیام به پشتیبانی» در مودال واریز (SMS و پیام‌رسان).
متغیر:
• %نام = نام صاحب حساب
پیش‌فرض اپ:
باسلام، %نام هستم. کدپیگیری و مبلغ واریزی من:`,
deposit_account_holder:
'نام صاحب حساب واریز — در متن راهنما و پیام پشتیبانی با ٪نام یا %نام جایگزین می‌شود.',
deposit_card_number:
@@ -0,0 +1,233 @@
<template>
<BasicModal
v-model:visible="visibleProxy"
title="ویرایش مقدار برداشت"
width="28rem"
wrap-class-name="physical-withdraw-adjust-modal"
:loading="loading"
:confirm-loading="saving"
@ok="submit"
@close="close"
>
<div v-if="loading" class="py-10 text-center text-sm text-gray-500">در حال بارگذاری...</div>
<div v-else-if="info && formLines.length" class="space-y-4">
<div class="rounded-lg bg-gray-50 p-3 text-sm leading-6">
<div>سند: <span class="font-semibold">#{{ info.withdraw_id }}</span></div>
<div>مشتری: <span class="font-semibold">{{ info.customer_name || '-' }}</span></div>
<div v-if="info.customer_mobile">موبایل: {{ info.customer_mobile }}</div>
</div>
<div
v-for="line in formLines"
:key="line.transaction_id"
class="rounded-lg border border-gray-200 p-3"
>
<div class="mb-2 text-sm font-semibold">{{ line.title }}</div>
<div class="mb-3 grid grid-cols-2 gap-2 text-xs text-gray-600">
<div>مقدار فعلی: <span class="font-medium text-gray-900">{{ formatQty(line.current_value, line) }}</span></div>
<div>قابل افزودن: <span class="font-medium text-gray-900">{{ formatQty(line.available_add, line) }}</span></div>
<div>حداکثر مجاز: <span class="font-medium text-gray-900">{{ formatQty(line.max_value, line) }}</span></div>
<div>حداقل مجاز: <span class="font-medium text-gray-900">{{ formatQty(line.min_value, line) }}</span></div>
</div>
<a-form-item
class="!mb-0"
:validate-status="lineError(line.transaction_id) ? 'error' : ''"
:help="lineError(line.transaction_id)"
>
<template #label>
<AdminLabelWithHelp label="مقدار جدید" :help="physicalWithdrawAdjustValueHelp" />
</template>
<BasicInput
v-model:value="line.valueInput"
type="number"
:step="line.allows_decimal ? '0.001' : '1'"
:place-holder="line.allows_decimal ? 'مثلاً 1.250' : 'مثلاً 2'"
@change="() => clearLineError(line.transaction_id)"
/>
</a-form-item>
</div>
</div>
<div v-else-if="info && !formLines.length" class="py-8 text-center text-sm text-gray-500">
خطی برای ویرایش یافت نشد.
</div>
</BasicModal>
</template>
<script setup lang="ts">
import { message } from 'ant-design-vue'
import AdminLabelWithHelp from '@/components/AdminLabelWithHelp.vue'
import {
adjustPhysicalWithdraw,
getPhysicalWithdrawAdjustInfo,
type PhysicalWithdrawAdjustInfo,
type PhysicalWithdrawAdjustLine
} from '@/api/physicalWithdraw'
import { physicalWithdrawAdjustValueHelp } from '@/utils/adminHelpCopy'
type FormLine = PhysicalWithdrawAdjustLine & { valueInput: string }
const props = defineProps<{
visible: boolean
withdrawId?: number
}>()
const emit = defineEmits<{
'update:visible': [value: boolean]
success: []
}>()
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value),
})
const loading = ref(false)
const saving = ref(false)
const info = ref<PhysicalWithdrawAdjustInfo | null>(null)
const formLines = ref<FormLine[]>([])
const lineErrors = ref<Record<number, string>>({})
watch(
() => [props.visible, props.withdrawId] as const,
([open, withdrawId]) => {
if (open && withdrawId) {
void loadInfo(withdrawId)
}
if (!open) {
resetState()
}
}
)
function resetState() {
info.value = null
formLines.value = []
lineErrors.value = {}
}
function formatQty(value: number, line: Pick<PhysicalWithdrawAdjustLine, 'unit' | 'allows_decimal'>) {
const decimals = line.allows_decimal ? 3 : 0
const formatted = Number(value).toLocaleString('fa-IR', {
minimumFractionDigits: 0,
maximumFractionDigits: decimals
})
return `${formatted} ${line.unit}`
}
function lineError(transactionId: number) {
return lineErrors.value[transactionId] ?? ''
}
function clearLineError(transactionId: number) {
if (lineErrors.value[transactionId]) {
const next = { ...lineErrors.value }
delete next[transactionId]
lineErrors.value = next
}
}
function parseValue(raw: string, line: PhysicalWithdrawAdjustLine): number | null {
const normalized = String(raw ?? '').trim().replace(',', '.')
if (!normalized) return null
const value = Number(normalized)
if (!Number.isFinite(value)) return null
if (!line.allows_decimal && Math.abs(value - Math.round(value)) > 0.000001) {
return null
}
return value
}
function validateLine(line: FormLine): string | null {
const value = parseValue(line.valueInput, line)
if (value == null) {
return line.allows_decimal ? 'مقدار معتبر وارد کنید' : 'مقدار باید عدد صحیح باشد'
}
if (value < line.min_value - 0.000001) {
return `حداقل مقدار ${formatQty(line.min_value, line)} است`
}
if (value > line.max_value + 0.000001) {
return `حداکثر مقدار ${formatQty(line.max_value, line)} است`
}
return null
}
async function loadInfo(withdrawId: number) {
loading.value = true
lineErrors.value = {}
try {
const data = await getPhysicalWithdrawAdjustInfo(withdrawId)
info.value = data
formLines.value = (data.lines ?? []).map((line) => ({
...line,
valueInput: String(line.current_value ?? '')
}))
} catch (e) {
console.error(e)
visibleProxy.value = false
} finally {
loading.value = false
}
}
function close() {
visibleProxy.value = false
}
async function submit() {
if (!props.withdrawId || !formLines.value.length) return
const errors: Record<number, string> = {}
const items: Array<{ transaction_id: number; value: number }> = []
for (const line of formLines.value) {
const error = validateLine(line)
if (error) {
errors[line.transaction_id] = error
continue
}
const value = parseValue(line.valueInput, line)
if (value == null) {
errors[line.transaction_id] = 'مقدار معتبر وارد کنید'
continue
}
if (Math.abs(value - line.current_value) < 0.000001) {
continue
}
items.push({
transaction_id: line.transaction_id,
value
})
}
lineErrors.value = errors
if (Object.keys(errors).length) {
message.error('لطفاً مقادیر را اصلاح کنید')
return
}
if (!items.length) {
message.info('تغییری اعمال نشده است')
return
}
saving.value = true
try {
await adjustPhysicalWithdraw(props.withdrawId, { items })
message.success('مقادیر با موفقیت به‌روزرسانی شد')
visibleProxy.value = false
emit('success')
} catch (e) {
console.error(e)
} finally {
saving.value = false
}
}
</script>
<style>
.physical-withdraw-adjust-modal {
z-index: 1100 !important;
}
</style>
@@ -1,26 +1,40 @@
<template>
<a-form ref="formRef" :model="form" layout="vertical" autocomplete="off">
<a-form-item label="مشتری" name="customer_id" :rules="inputRules()">
<a-form-item name="customer_id" :rules="inputRules()">
<template #label>
<AdminLabelWithHelp label="مشتری" :help="physicalWithdrawCustomerHelp" />
</template>
<BasicSelect
v-model:value="form.customer_id"
show-search
:options="customerOptions"
:items="customerItems"
key-name="id"
value-name="displayName"
placeholder="انتخاب مشتری"
:has-tooltip="false"
@change="onCustomerChange"
/>
</a-form-item>
<a-form-item v-if="form.customer_id" label="آدرس تحویل" name="address_id" :rules="inputRules()">
<a-form-item v-if="form.customer_id" name="address_id" :rules="inputRules()">
<template #label>
<AdminLabelWithHelp label="آدرس تحویل" :help="physicalWithdrawAddressHelp" />
</template>
<BasicSelect
v-model:value="form.address_id"
:options="addressOptions"
:items="addressItems"
key-name="id"
value-name="displayName"
placeholder="انتخاب آدرس"
:loading="optionsLoading"
:has-tooltip="false"
/>
</a-form-item>
<div v-if="form.customer_id" class="space-y-3">
<div class="text-sm font-semibold">محصولات</div>
<div class="flex items-center gap-2 text-sm font-semibold">
<span>محصولات</span>
<AdminFieldHelp :text="physicalWithdrawProductHelp" />
</div>
<div
v-for="(line, index) in form.lines"
:key="`${line.wallet_id}-${index}`"
@@ -30,13 +44,16 @@
class="!mb-0"
:name="['lines', index, 'wallet_id']"
:rules="inputRules()"
label="محصول"
>
<template #label>محصول</template>
<BasicSelect
v-model:value="line.wallet_id"
:options="productOptionsForLine(index)"
:items="productItemsForLine(index)"
key-name="wallet_id"
value-name="displayName"
placeholder="انتخاب محصول"
:loading="optionsLoading"
:has-tooltip="false"
@change="() => onProductChange(index)"
/>
</a-form-item>
@@ -44,8 +61,10 @@
class="!mb-0"
:name="['lines', index, 'value']"
:rules="inputRules()"
label="مقدار"
>
<template #label>
<AdminLabelWithHelp label="مقدار" :help="physicalWithdrawAmountHelp" />
</template>
<BasicInput v-model:value="line.value" type="number" min="0" />
</a-form-item>
<div class="flex items-end">
@@ -73,6 +92,14 @@
import { inputRules } from '@/utils/inputRules'
import type { Customer } from '@/model/customer'
import type { FormInstance } from 'ant-design-vue'
import AdminLabelWithHelp from '@/components/AdminLabelWithHelp.vue'
import AdminFieldHelp from '@/components/AdminFieldHelp.vue'
import {
physicalWithdrawAddressHelp,
physicalWithdrawAmountHelp,
physicalWithdrawCustomerHelp,
physicalWithdrawProductHelp
} from '@/utils/adminHelpCopy'
import {
getPhysicalWithdrawOptions,
type PhysicalWithdrawAddressOption,
@@ -100,21 +127,21 @@ const optionsLoading = ref(false)
const products = ref<PhysicalWithdrawProductOption[]>([])
const addresses = ref<PhysicalWithdrawAddressOption[]>([])
const customerOptions = computed(() =>
const customerItems = computed(() =>
(props.customers ?? []).map((customer) => ({
label: `${customer.name ?? '-'} (${customer.mobile ?? '-'})`,
value: customer.id
...customer,
displayName: `${customer.name ?? '-'} (${customer.mobile ?? '-'})`
}))
)
const addressOptions = computed(() =>
const addressItems = computed(() =>
addresses.value.map((addr) => ({
label: [addr.province, addr.city, addr.address].filter(Boolean).join(' / '),
value: addr.id
...addr,
displayName: [addr.province, addr.city, addr.address].filter(Boolean).join(' / ')
}))
)
function productOptionsForLine(index: number) {
function productItemsForLine(index: number) {
const usedWalletIds = props.form.lines
.map((line, i) => (i === index ? undefined : line.wallet_id))
.filter(Boolean)
@@ -122,8 +149,8 @@ function productOptionsForLine(index: number) {
return products.value
.filter((product) => !usedWalletIds.includes(product.wallet_id))
.map((product) => ({
label: `${product.title} (موجودی: ${product.available} ${product.unit})`,
value: product.wallet_id
...product,
displayName: `${product.title} (موجودی: ${product.available} ${product.unit})`
}))
}
@@ -1,83 +1,81 @@
<template>
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
<BasicShowContext title="لیست برداشت فیزیکی">
<template #extra>
<AdminFieldHelp :text="physicalWithdrawPageIntro" />
</template>
<div class="flex h-full min-h-0 flex-col">
<BaseTable class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader>
<BaseButton type="primary" size="small" icon="vuesax-linear:add-square" @click="emits('add')">
ثبت برداشت فیزیکی
</BaseButton>
<div class="flex items-center gap-2">
<BaseButton type="primary" size="small" icon="vuesax-linear:add-square" @click="emits('add')">
ثبت برداشت فیزیکی
</BaseButton>
</div>
</template>
<template #action="{ record }">
<div class="flex flex-col items-center gap-2 py-1">
<div
v-for="tx in record.transaction ?? []"
:key="tx.id"
class="flex w-full max-w-[220px] items-center justify-between gap-2 rounded border border-gray-200 px-2 py-1"
>
<span class="truncate text-xs">{{ tx.title?.trim() || 'محصول' }}</span>
<span class="shrink-0 text-xs font-semibold">{{ Math.abs(Number(tx.gold ?? 0)) }}</span>
<div v-if="canAdjust(record)" class="flex shrink-0 gap-1">
<BaseButton
size="small"
class="!min-w-0 !px-2"
:loading="isAdjusting(record.id, tx.id, 'decrease')"
@click="handleAdjust(record, tx, 'decrease')"
>
کم
</BaseButton>
<BaseButton
size="small"
type="primary"
class="!min-w-0 !px-2"
:loading="isAdjusting(record.id, tx.id, 'increase')"
@click="handleAdjust(record, tx, 'increase')"
>
زیاد
</BaseButton>
</div>
</div>
<div class="flex items-center justify-center gap-2">
<a-tooltip v-if="canAdjust(record)">
<template #title>ویرایش مقدار</template>
<Icon
icon="vuesax-linear:edit-2"
color="#2563EB"
class="cursor-pointer"
@click="openAdjustModal(record)"
/>
</a-tooltip>
<div class="flex flex-wrap items-center justify-center gap-2">
<BaseButton
v-if="canApprove(record)"
size="small"
type="primary"
:loading="isApproving(record.id)"
<a-tooltip v-if="canApprove(record)">
<template #title>{{ physicalWithdrawApproveHelp }}</template>
<Icon
icon="vuesax-linear:tick-circle"
color="#16A34A"
class="cursor-pointer"
:class="{ 'opacity-40 pointer-events-none': isApproving(record.id) }"
@click="handleApprove(record)"
>
تایید
</BaseButton>
<ADropdown v-if="canChangeStatus(record)" :trigger="['click']">
<BaseButton size="small">وضعیت</BaseButton>
<template #overlay>
<a-menu class="flex flex-col">
<a-menu-item
v-for="(item, key) in statusOptions(record.sts)"
:key="key"
class="border-none px-2 py-1"
@click="handleStatusChange(record.id, Number(key))"
>
{{ item.title }}
</a-menu-item>
</a-menu>
</template>
</ADropdown>
<a-tooltip v-if="record.sts >= 50 && record.sts <= 70">
<template #title>دریافت فاکتور</template>
<Icon
icon="vuesax-linear:document-download"
color="#1D4ED8"
class="cursor-pointer"
@click="$router.push({ name: 'factor', params: { id: record.id } })"
/>
/>
</a-tooltip>
<ADropdown v-if="canChangeStatus(record)" :trigger="['click']">
<a-tooltip>
<template #title>{{ physicalWithdrawStatusHelp }}</template>
<Icon icon="vuesax-linear:status-up" color="#CA8A04" class="cursor-pointer" />
</a-tooltip>
</div>
<template #overlay>
<a-menu class="flex flex-col min-w-[10rem]">
<a-menu-item
v-for="(item, key) in statusOptions(record.sts)"
:key="key"
class="border-none px-3 py-2"
:style="{ color: item.color }"
@click="handleStatusChange(record.id, Number(key))"
>
{{ item.title }}
</a-menu-item>
</a-menu>
</template>
</ADropdown>
<a-tooltip v-if="record.sts >= 50 && record.sts <= 70">
<template #title>دریافت فاکتور</template>
<Icon
icon="vuesax-linear:document-download"
color="#1D4ED8"
class="cursor-pointer"
@click="$router.push({ name: 'factor', params: { id: record.id } })"
/>
</a-tooltip>
</div>
</template>
</BaseTable>
</div>
</BasicShowContext>
<PhysicalWithdrawAdjustModal
v-model:visible="adjustVisible"
:withdraw-id="selectedWithdrawId"
@success="reload"
/>
</div>
</template>
@@ -86,21 +84,25 @@ import type { TableInput } from '@core/type/baseTable'
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import { getPhysicalWithdrawColumns } from '../hooks/physicalWithdrawColumns'
import {
adjustPhysicalWithdraw,
approvePhysicalWithdraw,
getPhysicalWithdrawTable
} from '@/api/physicalWithdraw'
import { approvePhysicalWithdraw, getPhysicalWithdrawTable } from '@/api/physicalWithdraw'
import { updateSts } from '@/api/wallet'
import { message } from 'ant-design-vue'
import type { Withdraw } from '@/model/wallet'
import { status as physicalStatusMap } from '@/pages/menuPages/withdraw/information/hooks/status'
import AdminFieldHelp from '@/components/AdminFieldHelp.vue'
import PhysicalWithdrawAdjustModal from './PhysicalWithdrawAdjustModal.vue'
import {
physicalWithdrawApproveHelp,
physicalWithdrawPageIntro,
physicalWithdrawStatusHelp
} from '@/utils/adminHelpCopy'
const emits = defineEmits(['add'])
const [submit, reload] = useBaseTable()
const adjustingKey = ref('')
const approvingId = ref<number | null>(null)
const adjustVisible = ref(false)
const selectedWithdrawId = ref<number>()
const tblProps = computed(() => ({
columns: getPhysicalWithdrawColumns(),
@@ -108,7 +110,7 @@ const tblProps = computed(() => ({
}))
function canAdjust(record: Withdraw) {
return Number(record.sts) === 50
return Number(record.sts) === 50 && (record.transaction?.length ?? 0) > 0
}
function canApprove(record: Withdraw) {
@@ -126,39 +128,18 @@ function statusOptions(currentSts: number) {
)
}
function adjustKey(withdrawId?: number, transactionId?: number, direction?: string) {
return `${withdrawId ?? 0}-${transactionId ?? 0}-${direction ?? ''}`
}
function isAdjusting(withdrawId?: number, transactionId?: number, direction?: string) {
return adjustingKey.value === adjustKey(withdrawId, transactionId, direction)
}
function isApproving(withdrawId?: number) {
return approvingId.value === withdrawId
}
async function handleAdjust(
record: Withdraw,
tx: { id?: number },
direction: 'increase' | 'decrease'
) {
if (!record.id || !tx.id) return
const key = adjustKey(record.id, tx.id, direction)
adjustingKey.value = key
try {
await adjustPhysicalWithdraw(record.id, { transaction_id: tx.id, direction })
message.success('مقدار بروزرسانی شد')
await reload()
} catch (e) {
console.error(e)
} finally {
if (adjustingKey.value === key) adjustingKey.value = ''
}
function openAdjustModal(record: Withdraw) {
if (!record.id) return
selectedWithdrawId.value = record.id
adjustVisible.value = true
}
async function handleApprove(record: Withdraw) {
if (!record.id) return
if (!record.id || isApproving(record.id)) return
approvingId.value = record.id
try {
await approvePhysicalWithdraw(record.id, 51)
@@ -3,15 +3,25 @@ import { toJalali } from '@core/utils/useDataTime'
import CustomTag from '@/components/CustomTag.vue'
import type { Withdraw } from '@/model/wallet'
function formatLineQty(tx: { gold?: number; product?: { unit?: string; trade_by_quantity?: boolean } }, title: string): string {
const qty = Math.abs(Number(tx.gold ?? 0))
const unit = tx.product?.unit || 'گرم'
const decimals = tx.product?.trade_by_quantity ? 0 : 3
const formatted = qty.toLocaleString('fa-IR', {
minimumFractionDigits: 0,
maximumFractionDigits: decimals
})
return `${title}: ${formatted} ${unit}`
}
function formatProductsSummary(record: Withdraw): string {
const lines = record.transaction ?? []
if (!lines.length) return '-'
return lines
.map((tx) => {
const qty = Math.abs(Number(tx.gold ?? 0))
const title = String(tx.title ?? '').trim() || 'محصول'
return `${title}: ${qty}`
const title = String(tx.title ?? '').trim() || tx.product?.title || 'محصول'
return formatLineQty(tx, title)
})
.join(' | ')
}
@@ -87,7 +97,7 @@ export function getPhysicalWithdrawColumns(): BaseTableColumn[] {
title: 'عملیات',
dataIndex: 'action',
key: 'action',
width: 260,
width: 140,
slot: 'action',
filterable: false,
fixed: 'right'
@@ -61,6 +61,7 @@ async function loadCustomers() {
customers.value = (await getCustomersList()) ?? []
} catch (e) {
console.error(e)
message.error('خطا در بارگذاری لیست مشتریان')
}
}
@@ -75,6 +76,7 @@ function resetForm() {
function openCreate() {
resetForm()
void loadCustomers()
createVisible.value = true
}
@@ -21,7 +21,6 @@
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
<template #extraHeader>
<div class="flex items-center gap-2">
<AdminFieldHelp :text="pricingPageIntro" />
<BaseButton
type="primary"
size="small"