kashkool ver 1
This commit is contained in:
@@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<BasicActionSheet
|
<BasicActionSheet
|
||||||
v-model:visible="visible"
|
v-model:visible="visibleProxy"
|
||||||
title="جزئیات همگامسازی قیمت"
|
title="جزئیات همگامسازی قیمت"
|
||||||
modal-width="52rem"
|
modal-width="52rem"
|
||||||
:showButton="false"
|
:showButton="false"
|
||||||
:showCancel="true"
|
:showCancel="true"
|
||||||
:showOk="false"
|
:showOk="false"
|
||||||
cancelText="بستن"
|
cancelText="بستن"
|
||||||
@cancel="visible = false"
|
@cancel="close"
|
||||||
@close="visible = false"
|
@close="close"
|
||||||
>
|
>
|
||||||
<a-spin :spinning="loading">
|
<a-spin :spinning="loading">
|
||||||
<div v-if="log" class="flex flex-col gap-4">
|
<div v-if="log" class="flex flex-col gap-4">
|
||||||
@@ -37,14 +37,29 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { TableColumnType } from 'ant-design-vue'
|
import type { TableColumnType } from 'ant-design-vue'
|
||||||
import { ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { getPriceSyncLogDetail } from '@/api/price'
|
import { getPriceSyncLogDetail } from '@/api/price'
|
||||||
import type { PriceSyncLog } from '@/model/priceSyncLog'
|
import type { PriceSyncLog } from '@/model/priceSyncLog'
|
||||||
import BasicActionSheet from '@/components/BasicActionSheet.vue'
|
import BasicActionSheet from '@/components/BasicActionSheet.vue'
|
||||||
import { usePrice } from '@core/utils/usePrice'
|
import { usePrice } from '@core/utils/usePrice'
|
||||||
|
|
||||||
const visible = defineModel<boolean>('visible', { default: false })
|
const props = defineProps<{
|
||||||
const props = defineProps<{ logId?: number | null }>()
|
visible: boolean
|
||||||
|
logId?: number | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', value: boolean): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const visibleProxy = computed({
|
||||||
|
get: () => props.visible,
|
||||||
|
set: (value: boolean) => emit('update:visible', value)
|
||||||
|
})
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
emit('update:visible', false)
|
||||||
|
}
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const log = ref<PriceSyncLog | null>(null)
|
const log = ref<PriceSyncLog | null>(null)
|
||||||
@@ -102,7 +117,7 @@ async function loadDetail(id: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [visible.value, props.logId] as const,
|
() => [props.visible, props.logId] as const,
|
||||||
([open, id]) => {
|
([open, id]) => {
|
||||||
if (open && id) {
|
if (open && id) {
|
||||||
void loadDetail(id)
|
void loadDetail(id)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
class="!w-16"
|
class="!w-16"
|
||||||
type="number"
|
type="number"
|
||||||
v-model:value="record.sort_order"
|
v-model:value="record.sort_order"
|
||||||
@blur="() => saveSortOrder(record)"
|
@onchange="() => scheduleSortOrder(record)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@@ -53,7 +53,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import type { TableColumnType } from 'ant-design-vue'
|
import type { TableColumnType } from 'ant-design-vue'
|
||||||
import { onMounted, ref } from 'vue'
|
import { debounce } from 'lodash'
|
||||||
|
import { onMounted, onUnmounted, ref } from 'vue'
|
||||||
import { getCategories, syncCategoriesFromProducts, updateCategory } from '@/api/category'
|
import { getCategories, syncCategoriesFromProducts, updateCategory } from '@/api/category'
|
||||||
import type { Category } from '@/model/category'
|
import type { Category } from '@/model/category'
|
||||||
|
|
||||||
@@ -61,6 +62,12 @@ const loading = ref(false)
|
|||||||
const syncing = ref(false)
|
const syncing = ref(false)
|
||||||
const items = ref<Category[]>([])
|
const items = ref<Category[]>([])
|
||||||
|
|
||||||
|
type CategoryField = 'active' | 'show_in_filter' | 'show_in_select' | 'sort_order'
|
||||||
|
|
||||||
|
const pendingPatches = new Map<number, Partial<Record<CategoryField, number>>>()
|
||||||
|
const debouncedFlush = new Map<number, ReturnType<typeof debounce>>()
|
||||||
|
let syncInFlight: Promise<void> | null = null
|
||||||
|
|
||||||
const columns: TableColumnType<Category>[] = [
|
const columns: TableColumnType<Category>[] = [
|
||||||
{ title: 'نام دسته', dataIndex: 'name', key: 'name' },
|
{ title: 'نام دسته', dataIndex: 'name', key: 'name' },
|
||||||
{
|
{
|
||||||
@@ -106,50 +113,90 @@ async function fetchList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleField(record: Category, field: 'active' | 'show_in_filter' | 'show_in_select', checked: boolean) {
|
function scheduleSave(record: Category, patch: Partial<Record<CategoryField, number>>) {
|
||||||
const prev = record[field]
|
const id = record.id
|
||||||
record[field] = checked ? 1 : 0
|
const current = pendingPatches.get(id) ?? {}
|
||||||
|
pendingPatches.set(id, { ...current, ...patch })
|
||||||
|
|
||||||
|
if (!debouncedFlush.has(id)) {
|
||||||
|
debouncedFlush.set(
|
||||||
|
id,
|
||||||
|
debounce(() => flushSave(id), 500)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
debouncedFlush.get(id)!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function flushSave(id: number) {
|
||||||
|
const patch = pendingPatches.get(id)
|
||||||
|
if (!patch || Object.keys(patch).length === 0) return
|
||||||
|
pendingPatches.delete(id)
|
||||||
|
|
||||||
|
const record = items.value.find((item) => item.id === id)
|
||||||
|
if (!record) return
|
||||||
|
|
||||||
|
const snapshot = {
|
||||||
|
active: record.active,
|
||||||
|
show_in_filter: record.show_in_filter,
|
||||||
|
show_in_select: record.show_in_select,
|
||||||
|
sort_order: record.sort_order
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateCategory(record.id, { [field]: checked ? 1 : 0 })
|
await updateCategory(id, patch)
|
||||||
message.success('ذخیره شد')
|
message.success('ذخیره شد')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
record[field] = prev
|
Object.assign(record, snapshot)
|
||||||
console.error(e)
|
console.error(e)
|
||||||
message.error('خطا در ذخیره')
|
message.error('خطا در ذخیره')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveSortOrder(record: Category) {
|
function toggleField(
|
||||||
|
record: Category,
|
||||||
|
field: 'active' | 'show_in_filter' | 'show_in_select',
|
||||||
|
checked: boolean
|
||||||
|
) {
|
||||||
|
if (asBool(record[field]) === checked) return
|
||||||
|
|
||||||
|
const next = checked ? 1 : 0
|
||||||
|
record[field] = next
|
||||||
|
scheduleSave(record, { [field]: next })
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleSortOrder(record: Category) {
|
||||||
const order = Number(record.sort_order)
|
const order = Number(record.sort_order)
|
||||||
if (Number.isNaN(order)) return
|
if (Number.isNaN(order)) return
|
||||||
|
scheduleSave(record, { sort_order: order })
|
||||||
try {
|
|
||||||
await updateCategory(record.id, { sort_order: order })
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
message.error('خطا در ذخیره ترتیب')
|
|
||||||
await fetchList()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function syncList(showMessage = false) {
|
async function syncList(showMessage = false) {
|
||||||
syncing.value = true
|
if (syncInFlight) {
|
||||||
try {
|
return syncInFlight
|
||||||
const res = await syncCategoriesFromProducts()
|
|
||||||
items.value = res.items ?? (await getCategories())
|
|
||||||
if (showMessage) {
|
|
||||||
message.success(res.message || 'همگامسازی انجام شد')
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
if (showMessage) {
|
|
||||||
message.error('خطا در همگامسازی')
|
|
||||||
} else {
|
|
||||||
await fetchList()
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
syncing.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
syncing.value = true
|
||||||
|
syncInFlight = (async () => {
|
||||||
|
try {
|
||||||
|
const res = await syncCategoriesFromProducts()
|
||||||
|
items.value = res.items ?? (await getCategories())
|
||||||
|
if (showMessage) {
|
||||||
|
message.success(res.message || 'همگامسازی انجام شد')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
if (showMessage) {
|
||||||
|
message.error('خطا در همگامسازی')
|
||||||
|
} else {
|
||||||
|
await fetchList()
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
syncing.value = false
|
||||||
|
syncInFlight = null
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
return syncInFlight
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSync() {
|
async function handleSync() {
|
||||||
@@ -159,4 +206,10 @@ async function handleSync() {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
void syncList()
|
void syncList()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
debouncedFlush.forEach((fn) => fn.cancel())
|
||||||
|
debouncedFlush.clear()
|
||||||
|
pendingPatches.clear()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user