added main page config
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"0": "khanegiSettingCatalog.ts",
|
||||||
|
"1": "formatValueForSave",
|
||||||
|
"2": "resolveSwitchChecked",
|
||||||
|
"3": "normalizeSettingRow",
|
||||||
|
"4": "superAdminSettingKeys.ts",
|
||||||
|
"5": "isSettingVisible"
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"0": "c47c6c9a6cdd6684", "1": "2504a600231e9767", "2": "04e8f5fc588d14b2", "3": "d0038b93f872317a", "4": "4fea0ed0deb11c56", "5": "8955a16d55c2d309"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
.
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# Graph Report - controlpanel_user (2026-10-04)
|
||||||
|
|
||||||
|
## Corpus Check
|
||||||
|
- 56 files · ~46,173 words
|
||||||
|
- Verdict: corpus is large enough that graph structure adds value.
|
||||||
|
- Unclassified: 38 file(s) not represented in the graph (top: .ttf 16, .eot 6, .woff 6)
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
- 63 nodes · 87 edges · 6 communities (4 shown, 2 thin omitted)
|
||||||
|
- Extraction: 100% EXTRACTED · 0% INFERRED · 0% AMBIGUOUS
|
||||||
|
- Token cost: 0 input · 0 output
|
||||||
|
|
||||||
|
## Graph Freshness
|
||||||
|
- Built from commit: `bef5da62`
|
||||||
|
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
|
||||||
|
- Run `graphify update .` after code changes (no API cost).
|
||||||
|
|
||||||
|
## Community Hubs (Navigation)
|
||||||
|
- khanegiSettingCatalog.ts
|
||||||
|
- formatValueForSave
|
||||||
|
- resolveSwitchChecked
|
||||||
|
- normalizeSettingRow
|
||||||
|
- superAdminSettingKeys.ts
|
||||||
|
- isSettingVisible
|
||||||
|
|
||||||
|
## God Nodes (most connected - your core abstractions)
|
||||||
|
1. `formatValueForSave()` - 7 edges
|
||||||
|
2. `resolveSwitchChecked()` - 5 edges
|
||||||
|
3. `switchValueFromChecked()` - 5 edges
|
||||||
|
4. `normalizeSettingRow()` - 5 edges
|
||||||
|
5. `normalizeMainWalletProductsValue()` - 4 edges
|
||||||
|
6. `isSupportMessengerType()` - 4 edges
|
||||||
|
7. `parseSupportMessengers()` - 4 edges
|
||||||
|
8. `resolveSettingFieldType()` - 4 edges
|
||||||
|
9. `resolveSettingDescription()` - 4 edges
|
||||||
|
10. `parseSupportMessengersDraft()` - 3 edges
|
||||||
|
|
||||||
|
## Surprising Connections (you probably didn't know these)
|
||||||
|
- `formatValueForSave()` --calls--> `resolveSwitchChecked()` [EXTRACTED]
|
||||||
|
src/config/khanegiSettingCatalog.ts → src/config/khanegiSettingCatalog.ts _Bridges community 2 → community 1_
|
||||||
|
|
||||||
|
## Import Cycles
|
||||||
|
- None detected.
|
||||||
|
|
||||||
|
## Communities (6 total, 2 thin omitted)
|
||||||
|
|
||||||
|
### Community 0 - "khanegiSettingCatalog.ts"
|
||||||
|
Cohesion: 0.06
|
||||||
|
Nodes (26): ref_config, ACTIVATE_DEACTIVATE_SWITCH_KEYS, conditionalOrderEnabledSetting, HIDDEN_LEGACY_SETTING_KEYS, KEY_HELP_FALLBACK, KEY_LABEL_FALLBACK, KEY_TYPE_FALLBACK, MAIN_WALLET_PRODUCTS_DEFAULT (+18 more)
|
||||||
|
|
||||||
|
### Community 1 - "formatValueForSave"
|
||||||
|
Cohesion: 0.28
|
||||||
|
Nodes (9): formatMainWalletProductIds(), formatSupportMessengers(), formatValueForSave(), isSupportMessengerType(), normalizeMainWalletProductsValue(), normalizeTimeValue(), parseMainWalletProductIds(), parseSupportMessengers() (+1 more)
|
||||||
|
|
||||||
|
### Community 2 - "resolveSwitchChecked"
|
||||||
|
Cohesion: 0.29
|
||||||
|
Nodes (7): isActivateDeactivateSwitch(), isReferralRequired(), isSwitchOn(), referralRequiredToString(), resolveSwitchChecked(), switchToString(), switchValueFromChecked()
|
||||||
|
|
||||||
|
### Community 3 - "normalizeSettingRow"
|
||||||
|
Cohesion: 0.47
|
||||||
|
Nodes (6): helpText(), normalizeSettingRow(), resolveSettingCanEdit(), resolveSettingDescription(), resolveSettingFieldType(), resolveSettingTitle()
|
||||||
|
|
||||||
|
## Knowledge Gaps
|
||||||
|
- **27 isolated node(s):** `conditionalOrderEnabledSetting`, `priceChannelAccessSetting`, `priceProviderSetting`, `showToUsersChatSetting`, `SettingFieldType` (+22 more)
|
||||||
|
These have ≤1 connection - possible missing edges or undocumented components. (Counts symbols only; 37 node(s) total have ≤1 connection when file, concept and rationale nodes are included.)
|
||||||
|
- **2 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
|
||||||
|
|
||||||
|
## Suggested Questions
|
||||||
|
_Questions this graph is uniquely positioned to answer:_
|
||||||
|
|
||||||
|
- **Why does `formatValueForSave()` connect `formatValueForSave` to `khanegiSettingCatalog.ts`, `resolveSwitchChecked`?**
|
||||||
|
_High betweenness centrality (0.004) - this node is a cross-community bridge._
|
||||||
|
- **Why does `resolveSwitchChecked()` connect `resolveSwitchChecked` to `khanegiSettingCatalog.ts`, `formatValueForSave`?**
|
||||||
|
_High betweenness centrality (0.001) - this node is a cross-community bridge._
|
||||||
|
- **Why does `switchValueFromChecked()` connect `resolveSwitchChecked` to `khanegiSettingCatalog.ts`, `formatValueForSave`?**
|
||||||
|
_High betweenness centrality (0.001) - this node is a cross-community bridge._
|
||||||
|
- **What connects `conditionalOrderEnabledSetting`, `priceChannelAccessSetting`, `priceProviderSetting` to the rest of the system?**
|
||||||
|
_27 weakly-connected nodes found - possible documentation gaps or missing edges._
|
||||||
|
- **Should `khanegiSettingCatalog.ts` be split into smaller, more focused modules?**
|
||||||
|
_Cohesion score 0.05714285714285714 - nodes in this community are weakly interconnected._
|
||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,356 @@
|
|||||||
|
{
|
||||||
|
"package.json": {
|
||||||
|
"mtime": 1787153733.2418692,
|
||||||
|
"seen": 1791133977.6193485,
|
||||||
|
"ast_hash": "0ea185a5e1e78aadf7025665e9b39e9e",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/App.vue": {
|
||||||
|
"mtime": 1786352915.1978676,
|
||||||
|
"seen": 1791133977.619354,
|
||||||
|
"ast_hash": "d87851861f641427b9beb7dab5a75980",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/api/http.ts": {
|
||||||
|
"mtime": 1786352915.1980276,
|
||||||
|
"seen": 1791133977.6193569,
|
||||||
|
"ast_hash": "9dc615bf4662aa79f29b0db92134b79e",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/auth/permissions.ts": {
|
||||||
|
"mtime": 1786377085.7866411,
|
||||||
|
"seen": 1791133977.6193588,
|
||||||
|
"ast_hash": "20cf34abc737e76f0fd1f9c2c233dc8f",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/components/JalaliDatePicker.vue": {
|
||||||
|
"mtime": 1789554412.0846374,
|
||||||
|
"seen": 1791133977.6193602,
|
||||||
|
"ast_hash": "a9754baab2ad0987a406000a6f61fbf8",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/components/KhanegiDepositPanel.vue": {
|
||||||
|
"mtime": 1788876816.247845,
|
||||||
|
"seen": 1791133977.6193616,
|
||||||
|
"ast_hash": "d1e30b30e0d99d792c6b705e2fe34991",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/components/MoneyInput.vue": {
|
||||||
|
"mtime": 1788875079.255983,
|
||||||
|
"seen": 1791133977.6193628,
|
||||||
|
"ast_hash": "06eff041abd7a55e4832cf98db8e754b",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/components/SettingsCatalogPanel.vue": {
|
||||||
|
"mtime": 1786986825.3334448,
|
||||||
|
"seen": 1791133977.619364,
|
||||||
|
"ast_hash": "baff7fe473f274cbaaacf87f81a5c615",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/components/TenantPanelShortcuts.vue": {
|
||||||
|
"mtime": 1786352915.2116265,
|
||||||
|
"seen": 1791133977.6193655,
|
||||||
|
"ast_hash": "4829dc2470e432c360cb4f00a3b7f372",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/components/VersionPageTabs.vue": {
|
||||||
|
"mtime": 1786990119.5367587,
|
||||||
|
"seen": 1791133977.6193666,
|
||||||
|
"ast_hash": "df3345b45d52c55e49244b7807cbcf1f",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/components/program-settings/SettingCard.vue": {
|
||||||
|
"mtime": 1786972416.6895914,
|
||||||
|
"seen": 1791133977.6193678,
|
||||||
|
"ast_hash": "7629045273cd0b04c29473fdd562a179",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/components/program-settings/SettingToggle.vue": {
|
||||||
|
"mtime": 1786972307.025287,
|
||||||
|
"seen": 1791133977.6193695,
|
||||||
|
"ast_hash": "e538c09c9a630204f5ff7a0287817ee3",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/components/program-settings/SettingValueField.vue": {
|
||||||
|
"mtime": 1786972346.8159938,
|
||||||
|
"seen": 1791133977.6193707,
|
||||||
|
"ast_hash": "41c146f501265f37d643dc76fc0d7392",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/config/defaultHamkarUiConfig.ts": {
|
||||||
|
"mtime": 1786352915.2118337,
|
||||||
|
"seen": 1791133977.6193721,
|
||||||
|
"ast_hash": "f3376ee71c1d8f7d68f9af6ec1db6e6b",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/config/defaultKhanegiUiConfig.ts": {
|
||||||
|
"mtime": 1787560898.434721,
|
||||||
|
"seen": 1791133977.6193733,
|
||||||
|
"ast_hash": "273b1d70ada3296dd50a9e92a5a8238d",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/config/hamkarEffectiveTheme.ts": {
|
||||||
|
"mtime": 1786352915.2121105,
|
||||||
|
"seen": 1791133977.6193743,
|
||||||
|
"ast_hash": "0fec3d7e57d42a3db8a76a2a78f11ea5",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/config/khanegiSettingCatalog.ts": {
|
||||||
|
"mtime": 1791015842.8297806,
|
||||||
|
"seen": 1791133977.619376,
|
||||||
|
"ast_hash": "ab5c90f95d716e0172f4cfa88b8ba56d",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/config/khanegiSettingRow.ts": {
|
||||||
|
"mtime": 1786972105.4408166,
|
||||||
|
"seen": 1791133977.6193771,
|
||||||
|
"ast_hash": "e231618c0266b4898b8250abd77bc8f9",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/config/khanegiSystemSettingGroups.ts": {
|
||||||
|
"mtime": 1788614855.8582234,
|
||||||
|
"seen": 1791133977.6193783,
|
||||||
|
"ast_hash": "50e2e92d1428b4eacb9c21315de227ce",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/config/khanegiThemePresets.ts": {
|
||||||
|
"mtime": 1786352915.2122703,
|
||||||
|
"seen": 1791133977.6193798,
|
||||||
|
"ast_hash": "40385f7f94dd9e317b2e2c46deed52ec",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/config/numeric.ts": {
|
||||||
|
"mtime": 1786972105.4405568,
|
||||||
|
"seen": 1791133977.619381,
|
||||||
|
"ast_hash": "cd431f7af9e4b0d4a03be856ef78341e",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/config/superAdminSettingKeys.ts": {
|
||||||
|
"mtime": 1791015842.8300276,
|
||||||
|
"seen": 1791133977.6193826,
|
||||||
|
"ast_hash": "09d0079d23d5f15fd0cc137844a18540",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/layouts/AppLayout.vue": {
|
||||||
|
"mtime": 1788873083.8511343,
|
||||||
|
"seen": 1791133977.619384,
|
||||||
|
"ast_hash": "cd71c1876ad38c9bcfc15eaa31ef2151",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/main.ts": {
|
||||||
|
"mtime": 1786352915.2125874,
|
||||||
|
"seen": 1791133977.6193852,
|
||||||
|
"ast_hash": "ecb1b140df8f5c8250545a335fa47c9e",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/AdminsPage.vue": {
|
||||||
|
"mtime": 1788873155.7517095,
|
||||||
|
"seen": 1791133977.6193867,
|
||||||
|
"ast_hash": "e5b4a807bdea957ba1ca0f21348d6a6c",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/AuditPage.vue": {
|
||||||
|
"mtime": 1786352915.2129347,
|
||||||
|
"seen": 1791133977.6193879,
|
||||||
|
"ast_hash": "26848d52a3dbb5069aa54775acab8e85",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/HamkarUiConfigPage.vue": {
|
||||||
|
"mtime": 1788873030.907565,
|
||||||
|
"seen": 1791133977.6193893,
|
||||||
|
"ast_hash": "51c4a855048ace46e1b37008827f0d47",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/HomePage.vue": {
|
||||||
|
"mtime": 1788942596.4667015,
|
||||||
|
"seen": 1791133977.6193905,
|
||||||
|
"ast_hash": "5e73449136257e730f06538014d05850",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/KhanegiModularUiConfigPage.vue": {
|
||||||
|
"mtime": 1788873133.2248478,
|
||||||
|
"seen": 1791133977.6193962,
|
||||||
|
"ast_hash": "43ae7cd439ddbd7206d5feae0a035808",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/LoginPage.vue": {
|
||||||
|
"mtime": 1786352915.2136188,
|
||||||
|
"seen": 1791133977.6193972,
|
||||||
|
"ast_hash": "b629d53ac0df833f863c19f9d2eedc61",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/PluginCatalogPage.vue": {
|
||||||
|
"mtime": 1788972334.617576,
|
||||||
|
"seen": 1791133977.619398,
|
||||||
|
"ast_hash": "12f79da56c91cd3b554b3eed346f1b63",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/PluginPanelPage.vue": {
|
||||||
|
"mtime": 1788972326.03432,
|
||||||
|
"seen": 1791133977.6193993,
|
||||||
|
"ast_hash": "2b3a45073ef16ab7063785956b913d80",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/PluginTenantPage.vue": {
|
||||||
|
"mtime": 1789554426.9246407,
|
||||||
|
"seen": 1791133977.6194007,
|
||||||
|
"ast_hash": "d23afb3fdde3aa701a3e02ad82bf73ef",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/ProductVersionsPage.vue": {
|
||||||
|
"mtime": 1790177785.5642588,
|
||||||
|
"seen": 1791133977.6194022,
|
||||||
|
"ast_hash": "458e757f30b3fa12021205581741ce10",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/ProgramSettingsPage.vue": {
|
||||||
|
"mtime": 1788873135.2695487,
|
||||||
|
"seen": 1791133977.6194036,
|
||||||
|
"ast_hash": "ec6a273ff3ff292f8e1fa07f21682518",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/ServerPanelPage.vue": {
|
||||||
|
"mtime": 1788873043.5658474,
|
||||||
|
"seen": 1791133977.6194046,
|
||||||
|
"ast_hash": "426a0e93e52bfc2b588b28669a0d28fa",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/ServerTenantPage.vue": {
|
||||||
|
"mtime": 1788873091.8089325,
|
||||||
|
"seen": 1791133977.619406,
|
||||||
|
"ast_hash": "7ae5ebd7cbdea8fde31ea4d78601a23e",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/SmsPanelPage.vue": {
|
||||||
|
"mtime": 1788873043.5656607,
|
||||||
|
"seen": 1791133977.6194072,
|
||||||
|
"ast_hash": "696297a851fae528b55756f8590284ad",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/SmsTenantPage.vue": {
|
||||||
|
"mtime": 1788873091.9305022,
|
||||||
|
"seen": 1791133977.6194084,
|
||||||
|
"ast_hash": "5dd4c6671629e829371c3d5b5dcb064f",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/SupportPanelPage.vue": {
|
||||||
|
"mtime": 1788873042.5870128,
|
||||||
|
"seen": 1791133977.6194096,
|
||||||
|
"ast_hash": "ec2f46ea98eb15d7ab9fc84f91fdb5c0",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/SupportTenantPage.vue": {
|
||||||
|
"mtime": 1788873155.5047865,
|
||||||
|
"seen": 1791133977.619411,
|
||||||
|
"ast_hash": "26119695bf9ae113f723761a96e9920c",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/TenantFormPage.vue": {
|
||||||
|
"mtime": 1789626500.5020382,
|
||||||
|
"seen": 1791133977.6194122,
|
||||||
|
"ast_hash": "bdc1ad360c59703855dde8852bbdf182",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/pages/UiConfigPage.vue": {
|
||||||
|
"mtime": 1788873137.8165565,
|
||||||
|
"seen": 1791133977.6194139,
|
||||||
|
"ast_hash": "5fb5e68a4aae67c73c7320a0f302575f",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/router/index.ts": {
|
||||||
|
"mtime": 1788852471.4690886,
|
||||||
|
"seen": 1791133977.6194155,
|
||||||
|
"ast_hash": "550d87d0ccccb911ab2337a740ddf081",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/stores/auth.ts": {
|
||||||
|
"mtime": 1786377085.7914898,
|
||||||
|
"seen": 1791133977.6194167,
|
||||||
|
"ast_hash": "1ba25b41a49803e021a3973a752e3d25",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/theme/tokens.ts": {
|
||||||
|
"mtime": 1786352915.216459,
|
||||||
|
"seen": 1791133977.6194181,
|
||||||
|
"ast_hash": "ceecfc226cf4db2c5fe72fd57834f094",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/types/vue3-persian-datetime-picker.d.ts": {
|
||||||
|
"mtime": 1786352915.2166362,
|
||||||
|
"seen": 1791133977.6194193,
|
||||||
|
"ast_hash": "d6c63254beaf8e4cba21615c737081f6",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/utils/datetime.ts": {
|
||||||
|
"mtime": 1789550690.3410506,
|
||||||
|
"seen": 1791133977.6194208,
|
||||||
|
"ast_hash": "3f44d9ee7b87d32892366eafc03e89b6",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/utils/money.ts": {
|
||||||
|
"mtime": 1786352915.2169576,
|
||||||
|
"seen": 1791133977.619422,
|
||||||
|
"ast_hash": "3dd9ef861fcaa479a7ce3c6c5da2c6e3",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/utils/rewriteLegacyUploadUrl.ts": {
|
||||||
|
"mtime": 1788799101.6215806,
|
||||||
|
"seen": 1791133977.6194232,
|
||||||
|
"ast_hash": "da4e9d04bd43513e9644e8203cbc3819",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"src/vite-env.d.ts": {
|
||||||
|
"mtime": 1786352915.2170582,
|
||||||
|
"seen": 1791133977.6194243,
|
||||||
|
"ast_hash": "53ef1e204e58e29f9e328d6ddb0d5ef5",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"tsconfig.app.json": {
|
||||||
|
"mtime": 1786352915.2171595,
|
||||||
|
"seen": 1791133977.6194255,
|
||||||
|
"ast_hash": "d067e3ba63ec213fcec98b687b5e54e2",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"tsconfig.json": {
|
||||||
|
"mtime": 1786352915.2172534,
|
||||||
|
"seen": 1791133977.6194267,
|
||||||
|
"ast_hash": "a3d39fa780ebff65444de257f291ce6c",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"tsconfig.node.json": {
|
||||||
|
"mtime": 1787153734.3009675,
|
||||||
|
"seen": 1791133977.6194282,
|
||||||
|
"ast_hash": "308bb19a1eb06cf3481482994c5c7bd5",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"vite.config.ts": {
|
||||||
|
"mtime": 1786973481.403445,
|
||||||
|
"seen": 1791133977.6194293,
|
||||||
|
"ast_hash": "90a02fc0c5386c89adc80703c4344995",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"README.md": {
|
||||||
|
"mtime": 1786352915.1963446,
|
||||||
|
"seen": 1791133977.619431,
|
||||||
|
"ast_hash": "64ba9c1ddf7098960f302d6dcbd66522",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"index.html": {
|
||||||
|
"mtime": 1786352915.196795,
|
||||||
|
"seen": 1791133977.6194324,
|
||||||
|
"ast_hash": "4a22619d085cffe0062757a639d1636e",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"public/favicon.svg": {
|
||||||
|
"mtime": 1786352915.1975787,
|
||||||
|
"seen": 1791133977.6194336,
|
||||||
|
"ast_hash": "ddb1b57182207e652a01e2b0266df08e",
|
||||||
|
"semantic_hash": ""
|
||||||
|
},
|
||||||
|
"public/icons.svg": {
|
||||||
|
"mtime": 1786352915.1977217,
|
||||||
|
"seen": 1791133977.619435,
|
||||||
|
"ast_hash": "3b4fcfcf393eca4d264dca4a4663bc37",
|
||||||
|
"semantic_hash": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, reactive } from 'vue'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import {
|
||||||
|
LAYOUT_OPTION_KEY,
|
||||||
|
nextLayoutThemeId,
|
||||||
|
type KhanegiLayoutVariants,
|
||||||
|
type LayoutElementConfig,
|
||||||
|
} from '@/config/khanegiLayoutVariants'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
layouts: KhanegiLayoutVariants
|
||||||
|
canWrite: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{ change: [] }>()
|
||||||
|
|
||||||
|
type OptionKind = 'boolean' | 'text' | 'number' | 'json'
|
||||||
|
type DraftKind = 'boolean' | 'text' | 'number'
|
||||||
|
|
||||||
|
const groups = [
|
||||||
|
{
|
||||||
|
id: 'shell',
|
||||||
|
title: 'هدر و فوتر',
|
||||||
|
hint: 'رنگ و منوی قبلی دست نمیخورد. تنظیم هر المان جدا ذخیره میشود.',
|
||||||
|
items: [
|
||||||
|
{ path: 'header', title: 'هدر', hint: 'هدر کل اپ. ظاهر از تمی میآید که اینجا انتخاب میکنید.' },
|
||||||
|
{ path: 'footer', title: 'فوتر و دکمههای ناوبری', hint: 'نوار پایین اپ. با زیاد شدن تمها از همین سلکت عوض میشود.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'home',
|
||||||
|
title: 'صفحهٔ اصلی',
|
||||||
|
hint: 'فقط خانه. کلید تنظیم را اضافه یا حذف کنید؛ مثلاً showChart برای نمودار کادر قیمت. رسم این کلیدها در اپ مرحلهٔ بعد است.',
|
||||||
|
items: [
|
||||||
|
{ path: 'home.assets', title: 'دارایی', hint: 'کارت دارایی صفحهٔ اصلی.' },
|
||||||
|
{ path: 'home.livePrice', title: 'قیمت لحظهای', hint: 'کادر قیمت. جزئیات مثل نمودار را پایین همین کارت اضافه یا حذف کنید.' },
|
||||||
|
{ path: 'home.quickAccess', title: 'دسترسی سریع', hint: 'بلوک دسترسی سریع خانه.' },
|
||||||
|
{ path: 'home.recentTransactions', title: 'تراکنشهای اخیر', hint: 'لیست تراکنش خانه.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const kindOptions = [
|
||||||
|
{ value: 'boolean', label: 'روشن / خاموش' },
|
||||||
|
{ value: 'text', label: 'متن' },
|
||||||
|
{ value: 'number', label: 'عدد' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const drafts = reactive<Record<string, { key: string; kind: DraftKind }>>({})
|
||||||
|
|
||||||
|
const themeOptions = computed(() =>
|
||||||
|
props.layouts.themes.map((theme) => ({
|
||||||
|
value: theme.id,
|
||||||
|
label: `${theme.label} (${theme.id})`,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
||||||
|
function touch() {
|
||||||
|
emit('change')
|
||||||
|
}
|
||||||
|
|
||||||
|
function elementAt(path: string): LayoutElementConfig {
|
||||||
|
if (path === 'header') return props.layouts.header
|
||||||
|
if (path === 'footer') return props.layouts.footer
|
||||||
|
const key = path.slice('home.'.length)
|
||||||
|
return props.layouts.home[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
function draftOf(path: string) {
|
||||||
|
if (!drafts[path]) drafts[path] = { key: '', kind: 'boolean' }
|
||||||
|
return drafts[path]
|
||||||
|
}
|
||||||
|
|
||||||
|
function optionKind(value: unknown): OptionKind {
|
||||||
|
if (typeof value === 'boolean') return 'boolean'
|
||||||
|
if (typeof value === 'number') return 'number'
|
||||||
|
if (typeof value === 'string') return 'text'
|
||||||
|
return 'json'
|
||||||
|
}
|
||||||
|
|
||||||
|
function optionRows(element: LayoutElementConfig) {
|
||||||
|
return Object.keys(element.options).map((key) => ({
|
||||||
|
key,
|
||||||
|
kind: optionKind(element.options[key]),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function setOption(element: LayoutElementConfig, key: string, value: unknown) {
|
||||||
|
element.options[key] = value
|
||||||
|
touch()
|
||||||
|
}
|
||||||
|
|
||||||
|
function addOption(path: string) {
|
||||||
|
const element = elementAt(path)
|
||||||
|
const draft = draftOf(path)
|
||||||
|
const key = draft.key.trim()
|
||||||
|
if (!LAYOUT_OPTION_KEY.test(key)) {
|
||||||
|
message.warning('کلید انگلیسی باشد و با حرف شروع شود')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(element.options, key)) {
|
||||||
|
message.warning('این کلید روی همین المان هست')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
element.options[key] = draft.kind === 'boolean' ? false : draft.kind === 'number' ? 0 : ''
|
||||||
|
draft.key = ''
|
||||||
|
touch()
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeOption(path: string, key: string) {
|
||||||
|
delete elementAt(path).options[key]
|
||||||
|
touch()
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTheme() {
|
||||||
|
const id = nextLayoutThemeId(props.layouts.themes.map((theme) => theme.id))
|
||||||
|
props.layouts.themes.push({ id, label: `تم ${id.replace('theme', '')}` })
|
||||||
|
touch()
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeTheme(id: string) {
|
||||||
|
if (props.layouts.themes.length <= 1) return
|
||||||
|
const fallback = props.layouts.themes.find((theme) => theme.id !== id)?.id || 'theme1'
|
||||||
|
const elements = [props.layouts.header, props.layouts.footer, ...Object.values(props.layouts.home)]
|
||||||
|
for (const element of elements) {
|
||||||
|
if (element.variant === id) element.variant = fallback
|
||||||
|
}
|
||||||
|
const index = props.layouts.themes.findIndex((theme) => theme.id === id)
|
||||||
|
if (index >= 0) props.layouts.themes.splice(index, 1)
|
||||||
|
touch()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="form-section">
|
||||||
|
<h3>فهرست تمها</h3>
|
||||||
|
<p class="page-sub layout-lead">
|
||||||
|
تم تازه را اینجا اضافه کنید. هر المان با سلکت یکی را برمیدارد. حذف تم، المانهای همان تم را به تم باقیمانده برمیگرداند.
|
||||||
|
</p>
|
||||||
|
<div class="theme-list">
|
||||||
|
<div v-for="theme in layouts.themes" :key="theme.id" class="theme-row">
|
||||||
|
<span class="theme-id" dir="ltr">{{ theme.id }}</span>
|
||||||
|
<a-input v-model:value="theme.label" :disabled="!canWrite" @change="touch" />
|
||||||
|
<a-button :disabled="!canWrite || layouts.themes.length <= 1" @click="removeTheme(theme.id)">
|
||||||
|
حذف
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a-button type="dashed" :disabled="!canWrite" @click="addTheme">افزودن تم</a-button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section v-for="group in groups" :key="group.id" class="form-section">
|
||||||
|
<h3>{{ group.title }}</h3>
|
||||||
|
<p class="page-sub layout-lead">{{ group.hint }}</p>
|
||||||
|
<div class="layout-stack">
|
||||||
|
<article v-for="item in group.items" :key="item.path" class="layout-card">
|
||||||
|
<div class="layout-card-top">
|
||||||
|
<div class="layout-card-head">
|
||||||
|
<div class="layout-title">{{ item.title }}</div>
|
||||||
|
<p class="page-sub">{{ item.hint }}</p>
|
||||||
|
</div>
|
||||||
|
<a-select
|
||||||
|
v-model:value="elementAt(item.path).variant"
|
||||||
|
class="layout-theme-select"
|
||||||
|
:options="themeOptions"
|
||||||
|
:disabled="!canWrite"
|
||||||
|
@change="touch"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layout-options">
|
||||||
|
<div class="layout-options-title">تنظیمات همین المان</div>
|
||||||
|
<p v-if="!optionRows(elementAt(item.path)).length" class="page-sub">
|
||||||
|
تنظیمی نیست. کلید انگلیسی اضافه کنید؛ مثلاً showChart.
|
||||||
|
</p>
|
||||||
|
<div v-for="row in optionRows(elementAt(item.path))" :key="row.key" class="option-row">
|
||||||
|
<code class="option-key" dir="ltr">{{ row.key }}</code>
|
||||||
|
<a-switch
|
||||||
|
v-if="row.kind === 'boolean'"
|
||||||
|
:checked="elementAt(item.path).options[row.key] === true"
|
||||||
|
:disabled="!canWrite"
|
||||||
|
checked-children="روشن"
|
||||||
|
un-checked-children="خاموش"
|
||||||
|
@change="(value: boolean) => setOption(elementAt(item.path), row.key, value)"
|
||||||
|
/>
|
||||||
|
<a-input-number
|
||||||
|
v-else-if="row.kind === 'number'"
|
||||||
|
:value="elementAt(item.path).options[row.key]"
|
||||||
|
:disabled="!canWrite"
|
||||||
|
class="option-value"
|
||||||
|
@change="(value: number | null) => setOption(elementAt(item.path), row.key, value == null ? 0 : value)"
|
||||||
|
/>
|
||||||
|
<a-input
|
||||||
|
v-else-if="row.kind === 'text'"
|
||||||
|
:value="elementAt(item.path).options[row.key]"
|
||||||
|
:disabled="!canWrite"
|
||||||
|
class="option-value"
|
||||||
|
@update:value="(value: string) => setOption(elementAt(item.path), row.key, value)"
|
||||||
|
/>
|
||||||
|
<span v-else class="page-sub">مقدار پیچیده است. از تب JSON عوضش کنید.</span>
|
||||||
|
<a-button type="text" danger :disabled="!canWrite" @click="removeOption(item.path, row.key)">
|
||||||
|
حذف
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<div class="option-add">
|
||||||
|
<a-input
|
||||||
|
v-model:value="draftOf(item.path).key"
|
||||||
|
:disabled="!canWrite"
|
||||||
|
placeholder="showChart"
|
||||||
|
dir="ltr"
|
||||||
|
@press-enter="addOption(item.path)"
|
||||||
|
/>
|
||||||
|
<a-select
|
||||||
|
v-model:value="draftOf(item.path).kind"
|
||||||
|
:options="kindOptions"
|
||||||
|
:disabled="!canWrite"
|
||||||
|
/>
|
||||||
|
<a-button :disabled="!canWrite" @click="addOption(item.path)">افزودن</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.layout-lead {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-row,
|
||||||
|
.option-row,
|
||||||
|
.option-add {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-id,
|
||||||
|
.option-key {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--ink-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-stack {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: var(--paper, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-card-top {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-card-head {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-title,
|
||||||
|
.layout-options-title {
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-options-title {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-card .page-sub {
|
||||||
|
margin: 4px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-theme-select,
|
||||||
|
.option-value {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-options {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.theme-row {
|
||||||
|
grid-template-columns: 7rem minmax(0, 1fr) auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-card-top {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-theme-select {
|
||||||
|
width: 220px;
|
||||||
|
flex: 0 0 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-row {
|
||||||
|
grid-template-columns: 9rem minmax(0, 1fr) auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-add {
|
||||||
|
grid-template-columns: minmax(0, 1fr) 9rem auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { applyKhanegiLayoutVariants, createDefaultKhanegiLayoutVariants } from '@/config/khanegiLayoutVariants'
|
||||||
|
|
||||||
export type ThemeColors = {
|
export type ThemeColors = {
|
||||||
primary: string
|
primary: string
|
||||||
secondary: string
|
secondary: string
|
||||||
@@ -112,6 +114,7 @@ export function createDefaultKhanegiUiConfig() {
|
|||||||
showProfile: true,
|
showProfile: true,
|
||||||
brandSubtitle: 'پنل کاربری',
|
brandSubtitle: 'پنل کاربری',
|
||||||
},
|
},
|
||||||
|
layoutVariants: createDefaultKhanegiLayoutVariants(),
|
||||||
},
|
},
|
||||||
pages: {} as Record<string, unknown>,
|
pages: {} as Record<string, unknown>,
|
||||||
}
|
}
|
||||||
@@ -303,6 +306,7 @@ export function normalizeKhanegiUiConfigForClient<T extends Record<string, any>>
|
|||||||
if (app.header && typeof app.header === 'object') {
|
if (app.header && typeof app.header === 'object') {
|
||||||
app.header.variant = 'default'
|
app.header.variant = 'default'
|
||||||
}
|
}
|
||||||
|
applyKhanegiLayoutVariants(app)
|
||||||
if (app.navigation && typeof app.navigation === 'object') {
|
if (app.navigation && typeof app.navigation === 'object') {
|
||||||
if (app.navigation.type === 'sidebar+bottom') app.navigation.type = 'both'
|
if (app.navigation.type === 'sidebar+bottom') app.navigation.type = 'both'
|
||||||
app.navigation.sidebar = mapLegacyNavItems(app.navigation.sidebar)
|
app.navigation.sidebar = mapLegacyNavItems(app.navigation.sidebar)
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
/**
|
||||||
|
* چیدمان المانهای تم جدید.
|
||||||
|
* با controlpanel_server/src/get-user/khanegi-layout-variants.ts یکی بماند.
|
||||||
|
* variant شناسهٔ themeN است. جزئیات المان داخل options میماند.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const LAYOUT_VARIANT_ID = /^theme[1-9]\d*$/
|
||||||
|
export const LAYOUT_OPTION_KEY = /^[a-zA-Z][a-zA-Z0-9_]*$/
|
||||||
|
|
||||||
|
export type LayoutThemeDef = {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LayoutElementConfig = {
|
||||||
|
id: string
|
||||||
|
variant: string
|
||||||
|
options: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type KhanegiLayoutVariants = {
|
||||||
|
schemaId: 'layout-variants'
|
||||||
|
version: '1.0.0'
|
||||||
|
themes: LayoutThemeDef[]
|
||||||
|
header: LayoutElementConfig
|
||||||
|
footer: LayoutElementConfig
|
||||||
|
home: {
|
||||||
|
assets: LayoutElementConfig
|
||||||
|
livePrice: LayoutElementConfig
|
||||||
|
quickAccess: LayoutElementConfig
|
||||||
|
recentTransactions: LayoutElementConfig
|
||||||
|
[elementId: string]: LayoutElementConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLayoutVariantId(value: unknown): value is string {
|
||||||
|
return typeof value === 'string' && LAYOUT_VARIANT_ID.test(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nextLayoutThemeId(ids: string[]): string {
|
||||||
|
let n = 1
|
||||||
|
const used = new Set(ids)
|
||||||
|
while (used.has(`theme${n}`)) n += 1
|
||||||
|
return `theme${n}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultThemes(): LayoutThemeDef[] {
|
||||||
|
return [
|
||||||
|
{ id: 'theme1', label: 'تم ۱' },
|
||||||
|
{ id: 'theme2', label: 'تم ۲' },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
function element(id: string): LayoutElementConfig {
|
||||||
|
return { id, variant: 'theme1', options: {} }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergeLayoutThemes(stored: unknown, usedIds: string[] = []): LayoutThemeDef[] {
|
||||||
|
const parsed: LayoutThemeDef[] = []
|
||||||
|
const seen = new Set<string>()
|
||||||
|
if (Array.isArray(stored)) {
|
||||||
|
for (const row of stored) {
|
||||||
|
if (!isRecord(row) || !isLayoutVariantId(row.id) || seen.has(row.id)) continue
|
||||||
|
seen.add(row.id)
|
||||||
|
const label = typeof row.label === 'string' && row.label.trim()
|
||||||
|
? row.label.trim().slice(0, 40)
|
||||||
|
: row.id
|
||||||
|
parsed.push({ id: row.id, label })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const list = parsed.length ? parsed : defaultThemes()
|
||||||
|
for (const id of usedIds) {
|
||||||
|
if (!isLayoutVariantId(id) || list.some((theme) => theme.id === id)) continue
|
||||||
|
list.push({ id, label: id })
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDefaultKhanegiLayoutVariants(): KhanegiLayoutVariants {
|
||||||
|
return {
|
||||||
|
schemaId: 'layout-variants',
|
||||||
|
version: '1.0.0',
|
||||||
|
themes: defaultThemes(),
|
||||||
|
header: element('header'),
|
||||||
|
footer: element('footer'),
|
||||||
|
home: {
|
||||||
|
assets: element('assets'),
|
||||||
|
livePrice: element('livePrice'),
|
||||||
|
quickAccess: element('quickAccess'),
|
||||||
|
recentTransactions: element('recentTransactions'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergeLayoutElement(id: string, stored: unknown): LayoutElementConfig {
|
||||||
|
const base = element(id)
|
||||||
|
if (!isRecord(stored)) return base
|
||||||
|
const options = isRecord(stored.options) ? { ...stored.options } : {}
|
||||||
|
return {
|
||||||
|
...stored,
|
||||||
|
id,
|
||||||
|
variant: isLayoutVariantId(stored.variant) ? stored.variant : 'theme1',
|
||||||
|
options,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** تم انتخابشده و کلید options پاک نمیشود. فهرست تم خالی به تم ۱ و ۲ برمیگردد. */
|
||||||
|
export function mergeKhanegiLayoutVariants(stored: unknown): KhanegiLayoutVariants {
|
||||||
|
const defaults = createDefaultKhanegiLayoutVariants()
|
||||||
|
const src = isRecord(stored) ? stored : {}
|
||||||
|
const homeSrc = isRecord(src.home) ? src.home : {}
|
||||||
|
const knownHome = Object.keys(defaults.home)
|
||||||
|
const home: KhanegiLayoutVariants['home'] = { ...defaults.home }
|
||||||
|
|
||||||
|
for (const key of Object.keys(homeSrc)) {
|
||||||
|
if (knownHome.includes(key)) continue
|
||||||
|
home[key] = mergeLayoutElement(key, homeSrc[key])
|
||||||
|
}
|
||||||
|
for (const key of knownHome) {
|
||||||
|
home[key] = mergeLayoutElement(key, homeSrc[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
const header = mergeLayoutElement('header', src.header)
|
||||||
|
const footer = mergeLayoutElement('footer', src.footer)
|
||||||
|
const usedIds = [header.variant, footer.variant, ...Object.values(home).map((item) => item.variant)]
|
||||||
|
|
||||||
|
return {
|
||||||
|
...src,
|
||||||
|
schemaId: 'layout-variants',
|
||||||
|
version: '1.0.0',
|
||||||
|
themes: mergeLayoutThemes(src.themes, usedIds),
|
||||||
|
header,
|
||||||
|
footer,
|
||||||
|
home,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyKhanegiLayoutVariants(app: { layoutVariants?: unknown } | null | undefined) {
|
||||||
|
if (!app || typeof app !== 'object') return
|
||||||
|
app.layoutVariants = mergeKhanegiLayoutVariants(app.layoutVariants)
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { useRoute, useRouter } from 'vue-router'
|
|||||||
import { message, Modal } from 'ant-design-vue'
|
import { message, Modal } from 'ant-design-vue'
|
||||||
import { ArrowRightOutlined, HolderOutlined } from '@ant-design/icons-vue'
|
import { ArrowRightOutlined, HolderOutlined } from '@ant-design/icons-vue'
|
||||||
import api from '@/api/http'
|
import api from '@/api/http'
|
||||||
|
import LayoutVariantsEditor from '@/components/LayoutVariantsEditor.vue'
|
||||||
import VersionPageTabs from '@/components/VersionPageTabs.vue'
|
import VersionPageTabs from '@/components/VersionPageTabs.vue'
|
||||||
import {
|
import {
|
||||||
applyKhanegiNavSample,
|
applyKhanegiNavSample,
|
||||||
@@ -14,6 +15,7 @@ import {
|
|||||||
normalizeKhanegiUiConfigForClient,
|
normalizeKhanegiUiConfigForClient,
|
||||||
type KhanegiNavItem,
|
type KhanegiNavItem,
|
||||||
} from '@/config/defaultKhanegiUiConfig'
|
} from '@/config/defaultKhanegiUiConfig'
|
||||||
|
import { applyKhanegiLayoutVariants } from '@/config/khanegiLayoutVariants'
|
||||||
import {
|
import {
|
||||||
applyKhanegiPreset,
|
applyKhanegiPreset,
|
||||||
khanegiThemePresets,
|
khanegiThemePresets,
|
||||||
@@ -260,6 +262,7 @@ function applyAdvancedJson() {
|
|||||||
config.defaultSnapshot = keepSnapshot
|
config.defaultSnapshot = keepSnapshot
|
||||||
}
|
}
|
||||||
applySharedNavSample()
|
applySharedNavSample()
|
||||||
|
applyKhanegiLayoutVariants(config.app)
|
||||||
ensureSchemes()
|
ensureSchemes()
|
||||||
selectedPreset.value = resolvePresetId(config)
|
selectedPreset.value = resolvePresetId(config)
|
||||||
jsonError.value = ''
|
jsonError.value = ''
|
||||||
@@ -312,6 +315,8 @@ async function load() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
applySharedNavSample()
|
applySharedNavSample()
|
||||||
|
applyKhanegiLayoutVariants(config.app)
|
||||||
|
applyKhanegiLayoutVariants(config.defaultSnapshot?.app)
|
||||||
ensureSchemes()
|
ensureSchemes()
|
||||||
if (!config.defaultSnapshot) {
|
if (!config.defaultSnapshot) {
|
||||||
config.defaultSnapshot = cloneKhanegiAppSnapshot(config)
|
config.defaultSnapshot = cloneKhanegiAppSnapshot(config)
|
||||||
@@ -418,6 +423,7 @@ function resetToDefault() {
|
|||||||
config.app = JSON.parse(JSON.stringify(snap.app))
|
config.app = JSON.parse(JSON.stringify(snap.app))
|
||||||
config.pages = JSON.parse(JSON.stringify(snap.pages || {}))
|
config.pages = JSON.parse(JSON.stringify(snap.pages || {}))
|
||||||
applySharedNavSample()
|
applySharedNavSample()
|
||||||
|
applyKhanegiLayoutVariants(config.app)
|
||||||
ensureSchemes()
|
ensureSchemes()
|
||||||
selectedPreset.value = resolvePresetId(config)
|
selectedPreset.value = resolvePresetId(config)
|
||||||
previewMode.value = config.app.theme.mode === 'light' ? 'light' : 'dark'
|
previewMode.value = config.app.theme.mode === 'light' ? 'light' : 'dark'
|
||||||
@@ -740,6 +746,14 @@ onMounted(load)
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
|
||||||
|
<a-tab-pane key="layouts" tab="چیدمان">
|
||||||
|
<LayoutVariantsEditor
|
||||||
|
:layouts="config.app.layoutVariants"
|
||||||
|
:can-write="canWrite"
|
||||||
|
@change="syncAdvancedJson"
|
||||||
|
/>
|
||||||
|
</a-tab-pane>
|
||||||
|
|
||||||
<a-tab-pane key="json" tab="JSON پیشرفته">
|
<a-tab-pane key="json" tab="JSON پیشرفته">
|
||||||
<p class="page-sub" style="margin-bottom: 12px">
|
<p class="page-sub" style="margin-bottom: 12px">
|
||||||
برای pages و جزئیات کامل میتوانید JSON را مستقیم ویرایش کنید.
|
برای pages و جزئیات کامل میتوانید JSON را مستقیم ویرایش کنید.
|
||||||
|
|||||||
Reference in New Issue
Block a user