admin big changes
This commit is contained in:
@@ -3,24 +3,13 @@
|
|||||||
class="setting-card"
|
class="setting-card"
|
||||||
:class="{
|
:class="{
|
||||||
'setting-card--readonly': readonly,
|
'setting-card--readonly': readonly,
|
||||||
'setting-card--saving': saving,
|
'setting-card--saving': saving
|
||||||
'setting-card--inactive': !isActive && fieldType !== 'switch'
|
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="setting-card__head">
|
<div class="setting-card__head">
|
||||||
<div class="setting-card__title-row">
|
<div class="setting-card__title-row">
|
||||||
<span v-if="readonly" class="setting-card__lock" title="فقطخواندنی">🔒</span>
|
<span v-if="readonly" class="setting-card__lock" title="فقطخواندنی">🔒</span>
|
||||||
<span class="setting-card__title">{{ title }}</span>
|
<span class="setting-card__title">{{ title }}</span>
|
||||||
<a-switch
|
|
||||||
v-if="fieldType !== 'switch'"
|
|
||||||
:checked="isActive"
|
|
||||||
size="small"
|
|
||||||
class="setting-card__active-switch"
|
|
||||||
@change="onActiveChange"
|
|
||||||
>
|
|
||||||
<template #checkedChildren>فعال</template>
|
|
||||||
<template #unCheckedChildren>غیرفعال</template>
|
|
||||||
</a-switch>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-card__key">کلید: {{ settingKey }}</div>
|
<div class="setting-card__key">کلید: {{ settingKey }}</div>
|
||||||
<div v-if="help" class="setting-card__help">{{ help }}</div>
|
<div v-if="help" class="setting-card__help">{{ help }}</div>
|
||||||
@@ -62,7 +51,6 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:value': [value: string]
|
'update:value': [value: string]
|
||||||
'update:isActive': [value: boolean]
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const settingKey = computed(() => props.row.key ?? '')
|
const settingKey = computed(() => props.row.key ?? '')
|
||||||
@@ -74,12 +62,6 @@ const fieldType = computed<SettingFieldType>(() =>
|
|||||||
resolveSettingFieldType({ key: props.row.key, type: props.row.type })
|
resolveSettingFieldType({ key: props.row.key, type: props.row.type })
|
||||||
)
|
)
|
||||||
|
|
||||||
const isActive = computed(() => props.row.is_active !== false && props.row.is_active !== 0)
|
|
||||||
|
|
||||||
function onActiveChange(checked: boolean) {
|
|
||||||
emit('update:isActive', checked)
|
|
||||||
}
|
|
||||||
|
|
||||||
const help = computed(() => helpText(props.row))
|
const help = computed(() => helpText(props.row))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -108,11 +90,6 @@ const help = computed(() => helpText(props.row))
|
|||||||
border-color: #91caff;
|
border-color: #91caff;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--inactive {
|
|
||||||
opacity: 0.72;
|
|
||||||
background: #fafafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__head {
|
&__head {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@@ -123,11 +100,6 @@ const help = computed(() => helpText(props.row))
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__active-switch {
|
|
||||||
margin-right: auto;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__lock {
|
&__lock {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@change="onSwitchChange"
|
@change="onSwitchChange"
|
||||||
>
|
>
|
||||||
<template #checkedChildren>فعال</template>
|
<template #checkedChildren>{{ labels.on }}</template>
|
||||||
<template #unCheckedChildren>غیرفعال</template>
|
<template #unCheckedChildren>{{ labels.off }}</template>
|
||||||
</a-switch>
|
</a-switch>
|
||||||
|
|
||||||
<ZaarboxPriceKeyField
|
<ZaarboxPriceKeyField
|
||||||
@@ -63,10 +63,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ZaarboxPriceKeyField from './ZaarboxPriceKeyField.vue'
|
import ZaarboxPriceKeyField from './ZaarboxPriceKeyField.vue'
|
||||||
import {
|
import {
|
||||||
isSwitchOn,
|
|
||||||
parseSelectOptions,
|
parseSelectOptions,
|
||||||
resolveSettingFieldType,
|
resolveSettingFieldType,
|
||||||
switchToString,
|
resolveSwitchChecked,
|
||||||
|
switchLabels,
|
||||||
|
switchValueFromChecked,
|
||||||
type SettingFieldType
|
type SettingFieldType
|
||||||
} from './settingTypes'
|
} from './settingTypes'
|
||||||
|
|
||||||
@@ -101,10 +102,12 @@ const selectItems = computed(() =>
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const switchChecked = computed(() => isSwitchOn(props.modelValue))
|
const labels = computed(() => switchLabels(props.settingKey))
|
||||||
|
|
||||||
|
const switchChecked = computed(() => resolveSwitchChecked(props.settingKey, props.modelValue))
|
||||||
|
|
||||||
function onSwitchChange(checked: boolean) {
|
function onSwitchChange(checked: boolean) {
|
||||||
emit('update:value', switchToString(checked))
|
emit('update:value', switchValueFromChecked(props.settingKey, checked))
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSelectChange(val: string | undefined) {
|
function onSelectChange(val: string | undefined) {
|
||||||
|
|||||||
@@ -14,16 +14,6 @@
|
|||||||
</a-input>
|
</a-input>
|
||||||
|
|
||||||
<div class="settings-toolbar__filters">
|
<div class="settings-toolbar__filters">
|
||||||
<label class="settings-filter-field">
|
|
||||||
<span class="settings-filter-field__label">وضعیت</span>
|
|
||||||
<a-select
|
|
||||||
v-model:value="filterActive"
|
|
||||||
allow-clear
|
|
||||||
placeholder="همه"
|
|
||||||
class="settings-filter-field__control"
|
|
||||||
:options="statusFilterOptions"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label class="settings-filter-field">
|
<label class="settings-filter-field">
|
||||||
<span class="settings-filter-field__label">نوع فیلد</span>
|
<span class="settings-filter-field__label">نوع فیلد</span>
|
||||||
<a-select
|
<a-select
|
||||||
@@ -80,7 +70,6 @@
|
|||||||
:saving="isSaving(row.id)"
|
:saving="isSaving(row.id)"
|
||||||
:saved-flash="isSavedFlash(row.id)"
|
:saved-flash="isSavedFlash(row.id)"
|
||||||
@update:value="onValueChange(row, $event)"
|
@update:value="onValueChange(row, $event)"
|
||||||
@update:is-active="onActiveChange(row, $event)"
|
|
||||||
/>
|
/>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -187,11 +176,6 @@ import {
|
|||||||
|
|
||||||
const settingTypeOptions = SETTING_TYPE_OPTIONS
|
const settingTypeOptions = SETTING_TYPE_OPTIONS
|
||||||
|
|
||||||
const statusFilterOptions = [
|
|
||||||
{ value: 'active', label: 'فعال' },
|
|
||||||
{ value: 'inactive', label: 'غیرفعال' }
|
|
||||||
]
|
|
||||||
|
|
||||||
const typeSelectOptions = SETTING_TYPE_OPTIONS.map((t) => ({
|
const typeSelectOptions = SETTING_TYPE_OPTIONS.map((t) => ({
|
||||||
value: t.key,
|
value: t.key,
|
||||||
label: t.value
|
label: t.value
|
||||||
@@ -202,11 +186,6 @@ const drafts = ref<Record<number, string>>({})
|
|||||||
const addConfigVisible = ref(false)
|
const addConfigVisible = ref(false)
|
||||||
const search = ref('')
|
const search = ref('')
|
||||||
const filterType = ref<string | undefined>()
|
const filterType = ref<string | undefined>()
|
||||||
const filterActive = ref<string | undefined>()
|
|
||||||
|
|
||||||
function isRowActive(row: Const): boolean {
|
|
||||||
return row.is_active !== false && row.is_active !== 0
|
|
||||||
}
|
|
||||||
|
|
||||||
const savingIds = ref<Set<number>>(new Set())
|
const savingIds = ref<Set<number>>(new Set())
|
||||||
const savedFlashIds = ref<Set<number>>(new Set())
|
const savedFlashIds = ref<Set<number>>(new Set())
|
||||||
@@ -249,8 +228,6 @@ function isEditable(row: Const): boolean {
|
|||||||
|
|
||||||
function matchesFilter(row: Const): boolean {
|
function matchesFilter(row: Const): boolean {
|
||||||
const q = search.value.trim().toLowerCase()
|
const q = search.value.trim().toLowerCase()
|
||||||
if (filterActive.value === 'active' && !isRowActive(row)) return false
|
|
||||||
if (filterActive.value === 'inactive' && isRowActive(row)) return false
|
|
||||||
if (filterType.value) {
|
if (filterType.value) {
|
||||||
const t = resolveSettingFieldType(row)
|
const t = resolveSettingFieldType(row)
|
||||||
if (t !== filterType.value) return false
|
if (t !== filterType.value) return false
|
||||||
@@ -297,16 +274,29 @@ const SETTING_GROUPS: Array<{
|
|||||||
id: 'general',
|
id: 'general',
|
||||||
title: 'عمومی و اطلاعات اپ',
|
title: 'عمومی و اطلاعات اپ',
|
||||||
icon: 'vuesax-linear:home-2',
|
icon: 'vuesax-linear:home-2',
|
||||||
match: (k) => /^(name_|app_|admin_|server_|bace_|key$|color_|logo_|title_pwa|title_fav|metal_type|unit$)/.test(k)
|
match: (k) => /^(name_fa|app_url|admin_url|metal_type|unit$|open_register|auto_active_customer|is_store)/.test(k)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'chart',
|
||||||
|
title: 'نمودار قیمت',
|
||||||
|
icon: 'vuesax-linear:chart-2',
|
||||||
|
match: (k) => /^chart_/.test(k)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'pricing',
|
id: 'pricing',
|
||||||
title: 'قیمتگذاری و کارمزدها',
|
title: 'قیمتگذاری و تابلو',
|
||||||
icon: 'vuesax-linear:dollar-circle',
|
icon: 'vuesax-linear:dollar-circle',
|
||||||
match: (k) =>
|
match: (k) =>
|
||||||
/^(fee_|buy_fee|sell_fee|min_|referral_|gift_for|price_provider|zaarbox_api_url|chart_id)/.test(k) ||
|
/^(fee_|buy_fee|sell_fee|min_|price_provider|zaarbox_|main_product$|show_last_price|close_price|startDay|endDay)/.test(k) ||
|
||||||
k.includes('charge_percentage')
|
k.includes('charge_percentage')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'referral',
|
||||||
|
title: 'معرف، پاداش و ثبتنام',
|
||||||
|
icon: 'vuesax-linear:people',
|
||||||
|
match: (k) =>
|
||||||
|
/^(referral_|gift_for|register_referral|register_jibit|has_page_earn_money|jibit_verification)/.test(k)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'montzar',
|
id: 'montzar',
|
||||||
title: 'سرویس همکار (montzar)',
|
title: 'سرویس همکار (montzar)',
|
||||||
@@ -315,22 +305,22 @@ const SETTING_GROUPS: Array<{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'features',
|
id: 'features',
|
||||||
title: 'ویژگیها و صفحات',
|
title: 'ویژگیها و اعلانها',
|
||||||
icon: 'vuesax-linear:menu',
|
icon: 'vuesax-linear:menu',
|
||||||
match: (k) =>
|
match: (k) => /^(charge_notif|group_sms|main_wallet_products|price_from_nasiryan)/.test(k)
|
||||||
/^(has_page_|has_chat|display_montzar|close_price|startDay|endDay|auto_active|open_register|charge_notif|group_sms|show_last_price|product_update|product_auto_close|is_store|store_status)/.test(k)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'integrations',
|
id: 'integrations',
|
||||||
title: 'اتصالات خارجی و درگاهها',
|
title: 'اتصالات خارجی و درگاهها',
|
||||||
icon: 'vuesax-linear:card',
|
icon: 'vuesax-linear:card',
|
||||||
match: (k) => /^(tahesab_|payping_|jibit_|telegram_|bale_|liman_|support_messenger|external_order|profit_gold)/.test(k)
|
match: (k) =>
|
||||||
|
/^(tahesab_|payping_|telegram_|bale_|support_messenger|external_order|profit_gold|node_)/.test(k)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'misc',
|
id: 'misc',
|
||||||
title: 'سایر تنظیمات (متفرقه)',
|
title: 'سایر تنظیمات',
|
||||||
icon: 'vuesax-linear:more-circle',
|
icon: 'vuesax-linear:more-circle',
|
||||||
match: () => true // catch-all
|
match: () => true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -434,12 +424,6 @@ function onValueChange(row: Const, value: string) {
|
|||||||
scheduleSave(row)
|
scheduleSave(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onActiveChange(row: Const, isActive: boolean) {
|
|
||||||
if (row.id == null) return
|
|
||||||
if (isRowActive(row) === isActive) return
|
|
||||||
await persistRow(row, { isActive })
|
|
||||||
}
|
|
||||||
|
|
||||||
function scheduleSave(row: Const) {
|
function scheduleSave(row: Const) {
|
||||||
const id = row.id!
|
const id = row.id!
|
||||||
if (!debouncedSave.has(id)) {
|
if (!debouncedSave.has(id)) {
|
||||||
@@ -457,21 +441,18 @@ async function persistRowValue(id: number) {
|
|||||||
await saveSettingRow(row)
|
await saveSettingRow(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function persistRow(row: Const, overrides?: { isActive?: boolean }) {
|
async function persistRow(row: Const) {
|
||||||
await saveSettingRow(row, overrides)
|
await saveSettingRow(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveSettingRow(row: Const, overrides?: { isActive?: boolean }) {
|
async function saveSettingRow(row: Const) {
|
||||||
if (row.id == null) return
|
if (row.id == null) return
|
||||||
const id = row.id
|
const id = row.id
|
||||||
|
|
||||||
const type = resolveSettingFieldType(row)
|
const type = resolveSettingFieldType(row)
|
||||||
const raw = drafts.value[id] ?? row.value ?? ''
|
const raw = drafts.value[id] ?? row.value ?? ''
|
||||||
const value = formatValueForSave(raw, type)
|
const value = formatValueForSave(raw, type, row.key)
|
||||||
const isActive =
|
if (value === (row.value ?? '')) return
|
||||||
overrides?.isActive !== undefined ? overrides.isActive : isRowActive(row)
|
|
||||||
|
|
||||||
if (overrides?.isActive === undefined && value === (row.value ?? '')) return
|
|
||||||
|
|
||||||
savingIds.value.add(id)
|
savingIds.value.add(id)
|
||||||
savedFlashIds.value.delete(id)
|
savedFlashIds.value.delete(id)
|
||||||
@@ -482,12 +463,12 @@ async function saveSettingRow(row: Const, overrides?: { isActive?: boolean }) {
|
|||||||
key: row.key,
|
key: row.key,
|
||||||
value,
|
value,
|
||||||
description: row.description ?? '',
|
description: row.description ?? '',
|
||||||
is_active: isActive,
|
is_active: true,
|
||||||
title_fa: row.title_fa ?? '',
|
title_fa: row.title_fa ?? '',
|
||||||
type
|
type
|
||||||
})
|
})
|
||||||
row.value = value
|
row.value = value
|
||||||
row.is_active = isActive
|
row.is_active = true
|
||||||
drafts.value[id] = value
|
drafts.value[id] = value
|
||||||
if (row.key === 'is_store') {
|
if (row.key === 'is_store') {
|
||||||
setStoreModeFromSettings(isSwitchOn(value))
|
setStoreModeFromSettings(isSwitchOn(value))
|
||||||
@@ -536,7 +517,7 @@ async function ok() {
|
|||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
key: form.value.key,
|
key: form.value.key,
|
||||||
value: formatValueForSave(form.value.value ?? '', type),
|
value: formatValueForSave(form.value.value ?? '', type, form.value.key),
|
||||||
description,
|
description,
|
||||||
is_active: form.value.is_active,
|
is_active: form.value.is_active,
|
||||||
title_fa: form.value.title_fa ?? '',
|
title_fa: form.value.title_fa ?? '',
|
||||||
|
|||||||
@@ -3,18 +3,21 @@ import { isStoreApp } from '@/utils/config'
|
|||||||
|
|
||||||
export type SettingFieldType = 'text' | 'number' | 'switch' | 'select' | 'longtext'
|
export type SettingFieldType = 'text' | 'number' | 'switch' | 'select' | 'longtext'
|
||||||
|
|
||||||
/** کلیدهای legacy — در پنل نمایش داده نمیشوند (جایگزین: price_provider) */
|
/** کلیدهای legacy — در پنل نمایش داده نمیشوند */
|
||||||
export const HIDDEN_LEGACY_SETTING_KEYS = new Set([
|
export const HIDDEN_LEGACY_SETTING_KEYS = new Set([
|
||||||
'main_product_price',
|
'main_product_price',
|
||||||
'montzar_price',
|
'montzar_price',
|
||||||
'etehadie_api_url',
|
'etehadie_api_url',
|
||||||
'goldir_price',
|
'goldir_price',
|
||||||
'price_primary_source',
|
'price_primary_source',
|
||||||
'price_method'
|
'price_method',
|
||||||
|
'montzar_last_product_log',
|
||||||
|
'montzar_order_response',
|
||||||
|
'service_charge_percentage'
|
||||||
])
|
])
|
||||||
|
|
||||||
/** فقط وقتی is_store فعال است در پنل دیده میشوند */
|
/** فقط وقتی is_store فعال است در پنل دیده میشوند */
|
||||||
export const STORE_ONLY_SETTING_KEYS = new Set(['store_status'])
|
export const STORE_ONLY_SETTING_KEYS = new Set(['store_status', 'price_from_nasiryan'])
|
||||||
|
|
||||||
export function isSettingVisible(key: string | undefined | null): boolean {
|
export function isSettingVisible(key: string | undefined | null): boolean {
|
||||||
const k = (key || '').trim()
|
const k = (key || '').trim()
|
||||||
@@ -33,8 +36,9 @@ export const SETTING_TYPE_OPTIONS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const KEY_TYPE_FALLBACK: Record<string, SettingFieldType> = {
|
const KEY_TYPE_FALLBACK: Record<string, SettingFieldType> = {
|
||||||
|
chart_use_local_data: 'switch',
|
||||||
price_provider: 'select',
|
price_provider: 'select',
|
||||||
referral_id: 'select',
|
referral_id: 'switch',
|
||||||
payping_gateway_name: 'select',
|
payping_gateway_name: 'select',
|
||||||
text_rule: 'longtext',
|
text_rule: 'longtext',
|
||||||
text_about: 'longtext',
|
text_about: 'longtext',
|
||||||
@@ -43,66 +47,43 @@ const KEY_TYPE_FALLBACK: Record<string, SettingFieldType> = {
|
|||||||
buy_fee: 'number',
|
buy_fee: 'number',
|
||||||
sell_fee: 'number',
|
sell_fee: 'number',
|
||||||
fee_cash: 'number',
|
fee_cash: 'number',
|
||||||
|
fee_free: 'number',
|
||||||
min_withdraw: 'number',
|
min_withdraw: 'number',
|
||||||
min_first_buy: 'number',
|
min_first_buy: 'number',
|
||||||
minimum_sell: 'number',
|
minimum_sell: 'number',
|
||||||
gift_for_referrer: 'number',
|
gift_for_referrer: 'number',
|
||||||
gift_for_referral: 'number',
|
gift_for_referral: 'number',
|
||||||
referral_bonus_amount: 'text',
|
referral_bonus_amount: 'text',
|
||||||
referral_count_for_gift: 'number',
|
|
||||||
montzar_product_id: 'number',
|
montzar_product_id: 'number',
|
||||||
balance_report_exclude_customer_id: 'number',
|
balance_report_exclude_customer_id: 'number',
|
||||||
jibit_verification: 'switch',
|
jibit_verification: 'switch',
|
||||||
register_jibit_verify: 'switch',
|
register_jibit_verify: 'switch',
|
||||||
|
register_referral_sms_to_referrer: 'switch',
|
||||||
|
register_referral_bonus_message: 'switch',
|
||||||
open_register: 'switch',
|
open_register: 'switch',
|
||||||
auto_active_customer: 'switch',
|
auto_active_customer: 'switch',
|
||||||
close_price: 'switch',
|
close_price: 'switch',
|
||||||
montzar_price: 'switch',
|
|
||||||
main_product_price: 'switch',
|
|
||||||
show_last_price: 'switch',
|
show_last_price: 'switch',
|
||||||
send_to_tahesab: 'switch',
|
send_to_tahesab: 'switch',
|
||||||
profit_gold_api_enabled: 'switch',
|
profit_gold_api_enabled: 'switch',
|
||||||
payping_round_toman: 'switch',
|
payping_round_toman: 'switch',
|
||||||
payping_strict_failed_withdraw: 'switch',
|
payping_strict_failed_withdraw: 'switch',
|
||||||
has_page_earn_money: 'switch',
|
has_page_earn_money: 'switch',
|
||||||
has_page_fund: 'switch',
|
|
||||||
has_page_permission: 'switch',
|
|
||||||
has_page_price_trigger: 'switch',
|
|
||||||
has_page_physical_gold: 'switch',
|
|
||||||
has_chat: 'switch',
|
|
||||||
charge_notif: 'switch',
|
charge_notif: 'switch',
|
||||||
group_sms: 'switch',
|
group_sms: 'switch',
|
||||||
display_montzar: 'switch',
|
|
||||||
credit: 'switch',
|
|
||||||
credit_daily: 'switch',
|
|
||||||
credit_error_in_orders: 'switch',
|
|
||||||
credit_rial_dynamic: 'switch',
|
|
||||||
open_closed_messages: 'switch',
|
|
||||||
new_style_create_order: 'switch',
|
|
||||||
new_style_admin_layout: 'switch',
|
|
||||||
register: 'switch',
|
|
||||||
sharh: 'switch',
|
|
||||||
multi_source: 'switch',
|
|
||||||
get_orders_pdf: 'switch',
|
|
||||||
get_orders_excel: 'switch',
|
|
||||||
auto_order_buy_sell: 'switch',
|
|
||||||
invite_link: 'switch',
|
|
||||||
montzar_closed: 'switch',
|
montzar_closed: 'switch',
|
||||||
montzar_open_buy: 'switch',
|
montzar_open_buy: 'switch',
|
||||||
montzar_open_sell: 'switch',
|
montzar_open_sell: 'switch',
|
||||||
product_update_price_on_list: 'switch',
|
|
||||||
product_auto_close_open_on_list: 'switch',
|
|
||||||
payping_fin_sync_enabled: 'switch',
|
payping_fin_sync_enabled: 'switch',
|
||||||
external_order_api_enabled: 'switch',
|
external_order_api_enabled: 'switch',
|
||||||
is_store: 'switch',
|
is_store: 'switch',
|
||||||
store_status: 'select',
|
price_from_nasiryan: 'switch',
|
||||||
credit_rial_price_unit: 'select',
|
|
||||||
zaarbox_price_key: 'select',
|
|
||||||
node_client_id: 'text',
|
node_client_id: 'text',
|
||||||
node_shared_secret: 'text',
|
node_shared_secret: 'text',
|
||||||
support_messenger_id: 'text',
|
support_messenger_id: 'text',
|
||||||
support_messenger_text: 'text',
|
support_messenger_text: 'text',
|
||||||
support_messenger: 'select'
|
support_messenger: 'select',
|
||||||
|
zaarbox_price_key: 'select'
|
||||||
}
|
}
|
||||||
|
|
||||||
const SELECT_LABELS_BY_KEY: Record<string, Record<string, string>> = {
|
const SELECT_LABELS_BY_KEY: Record<string, Record<string, string>> = {
|
||||||
@@ -118,14 +99,22 @@ const SELECT_LABELS_BY_KEY: Record<string, Record<string, string>> = {
|
|||||||
|
|
||||||
const SELECT_OPTIONS_BY_KEY: Record<string, string> = {
|
const SELECT_OPTIONS_BY_KEY: Record<string, string> = {
|
||||||
price_provider: 'montzar,zaarbox,main_product',
|
price_provider: 'montzar,zaarbox,main_product',
|
||||||
referral_id: 'required,',
|
|
||||||
payping_gateway_name: 'sepal,',
|
payping_gateway_name: 'sepal,',
|
||||||
credit_rial_price_unit: 'gram,mesghal,unit',
|
|
||||||
store_status: 'باز,بسته',
|
|
||||||
unit: 'گرم,کیلوگرم',
|
unit: 'گرم,کیلوگرم',
|
||||||
support_messenger: 'bale,telegram,whatsapp,rubika,eitaa,soroush'
|
support_messenger: 'bale,telegram,whatsapp,rubika,eitaa,soroush'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** سوییچهایی که مقدار required/خالی دارند (نه true/false) */
|
||||||
|
export const REFERRAL_REQUIRED_KEY = 'referral_id'
|
||||||
|
|
||||||
|
export function isReferralRequired(value: string | undefined): boolean {
|
||||||
|
return (value ?? '').trim().toLowerCase() === 'required'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function referralRequiredToString(checked: boolean): string {
|
||||||
|
return checked ? 'required' : ''
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveSettingFieldType(row: Pick<Const, 'key' | 'type'>): SettingFieldType {
|
export function resolveSettingFieldType(row: Pick<Const, 'key' | 'type'>): SettingFieldType {
|
||||||
const raw = (row.type || '').trim().toLowerCase()
|
const raw = (row.type || '').trim().toLowerCase()
|
||||||
if (['text', 'number', 'switch', 'select', 'longtext'].includes(raw)) {
|
if (['text', 'number', 'switch', 'select', 'longtext'].includes(raw)) {
|
||||||
@@ -169,9 +158,27 @@ export function switchToString(checked: boolean): string {
|
|||||||
return checked ? 'true' : 'false'
|
return checked ? 'true' : 'false'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatValueForSave(value: string, type: SettingFieldType): string {
|
export function resolveSwitchChecked(key: string | undefined, value: string | undefined): boolean {
|
||||||
|
if (key === REFERRAL_REQUIRED_KEY) {
|
||||||
|
return isReferralRequired(value)
|
||||||
|
}
|
||||||
|
return isSwitchOn(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function switchValueFromChecked(key: string | undefined, checked: boolean): string {
|
||||||
|
if (key === REFERRAL_REQUIRED_KEY) {
|
||||||
|
return referralRequiredToString(checked)
|
||||||
|
}
|
||||||
|
return switchToString(checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatValueForSave(
|
||||||
|
value: string,
|
||||||
|
type: SettingFieldType,
|
||||||
|
key?: string
|
||||||
|
): string {
|
||||||
if (type === 'switch') {
|
if (type === 'switch') {
|
||||||
return switchToString(isSwitchOn(value))
|
return switchValueFromChecked(key, isSwitchOn(value) || (key === REFERRAL_REQUIRED_KEY && isReferralRequired(value)))
|
||||||
}
|
}
|
||||||
if (type === 'number') {
|
if (type === 'number') {
|
||||||
return value === '' ? '0' : String(value).replace(/,/g, '')
|
return value === '' ? '0' : String(value).replace(/,/g, '')
|
||||||
@@ -179,6 +186,16 @@ export function formatValueForSave(value: string, type: SettingFieldType): strin
|
|||||||
return value ?? ''
|
return value ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function switchLabels(key: string | undefined): { on: string; off: string } {
|
||||||
|
if (key === REFERRAL_REQUIRED_KEY) {
|
||||||
|
return { on: 'اجباری', off: 'اختیاری' }
|
||||||
|
}
|
||||||
|
if (key === 'chart_use_local_data') {
|
||||||
|
return { on: 'داده خود سایت', off: 'Zaarbox' }
|
||||||
|
}
|
||||||
|
return { on: 'فعال', off: 'غیرفعال' }
|
||||||
|
}
|
||||||
|
|
||||||
export function helpText(row: Const): string {
|
export function helpText(row: Const): string {
|
||||||
const desc = (row.description || '').trim()
|
const desc = (row.description || '').trim()
|
||||||
if (!desc) return ''
|
if (!desc) return ''
|
||||||
|
|||||||
@@ -603,7 +603,7 @@ const resetData = () => {
|
|||||||
form.value = {
|
form.value = {
|
||||||
fname: '',
|
fname: '',
|
||||||
lname: '',
|
lname: '',
|
||||||
active: false,
|
active: true,
|
||||||
avatar_id: undefined,
|
avatar_id: undefined,
|
||||||
sts: undefined,
|
sts: undefined,
|
||||||
id: undefined,
|
id: undefined,
|
||||||
|
|||||||
@@ -11,29 +11,30 @@
|
|||||||
@refresh="reload"
|
@refresh="reload"
|
||||||
@load-more="loadMore"
|
@load-more="loadMore"
|
||||||
>
|
>
|
||||||
<PriceMobileListItem v-for="item in items" :key="item.id" :item="item" @open="openSheet" />
|
<PriceMobileListItem v-for="item in items" :key="item.id" :item="item" @open="openDetail" />
|
||||||
</MobileCardList>
|
</MobileCardList>
|
||||||
|
|
||||||
<PriceMobileDetailSheet v-model:visible="sheetVisible" :item="selectedItem" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getPriceTable } from '@/api/price'
|
import { getPriceSyncLogTable } from '@/api/price'
|
||||||
import type { Price } from '@/model/price'
|
import type { PriceSyncLog } from '@/model/priceSyncLog'
|
||||||
import MobileCardList from '@/components/mobile/MobileCardList.vue'
|
import MobileCardList from '@/components/mobile/MobileCardList.vue'
|
||||||
import { useMobileDetailSheet } from '@/components/mobile/composables/useMobileDetailSheet'
|
|
||||||
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
|
import { useMobileTableList } from '@/components/mobile/composables/useMobileTableList'
|
||||||
import PriceMobileDetailSheet from './PriceMobileDetailSheet.vue'
|
|
||||||
import PriceMobileListItem from './PriceMobileListItem.vue'
|
import PriceMobileListItem from './PriceMobileListItem.vue'
|
||||||
|
|
||||||
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<Price>()
|
const emit = defineEmits<{
|
||||||
|
(e: 'detail', item: PriceSyncLog): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<Price>({
|
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<PriceSyncLog>({
|
||||||
fetchApi: getPriceTable,
|
fetchApi: getPriceSyncLogTable
|
||||||
onFetched: (rows) => syncSelectedItem(rows)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function openDetail(item: PriceSyncLog) {
|
||||||
|
emit('detail', item)
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => reload())
|
onMounted(() => reload())
|
||||||
|
|
||||||
defineExpose({ fetch, openSheet })
|
defineExpose({ fetch, openDetail })
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
<MobileCard clickable @click="$emit('open', item)">
|
<MobileCard clickable @click="$emit('open', item)">
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<div class="flex items-center justify-between gap-2">
|
<div class="flex items-center justify-between gap-2">
|
||||||
<span class="truncate text-sm font-700 text-gray-900">{{ userName }}</span>
|
<span class="truncate text-sm font-700 text-gray-900">{{ sourceLabel }}</span>
|
||||||
<span class="shrink-0 text-xs text-gray-400">{{ dateLabel }}</span>
|
<span class="shrink-0 text-xs text-gray-400">{{ dateLabel }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-between gap-2 text-xs">
|
<div class="flex items-center justify-between gap-2 text-xs">
|
||||||
<span class="truncate text-gray-600">#{{ item.id ?? '-' }}</span>
|
<span class="truncate text-gray-600">{{ item.products_updated }} محصول</span>
|
||||||
<span class="shrink-0 font-700 text-green-700">{{ priceLabel }}</span>
|
<span class="shrink-0 font-700 text-green-700">{{ priceLabel }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -15,20 +15,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Price } from '@/model/price'
|
import type { PriceSyncLog } from '@/model/priceSyncLog'
|
||||||
import MobileCard from '@/components/mobile/MobileCard.vue'
|
import MobileCard from '@/components/mobile/MobileCard.vue'
|
||||||
import { toJalaliNoTime } from '@core/utils/useDataTime'
|
import { toJalaliNoTime } from '@core/utils/useDataTime'
|
||||||
import { usePrice } from '@core/utils/usePrice'
|
import { usePrice } from '@core/utils/usePrice'
|
||||||
|
|
||||||
|
const SOURCE_LABELS: Record<string, string> = {
|
||||||
|
zaarbox: 'تابلو Zaarbox',
|
||||||
|
legacy: 'فید قدیمی'
|
||||||
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
item: Price
|
item: PriceSyncLog
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
(e: 'open', item: Price): void
|
(e: 'open', item: PriceSyncLog): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const userName = computed(() => props.item.user?.name || '-')
|
const sourceLabel = computed(() => SOURCE_LABELS[props.item.source] ?? props.item.source)
|
||||||
const dateLabel = computed(() => (props.item.created_at ? toJalaliNoTime(props.item.created_at) : '-'))
|
const dateLabel = computed(() => (props.item.created_at ? toJalaliNoTime(props.item.created_at) : '-'))
|
||||||
const priceLabel = computed(() => usePrice(props.item.price ?? 0, true, ''))
|
const priceLabel = computed(() =>
|
||||||
|
props.item.liquid_gold_price > 0 ? usePrice(props.item.liquid_gold_price, true, '') : '—'
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
|
<div class="h-full min-h-0 min-w-0 max-w-full overflow-hidden">
|
||||||
<BasicShowContext :title="isStore ? 'تاریخچه همگامسازی قیمت' : 'تاریخچه قیمت'">
|
<BasicShowContext title="تاریخچه قیمت">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
v-if="isStore"
|
|
||||||
type="primary"
|
type="primary"
|
||||||
size="small"
|
size="small"
|
||||||
class="flex shrink-0 md:hidden"
|
class="flex shrink-0 md:hidden"
|
||||||
@@ -13,23 +12,12 @@
|
|||||||
<div>بهروزرسانی قیمت</div>
|
<div>بهروزرسانی قیمت</div>
|
||||||
<BaseIcon icon="vuesax-linear:refresh" Size="16px" />
|
<BaseIcon icon="vuesax-linear:refresh" Size="16px" />
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
<BaseButton
|
|
||||||
v-else
|
|
||||||
type="primary"
|
|
||||||
size="small"
|
|
||||||
class="flex shrink-0 md:hidden"
|
|
||||||
@click="emits('add')"
|
|
||||||
>
|
|
||||||
<div>قیمت گذاری</div>
|
|
||||||
<BaseIcon icon="vuesax-linear:dollar-circle" Size="16px" />
|
|
||||||
</BaseButton>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="flex h-full min-h-0 flex-col">
|
<div class="flex h-full min-h-0 flex-col">
|
||||||
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
|
<BaseTable v-if="!isMobile" class="h-full min-h-0 min-w-0" v-bind="tblProps" @register="submit" ref="tbl">
|
||||||
<template #extraHeader>
|
<template #extraHeader>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
v-if="isStore"
|
|
||||||
type="primary"
|
type="primary"
|
||||||
size="small"
|
size="small"
|
||||||
class="flex"
|
class="flex"
|
||||||
@@ -39,10 +27,6 @@
|
|||||||
<div>بهروزرسانی قیمت</div>
|
<div>بهروزرسانی قیمت</div>
|
||||||
<BaseIcon icon="vuesax-linear:refresh" Size="16px" />
|
<BaseIcon icon="vuesax-linear:refresh" Size="16px" />
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
<BaseButton v-else type="primary" size="small" class="flex" @click="emits('add')">
|
|
||||||
<div>قیمت گذاری</div>
|
|
||||||
<BaseIcon icon="vuesax-linear:dollar-circle" Size="16px" />
|
|
||||||
</BaseButton>
|
|
||||||
</template>
|
</template>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<div class="flex justify-center gap-2">
|
<div class="flex justify-center gap-2">
|
||||||
@@ -56,8 +40,12 @@
|
|||||||
</template>
|
</template>
|
||||||
</BaseTable>
|
</BaseTable>
|
||||||
|
|
||||||
<PriceMobileCardList v-else-if="!isStore" ref="mobileListRef" class="min-h-0 flex-1" />
|
<PriceMobileCardList
|
||||||
<div v-else class="p-4 text-center text-gray-500">در موبایل از نسخه دسکتاپ استفاده کنید</div>
|
v-else
|
||||||
|
ref="mobileListRef"
|
||||||
|
class="min-h-0 flex-1"
|
||||||
|
@detail="emits('detail', $event)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</BasicShowContext>
|
</BasicShowContext>
|
||||||
</div>
|
</div>
|
||||||
@@ -67,30 +55,26 @@
|
|||||||
import type { TableInput } from '@core/type/baseTable'
|
import type { TableInput } from '@core/type/baseTable'
|
||||||
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
|
import useBaseTable from '@core/components/BaseTableOld/hooks/useBaseTable'
|
||||||
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||||
import { cols } from '../hooks/priceColumns'
|
|
||||||
import { storePriceColumns } from '../hooks/storePriceColumns'
|
import { storePriceColumns } from '../hooks/storePriceColumns'
|
||||||
import { usePrefix } from '@core/composable/usePrefix'
|
import { usePrefix } from '@core/composable/usePrefix'
|
||||||
import { getPriceSyncLogTable, getPriceTable } from '@/api/price'
|
import { getPriceSyncLogTable } from '@/api/price'
|
||||||
import { isStoreApp } from '@/utils/config'
|
|
||||||
import PriceMobileCardList from './PriceMobileCardList.vue'
|
import PriceMobileCardList from './PriceMobileCardList.vue'
|
||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import { computed } from 'vue'
|
import type { PriceSyncLog } from '@/model/priceSyncLog'
|
||||||
|
|
||||||
const props = defineProps<{ syncing?: boolean }>()
|
defineProps<{ syncing?: boolean }>()
|
||||||
|
|
||||||
const [submit, reload] = useBaseTable()
|
const [submit, reload] = useBaseTable()
|
||||||
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
const mobileListRef = ref<null | { fetch: () => Promise<void> }>(null)
|
||||||
|
|
||||||
const emits = defineEmits(['delete', 'edit', 'add', 'info', 'detail', 'sync'])
|
const emits = defineEmits(['detail', 'sync'])
|
||||||
|
|
||||||
const isStore = computed(() => isStoreApp())
|
|
||||||
const { prefixCls } = usePrefix('pophoverstatus')
|
const { prefixCls } = usePrefix('pophoverstatus')
|
||||||
|
|
||||||
const tblProps = computed(() => ({
|
const tblProps = {
|
||||||
columns: isStore.value ? storePriceColumns : cols,
|
columns: storePriceColumns,
|
||||||
api: (params: TableInput) =>
|
api: (params: TableInput) => getPriceSyncLogTable(params)
|
||||||
isStore.value ? getPriceSyncLogTable(params) : getPriceTable(params)
|
}
|
||||||
}))
|
|
||||||
|
|
||||||
async function fetch() {
|
async function fetch() {
|
||||||
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
|
await Promise.allSettled([reload(), mobileListRef.value?.fetch?.() as Promise<void>])
|
||||||
|
|||||||
@@ -6,74 +6,28 @@
|
|||||||
ref="table"
|
ref="table"
|
||||||
class="h-full w-full"
|
class="h-full w-full"
|
||||||
:syncing="syncing"
|
:syncing="syncing"
|
||||||
@add="add"
|
|
||||||
@detail="openDetail"
|
@detail="openDetail"
|
||||||
@sync="handleSync"
|
@sync="handleSync"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</BasicMainCard>
|
</BasicMainCard>
|
||||||
|
|
||||||
<template v-if="!isStore">
|
|
||||||
<BasicModal
|
|
||||||
v-model:visible="priceConfirmVisible"
|
|
||||||
width="25rem"
|
|
||||||
@ok="ok"
|
|
||||||
:confirm-loading="loading"
|
|
||||||
@close="priceConfirmVisible = false"
|
|
||||||
>
|
|
||||||
<div class="flex items-center gap-2 font-bold">
|
|
||||||
<BaseIcon icon="vuesax-linear:info-circle" color="orange" />
|
|
||||||
<div>آیا مطمئن به تغییر قیمت می باشید؟</div>
|
|
||||||
</div>
|
|
||||||
</BasicModal>
|
|
||||||
|
|
||||||
<BasicModal
|
|
||||||
v-model:visible="addPriceVisible"
|
|
||||||
width="25rem"
|
|
||||||
title="قیمت گذاری"
|
|
||||||
@ok="confirmPrice"
|
|
||||||
:loading="loading"
|
|
||||||
@close="addPriceVisible = false"
|
|
||||||
>
|
|
||||||
<a-form :model="form" ref="formRef" autocomplete="off">
|
|
||||||
<div class="gap-4">
|
|
||||||
<a-form-item label="قیمت جدید" name="price" :rules="inputRules()">
|
|
||||||
<BasicInput v-model:value="form.price" type="price" addon-before="ریال" />
|
|
||||||
</a-form-item>
|
|
||||||
<div v-show="form.price > 10">معادل {{ usePrice(form.price / 10, true, 'تومان') }}</div>
|
|
||||||
</div>
|
|
||||||
</a-form>
|
|
||||||
</BasicModal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<PriceSyncDetailModal v-model:visible="detailVisible" :log-id="selectedLogId" />
|
<PriceSyncDetailModal v-model:visible="detailVisible" :log-id="selectedLogId" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { inputRules } from '@/utils/inputRules'
|
import { ref } from 'vue'
|
||||||
import { computed, ref } from 'vue'
|
|
||||||
import tbl from './components/table.vue'
|
import tbl from './components/table.vue'
|
||||||
import type { FormInstance } from 'ant-design-vue'
|
|
||||||
import { message } from 'ant-design-vue/es'
|
import { message } from 'ant-design-vue/es'
|
||||||
import type { Price } from '@/model/price'
|
import { runPriceSync } from '@/api/price'
|
||||||
import { createPrice, runPriceSync } from '@/api/price'
|
|
||||||
import { usePrice } from '@core/utils/usePrice'
|
|
||||||
import { isStoreApp } from '@/utils/config'
|
|
||||||
import PriceSyncDetailModal from './components/PriceSyncDetailModal.vue'
|
import PriceSyncDetailModal from './components/PriceSyncDetailModal.vue'
|
||||||
import type { PriceSyncLog } from '@/model/priceSyncLog'
|
import type { PriceSyncLog } from '@/model/priceSyncLog'
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
const syncing = ref(false)
|
const syncing = ref(false)
|
||||||
const table = ref<null | { fetch: () => void }>(null)
|
const table = ref<null | { fetch: () => void }>(null)
|
||||||
const refreshTable = () => table.value?.fetch()
|
const refreshTable = () => table.value?.fetch()
|
||||||
|
|
||||||
const isStore = computed(() => isStoreApp())
|
|
||||||
|
|
||||||
const form = ref<Price>({})
|
|
||||||
const formRef = ref<FormInstance>()
|
|
||||||
const addPriceVisible = ref(false)
|
|
||||||
const priceConfirmVisible = ref(false)
|
|
||||||
const detailVisible = ref(false)
|
const detailVisible = ref(false)
|
||||||
const selectedLogId = ref<number | null>(null)
|
const selectedLogId = ref<number | null>(null)
|
||||||
|
|
||||||
@@ -95,31 +49,6 @@ async function handleSync() {
|
|||||||
syncing.value = false
|
syncing.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function add() {
|
|
||||||
addPriceVisible.value = true
|
|
||||||
form.value = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function confirmPrice() {
|
|
||||||
formRef.value?.validateFields().then(async () => {
|
|
||||||
priceConfirmVisible.value = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function ok() {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
await createPrice(form.value)
|
|
||||||
refreshTable()
|
|
||||||
priceConfirmVisible.value = false
|
|
||||||
addPriceVisible.value = false
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|||||||
@@ -80,12 +80,10 @@ import {
|
|||||||
} from '@/utils/file'
|
} from '@/utils/file'
|
||||||
import { normalizeProductOrderLimitForm, validateProductOrderLimitForm } from '@/utils/productOrderLimit'
|
import { normalizeProductOrderLimitForm, validateProductOrderLimitForm } from '@/utils/productOrderLimit'
|
||||||
import {
|
import {
|
||||||
buildCategorySelectItems,
|
|
||||||
isMeltedTradeGroup,
|
isMeltedTradeGroup,
|
||||||
PRODUCT_CATEGORY_ABSHODE,
|
|
||||||
resolveMeltedPriceSource
|
resolveMeltedPriceSource
|
||||||
} from '@/utils/productCategories'
|
} from '@/utils/productCategories'
|
||||||
import { getCategories } from '@/api/category'
|
import { syncAndLoadCategories } from '@/utils/categorySelect'
|
||||||
|
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
@@ -128,13 +126,8 @@ const metalItems: Array<Item> = [
|
|||||||
{ key: 2, value: 'نقره' },
|
{ key: 2, value: 'نقره' },
|
||||||
{ key: 3, value: 'مس' },
|
{ key: 3, value: 'مس' },
|
||||||
]
|
]
|
||||||
const storeCategorySelectItems = ref<Array<{ key: string; value: string }>>([])
|
const apiCategorySelectItems = ref<Array<{ key: string; value: string }>>([])
|
||||||
const categorySelectItems = computed(() => {
|
const categorySelectItems = computed(() => apiCategorySelectItems.value)
|
||||||
if (isStoreApp() && storeCategorySelectItems.value.length) {
|
|
||||||
return storeCategorySelectItems.value
|
|
||||||
}
|
|
||||||
return buildCategorySelectItems(metalItems)
|
|
||||||
})
|
|
||||||
const priceItems: Array<Item> = [
|
const priceItems: Array<Item> = [
|
||||||
{ key: 0, value: 'متغیر' },
|
{ key: 0, value: 'متغیر' },
|
||||||
{ key: 1, value: 'ثابت' }
|
{ key: 1, value: 'ثابت' }
|
||||||
@@ -197,25 +190,17 @@ watch(addUserVisible, (visible) => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
void refreshLivePrices()
|
void refreshLivePrices()
|
||||||
void loadStoreCategories()
|
void loadCategorySelectItems()
|
||||||
})
|
})
|
||||||
|
|
||||||
async function loadStoreCategories() {
|
async function loadCategorySelectItems() {
|
||||||
if (!isStoreApp()) return
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const rows = await getCategories({ select_only: true })
|
|
||||||
const metalHint = metalItems.map((item) => item.value).join('، ')
|
const metalHint = metalItems.map((item) => item.value).join('، ')
|
||||||
storeCategorySelectItems.value = rows.map((category) => ({
|
apiCategorySelectItems.value = await syncAndLoadCategories({ metalHint })
|
||||||
key: category.name,
|
|
||||||
value:
|
|
||||||
category.name === PRODUCT_CATEGORY_ABSHODE
|
|
||||||
? `${category.name} (${metalHint})`
|
|
||||||
: category.name
|
|
||||||
}))
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
storeCategorySelectItems.value = []
|
apiCategorySelectItems.value = []
|
||||||
|
message.error('خطا در دریافت دستهبندیها')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,6 +270,7 @@ async function setProductIndexImage(record: Record<string, any>, productId?: num
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function edit(record: Product) {
|
async function edit(record: Product) {
|
||||||
|
await loadCategorySelectItems()
|
||||||
addUserVisible.value = true
|
addUserVisible.value = true
|
||||||
editId = record.id
|
editId = record.id
|
||||||
let productData = cloneDeep(record) as Record<string, any>
|
let productData = cloneDeep(record) as Record<string, any>
|
||||||
@@ -333,6 +319,7 @@ async function add() {
|
|||||||
editId = undefined
|
editId = undefined
|
||||||
addUserVisible.value = true
|
addUserVisible.value = true
|
||||||
resetData()
|
resetData()
|
||||||
|
await loadCategorySelectItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetData() {
|
function resetData() {
|
||||||
|
|||||||
+2
-5
@@ -11,7 +11,7 @@ function applyAppModeMeta(route: RouteRecordRaw, appName: string | null) {
|
|||||||
const store = isStoreApp()
|
const store = isStoreApp()
|
||||||
|
|
||||||
if (route.name === 'pricing' && route.meta) {
|
if (route.name === 'pricing' && route.meta) {
|
||||||
route.meta.title = store ? 'تاریخچه قیمت' : 'قیمت گذاری'
|
route.meta.title = 'تاریخچه قیمت'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!route.children?.length) return
|
if (!route.children?.length) return
|
||||||
@@ -27,11 +27,8 @@ function applyAppModeMeta(route: RouteRecordRaw, appName: string | null) {
|
|||||||
if (child.name === 'banners' && child.meta) {
|
if (child.name === 'banners' && child.meta) {
|
||||||
child.meta.hideMenu = appName !== 'garoosi'
|
child.meta.hideMenu = appName !== 'garoosi'
|
||||||
}
|
}
|
||||||
if (child.name === 'productsMain_categories' && child.meta) {
|
|
||||||
child.meta.hideMenu = !store
|
|
||||||
}
|
|
||||||
if (child.name === 'pricingMain' && child.meta) {
|
if (child.name === 'pricingMain' && child.meta) {
|
||||||
child.meta.title = store ? 'تاریخچه قیمت' : 'قیمت گذاری'
|
child.meta.title = 'تاریخچه قیمت'
|
||||||
}
|
}
|
||||||
applyAppModeMeta(child, appName)
|
applyAppModeMeta(child, appName)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,8 +54,7 @@ const Products: RouteRecordRaw = {
|
|||||||
component: () => import('@/pages/menuPages/products/categories/index.vue'),
|
component: () => import('@/pages/menuPages/products/categories/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'vuesax-linear:category',
|
icon: 'vuesax-linear:category',
|
||||||
title: 'دستهبندیها',
|
title: 'دستهبندیها'
|
||||||
hideMenu: true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { getCategories, syncCategoriesFromProducts } from '@/api/category'
|
||||||
|
import type { Category } from '@/model/category'
|
||||||
|
import { PRODUCT_CATEGORY_ABSHODE } from '@/utils/productCategories'
|
||||||
|
|
||||||
|
export type CategorySelectItem = { key: string; value: string }
|
||||||
|
|
||||||
|
export function mapCategoriesToSelectItems(
|
||||||
|
rows: Category[],
|
||||||
|
metalHint = 'طلا، نقره، مس'
|
||||||
|
): CategorySelectItem[] {
|
||||||
|
return rows.map((category) => ({
|
||||||
|
key: category.name,
|
||||||
|
value:
|
||||||
|
category.name === PRODUCT_CATEGORY_ABSHODE
|
||||||
|
? `${category.name} (${metalHint})`
|
||||||
|
: category.name
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** همان منبع جدول دستهبندیها — قبل از خواندن، همگامسازی میکند */
|
||||||
|
export async function syncAndLoadCategories(options?: {
|
||||||
|
selectOnly?: boolean
|
||||||
|
metalHint?: string
|
||||||
|
}): Promise<CategorySelectItem[]> {
|
||||||
|
await syncCategoriesFromProducts()
|
||||||
|
|
||||||
|
const rows = await getCategories(
|
||||||
|
options?.selectOnly ? { select_only: true } : undefined
|
||||||
|
)
|
||||||
|
|
||||||
|
return mapCategoriesToSelectItems(rows, options?.metalHint)
|
||||||
|
}
|
||||||
@@ -7,6 +7,9 @@ export const UNIFIED_PRODUCT_CATEGORIES = [
|
|||||||
'گوی',
|
'گوی',
|
||||||
'سکه',
|
'سکه',
|
||||||
'سکه پارسیان',
|
'سکه پارسیان',
|
||||||
|
'شمش',
|
||||||
|
'شمش ونوس',
|
||||||
|
'شمش برنا',
|
||||||
'مس',
|
'مس',
|
||||||
'نقره',
|
'نقره',
|
||||||
] as const
|
] as const
|
||||||
|
|||||||
Reference in New Issue
Block a user