create-m2m-bo-template-vue 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/src/components/DataGrid/CellView.vue +11 -1
- package/template/src/components/DataGrid/DataGrid.module.css +41 -1
- package/template/src/components/DataGrid/DataGrid.vue +94 -10
- package/template/src/components/DataGrid/types.ts +6 -0
- package/template/src/components/common/ExcelDownloadButton.vue +1 -1
- package/template/src/components/common/PageSizeSelect.module.css +4 -10
- package/template/src/components/common/PageSizeSelect.vue +3 -2
- package/template/src/components/shell/GlobalSearchBar.module.css +13 -0
- package/template/src/components/shell/GlobalSearchBar.vue +64 -0
- package/template/src/components/shell/Header.module.css +31 -9
- package/template/src/components/shell/Header.vue +39 -30
- package/template/src/lib/globalSearch.ts +317 -0
- package/template/src/lib/nav.ts +8 -9
- package/template/src/lib/searchIndex.ts +429 -0
- package/template/src/pages/admin/AdminHistoryPage.vue +3 -1
- package/template/src/pages/admin/AdminRolesPage.vue +9 -8
- package/template/src/pages/admin/AdminsListPage.vue +1 -0
- package/template/src/pages/b2c/operations/ClaimPage.vue +2 -1
- package/template/src/pages/b2c/operations/DeliveryTablePage.vue +1 -0
- package/template/src/pages/b2c/products/B2cCategoryTreeRow.vue +75 -0
- package/template/src/pages/b2c/products/B2cProductBatchModal.module.css +168 -0
- package/template/src/pages/b2c/products/B2cProductBatchModal.vue +215 -0
- package/template/src/pages/b2c/products/B2cProductCreatePage.vue +42 -36
- package/template/src/pages/b2c/products/B2cProductHistoryDrawer.module.css +270 -0
- package/template/src/pages/b2c/products/B2cProductHistoryDrawer.vue +248 -0
- package/template/src/pages/b2c/products/B2cProductHistoryPage.vue +2 -1
- package/template/src/pages/b2c/products/B2cProductManagementPage.module.css +421 -0
- package/template/src/pages/b2c/products/B2cProductManagementPage.vue +780 -0
- package/template/src/pages/b2c/products/B2cStandardInfoPage.module.css +1200 -0
- package/template/src/pages/b2c/products/B2cStandardInfoPage.vue +996 -0
- package/template/src/pages/b2c/products/b2cProductsData.ts +362 -28
- package/template/src/pages/brands/BrandExposurePage.vue +2 -1
- package/template/src/pages/brands/BrandProductsPage.vue +2 -1
- package/template/src/pages/brands/BrandsListPage.vue +1 -1
- package/template/src/pages/brands/brandsData.ts +35 -2
- package/template/src/pages/c2c/chat/ChatAccessHistoryPage.vue +1 -0
- package/template/src/pages/c2c/chat/ChatListPage.vue +1 -1
- package/template/src/pages/c2c/proceeds/C2CSettlementManagementPage.vue +2 -1
- package/template/src/pages/c2c/proceeds/ProceedsLedgerPage.vue +1 -0
- package/template/src/pages/c2c/proceeds/ProceedsOverviewPage.vue +2 -1
- package/template/src/pages/c2c/proceeds/WithdrawalManagementPage.vue +2 -1
- package/template/src/pages/c2c/productPolicy/DetectionHistoryPage.vue +1 -0
- package/template/src/pages/c2c/productPolicy/ProductPolicyPage.vue +2 -1
- package/template/src/pages/c2c/products/UserProductModerationPage.vue +4 -3
- package/template/src/pages/c2c/products/UserProductReviewPage.vue +1 -1
- package/template/src/pages/c2c/purchases/PurchaseCancelPage.vue +1 -1
- package/template/src/pages/c2c/purchases/PurchaseDisputesPage.vue +1 -1
- package/template/src/pages/c2c/purchases/PurchaseHistoryPage.vue +1 -1
- package/template/src/pages/c2c/reports/ReportHistoryPage.vue +1 -0
- package/template/src/pages/c2c/reports/ReportProcessingPage.vue +1 -1
- package/template/src/pages/c2c/safety/TradeHoldManagementPage.vue +2 -1
- package/template/src/pages/c2c/safety/TradeRiskMonitoringPage.vue +2 -1
- package/template/src/pages/c2c/sales/SalesPerformancePage.vue +2 -1
- package/template/src/pages/c2c/sales/SalesStatusPage.vue +1 -1
- package/template/src/pages/c2c/sales/SalesTradesPage.vue +1 -1
- package/template/src/pages/c2c/sales/SellerListPage.vue +1 -1
- package/template/src/pages/c2c/sales/SellerProductsPage.vue +1 -1
- package/template/src/pages/c2c/sanctions/SanctionProcessingPage.vue +1 -1
- package/template/src/pages/c2c/verification/VerificationHistoryPage.vue +2 -1
- package/template/src/pages/c2c/verification/VerificationPolicyPage.vue +2 -1
- package/template/src/pages/c2c/verification/VerificationReviewPage.vue +2 -1
- package/template/src/pages/cancel/CancelApprovalPage.vue +1 -0
- package/template/src/pages/cancel/CancelCompletedPage.vue +1 -0
- package/template/src/pages/cancel/CancelHistoryPage.vue +1 -0
- package/template/src/pages/cancel/CancelPartialPage.vue +1 -0
- package/template/src/pages/cancel/CancelRejectedPage.vue +1 -0
- package/template/src/pages/cancel/CancelRequestsPage.vue +1 -0
- package/template/src/pages/cartconversion/CartConversionPage.vue +3 -2
- package/template/src/pages/common/ModulePlaceholderPage.vue +3 -1
- package/template/src/pages/components/ComponentsPage.vue +32 -32
- package/template/src/pages/content/CategoriesPage.vue +2 -2
- package/template/src/pages/content/ContentListPage.vue +3 -4
- package/template/src/pages/content/ExposurePage.vue +2 -2
- package/template/src/pages/content/ReviewPage.vue +2 -2
- package/template/src/pages/content/TagsPage.vue +2 -2
- package/template/src/pages/contracts/ContractPeriodPage.vue +2 -1
- package/template/src/pages/contracts/ContractsPage.vue +2 -1
- package/template/src/pages/contracts/TradeTermsPage.vue +2 -1
- package/template/src/pages/contracts/UnitPricePage.vue +2 -1
- package/template/src/pages/coupons/AutomaticCouponsPage.vue +1 -0
- package/template/src/pages/coupons/CouponIssuesPage.vue +1 -1
- package/template/src/pages/coupons/CouponPolicyPage.vue +25 -24
- package/template/src/pages/coupons/CouponUsagePage.vue +2 -1
- package/template/src/pages/coupons/CouponsListPage.vue +1 -1
- package/template/src/pages/coupons/ExpiredCouponsPage.vue +1 -0
- package/template/src/pages/cs/AdminMemoDrawer.vue +4 -3
- package/template/src/pages/cs/ConsultationDetailDrawer.vue +3 -1
- package/template/src/pages/cs/ConsultationsPage.vue +5 -1
- package/template/src/pages/cs/CsHistoryDetailDrawer.vue +6 -2
- package/template/src/pages/cs/ProductInquiriesListPage.vue +1 -0
- package/template/src/pages/cs/ProductInquiryDetailDrawer.vue +6 -2
- package/template/src/pages/delivery/DeliveryCarriersPage.vue +1 -0
- package/template/src/pages/delivery/DeliveryCompletePage.vue +1 -0
- package/template/src/pages/delivery/DeliveryFailedPage.vue +1 -0
- package/template/src/pages/delivery/DeliveryHistoryPage.vue +1 -0
- package/template/src/pages/delivery/DeliveryHoldPage.vue +1 -0
- package/template/src/pages/delivery/DeliveryInvoicesPage.vue +1 -0
- package/template/src/pages/delivery/DeliveryListPage.vue +2 -1
- package/template/src/pages/delivery/DeliveryPrepPage.vue +1 -0
- package/template/src/pages/delivery/DeliveryTrackingPage.vue +1 -0
- package/template/src/pages/delivery/InTransitPage.vue +1 -0
- package/template/src/pages/delivery/OutboundCompletePage.vue +1 -0
- package/template/src/pages/delivery/OutboundWaitingPage.vue +1 -0
- package/template/src/pages/delivery/deliveryShared.module.css +81 -0
- package/template/src/pages/deliverypolicy/RegionalShippingFeePage.vue +1 -0
- package/template/src/pages/deliverypolicy/ShippingBaseFeePage.vue +8 -7
- package/template/src/pages/exchanges/ExchangeCollectionPage.vue +1 -0
- package/template/src/pages/exchanges/ExchangeHistoryPage.vue +1 -0
- package/template/src/pages/exchanges/ExchangeHoldPage.vue +1 -0
- package/template/src/pages/exchanges/ExchangeRequestsPage.vue +1 -0
- package/template/src/pages/exchanges/ExchangeReshipPage.vue +1 -0
- package/template/src/pages/inventory/InboundManagementPage.vue +1 -1
- package/template/src/pages/inventory/InventoryAdjustmentPage.vue +1 -1
- package/template/src/pages/inventory/InventoryAlertsConfigPage.vue +13 -9
- package/template/src/pages/inventory/InventoryAlertsPage.vue +6 -4
- package/template/src/pages/inventory/InventoryStatusPage.vue +4 -3
- package/template/src/pages/inventory/SafetyStockPage.vue +6 -4
- package/template/src/pages/inventory/StockMovementHistoryPage.vue +5 -3
- package/template/src/pages/inventory/StockMovementManagementPage.vue +8 -4
- package/template/src/pages/inventory/StockOutboundManagementPage.vue +1 -1
- package/template/src/pages/logs/ApiLogDetailDrawer.vue +83 -0
- package/template/src/pages/logs/ErrorGroupDetailDrawer.vue +74 -0
- package/template/src/pages/logs/SecurityLogDetailDrawer.vue +116 -0
- package/template/src/pages/logs/SecurityLogPage.module.css +2 -0
- package/template/src/pages/logs/SecurityLogPage.vue +225 -0
- package/template/src/pages/logs/SystemLogPage.module.css +130 -0
- package/template/src/pages/logs/SystemLogPage.vue +349 -0
- package/template/src/pages/logs/securityLogData.ts +39 -0
- package/template/src/pages/logs/systemLogData.ts +160 -0
- package/template/src/pages/members/MembersPage.vue +6 -5
- package/template/src/pages/notifications/NotificationDispatchPage.vue +8 -6
- package/template/src/pages/notifications/NotificationTemplatesPage.vue +1005 -0
- package/template/src/pages/ops/BannerDetailDrawer.vue +185 -0
- package/template/src/pages/ops/BannerEditorDrawer.vue +272 -0
- package/template/src/pages/ops/BannersPage.vue +582 -0
- package/template/src/pages/ops/EventDetailDrawer.vue +229 -0
- package/template/src/pages/ops/EventEditorPage.vue +464 -0
- package/template/src/pages/ops/EventPreviewDialog.vue +104 -0
- package/template/src/pages/ops/EventsPage.vue +644 -0
- package/template/src/pages/ops/FaqDetailDrawer.vue +188 -0
- package/template/src/pages/ops/FaqEditorDrawer.vue +263 -0
- package/template/src/pages/ops/FaqPage.vue +648 -0
- package/template/src/pages/ops/NoticeEditorDrawer.vue +217 -0
- package/template/src/pages/ops/NoticesPage.vue +498 -0
- package/template/src/pages/ops/PolicyDetailDrawer.vue +122 -0
- package/template/src/pages/ops/PolicyManagementPage.vue +337 -0
- package/template/src/pages/ops/PopupDetailDrawer.vue +205 -0
- package/template/src/pages/ops/PopupEditorDrawer.vue +342 -0
- package/template/src/pages/ops/PopupsPage.vue +554 -0
- package/template/src/pages/ops/TermsDetailDrawer.vue +143 -0
- package/template/src/pages/ops/TermsManagementPage.vue +400 -0
- package/template/src/pages/orders/OrderApprovalPage.vue +2 -1
- package/template/src/pages/orders/OrderCompletedPage.vue +2 -1
- package/template/src/pages/orders/OrderProcessingPage.vue +2 -1
- package/template/src/pages/orders/PurchaseOrdersPage.vue +2 -1
- package/template/src/pages/orders/RefundPage.vue +7 -6
- package/template/src/pages/partners/CompaniesPage.vue +2 -1
- package/template/src/pages/partners/ContactsPage.vue +2 -1
- package/template/src/pages/partners/CreditTermsPage.vue +2 -1
- package/template/src/pages/partners/TradeStatusPage.vue +2 -1
- package/template/src/pages/paymentmgmt/ExternalTransactionPage.vue +2 -1
- package/template/src/pages/paymentmgmt/PaymentAuditPage.vue +1 -1
- package/template/src/pages/paymentmgmt/PaymentListPage.vue +1 -1
- package/template/src/pages/payments/DepositConfirmPage.vue +2 -1
- package/template/src/pages/payments/PaymentRegisterDrawer.vue +13 -12
- package/template/src/pages/payments/PaymentsPage.vue +2 -1
- package/template/src/pages/payments/ReceivablesPage.vue +2 -1
- package/template/src/pages/payments/TaxInvoicesPage.vue +2 -1
- package/template/src/pages/points/PointExpiringPage.vue +5 -4
- package/template/src/pages/points/PointLedgerPage.vue +2 -1
- package/template/src/pages/points/PointPolicyPage.vue +40 -37
- package/template/src/pages/points/PointsBalancePage.vue +1 -1
- package/template/src/pages/policy/CancelPolicyPage.module.css +396 -0
- package/template/src/pages/policy/CancelPolicyPage.vue +1134 -0
- package/template/src/pages/policy/CancelReasonEditDialog.vue +53 -0
- package/template/src/pages/policy/FeePolicyDrawer.vue +405 -0
- package/template/src/pages/policy/FeePolicyPage.module.css +757 -0
- package/template/src/pages/policy/FeePolicyPage.vue +1270 -0
- package/template/src/pages/policy/OrderStatusDrawer.vue +259 -0
- package/template/src/pages/policy/OrderStatusPage.module.css +114 -0
- package/template/src/pages/policy/OrderStatusPage.vue +467 -0
- package/template/src/pages/policy/PaymentMethodEditDialog.vue +76 -0
- package/template/src/pages/policy/PaymentPolicyPage.module.css +355 -0
- package/template/src/pages/policy/PaymentPolicyPage.vue +1056 -0
- package/template/src/pages/policy/RefundPolicyPage.module.css +273 -0
- package/template/src/pages/policy/RefundPolicyPage.vue +921 -0
- package/template/src/pages/policy/RefundReasonEditDialog.vue +76 -0
- package/template/src/pages/policy/SettlementPolicyPage.module.css +296 -0
- package/template/src/pages/policy/SettlementPolicyPage.vue +983 -0
- package/template/src/pages/policy/TransitionEditDialog.vue +67 -0
- package/template/src/pages/policy/cancelPolicyData.ts +294 -0
- package/template/src/pages/policy/feePolicyData.ts +688 -0
- package/template/src/pages/policy/orderStatusData.ts +202 -0
- package/template/src/pages/policy/paymentPolicyData.ts +240 -0
- package/template/src/pages/policy/refundPolicyData.ts +294 -0
- package/template/src/pages/policy/settlementPolicyData.ts +296 -0
- package/template/src/pages/products/MinOrderQtyPage.vue +2 -1
- package/template/src/pages/products/PartnerPricingPage.vue +2 -1
- package/template/src/pages/products/ProductsListPage.vue +2 -1
- package/template/src/pages/products/SupplyPricePage.vue +2 -1
- package/template/src/pages/promotions/PromotionApplicationsPage.vue +2 -1
- package/template/src/pages/promotions/PromotionStackSettingPage.vue +13 -12
- package/template/src/pages/promotions/PromotionsListPage.vue +1 -1
- package/template/src/pages/quotes/QuoteApprovalPage.vue +2 -1
- package/template/src/pages/quotes/QuoteHistoryPage.vue +2 -1
- package/template/src/pages/quotes/QuoteRequestsPage.vue +2 -1
- package/template/src/pages/quotes/QuotesPage.vue +2 -1
- package/template/src/pages/records/RecordsPage.vue +7 -5
- package/template/src/pages/returns/ReturnHistoryPage.vue +1 -0
- package/template/src/pages/returns/ReturnListPage.vue +1 -0
- package/template/src/pages/reviews/ReviewAnalyticsPage.vue +2 -1
- package/template/src/pages/reviews/ReviewsListPage.vue +1 -1
- package/template/src/pages/search/GlobalSearchPage.module.css +706 -0
- package/template/src/pages/search/GlobalSearchPage.vue +632 -0
- package/template/src/pages/settlement/SettlementAdjustmentsPage.vue +2 -1
- package/template/src/pages/settlement/SettlementPage.vue +2 -1
- package/template/src/pages/settlement/SettlementTransactionsPage.vue +2 -1
- package/template/src/pages/stats/CancelRateStatsPage.vue +4 -2
- package/template/src/pages/stats/CustomerPurchaseAnalysisPage.vue +4 -2
- package/template/src/pages/stats/DisputeRateStatsPage.vue +4 -2
- package/template/src/pages/stats/ProductRegistrationStatsPage.vue +3 -1
- package/template/src/pages/stats/ReportRateStatsPage.vue +4 -2
- package/template/src/pages/stats/SalesAnalysisPage.vue +9 -7
- package/template/src/pages/stats/SellerProceedsStatsPage.vue +4 -2
- package/template/src/pages/stats/StatisticsFilterToolbar.vue +2 -1
- package/template/src/pages/stats/TrafficStatsPage.vue +2 -1
- package/template/src/pages/stats/useStatisticsPeriod.ts +10 -0
- package/template/src/pages/system/CommonCodesPage.vue +1 -1
- package/template/src/pages/system/IntegrationManagementPage.vue +1 -1
- package/template/src/pages/system/JobManagementPage.vue +1 -1
- package/template/src/pages/system/ServiceSettingsPage.vue +1 -1
- package/template/src/router/routes.b2cProducts.ts +10 -12
- package/template/src/router/routes.brands.ts +3 -4
- package/template/src/router/routes.notifications.ts +16 -0
- package/template/src/router/routes.ops.ts +21 -0
- package/template/src/router/routes.policy.ts +17 -0
- package/template/src/router/routes.search.ts +6 -0
- package/template/src/router/routes.ts +8 -0
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue';
|
|
2
3
|
import { Badge } from 'm2m-uiux-vue/Badge';
|
|
3
4
|
import styles from './DataGrid.module.css';
|
|
4
5
|
import RowMenuView from './RowMenuView.vue';
|
|
5
6
|
import type { Cell } from './types';
|
|
6
7
|
|
|
7
8
|
defineProps<{ cell: Cell }>();
|
|
9
|
+
|
|
10
|
+
const thumbFailed = ref(false);
|
|
8
11
|
</script>
|
|
9
12
|
|
|
10
13
|
<template>
|
|
@@ -85,7 +88,14 @@ defineProps<{ cell: Cell }>();
|
|
|
85
88
|
</span>
|
|
86
89
|
|
|
87
90
|
<div v-else-if="cell.kind === 'thumbTitle'" :class="styles.thumbTitle">
|
|
88
|
-
<
|
|
91
|
+
<img
|
|
92
|
+
v-if="cell.imageSrc && !thumbFailed"
|
|
93
|
+
:class="[styles.thumb, styles.thumbImage]"
|
|
94
|
+
:src="cell.imageSrc"
|
|
95
|
+
alt=""
|
|
96
|
+
@error="thumbFailed = true"
|
|
97
|
+
/>
|
|
98
|
+
<div v-else :class="styles.thumb" :style="{ background: `repeating-linear-gradient(135deg, ${cell.thumb} 0 6px, #fff 6px 12px)` }" aria-hidden="true" />
|
|
89
99
|
<div style="min-width: 0">
|
|
90
100
|
<button type="button" :class="styles.thumbTitleBtn" :style="{ color: cell.titleFg }" @click="cell.onClick?.()">{{ cell.title }}</button>
|
|
91
101
|
<span :class="styles.thumbTitleId">#{{ cell.id }}</span>
|
|
@@ -71,12 +71,49 @@
|
|
|
71
71
|
position: sticky;
|
|
72
72
|
}
|
|
73
73
|
.headBtn {
|
|
74
|
+
display: inline-flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
gap: 3px;
|
|
74
77
|
border: 0;
|
|
75
78
|
background: transparent;
|
|
76
|
-
padding:
|
|
79
|
+
padding: 3px 6px;
|
|
80
|
+
margin: -3px -6px;
|
|
81
|
+
border-radius: 5px;
|
|
77
82
|
font: inherit;
|
|
78
83
|
color: inherit;
|
|
79
84
|
cursor: pointer;
|
|
85
|
+
transition:
|
|
86
|
+
background-color 0.12s ease,
|
|
87
|
+
color 0.12s ease;
|
|
88
|
+
user-select: none;
|
|
89
|
+
max-width: 100%;
|
|
90
|
+
}
|
|
91
|
+
.headBtn:hover {
|
|
92
|
+
background: rgba(0, 0, 0, 0.05);
|
|
93
|
+
color: #18181b;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.sortIcon {
|
|
97
|
+
display: inline-flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
justify-content: center;
|
|
100
|
+
font-size: 0.59375rem;
|
|
101
|
+
line-height: 1;
|
|
102
|
+
color: #a1a1aa;
|
|
103
|
+
opacity: 0.35;
|
|
104
|
+
transition:
|
|
105
|
+
opacity 0.12s ease,
|
|
106
|
+
color 0.12s ease;
|
|
107
|
+
flex: none;
|
|
108
|
+
}
|
|
109
|
+
.headBtn:hover .sortIcon {
|
|
110
|
+
opacity: 0.85;
|
|
111
|
+
color: #52525b;
|
|
112
|
+
}
|
|
113
|
+
.sortIconActive {
|
|
114
|
+
opacity: 1 !important;
|
|
115
|
+
color: var(--common-primary, #2563eb) !important;
|
|
116
|
+
font-weight: 700;
|
|
80
117
|
}
|
|
81
118
|
|
|
82
119
|
.row {
|
|
@@ -378,6 +415,9 @@
|
|
|
378
415
|
border-radius: 6px;
|
|
379
416
|
border: 1px solid rgba(0, 0, 0, 0.07);
|
|
380
417
|
}
|
|
418
|
+
.thumbImage {
|
|
419
|
+
object-fit: cover;
|
|
420
|
+
}
|
|
381
421
|
.thumbTitleBtn {
|
|
382
422
|
display: block;
|
|
383
423
|
max-width: 100%;
|
|
@@ -6,7 +6,10 @@ import CellView from './CellView.vue';
|
|
|
6
6
|
import styles from './DataGrid.module.css';
|
|
7
7
|
import type { Cell, DataGridProps, GridColumn, GridRow } from './types';
|
|
8
8
|
|
|
9
|
-
const props = defineProps<DataGridProps>()
|
|
9
|
+
const props = withDefaults(defineProps<DataGridProps>(), {
|
|
10
|
+
sortable: true,
|
|
11
|
+
showPagination: true,
|
|
12
|
+
});
|
|
10
13
|
|
|
11
14
|
const isInlineDetailLabel = (value?: string) => Boolean(value && /^상세(?:\s*보기)?$/.test(value.trim()));
|
|
12
15
|
|
|
@@ -62,6 +65,38 @@ function withoutGridTracks(template: string, removed: Set<number>, columnCount:
|
|
|
62
65
|
return template;
|
|
63
66
|
}
|
|
64
67
|
|
|
68
|
+
function parseSortableValue(val: unknown): { isNum: boolean; num: number; str: string } {
|
|
69
|
+
if (typeof val === 'number') return { isNum: !Number.isNaN(val), num: val, str: String(val) };
|
|
70
|
+
if (typeof val !== 'string') val = String(val ?? '');
|
|
71
|
+
const str = (val as string).trim();
|
|
72
|
+
if (!str) return { isNum: false, num: 0, str: '' };
|
|
73
|
+
const cleanStr = str.replace(/,/g, '').replace(/[원개건회명%₩$\s]/g, '');
|
|
74
|
+
if (cleanStr && !Number.isNaN(Number(cleanStr)) && !str.includes('/') && !str.includes(':')) {
|
|
75
|
+
const isNegativeOrSingle = !str.includes('-') || (str.startsWith('-') && str.indexOf('-', 1) === -1);
|
|
76
|
+
if (isNegativeOrSingle) return { isNum: true, num: Number(cleanStr), str };
|
|
77
|
+
}
|
|
78
|
+
return { isNum: false, num: 0, str };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getCellRawValue(cell: Cell | undefined): unknown {
|
|
82
|
+
if (!cell) return '';
|
|
83
|
+
if (cell.kind === 'progress') return cell.pct;
|
|
84
|
+
if (cell.kind === 'custom') return cell.exportValue ?? '';
|
|
85
|
+
return cellExportValue(cell);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function compareCells(cellA: Cell | undefined, cellB: Cell | undefined, direction: 'asc' | 'desc'): number {
|
|
89
|
+
const parsedA = parseSortableValue(getCellRawValue(cellA));
|
|
90
|
+
const parsedB = parseSortableValue(getCellRawValue(cellB));
|
|
91
|
+
if (!parsedA.str && !parsedB.str) return 0;
|
|
92
|
+
if (!parsedA.str) return 1;
|
|
93
|
+
if (!parsedB.str) return -1;
|
|
94
|
+
let comparison = 0;
|
|
95
|
+
if (parsedA.isNum && parsedB.isNum) comparison = parsedA.num - parsedB.num;
|
|
96
|
+
else comparison = parsedA.str.localeCompare(parsedB.str, 'ko', { numeric: true, sensitivity: 'base' });
|
|
97
|
+
return direction === 'asc' ? comparison : -comparison;
|
|
98
|
+
}
|
|
99
|
+
|
|
65
100
|
function cellExportValue(cell: Cell): string {
|
|
66
101
|
switch (cell.kind) {
|
|
67
102
|
case 'text':
|
|
@@ -136,10 +171,12 @@ function cellFgColor(cell: Cell): string | undefined {
|
|
|
136
171
|
}
|
|
137
172
|
|
|
138
173
|
const rootRef = ref<HTMLDivElement | null>(null);
|
|
139
|
-
const autoSelectable = ref(false);
|
|
140
174
|
const internalSelected = ref<Set<string | number>>(new Set());
|
|
141
175
|
const pageSize = ref<number | null>(null);
|
|
142
176
|
const currentPage = ref(1);
|
|
177
|
+
const sortColumnIndex = ref<number | null>(props.defaultSortColumn ?? null);
|
|
178
|
+
const sortDirection = ref<'asc' | 'desc' | null>(props.defaultSortDirection ?? null);
|
|
179
|
+
const effectiveSortable = computed(() => props.sortable ?? true);
|
|
143
180
|
|
|
144
181
|
function handlePageSize(event: Event) {
|
|
145
182
|
const size = (event as CustomEvent<{ pageSize?: number }>).detail?.pageSize;
|
|
@@ -150,9 +187,6 @@ function handlePageSize(event: Event) {
|
|
|
150
187
|
|
|
151
188
|
onMounted(() => {
|
|
152
189
|
rootRef.value?.addEventListener(DATA_GRID_PAGE_SIZE_EVENT, handlePageSize);
|
|
153
|
-
if (props.selectable === undefined) {
|
|
154
|
-
autoSelectable.value = Boolean(rootRef.value?.ownerDocument.querySelector('[data-excel-download]'));
|
|
155
|
-
}
|
|
156
190
|
});
|
|
157
191
|
onUnmounted(() => {
|
|
158
192
|
rootRef.value?.removeEventListener(DATA_GRID_PAGE_SIZE_EVENT, handlePageSize);
|
|
@@ -172,7 +206,7 @@ watch(
|
|
|
172
206
|
}
|
|
173
207
|
);
|
|
174
208
|
|
|
175
|
-
const effectiveSelectable = computed(() => props.selectable ??
|
|
209
|
+
const effectiveSelectable = computed(() => props.selectable ?? false);
|
|
176
210
|
const pageStart = computed(() => (pageSize.value ? (currentPage.value - 1) * pageSize.value : 0));
|
|
177
211
|
const visibleSourceRows = computed(() => (pageSize.value ? props.rows.slice(pageStart.value, pageStart.value + pageSize.value) : props.rows));
|
|
178
212
|
|
|
@@ -226,7 +260,42 @@ const displayRows = computed(() =>
|
|
|
226
260
|
}),
|
|
227
261
|
}))
|
|
228
262
|
);
|
|
229
|
-
const
|
|
263
|
+
const sortedDisplayRows = computed(() => {
|
|
264
|
+
if (sortColumnIndex.value === null || sortDirection.value === null) return displayRows.value;
|
|
265
|
+
const index = sortColumnIndex.value;
|
|
266
|
+
const direction = sortDirection.value;
|
|
267
|
+
return [...displayRows.value].sort((rowA, rowB) => compareCells(rowA.cells[index], rowB.cells[index], direction));
|
|
268
|
+
});
|
|
269
|
+
const pagedDisplayRows = computed(() => (pageSize.value ? sortedDisplayRows.value.slice(pageStart.value, pageStart.value + pageSize.value) : sortedDisplayRows.value));
|
|
270
|
+
|
|
271
|
+
function isSortableColumn(col: GridColumn) {
|
|
272
|
+
return effectiveSortable.value && (col.sortable ?? true) && !(typeof col.label === 'string' && ['관리', '상세', '설정', '메뉴'].includes(col.label.trim()));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
watch(displayColumns, (columns) => {
|
|
276
|
+
if (sortColumnIndex.value !== null && sortColumnIndex.value >= columns.length) {
|
|
277
|
+
sortColumnIndex.value = null;
|
|
278
|
+
sortDirection.value = null;
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
function handleHeaderClick(columnIndex: number, col: GridColumn) {
|
|
283
|
+
col.onClick?.();
|
|
284
|
+
if (!isSortableColumn(col)) return;
|
|
285
|
+
let nextDirection: 'asc' | 'desc' | null = 'asc';
|
|
286
|
+
let nextColumn: number | null = columnIndex;
|
|
287
|
+
if (sortColumnIndex.value === columnIndex) {
|
|
288
|
+
if (sortDirection.value === 'asc') nextDirection = 'desc';
|
|
289
|
+
else if (sortDirection.value === 'desc') {
|
|
290
|
+
nextDirection = null;
|
|
291
|
+
nextColumn = null;
|
|
292
|
+
} else nextDirection = 'asc';
|
|
293
|
+
}
|
|
294
|
+
sortColumnIndex.value = nextColumn;
|
|
295
|
+
sortDirection.value = nextDirection;
|
|
296
|
+
currentPage.value = 1;
|
|
297
|
+
props.onSortChange?.(nextColumn, nextDirection);
|
|
298
|
+
}
|
|
230
299
|
|
|
231
300
|
const temporalColumnIndexes = computed(() => new Set(displayColumns.value.flatMap((column, columnIndex) => (isTemporalColumnLabel(column.label) ? [columnIndex] : []))));
|
|
232
301
|
const serialColumnIndexes = computed(() => new Set(displayColumns.value.flatMap((column, columnIndex) => (isSerialColumnLabel(column.label) ? [columnIndex] : []))));
|
|
@@ -315,9 +384,24 @@ function cellAlignOf(cell: Cell, index: number) {
|
|
|
315
384
|
<input type="checkbox" :class="styles.checkbox" :checked="effectiveAllSelected" style="pointer-events: none" aria-label="현재 목록 전체 선택" @change="() => {}" />
|
|
316
385
|
</div>
|
|
317
386
|
<template v-for="(col, i) in displayColumns" :key="i">
|
|
318
|
-
<
|
|
319
|
-
|
|
320
|
-
|
|
387
|
+
<div
|
|
388
|
+
v-if="isSortableColumn(col) || col.onClick"
|
|
389
|
+
:style="{ display: 'flex', justifyContent: centeredColumnIndexes.has(i) ? 'center' : col.align === 'right' ? 'flex-end' : 'flex-start', minWidth: 0 }"
|
|
390
|
+
>
|
|
391
|
+
<button
|
|
392
|
+
type="button"
|
|
393
|
+
:class="styles.headBtn"
|
|
394
|
+
:style="{ justifyContent: centeredColumnIndexes.has(i) ? 'center' : col.align === 'right' ? 'flex-end' : 'flex-start' }"
|
|
395
|
+
:aria-sort="sortColumnIndex === i && sortDirection ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'"
|
|
396
|
+
:title="isSortableColumn(col) ? (sortColumnIndex !== i || !sortDirection ? '오름차순 정렬' : sortDirection === 'asc' ? '내림차순 정렬' : '정렬 해제') : undefined"
|
|
397
|
+
@click="handleHeaderClick(i, col)"
|
|
398
|
+
>
|
|
399
|
+
<span data-datagrid-column>{{ col.label }}</span>
|
|
400
|
+
<span v-if="isSortableColumn(col)" :class="[styles.sortIcon, sortColumnIndex === i && sortDirection && styles.sortIconActive]" aria-hidden="true">{{
|
|
401
|
+
sortColumnIndex === i && sortDirection === 'asc' ? '▲' : sortColumnIndex === i && sortDirection === 'desc' ? '▼' : '⇅'
|
|
402
|
+
}}</span>
|
|
403
|
+
</button>
|
|
404
|
+
</div>
|
|
321
405
|
<span v-else :style="{ textAlign: centeredColumnIndexes.has(i) ? 'center' : col.align }" data-datagrid-column>{{ col.label }}</span>
|
|
322
406
|
</template>
|
|
323
407
|
</div>
|
|
@@ -100,6 +100,7 @@ export interface NoTagCell extends BaseCell {
|
|
|
100
100
|
export interface ThumbTitleCell extends BaseCell {
|
|
101
101
|
kind: 'thumbTitle';
|
|
102
102
|
thumb: string;
|
|
103
|
+
imageSrc?: string;
|
|
103
104
|
title: string;
|
|
104
105
|
id: string;
|
|
105
106
|
titleFg?: string;
|
|
@@ -161,6 +162,7 @@ export interface GridColumn {
|
|
|
161
162
|
label: string;
|
|
162
163
|
align?: Align;
|
|
163
164
|
onClick?: () => void;
|
|
165
|
+
sortable?: boolean;
|
|
164
166
|
}
|
|
165
167
|
|
|
166
168
|
export interface PageBtn {
|
|
@@ -191,4 +193,8 @@ export interface DataGridProps {
|
|
|
191
193
|
showTopBar?: boolean;
|
|
192
194
|
totalLabel?: string;
|
|
193
195
|
actions?: { label: string; onClick: () => void }[];
|
|
196
|
+
sortable?: boolean;
|
|
197
|
+
defaultSortColumn?: number;
|
|
198
|
+
defaultSortDirection?: 'asc' | 'desc';
|
|
199
|
+
onSortChange?: (columnIndex: number | null, direction: 'asc' | 'desc' | null) => void;
|
|
194
200
|
}
|
|
@@ -15,7 +15,7 @@ const emit = defineEmits<{ click: [MouseEvent] }>();
|
|
|
15
15
|
type="button"
|
|
16
16
|
variant="secondary"
|
|
17
17
|
size="sm"
|
|
18
|
-
:class="[$style.button, className]"
|
|
18
|
+
:class-names="[$style.button, className ?? '']"
|
|
19
19
|
data-excel-download
|
|
20
20
|
data-grid-download
|
|
21
21
|
@click="emit('click', $event)"
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
display: inline-flex;
|
|
4
4
|
align-items: center;
|
|
5
5
|
vertical-align: middle;
|
|
6
|
+
/* 부모가 flex/grid일 때 늘어나면 화살표(absolute)가 박스 밖에 떠버린다 */
|
|
7
|
+
width: fit-content;
|
|
8
|
+
flex: none;
|
|
9
|
+
align-self: flex-start;
|
|
6
10
|
}
|
|
7
11
|
|
|
8
12
|
.select {
|
|
@@ -39,13 +43,3 @@
|
|
|
39
43
|
cursor: not-allowed;
|
|
40
44
|
background-color: var(--common-bg-disabled, #f4f4f5);
|
|
41
45
|
}
|
|
42
|
-
|
|
43
|
-
.arrowIcon {
|
|
44
|
-
position: absolute;
|
|
45
|
-
right: 9px;
|
|
46
|
-
top: 50%;
|
|
47
|
-
transform: translateY(-50%);
|
|
48
|
-
pointer-events: none;
|
|
49
|
-
color: var(--common-text-muted, #71717a);
|
|
50
|
-
flex-shrink: 0;
|
|
51
|
-
}
|
|
@@ -19,7 +19,6 @@ export const DATA_GRID_PAGE_SIZE_EVENT = 'data-grid-page-size-change';
|
|
|
19
19
|
|
|
20
20
|
<script setup lang="ts">
|
|
21
21
|
import { onMounted, onUnmounted, ref } from 'vue';
|
|
22
|
-
import { ChevronDown } from 'lucide-vue-next';
|
|
23
22
|
|
|
24
23
|
function pageSizeOf(value: string) {
|
|
25
24
|
const parsed = Number.parseInt(value.replace(/[^0-9]/g, ''), 10);
|
|
@@ -50,6 +49,8 @@ function notifyNearestDataGrid(select: HTMLSelectElement) {
|
|
|
50
49
|
grid?.dispatchEvent(new CustomEvent(DATA_GRID_PAGE_SIZE_EVENT, { detail: { pageSize } }));
|
|
51
50
|
}
|
|
52
51
|
|
|
52
|
+
defineOptions({ inheritAttrs: false });
|
|
53
|
+
|
|
53
54
|
const props = withDefaults(defineProps<PageSizeSelectProps>(), {
|
|
54
55
|
options: () => DEFAULT_PAGE_SIZE_OPTIONS,
|
|
55
56
|
defaultValue: '20개씩 보기',
|
|
@@ -81,6 +82,7 @@ function handleChange(event: Event) {
|
|
|
81
82
|
<div :class="[$style.wrapper, wrapperClassName].filter(Boolean).join(' ')">
|
|
82
83
|
<select
|
|
83
84
|
ref="selectRef"
|
|
85
|
+
v-bind="$attrs"
|
|
84
86
|
:aria-label="props['aria-label'] ?? '페이지당 표시 개수'"
|
|
85
87
|
:value="value ?? defaultValue"
|
|
86
88
|
:class="[$style.select, className].filter(Boolean).join(' ')"
|
|
@@ -92,7 +94,6 @@ function handleChange(event: Event) {
|
|
|
92
94
|
</option>
|
|
93
95
|
</slot>
|
|
94
96
|
</select>
|
|
95
|
-
<ChevronDown :size="14" :class="$style.arrowIcon" aria-hidden="true" />
|
|
96
97
|
</div>
|
|
97
98
|
</template>
|
|
98
99
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onMounted, onUnmounted, ref } from 'vue';
|
|
3
|
+
import { useRouter } from 'vue-router';
|
|
4
|
+
import SearchField from '../SearchField/SearchField.vue';
|
|
5
|
+
import styles from './GlobalSearchBar.module.css';
|
|
6
|
+
|
|
7
|
+
withDefaults(defineProps<{ placeholder?: string; className?: string }>(), {
|
|
8
|
+
placeholder: '메뉴, 상품, 주문 검색',
|
|
9
|
+
className: '',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const router = useRouter();
|
|
13
|
+
const wrapRef = ref<HTMLElement | null>(null);
|
|
14
|
+
const keyword = ref('');
|
|
15
|
+
|
|
16
|
+
function focusInput() {
|
|
17
|
+
wrapRef.value?.querySelector('input')?.focus();
|
|
18
|
+
wrapRef.value?.querySelector('input')?.select();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function handler(e: KeyboardEvent) {
|
|
22
|
+
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') {
|
|
23
|
+
e.preventDefault();
|
|
24
|
+
focusInput();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
onMounted(() => window.addEventListener('keydown', handler));
|
|
29
|
+
onUnmounted(() => window.removeEventListener('keydown', handler));
|
|
30
|
+
|
|
31
|
+
function runSearch(value: string) {
|
|
32
|
+
const query = value.trim();
|
|
33
|
+
if (!query) {
|
|
34
|
+
wrapRef.value?.querySelector('input')?.focus();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
router.push(`/search?q=${encodeURIComponent(query)}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
41
|
+
if (e.key === 'Enter') {
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
runSearch(keyword.value);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<template>
|
|
49
|
+
<span ref="wrapRef">
|
|
50
|
+
<SearchField
|
|
51
|
+
size="sm"
|
|
52
|
+
clearable
|
|
53
|
+
full-width
|
|
54
|
+
:class-name="`${styles.bar} ${className}`"
|
|
55
|
+
:placeholder="placeholder"
|
|
56
|
+
shortcut-hint="Ctrl K"
|
|
57
|
+
:value="keyword"
|
|
58
|
+
:on-value-change="(v: string) => (keyword = v)"
|
|
59
|
+
:on-search="runSearch"
|
|
60
|
+
@keydown="handleKeyDown"
|
|
61
|
+
aria-label="통합 검색"
|
|
62
|
+
/>
|
|
63
|
+
</span>
|
|
64
|
+
</template>
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
.header {
|
|
2
2
|
display: flex;
|
|
3
3
|
align-items: center;
|
|
4
|
-
gap:
|
|
4
|
+
gap: 12px;
|
|
5
5
|
height: 52px;
|
|
6
|
-
padding: 0
|
|
6
|
+
padding: 0 20px;
|
|
7
7
|
background: var(--common-bg);
|
|
8
8
|
border-bottom: 1px solid var(--common-border);
|
|
9
|
+
border-radius: 0;
|
|
9
10
|
flex: none;
|
|
11
|
+
width: 100%;
|
|
12
|
+
box-sizing: border-box;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
.crumb {
|
|
@@ -15,6 +18,9 @@
|
|
|
15
18
|
gap: 7px;
|
|
16
19
|
font-size: 0.78125rem;
|
|
17
20
|
color: var(--common-text-muted);
|
|
21
|
+
white-space: nowrap;
|
|
22
|
+
flex-shrink: 0;
|
|
23
|
+
min-width: max-content;
|
|
18
24
|
}
|
|
19
25
|
.crumbSep {
|
|
20
26
|
color: var(--common-border-strong);
|
|
@@ -22,10 +28,26 @@
|
|
|
22
28
|
.crumbLeaf {
|
|
23
29
|
color: var(--common-text);
|
|
24
30
|
font-weight: 600;
|
|
31
|
+
white-space: nowrap;
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
.spacer {
|
|
28
35
|
flex: 1;
|
|
36
|
+
min-width: 8px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.searchWrapper {
|
|
40
|
+
flex: 0 1 340px;
|
|
41
|
+
min-width: 180px;
|
|
42
|
+
max-width: 400px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.rightGroup {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: 8px;
|
|
49
|
+
flex-shrink: 0;
|
|
50
|
+
white-space: nowrap;
|
|
29
51
|
}
|
|
30
52
|
|
|
31
53
|
.modeSwitcherWrapper {
|
|
@@ -37,7 +59,7 @@
|
|
|
37
59
|
padding: 0 10px;
|
|
38
60
|
border: 1px solid var(--common-border);
|
|
39
61
|
background: var(--common-bg);
|
|
40
|
-
border-radius:
|
|
62
|
+
border-radius: var(--common-radius-md);
|
|
41
63
|
color: var(--common-text);
|
|
42
64
|
font-size: 0.75rem;
|
|
43
65
|
font-weight: 600;
|
|
@@ -57,7 +79,7 @@
|
|
|
57
79
|
font-size: 0.65625rem;
|
|
58
80
|
font-weight: 700;
|
|
59
81
|
padding: 1px 6px;
|
|
60
|
-
border-radius:
|
|
82
|
+
border-radius: var(--common-radius-sm);
|
|
61
83
|
}
|
|
62
84
|
|
|
63
85
|
.modeDropdown {
|
|
@@ -67,7 +89,7 @@
|
|
|
67
89
|
min-width: 175px;
|
|
68
90
|
background: var(--common-bg);
|
|
69
91
|
border: 1px solid var(--common-border);
|
|
70
|
-
border-radius:
|
|
92
|
+
border-radius: var(--common-radius-lg);
|
|
71
93
|
box-shadow: var(--common-shadow-popover);
|
|
72
94
|
padding: 6px;
|
|
73
95
|
z-index: 1000;
|
|
@@ -81,7 +103,7 @@
|
|
|
81
103
|
align-items: center;
|
|
82
104
|
justify-content: space-between;
|
|
83
105
|
padding: 7px 10px;
|
|
84
|
-
border-radius:
|
|
106
|
+
border-radius: var(--common-radius-sm);
|
|
85
107
|
border: none;
|
|
86
108
|
background: none;
|
|
87
109
|
font-size: 0.75rem;
|
|
@@ -115,7 +137,7 @@
|
|
|
115
137
|
cursor: pointer;
|
|
116
138
|
border: none;
|
|
117
139
|
background: none;
|
|
118
|
-
border-radius:
|
|
140
|
+
border-radius: var(--common-radius-sm);
|
|
119
141
|
}
|
|
120
142
|
|
|
121
143
|
.modeDropdownLink:hover {
|
|
@@ -127,7 +149,7 @@
|
|
|
127
149
|
width: 32px;
|
|
128
150
|
border: 1px solid var(--common-border);
|
|
129
151
|
background: var(--common-bg);
|
|
130
|
-
border-radius:
|
|
152
|
+
border-radius: var(--common-radius-md);
|
|
131
153
|
color: var(--common-text-sub);
|
|
132
154
|
cursor: pointer;
|
|
133
155
|
display: flex;
|
|
@@ -154,7 +176,7 @@
|
|
|
154
176
|
.accountAvatar {
|
|
155
177
|
width: 26px;
|
|
156
178
|
height: 26px;
|
|
157
|
-
border-radius:
|
|
179
|
+
border-radius: var(--common-radius-md);
|
|
158
180
|
background: var(--common-primary-light);
|
|
159
181
|
color: var(--common-primary);
|
|
160
182
|
display: flex;
|
|
@@ -4,6 +4,7 @@ import { Blocks, Settings, ChevronDown, Check } from 'lucide-vue-next';
|
|
|
4
4
|
import { Header as M2MHeader } from 'm2m-uiux-vue/Header';
|
|
5
5
|
import { useRoute, useRouter } from 'vue-router';
|
|
6
6
|
import { activeKeyForPath, breadcrumbForKey } from '../../lib/nav';
|
|
7
|
+
import GlobalSearchBar from './GlobalSearchBar.vue';
|
|
7
8
|
import {
|
|
8
9
|
loadBusinessSettings,
|
|
9
10
|
saveBusinessSettings,
|
|
@@ -86,43 +87,51 @@ function goSystemDisplay() {
|
|
|
86
87
|
|
|
87
88
|
<div :class="styles.spacer" />
|
|
88
89
|
|
|
89
|
-
<div
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
</button>
|
|
90
|
+
<div :class="styles.searchWrapper">
|
|
91
|
+
<GlobalSearchBar />
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div :class="styles.spacer" />
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
:
|
|
100
|
-
|
|
101
|
-
:
|
|
102
|
-
:disabled="!isEnabled(card.mode)"
|
|
103
|
-
:style="{ opacity: isEnabled(card.mode) ? 1 : 0.45 }"
|
|
104
|
-
@click="isEnabled(card.mode) && handleSelectMode(card.mode)"
|
|
105
|
-
>
|
|
106
|
-
<span>{{ card.title }}</span>
|
|
107
|
-
<Check v-if="businessSettings.activeMode === card.mode" :size="14" />
|
|
96
|
+
<div :class="styles.rightGroup">
|
|
97
|
+
<div ref="dropdownRef" :class="styles.modeSwitcherWrapper">
|
|
98
|
+
<button type="button" :class="styles.modeSwitcherBtn" title="작업 비즈니스 모드 변경" @click="modeDropdownOpen = !modeDropdownOpen">
|
|
99
|
+
<span :class="styles.modeTag" :style="{ backgroundColor: modeBadgeColor.bg, color: modeBadgeColor.fg }">{{ currentModeInfo.badge }}</span>
|
|
100
|
+
<span>{{ currentModeInfo.title.split(' ')[0] }}</span>
|
|
101
|
+
<ChevronDown :size="14" />
|
|
108
102
|
</button>
|
|
109
103
|
|
|
110
|
-
<div :class="styles.
|
|
111
|
-
|
|
104
|
+
<div v-if="modeDropdownOpen" :class="styles.modeDropdown">
|
|
105
|
+
<button
|
|
106
|
+
v-for="card in BUSINESS_MODE_CARDS"
|
|
107
|
+
:key="card.mode"
|
|
108
|
+
type="button"
|
|
109
|
+
:class="[styles.modeOption, businessSettings.activeMode === card.mode && styles.modeOptionActive]"
|
|
110
|
+
:disabled="!isEnabled(card.mode)"
|
|
111
|
+
:style="{ opacity: isEnabled(card.mode) ? 1 : 0.45 }"
|
|
112
|
+
@click="isEnabled(card.mode) && handleSelectMode(card.mode)"
|
|
113
|
+
>
|
|
114
|
+
<span>{{ card.title }}</span>
|
|
115
|
+
<Check v-if="businessSettings.activeMode === card.mode" :size="14" />
|
|
116
|
+
</button>
|
|
117
|
+
|
|
118
|
+
<div :class="styles.modeDropdownDivider" />
|
|
119
|
+
<button type="button" :class="styles.modeDropdownLink" @click="goSystemDisplay">운영 설정 관리 →</button>
|
|
120
|
+
</div>
|
|
112
121
|
</div>
|
|
113
|
-
</div>
|
|
114
122
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
<button type="button" :class="[styles.iconBtn, route.path === '/components' && styles.iconBtnActive]" title="컴포넌트" @click="goComponents">
|
|
124
|
+
<Blocks :size="15" :stroke-width="1.8" />
|
|
125
|
+
</button>
|
|
118
126
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
127
|
+
<button type="button" :class="[styles.iconBtn, route.path.startsWith('/system') && styles.iconBtnActive]" title="환경 설정" @click="goSystemDisplay">
|
|
128
|
+
<Settings :size="15" :stroke-width="1.8" />
|
|
129
|
+
</button>
|
|
122
130
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
<div :class="styles.account">
|
|
132
|
+
<div :class="styles.accountAvatar">관</div>
|
|
133
|
+
<span :class="styles.accountName">관리자</span>
|
|
134
|
+
</div>
|
|
126
135
|
</div>
|
|
127
136
|
</M2MHeader>
|
|
128
137
|
</template>
|