fix bugs
This commit is contained in:
+2
-2
@@ -2,7 +2,7 @@ import useAxios from '@core/utils/useAxios'
|
|||||||
import type { AddId, Customer } from '@/model/customer'
|
import type { AddId, Customer } from '@/model/customer'
|
||||||
import type { TableInput } from '@core/type/baseTable'
|
import type { TableInput } from '@core/type/baseTable'
|
||||||
import type { Accounting } from "@/model/accounting"
|
import type { Accounting } from "@/model/accounting"
|
||||||
import type { Balance, SetActiveTime } from "@/model/product";
|
import type { Balance, Product, SetActiveTime } from "@/model/product";
|
||||||
|
|
||||||
export const createProduct = async (obj: Customer) =>
|
export const createProduct = async (obj: Customer) =>
|
||||||
await useAxios.post<AddId>(`/admin/product`, obj)
|
await useAxios.post<AddId>(`/admin/product`, obj)
|
||||||
@@ -14,7 +14,7 @@ export const updateCustomer = async (id: number, obj: Customer) =>
|
|||||||
export const getCustomerTable = async (params: TableInput) =>
|
export const getCustomerTable = async (params: TableInput) =>
|
||||||
await useAxios.post<Array<Customer>>('/admin/product/table', params)
|
await useAxios.post<Array<Customer>>('/admin/product/table', params)
|
||||||
|
|
||||||
export const getCustomersList = async () => await useAxios.get<Customer[]>(`/admin/product/list`)
|
export const getCustomersList = async () => await useAxios.get<Product[]>(`/admin/product/list`)
|
||||||
|
|
||||||
export const changeProductActivation = async (id: number) =>
|
export const changeProductActivation = async (id: number) =>
|
||||||
await useAxios.get(`/admin/product/active/${id}`)
|
await useAxios.get(`/admin/product/active/${id}`)
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
<template>
|
||||||
|
<a-select
|
||||||
|
mode="multiple"
|
||||||
|
:value="selectedIds"
|
||||||
|
:disabled="disabled"
|
||||||
|
:loading="loading"
|
||||||
|
allow-clear
|
||||||
|
show-search
|
||||||
|
placeholder="انتخاب محصولات متصل به کیف اصلی"
|
||||||
|
class="w-full"
|
||||||
|
option-filter-prop="label"
|
||||||
|
@update:value="onChange"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="item in allOptions"
|
||||||
|
:key="String(item.id)"
|
||||||
|
:value="String(item.id)"
|
||||||
|
:label="`${item.name} ${item.id}`"
|
||||||
|
>
|
||||||
|
<div class="leading-snug">
|
||||||
|
<div>{{ item.name }}</div>
|
||||||
|
<div class="text-xs text-gray-400">شناسه: {{ item.id }}</div>
|
||||||
|
</div>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import { getCustomersList } from '@/api/product'
|
||||||
|
import type { Product } from '@/model/product'
|
||||||
|
import {
|
||||||
|
MAIN_WALLET_PRODUCTS_DEFAULT,
|
||||||
|
formatMainWalletProductIds,
|
||||||
|
parseMainWalletProductIds
|
||||||
|
} from './settingTypes'
|
||||||
|
|
||||||
|
type ProductOption = { id: number; name: string }
|
||||||
|
|
||||||
|
function productLabel(row: Pick<Product, 'id' | 'title'> & { name?: string }): string {
|
||||||
|
const label = (row.title || row.name || '').trim()
|
||||||
|
return label || `محصول #${row.id}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeProductList(response: unknown): Product[] {
|
||||||
|
if (Array.isArray(response)) {
|
||||||
|
return response as Product[]
|
||||||
|
}
|
||||||
|
if (response && typeof response === 'object' && Array.isArray((response as { data?: unknown }).data)) {
|
||||||
|
return (response as { data: Product[] }).data
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
modelValue: string
|
||||||
|
disabled?: boolean
|
||||||
|
}>(),
|
||||||
|
{ disabled: false }
|
||||||
|
)
|
||||||
|
|
||||||
|
const emit = defineEmits<{ 'update:value': [value: string] }>()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const products = ref<ProductOption[]>([])
|
||||||
|
|
||||||
|
const selectedIds = computed(() => {
|
||||||
|
const parsed = parseMainWalletProductIds(props.modelValue)
|
||||||
|
return parsed.length > 0 ? parsed : [MAIN_WALLET_PRODUCTS_DEFAULT]
|
||||||
|
})
|
||||||
|
|
||||||
|
const allOptions = computed(() => {
|
||||||
|
const known = new Set(products.value.map((p) => p.id))
|
||||||
|
const extras: ProductOption[] = []
|
||||||
|
|
||||||
|
for (const idStr of selectedIds.value) {
|
||||||
|
const id = Number(idStr)
|
||||||
|
if (!Number.isFinite(id) || id <= 0 || known.has(id)) continue
|
||||||
|
extras.push({ id, name: `محصول #${id}` })
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...extras, ...products.value]
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
void loadProducts()
|
||||||
|
})
|
||||||
|
|
||||||
|
async function loadProducts() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const response = await getCustomersList()
|
||||||
|
const rows = normalizeProductList(response)
|
||||||
|
products.value = rows
|
||||||
|
.map((row) => ({ id: row.id, name: productLabel(row) }))
|
||||||
|
.filter((row) => row.id > 0)
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name, 'fa'))
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
products.value = []
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange(val: string[] | undefined) {
|
||||||
|
emit('update:value', formatMainWalletProductIds(val ?? []))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -10,6 +10,13 @@
|
|||||||
<template #unCheckedChildren>{{ labels.off }}</template>
|
<template #unCheckedChildren>{{ labels.off }}</template>
|
||||||
</a-switch>
|
</a-switch>
|
||||||
|
|
||||||
|
<MainWalletProductsField
|
||||||
|
v-else-if="isMainWalletProducts"
|
||||||
|
:model-value="modelValue"
|
||||||
|
:disabled="disabled"
|
||||||
|
@update:value="emit('update:value', $event)"
|
||||||
|
/>
|
||||||
|
|
||||||
<ZaarboxPriceKeyField
|
<ZaarboxPriceKeyField
|
||||||
v-else-if="isZaarboxPriceKey"
|
v-else-if="isZaarboxPriceKey"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
@@ -86,6 +93,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import MainWalletProductsField from './MainWalletProductsField.vue'
|
||||||
import ZaarboxPriceKeyField from './ZaarboxPriceKeyField.vue'
|
import ZaarboxPriceKeyField from './ZaarboxPriceKeyField.vue'
|
||||||
import {
|
import {
|
||||||
parseSelectOptions,
|
parseSelectOptions,
|
||||||
@@ -121,6 +129,7 @@ const fieldType = computed<SettingFieldType>(() =>
|
|||||||
)
|
)
|
||||||
|
|
||||||
const isZaarboxPriceKey = computed(() => props.settingKey === 'zaarbox_price_key')
|
const isZaarboxPriceKey = computed(() => props.settingKey === 'zaarbox_price_key')
|
||||||
|
const isMainWalletProducts = computed(() => props.settingKey === 'main_wallet_products')
|
||||||
|
|
||||||
const selectItems = computed(() =>
|
const selectItems = computed(() =>
|
||||||
parseSelectOptions({
|
parseSelectOptions({
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export const SYSTEM_SETTING_GROUPS: SettingGroupDef[] = [
|
|||||||
title: 'منبع قیمت و تابلو',
|
title: 'منبع قیمت و تابلو',
|
||||||
icon: 'vuesax-linear:dollar-circle',
|
icon: 'vuesax-linear:dollar-circle',
|
||||||
match: (k) =>
|
match: (k) =>
|
||||||
/^(price_provider|zaarbox_|main_product$|default_vc_number)/.test(k) ||
|
/^(price_provider|zaarbox_|main_product$)/.test(k) ||
|
||||||
k.includes('charge_percentage')
|
k.includes('charge_percentage')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -145,7 +145,7 @@ export const SYSTEM_SETTING_GROUPS: SettingGroupDef[] = [
|
|||||||
id: 'infra',
|
id: 'infra',
|
||||||
title: 'زیرساخت',
|
title: 'زیرساخت',
|
||||||
icon: 'vuesax-linear:cpu',
|
icon: 'vuesax-linear:cpu',
|
||||||
match: (k) => /^(wss_enabled|sse_path|wss_path|wss_link|wss_trigger_path|profit_gold_api_enabled)/.test(k)
|
match: (k) => /^(wss_enabled|sse_path|profit_gold_api_enabled)/.test(k)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'misc',
|
id: 'misc',
|
||||||
|
|||||||
@@ -3,6 +3,43 @@ import { isStoreApp } from '@/utils/config'
|
|||||||
|
|
||||||
export type SettingFieldType = 'text' | 'number' | 'price' | 'switch' | 'select' | 'longtext' | 'time'
|
export type SettingFieldType = 'text' | 'number' | 'price' | 'switch' | 'select' | 'longtext' | 'time'
|
||||||
|
|
||||||
|
export const MAIN_WALLET_PRODUCTS_DEFAULT = '13'
|
||||||
|
|
||||||
|
/** پارس setting `main_wallet_products` — id با کاما یا JSON */
|
||||||
|
export function parseMainWalletProductIds(value: string | undefined | null): string[] {
|
||||||
|
const raw = (value ?? '').trim()
|
||||||
|
if (!raw) return []
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoded = JSON.parse(raw)
|
||||||
|
if (Array.isArray(decoded)) {
|
||||||
|
return decoded
|
||||||
|
.map((part) => String(part).trim())
|
||||||
|
.filter((part) => /^\d+$/.test(part))
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// not JSON — comma-separated
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw
|
||||||
|
.split(',')
|
||||||
|
.map((part) => part.trim())
|
||||||
|
.filter((part) => /^\d+$/.test(part))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatMainWalletProductIds(ids: Array<string | number>): string {
|
||||||
|
const unique = [...new Set(ids.map((id) => String(id).trim()).filter((id) => /^\d+$/.test(id)))]
|
||||||
|
if (unique.length === 0) {
|
||||||
|
return MAIN_WALLET_PRODUCTS_DEFAULT
|
||||||
|
}
|
||||||
|
return unique.join(',')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeMainWalletProductsValue(value: string | undefined | null): string {
|
||||||
|
const formatted = formatMainWalletProductIds(parseMainWalletProductIds(value))
|
||||||
|
return formatted || MAIN_WALLET_PRODUCTS_DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
/** کلیدهای legacy — در پنل نمایش داده نمیشوند */
|
/** کلیدهای legacy — در پنل نمایش داده نمیشوند */
|
||||||
export const HIDDEN_LEGACY_SETTING_KEYS = new Set([
|
export const HIDDEN_LEGACY_SETTING_KEYS = new Set([
|
||||||
'main_product_price',
|
'main_product_price',
|
||||||
@@ -56,7 +93,6 @@ const KEY_TYPE_FALLBACK: Record<string, SettingFieldType> = {
|
|||||||
fee_single: 'price',
|
fee_single: 'price',
|
||||||
buy_fee: 'price',
|
buy_fee: 'price',
|
||||||
sell_fee: 'price',
|
sell_fee: 'price',
|
||||||
fee_cash: 'price',
|
|
||||||
fee_free: 'number',
|
fee_free: 'number',
|
||||||
min_withdraw: 'price',
|
min_withdraw: 'price',
|
||||||
min_first_buy: 'price',
|
min_first_buy: 'price',
|
||||||
@@ -209,6 +245,9 @@ export function formatValueForSave(
|
|||||||
if (type === 'time') {
|
if (type === 'time') {
|
||||||
return normalizeTimeValue(value)
|
return normalizeTimeValue(value)
|
||||||
}
|
}
|
||||||
|
if (key === 'main_wallet_products') {
|
||||||
|
return normalizeMainWalletProductsValue(value)
|
||||||
|
}
|
||||||
return value ?? ''
|
return value ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,8 +355,8 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
|
|||||||
'روشن: امکان ارسال پیامک گروهی از پنل ادمین فعال است.',
|
'روشن: امکان ارسال پیامک گروهی از پنل ادمین فعال است.',
|
||||||
profit_gold_api_enabled:
|
profit_gold_api_enabled:
|
||||||
'روشن: محاسبه سود طلا از API خارجی فعال است. در اکثر نصبها خاموش میماند.',
|
'روشن: محاسبه سود طلا از API خارجی فعال است. در اکثر نصبها خاموش میماند.',
|
||||||
default_vc_number:
|
main_wallet_products:
|
||||||
'پیشوند اختیاری برای شمارهگذاری حوالهها در حسابداری خارجی. خالی = بدون پیشوند.',
|
'محصولاتی که موجودیشان از کیف طلای اصلی خوانده میشود. میتوانید چند محصول انتخاب کنید. اگر خالی بماند، پیشفرض محصول ۱۳ است.',
|
||||||
unit:
|
unit:
|
||||||
'واحد نمایش وزن در داراییها، کیف پول و جزئیات معاملات (گرم یا کیلوگرم).',
|
'واحد نمایش وزن در داراییها، کیف پول و جزئیات معاملات (گرم یا کیلوگرم).',
|
||||||
chart_use_local_data:
|
chart_use_local_data:
|
||||||
@@ -342,10 +381,8 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
|
|||||||
'شناسه مشتری در سامانه حسابداری که تراکنشها به نام آن ثبت میشوند.',
|
'شناسه مشتری در سامانه حسابداری که تراکنشها به نام آن ثبت میشوند.',
|
||||||
payping_gateway_name:
|
payping_gateway_name:
|
||||||
'نام درگاه پرداخت فعال که به واسط درگاه ارسال میشود.',
|
'نام درگاه پرداخت فعال که به واسط درگاه ارسال میشود.',
|
||||||
payping_gateway_item:
|
|
||||||
'کد درگاه (تکی). اگر لیست چرخشی درگاه پر باشد، اولویت با آن است.',
|
|
||||||
payping_gateway_items:
|
payping_gateway_items:
|
||||||
'لیست کدهای درگاه با ویرگول برای چرخش بین چند پذیرنده. خالی = استفاده از کد تکی.',
|
'لیست کدهای درگاه با ویرگول برای چرخش بین چند پذیرنده.',
|
||||||
payping_start_url:
|
payping_start_url:
|
||||||
'آدرس API شروع پرداخت (هدایت مشتری به درگاه).',
|
'آدرس API شروع پرداخت (هدایت مشتری به درگاه).',
|
||||||
payping_verify_url:
|
payping_verify_url:
|
||||||
|
|||||||
Reference in New Issue
Block a user