diff --git a/src/api/category.ts b/src/api/category.ts new file mode 100644 index 0000000..e2af07d --- /dev/null +++ b/src/api/category.ts @@ -0,0 +1,22 @@ +import useAxios from '@core/utils/useAxios' +import type { Category, CategoryInput } from '@/model/category' + +export type CategoryListParams = { + active?: number + select_only?: boolean + search?: string +} + +export const getCategories = async (params?: CategoryListParams) => + await useAxios.get('/admin/categories', { params }) + +export const updateCategory = async (id: number, payload: Partial) => + await useAxios.put(`/admin/categories/${id}`, payload) + +export const syncCategoriesFromProducts = async () => + await useAxios.post<{ message: string; created: number; items: Category[] }>( + '/admin/categories/sync-from-products' + ) + +export const changeCategoryActive = async (id: number, active: 0 | 1) => + await useAxios.get(`/admin/categories/${id}/active/${active}`) diff --git a/src/main.ts b/src/main.ts index 62ca4d7..157c2d2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -30,6 +30,8 @@ const init = async () => { await userStore.getMyInfo() await initStoreModeFromSettings() setupRoutes(['*']) + const { refreshAppModeMenu } = await import('@/router') + refreshAppModeMenu() //setupRoutes(['project']) /*router.removeRoute('usersMain') router.removeRoute('users')*/ diff --git a/src/model/category.ts b/src/model/category.ts new file mode 100644 index 0000000..9f6acb6 --- /dev/null +++ b/src/model/category.ts @@ -0,0 +1,17 @@ +export interface Category { + id: number + name: string + active: boolean | number + show_in_filter: boolean | number + show_in_select: boolean | number + sort_order: number + created_at?: string +} + +export interface CategoryInput { + name: string + active?: number + show_in_filter?: number + show_in_select?: number + sort_order?: number +} diff --git a/src/pages/login/components/LoginPanel.vue b/src/pages/login/components/LoginPanel.vue index d3bf0c6..3e4a323 100644 --- a/src/pages/login/components/LoginPanel.vue +++ b/src/pages/login/components/LoginPanel.vue @@ -100,7 +100,7 @@ import { useStaticApiListsStore } from '@/store/staticApisStore' import OtpInput from 'vue3-otp-input' import { useUserStore } from '@/store/userStore' import { setupRoutes } from '@/router' -import {getConfigApp} from "@/utils/config"; +import { getConfigApp, initStoreModeFromSettings } from '@/utils/config' //define emit const emits = defineEmits(['enter']) @@ -160,7 +160,10 @@ const onUserPassFormFinish = async () => { await setToken(access_token) } await userStore.getMyInfo() + await initStoreModeFromSettings() setupRoutes(['*'], router) + const { refreshAppModeMenu } = await import('@/router') + refreshAppModeMenu() // const info = await getMyInformation() // await setMyInfo(JSON.stringify(info)) // emits('enter') diff --git a/src/pages/menuPages/configuration/index.vue b/src/pages/menuPages/configuration/index.vue index 4f0d04f..be5f49e 100644 --- a/src/pages/menuPages/configuration/index.vue +++ b/src/pages/menuPages/configuration/index.vue @@ -174,6 +174,7 @@ import { loading } from '@/utils' import { getEditableConfiguration, setConfiguration } from '@/api/configuration' import SettingValueField from './SettingValueField.vue' import SettingCard from './SettingCard.vue' +import { refreshAppModeMenu } from '@/router' import { setStoreModeFromSettings } from '@/utils/config' import { SETTING_TYPE_OPTIONS, @@ -416,6 +417,7 @@ async function initConfig() { const storeRow = baseData.value.find((row) => row.key === 'is_store') if (storeRow) { setStoreModeFromSettings(isSwitchOn(storeRow.value)) + refreshAppModeMenu() } syncDraftsFromBase() } catch (e) { @@ -489,6 +491,7 @@ async function saveSettingRow(row: Const, overrides?: { isActive?: boolean }) { drafts.value[id] = value if (row.key === 'is_store') { setStoreModeFromSettings(isSwitchOn(value)) + refreshAppModeMenu() } savedFlashIds.value.add(id) if (saveTimers.has(id)) clearTimeout(saveTimers.get(id)) diff --git a/src/pages/menuPages/products/base/index.vue b/src/pages/menuPages/products/base/index.vue index 8280cbd..0696550 100644 --- a/src/pages/menuPages/products/base/index.vue +++ b/src/pages/menuPages/products/base/index.vue @@ -79,7 +79,13 @@ import { unwrapApiRecord } from '@/utils/file' import { normalizeProductOrderLimitForm, validateProductOrderLimitForm } from '@/utils/productOrderLimit' -import { buildCategorySelectItems, isMeltedTradeGroup, resolveMeltedPriceSource } from '@/utils/productCategories' +import { + buildCategorySelectItems, + isMeltedTradeGroup, + PRODUCT_CATEGORY_ABSHODE, + resolveMeltedPriceSource +} from '@/utils/productCategories' +import { getCategories } from '@/api/category' dayjs.extend(customParseFormat) @@ -122,7 +128,13 @@ const metalItems: Array = [ { key: 2, value: 'نقره' }, { key: 3, value: 'مس' }, ] -const categorySelectItems = computed(() => buildCategorySelectItems(metalItems)) +const storeCategorySelectItems = ref>([]) +const categorySelectItems = computed(() => { + if (isStoreApp() && storeCategorySelectItems.value.length) { + return storeCategorySelectItems.value + } + return buildCategorySelectItems(metalItems) +}) const priceItems: Array = [ { key: 0, value: 'متغیر' }, { key: 1, value: 'ثابت' } @@ -185,8 +197,28 @@ watch(addUserVisible, (visible) => { onMounted(() => { void refreshLivePrices() + void loadStoreCategories() }) +async function loadStoreCategories() { + if (!isStoreApp()) return + + try { + const rows = await getCategories({ select_only: true }) + const metalHint = metalItems.map((item) => item.value).join('، ') + storeCategorySelectItems.value = rows.map((category) => ({ + key: category.name, + value: + category.name === PRODUCT_CATEGORY_ABSHODE + ? `${category.name} (${metalHint})` + : category.name + })) + } catch (err) { + console.error(err) + storeCategorySelectItems.value = [] + } +} + onUnmounted(() => { stopPricePolling() }) diff --git a/src/pages/menuPages/products/categories/index.vue b/src/pages/menuPages/products/categories/index.vue new file mode 100644 index 0000000..e9b742a --- /dev/null +++ b/src/pages/menuPages/products/categories/index.vue @@ -0,0 +1,162 @@ + + + diff --git a/src/pages/menuPages/products/index.vue b/src/pages/menuPages/products/index.vue index 52fcd52..735624f 100644 --- a/src/pages/menuPages/products/index.vue +++ b/src/pages/menuPages/products/index.vue @@ -18,10 +18,12 @@ import BasicMenu from '@/components/BasicMenu/index.vue' import { useRouterStore } from '@core/stores/router' import { get } from 'lodash' +import type { RouteRecordRaw } from 'vue-router' const routerStore = useRouterStore() const menu = computed(() => { const item = routerStore.routes.find((item) => item.name === 'products') - return get(item, 'children[0].children', []) + const children = get(item, 'children[0].children', []) as RouteRecordRaw[] + return children.filter((child) => !child.meta?.hideMenu) }) diff --git a/src/pages/menuPages/setting/index.vue b/src/pages/menuPages/setting/index.vue index 4591970..7b2607a 100644 --- a/src/pages/menuPages/setting/index.vue +++ b/src/pages/menuPages/setting/index.vue @@ -21,22 +21,13 @@ import BasicMenu from '@/components/BasicMenu/index.vue' import { useRouterStore } from '@core/stores/router' import { get } from 'lodash' -//import Nature from '@/pages/menuPages/setting/limitedOrder/index.vue' -//s import test from './limitedOrder' -//import Budget from '@/pages/menuPages/setting/budget/index.vue' -//import ProjectType from '@/pages/menuPages/setting/projectType/index.vue' -//import DocumentType from '@/pages/menuPages/setting/documentType/index.vue' -// import Management from '@/pages/menuPages/setting/management/index.vue' -//import Accessibility from '@/pages/menuPages/setting/accessibility/index.vue' -//import SubmitProcess from '@/pages/menuPages/setting/submitProcess/index.vue' -//import documentGroup from '@/pages/menuPages/setting/documentGroup/index.vue' -//import userType from '@/pages/menuPages/setting/userType/index.vue' +import type { RouteRecordRaw } from 'vue-router' const routerStore = useRouterStore() const menu = computed(() => { const item = routerStore.routes.find((item) => item.name === 'settings') - //console.log('aaaa', get(item, 'children[0].children', [])) - return get(item, 'children[0].children', []) + const children = get(item, 'children[0].children', []) as RouteRecordRaw[] + return children.filter((child) => !child.meta?.hideMenu) }) diff --git a/src/router/index.ts b/src/router/index.ts index 2c065a0..3a2667b 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -3,9 +3,72 @@ import { useRouterStore } from '@core/stores/router' import type { Router, RouteRecordRaw } from 'vue-router' import { getConfigApp, isStoreApp } from '@/utils/config' +import { cloneDeep } from 'lodash' //const _router = useRouter() //import { cloneDeep } from 'lodash' +function applyAppModeMeta(route: RouteRecordRaw, appName: string | null) { + const store = isStoreApp() + + if (!route.children?.length) return + + for (const child of route.children) { + if (child.name === 'giftCardMain' && child.meta) { + child.meta.title = store ? 'کارت هدیه و تخفیف' : 'کارت هدیه' + child.meta.hideMenu = !store && appName !== 'garoosi' + } + if (child.name === 'discounts' && child.meta) { + child.meta.hideMenu = store || appName !== 'goldsun' + } + if (child.name === 'banners' && child.meta) { + child.meta.hideMenu = appName !== 'garoosi' + } + if (child.name === 'productsMain_categories' && child.meta) { + child.meta.hideMenu = !store + } + applyAppModeMeta(child, appName) + } +} + +function filterRoutesByApp(routes: RouteRecordRaw, appName: string | null) { + if (!routes.children) return + + routes.children = routes.children.filter((item) => { + if (item.name === 'discounts' && (isStoreApp() || appName !== 'goldsun')) return false + if (item.name === 'banners' && appName !== 'garoosi') return false + if (item.name === 'authentication' && appName !== 'garoosi') return false + if (item.name === 'giftCardMain' && !isStoreApp() && appName !== 'garoosi') return false + + return true + }) + + routes.children.forEach((child) => filterRoutesByApp(child, appName)) +} + +/** بعد از تغییر is_store — منو را از روت‌های تازه (clone) دوباره می‌سازد */ +export function refreshAppModeMenu() { + const routerStore = useRouterStore() + const modules = import.meta.glob('./routes/*.ts', { eager: true }) + const appName = getConfigApp('name') + + const preserved = routerStore.routes.filter( + (route) => route.name === 'notFound' || route.name === 'factor' + ) + + const dynamicRoutes: RouteRecordRaw[] = [] + for (const modulesKey in modules) { + const sourceRoute: RouteRecordRaw | undefined = modules[modulesKey].default ?? undefined + if (!sourceRoute) continue + + const route = cloneDeep(sourceRoute) + applyAppModeMeta(route, appName) + filterRoutesByApp(route, appName) + dynamicRoutes.push(route) + } + + routerStore.routes = [...preserved, ...dynamicRoutes] +} + export function setupRoutes(permissionsKeys: string[], router?: Router) { //console.log('this is permission', permissionsKeys) //console.log('this is use router', _router) @@ -23,42 +86,11 @@ export function setupRoutes(permissionsKeys: string[], router?: Router) { (item.meta?.permissionsKey && permissionsKeys.includes(item.meta?.permissionsKey)) ) for (const route of routes.children) { - /*if (!route.children || (route.children && route.children.length == 0)) { - //path.push(route.name) - //return - continue - }*/ - /*if (routes.children) { - }*/ walkInRoutes(route) - /*if (route.children && route.children.length > 0) { - path.push(route.name) - const res = walkInRoutes(route.children) - if (!res) { - path.pop() - } else { - return true - } - }*/ } return } - function filterRoutesByApp(routes: RouteRecordRaw) { - if (!routes.children) return - routes.children = routes.children.filter((item) => { - - if (item.name === 'discounts' && (isStoreApp() || appName !== 'goldsun')) return false - if (item.name === 'banners' && appName !== 'garoosi') return false - if (item.name === 'authentication' && appName !== 'garoosi') return false - if (item.name === 'giftCardMain' && !isStoreApp() && appName !== 'garoosi') return false - - - return true - }) - routes.children.forEach((child) => filterRoutesByApp(child)) - } - routerStore.routes = [ { path: '/:pathMatch(.*)*', @@ -79,33 +111,31 @@ export function setupRoutes(permissionsKeys: string[], router?: Router) { }, ] for (const modulesKey in modules) { - // console.log(modulesKey) - const route: RouteRecordRaw | undefined = modules[modulesKey].default ?? undefined - if (route) { - if (permissionsKeys.length && permissionsKeys[0] == '*') { - filterRoutesByApp(route) + const sourceRoute: RouteRecordRaw | undefined = modules[modulesKey].default ?? undefined + if (!sourceRoute) continue + + const route = cloneDeep(sourceRoute) + + if (permissionsKeys.length && permissionsKeys[0] == '*') { + applyAppModeMeta(route, appName) + filterRoutesByApp(route, appName) + routerStore.routes.push(route) + if (router) { + router.addRoute(route) + } + } else { + if ( + !route?.meta?.permissionsKey || + (route.meta.permissionsKey && permissionsKeys.includes(route.meta.permissionsKey)) + ) { + walkInRoutes(route) + applyAppModeMeta(route, appName) + filterRoutesByApp(route, appName) routerStore.routes.push(route) if (router) { router.addRoute(route) } - } else { - if ( - !route?.meta?.permissionsKey || - (route.meta.permissionsKey && permissionsKeys.includes(route.meta.permissionsKey)) - ) { - walkInRoutes(route) - filterRoutesByApp(route) - routerStore.routes.push(route) - if (router) { - router.addRoute(route) - } - } } } } - // console.log(router) - //console.log('this is store', routerStore.routes) - /* if (router) { - console.log('this is router routes', router.getRoutes()) - }*/ } diff --git a/src/router/routes/giftCard.ts b/src/router/routes/giftCard.ts index f935760..1cef8ad 100644 --- a/src/router/routes/giftCard.ts +++ b/src/router/routes/giftCard.ts @@ -1,36 +1,13 @@ import type { RouteRecordRaw } from 'vue-router' -import { getConfigApp, isStoreApp } from '@/utils/config' -const Orders: RouteRecordRaw = { +/** ریدایرکت آدرس قدیمی — منو زیر پیکربندی است */ +const GiftCard: RouteRecordRaw = { name: 'giftCard', path: '/giftCard', - component: () => import('@core/layouts/BasicLayout.vue'), redirect: { name: 'giftCardMain' }, meta: { - icon: 'vuesax-linear:gift', - permissionsKey: 'giftCard', - title: isStoreApp() ? 'کارت هدیه و تخفیف' : 'کارت هدیه', - disabled: false, - hideMenu: !isStoreApp() && getConfigApp('name') != 'garoosi', - hideChildren: true, - order: 7 - }, - children: [ - { - name: 'giftCardMain', - path: '', - component: () => import('@/pages/menuPages/giftCards/index.vue'), - meta: { - permissionsKey: 'project', - icon: 'vuesax-linear:empty-wallet-time', - order: 3, - title: isStoreApp() ? 'کارت هدیه و تخفیف' : 'کارت هدیه', - disabled: false, - hideMenu: false - //hideChildren: false - } - } - ] + hideMenu: true + } } -export default Orders +export default GiftCard diff --git a/src/router/routes/products.ts b/src/router/routes/products.ts index 45c9bd0..1d94be7 100644 --- a/src/router/routes/products.ts +++ b/src/router/routes/products.ts @@ -47,6 +47,16 @@ const Products: RouteRecordRaw = { icon: 'vuesax-linear:save-2', title: 'بایگانی' } + }, + { + name: 'productsMain_categories', + path: 'categories', + component: () => import('@/pages/menuPages/products/categories/index.vue'), + meta: { + icon: 'vuesax-linear:category', + title: 'دسته‌بندی‌ها', + hideMenu: true + } } ] } diff --git a/src/router/routes/setting.ts b/src/router/routes/setting.ts index 720c46e..c01b53b 100644 --- a/src/router/routes/setting.ts +++ b/src/router/routes/setting.ts @@ -1,5 +1,4 @@ import type { RouteRecordRaw } from 'vue-router' -import { getConfigApp, isStoreApp } from '@/utils/config' const Setting: RouteRecordRaw = { name: 'settings', path: '/settings', @@ -62,6 +61,27 @@ const Setting: RouteRecordRaw = { title: 'هشدار قیمت' } }, + { + name: 'smsMain', + path: 'sms', + component: () => import('@/pages/menuPages/sms/index.vue'), + meta: { + permissionsKey: 'sms', + icon: 'vuesax-linear:sms', + title: 'پیامک‌ها' + } + }, + { + name: 'giftCardMain', + path: 'giftCard', + component: () => import('@/pages/menuPages/giftCards/index.vue'), + meta: { + permissionsKey: 'project', + icon: 'vuesax-linear:gift', + title: 'کارت هدیه', + hideMenu: true + } + }, { name: 'discounts', path: 'discounts', @@ -70,7 +90,7 @@ const Setting: RouteRecordRaw = { permissionsKey: 'discounts', icon: 'vuesax-linear:discount-shape', title: 'تخفیفات', - hideMenu: isStoreApp() || getConfigApp('name') !== 'goldsun' + hideMenu: true } }, { @@ -90,7 +110,7 @@ const Setting: RouteRecordRaw = { meta: { icon: 'vuesax-linear:image', title: 'بنرها', - hideMenu: getConfigApp('name') !== "garoosi" + hideMenu: true } }, ] diff --git a/src/router/routes/sms.ts b/src/router/routes/sms.ts index e84b268..bde5332 100644 --- a/src/router/routes/sms.ts +++ b/src/router/routes/sms.ts @@ -1,35 +1,13 @@ import type { RouteRecordRaw } from 'vue-router' -const Orders: RouteRecordRaw = { +/** ریدایرکت آدرس قدیمی — منو زیر پیکربندی است */ +const Sms: RouteRecordRaw = { name: 'sms', path: '/sms', - component: () => import('@core/layouts/BasicLayout.vue'), redirect: { name: 'smsMain' }, meta: { - icon: 'vuesax-linear:sms', - permissionsKey: 'sms', - title: 'پیامک ها', - disabled: false, - hideMenu: false, - hideChildren: true, - order: 9 - }, - children: [ - { - name: 'smsMain', - path: '', - component: () => import('@/pages/menuPages/sms/index.vue'), - meta: { - permissionsKey: 'sms', - icon: 'vuesax-linear:setting', - order: 3, - title: 'پیامک ها', - disabled: false, - hideMenu: false - //hideChildren: false - } - } - ] + hideMenu: true + } } -export default Orders +export default Sms