create-m2m-bo-template-vue 1.0.5 → 1.0.7

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.
Files changed (89) hide show
  1. package/package.json +1 -1
  2. package/template/src/components/DataGrid/DataGrid.vue +9 -1
  3. package/template/src/components/SearchField/SearchField.module.css +11 -2
  4. package/template/src/components/SearchField/SearchField.vue +5 -1
  5. package/template/src/components/shell/GlobalSearchBar.module.css +6 -0
  6. package/template/src/components/shell/GlobalSearchBar.vue +49 -6
  7. package/template/src/components/shell/Header.vue +8 -1
  8. package/template/src/components/shell/QuickSearchPanel.module.css +547 -0
  9. package/template/src/components/shell/QuickSearchPanel.vue +446 -0
  10. package/template/src/lib/globalSearch.ts +7 -0
  11. package/template/src/lib/recentSearches.ts +29 -0
  12. package/template/src/lib/relatedKeywords.ts +134 -0
  13. package/template/src/lib/searchHistory.ts +169 -0
  14. package/template/src/lib/searchIndex.ts +14 -0
  15. package/template/src/pages/admin/AdminRolesPage.vue +7 -7
  16. package/template/src/pages/auth/Auth.module.css +164 -0
  17. package/template/src/pages/auth/AuthLayout.module.css +85 -0
  18. package/template/src/pages/auth/AuthLayout.vue +29 -0
  19. package/template/src/pages/auth/LoginPage.vue +52 -0
  20. package/template/src/pages/auth/PasswordResetPage.vue +96 -0
  21. package/template/src/pages/auth/TwoFactorPage.vue +97 -0
  22. package/template/src/pages/b2c/products/B2cProductCreatePage.module.css +383 -14
  23. package/template/src/pages/b2c/products/B2cProductCreatePage.vue +315 -113
  24. package/template/src/pages/b2c/products/B2cProductDetailDrawer.module.css +227 -128
  25. package/template/src/pages/b2c/products/B2cProductDetailDrawer.vue +332 -193
  26. package/template/src/pages/b2c/products/B2cProductListPage.vue +5 -7
  27. package/template/src/pages/b2c/products/B2cProductManagementPage.vue +34 -5
  28. package/template/src/pages/b2c/products/B2cStandardInfoPage.vue +0 -8
  29. package/template/src/pages/b2c/products/b2cProductCreateModel.ts +38 -8
  30. package/template/src/pages/c2c/chat/ChatListPage.vue +1 -0
  31. package/template/src/pages/c2c/chat/MessagePolicyPage.vue +1 -0
  32. package/template/src/pages/c2c/products/UserProductModerationPage.vue +0 -1
  33. package/template/src/pages/c2c/purchases/DisputeHistoryPage.vue +1 -0
  34. package/template/src/pages/c2c/purchases/PurchaseActivityHistoryPage.vue +200 -0
  35. package/template/src/pages/c2c/purchases/PurchaseDisputesPage.vue +1 -0
  36. package/template/src/pages/c2c/reports/ReportProcessingPage.vue +1 -1
  37. package/template/src/pages/c2c/sales/SalesTradesPage.vue +1 -1
  38. package/template/src/pages/c2c/sales/SellerListPage.vue +1 -1
  39. package/template/src/pages/c2c/sales/SellerProductsPage.vue +1 -1
  40. package/template/src/pages/c2c/sanctions/SanctionHistoryPage.vue +1 -0
  41. package/template/src/pages/c2c/sanctions/SanctionPolicyPage.vue +1 -0
  42. package/template/src/pages/c2c/sanctions/SanctionProcessingPage.vue +1 -0
  43. package/template/src/pages/cartconversion/CartConversionPage.vue +5 -43
  44. package/template/src/pages/coupons/CouponPolicyPage.vue +30 -21
  45. package/template/src/pages/cs/ConsultationsPage.vue +3 -1
  46. package/template/src/pages/cs/CsHistoryDetailDrawer.vue +3 -1
  47. package/template/src/pages/cs/CsInquiriesPage.vue +3 -2
  48. package/template/src/pages/cs/ProductInquiryDetailDrawer.vue +3 -1
  49. package/template/src/pages/deliverypolicy/RegionalShippingFeePage.vue +4 -4
  50. package/template/src/pages/deliverypolicy/ShippingBaseFeePage.module.css +1 -1
  51. package/template/src/pages/deliverypolicy/ShippingBaseFeePage.vue +26 -28
  52. package/template/src/pages/logs/SystemLogPage.vue +2 -2
  53. package/template/src/pages/members/MembersPage.module.css +4 -0
  54. package/template/src/pages/members/MembersPage.vue +1 -1
  55. package/template/src/pages/orders/ordersShared.module.css +12 -0
  56. package/template/src/pages/points/PointPolicyPage.vue +103 -32
  57. package/template/src/pages/policy/CancelPolicyPage.vue +37 -32
  58. package/template/src/pages/policy/FeePolicyPage.vue +32 -28
  59. package/template/src/pages/policy/PaymentPolicyPage.vue +41 -34
  60. package/template/src/pages/products/MinOrderQtyPage.vue +1 -2
  61. package/template/src/pages/products/PartnerPricingPage.vue +1 -2
  62. package/template/src/pages/products/ProductDetailDrawer.vue +17 -11
  63. package/template/src/pages/products/ProductsListPage.vue +223 -89
  64. package/template/src/pages/products/SupplyPricePage.vue +1 -2
  65. package/template/src/pages/products/productsData.ts +21 -0
  66. package/template/src/pages/promotions/ExposureTooltipCell.vue +108 -0
  67. package/template/src/pages/promotions/PromotionDetailDrawer.module.css +553 -0
  68. package/template/src/pages/promotions/PromotionDetailDrawer.vue +438 -100
  69. package/template/src/pages/promotions/PromotionStackSettingPage.vue +8 -8
  70. package/template/src/pages/promotions/PromotionsListPage.module.css +209 -0
  71. package/template/src/pages/promotions/PromotionsListPage.vue +107 -17
  72. package/template/src/pages/promotions/applicationsData.ts +46 -15
  73. package/template/src/pages/promotions/promotionsData.ts +349 -28
  74. package/template/src/pages/stats/DeliveryClaimsStatsPage.vue +411 -0
  75. package/template/src/pages/stats/InventoryAnalysisPage.vue +853 -0
  76. package/template/src/pages/stats/PromotionAnalysisPage.vue +444 -0
  77. package/template/src/pages/stats/SellerActivityStatsPage.vue +535 -0
  78. package/template/src/pages/stats/StatisticsFilterToolbar.vue +3 -2
  79. package/template/src/pages/stats/TransactionConversionStatsPage.vue +470 -0
  80. package/template/src/pages/system/CommonCodesPage.vue +4 -2
  81. package/template/src/pages/system/DisplaySettingsPage.vue +3 -2
  82. package/template/src/pages/system/IntegrationManagementPage.vue +4 -2
  83. package/template/src/pages/system/JobManagementPage.vue +4 -2
  84. package/template/src/pages/system/ServiceSettingsPage.vue +4 -2
  85. package/template/src/router/index.ts +4 -2
  86. package/template/src/router/routes.auth.ts +12 -0
  87. package/template/src/router/routes.c2cPurchases.ts +2 -0
  88. package/template/src/router/routes.stats.ts +17 -5
  89. package/template/src/style.css +0 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-m2m-bo-template-vue",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Scaffolding CLI for the M2M back-office vue template.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -172,6 +172,7 @@ function cellFgColor(cell: Cell): string | undefined {
172
172
 
173
173
  const rootRef = ref<HTMLDivElement | null>(null);
174
174
  const internalSelected = ref<Set<string | number>>(new Set());
175
+ const autoSelectable = ref(false);
175
176
  const pageSize = ref<number | null>(null);
176
177
  const currentPage = ref(1);
177
178
  const sortColumnIndex = ref<number | null>(props.defaultSortColumn ?? null);
@@ -185,12 +186,19 @@ function handlePageSize(event: Event) {
185
186
  currentPage.value = 1;
186
187
  }
187
188
 
189
+ function updateAutoSelectable() {
190
+ if (props.selectable !== undefined) return;
191
+ autoSelectable.value = Boolean(rootRef.value?.ownerDocument.querySelector('[data-excel-download]'));
192
+ }
193
+
188
194
  onMounted(() => {
189
195
  rootRef.value?.addEventListener(DATA_GRID_PAGE_SIZE_EVENT, handlePageSize);
196
+ updateAutoSelectable();
190
197
  });
191
198
  onUnmounted(() => {
192
199
  rootRef.value?.removeEventListener(DATA_GRID_PAGE_SIZE_EVENT, handlePageSize);
193
200
  });
201
+ watch(() => props.selectable, updateAutoSelectable);
194
202
 
195
203
  const totalPageCount = computed(() => (pageSize.value ? Math.max(1, Math.ceil(props.rows.length / pageSize.value)) : 1));
196
204
  watch(totalPageCount, (count) => {
@@ -206,7 +214,7 @@ watch(
206
214
  }
207
215
  );
208
216
 
209
- const effectiveSelectable = computed(() => props.selectable ?? false);
217
+ const effectiveSelectable = computed(() => props.selectable ?? autoSelectable.value);
210
218
  const pageStart = computed(() => (pageSize.value ? (currentPage.value - 1) * pageSize.value : 0));
211
219
  const visibleSourceRows = computed(() => (pageSize.value ? props.rows.slice(pageStart.value, pageStart.value + pageSize.value) : props.rows));
212
220
 
@@ -36,7 +36,9 @@
36
36
  min-width: 0;
37
37
  max-width: none;
38
38
  height: 100%;
39
- padding: 0 36px 0 12px;
39
+ /* m2m-uiux-vue의 .bsSearchField .bsSearchFieldInput 리셋 규칙과 클래스 특이도가
40
+ 같아 로드 순서에 따라 우리 패딩이 밀릴 수 있다 — !important로 확정 고정 */
41
+ padding: 0 36px 0 12px !important;
40
42
  border: 0;
41
43
  outline: 0;
42
44
  background: transparent;
@@ -46,7 +48,7 @@
46
48
  }
47
49
 
48
50
  .field:has(.shortcut) .control > input {
49
- padding-right: 64px;
51
+ padding-right: 64px !important;
50
52
  }
51
53
 
52
54
  .control > button:first-child {
@@ -71,6 +73,13 @@
71
73
  font-size: 0.625rem;
72
74
  pointer-events: none;
73
75
  transform: translateY(-50%);
76
+ background: #fff;
77
+ }
78
+
79
+ /* 입력 내용이 있거나 포커스 시 shortcut 자동 숨김 */
80
+ .field:has(input:not(:placeholder-shown)) .shortcut,
81
+ .field:has(input:focus) .shortcut {
82
+ display: none !important;
74
83
  }
75
84
 
76
85
  @media (max-width: 720px) {
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { computed } from 'vue';
2
3
  import { SearchField as M2MSearchField } from 'm2m-uiux-vue/SearchField';
3
4
 
4
5
  export interface SearchFieldProps {
@@ -26,6 +27,9 @@ function handleUpdate(value: string) {
26
27
  props.onChange?.({ target: { value } } as unknown as Event);
27
28
  props.onValueChange?.(value);
28
29
  }
30
+
31
+ const currentVal = computed(() => String((props.value ?? props.defaultValue) ?? ''));
32
+ const hasValue = computed(() => currentVal.value.trim().length > 0);
29
33
  </script>
30
34
 
31
35
  <template>
@@ -39,7 +43,7 @@ function handleUpdate(value: string) {
39
43
  :class-names="$style.control"
40
44
  @update:model-value="handleUpdate"
41
45
  />
42
- <span v-if="shortcutHint" :class="$style.shortcut" aria-hidden="true">{{ shortcutHint }}</span>
46
+ <span v-if="shortcutHint && !hasValue" :class="$style.shortcut" aria-hidden="true">{{ shortcutHint }}</span>
43
47
  </span>
44
48
  </template>
45
49
 
@@ -1,3 +1,9 @@
1
+ .wrapper {
2
+ width: 100%;
3
+ position: relative;
4
+ /* 퀵서치 패널이 wrapper를 기준으로 absolute 포지셔닝되도록 */
5
+ }
6
+
1
7
  .bar {
2
8
  width: 100%;
3
9
  }
@@ -2,6 +2,8 @@
2
2
  import { onMounted, onUnmounted, ref } from 'vue';
3
3
  import { useRouter } from 'vue-router';
4
4
  import SearchField from '../SearchField/SearchField.vue';
5
+ import QuickSearchPanel from './QuickSearchPanel.vue';
6
+ import { addRecentSearch } from '../../lib/recentSearches';
5
7
  import styles from './GlobalSearchBar.module.css';
6
8
 
7
9
  withDefaults(defineProps<{ placeholder?: string; className?: string }>(), {
@@ -12,12 +14,18 @@ withDefaults(defineProps<{ placeholder?: string; className?: string }>(), {
12
14
  const router = useRouter();
13
15
  const wrapRef = ref<HTMLElement | null>(null);
14
16
  const keyword = ref('');
17
+ const panelOpen = ref(false);
18
+
19
+ function getInput() {
20
+ return wrapRef.value?.querySelector('input') ?? null;
21
+ }
15
22
 
16
23
  function focusInput() {
17
- wrapRef.value?.querySelector('input')?.focus();
18
- wrapRef.value?.querySelector('input')?.select();
24
+ getInput()?.focus();
25
+ getInput()?.select();
19
26
  }
20
27
 
28
+ // Ctrl/⌘ + K 로 검색창 포커스
21
29
  function handler(e: KeyboardEvent) {
22
30
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') {
23
31
  e.preventDefault();
@@ -31,22 +39,46 @@ onUnmounted(() => window.removeEventListener('keydown', handler));
31
39
  function runSearch(value: string) {
32
40
  const query = value.trim();
33
41
  if (!query) {
34
- wrapRef.value?.querySelector('input')?.focus();
42
+ focusInput();
35
43
  return;
36
44
  }
45
+ addRecentSearch(query);
46
+ panelOpen.value = false;
37
47
  router.push(`/search?q=${encodeURIComponent(query)}`);
38
48
  }
39
49
 
50
+ function handleSelectKeyword(kw: string) {
51
+ keyword.value = kw;
52
+ runSearch(kw);
53
+ }
54
+
55
+ function handleValueChange(value: string) {
56
+ keyword.value = value;
57
+ panelOpen.value = true;
58
+ }
59
+
60
+ function handleFocus() {
61
+ panelOpen.value = true;
62
+ }
63
+
64
+ function handleClose() {
65
+ panelOpen.value = false;
66
+ focusInput();
67
+ }
68
+
40
69
  function handleKeyDown(e: KeyboardEvent) {
41
70
  if (e.key === 'Enter') {
42
71
  e.preventDefault();
43
72
  runSearch(keyword.value);
44
73
  }
74
+ if ((e.key === 'ArrowDown' || e.key === 'ArrowUp') && panelOpen.value) {
75
+ e.preventDefault();
76
+ }
45
77
  }
46
78
  </script>
47
79
 
48
80
  <template>
49
- <span ref="wrapRef">
81
+ <div ref="wrapRef" :class="`${styles.wrapper} ${className}`" style="position: relative">
50
82
  <SearchField
51
83
  size="sm"
52
84
  clearable
@@ -55,10 +87,21 @@ function handleKeyDown(e: KeyboardEvent) {
55
87
  :placeholder="placeholder"
56
88
  shortcut-hint="Ctrl K"
57
89
  :value="keyword"
58
- :on-value-change="(v: string) => (keyword = v)"
90
+ :on-value-change="handleValueChange"
59
91
  :on-search="runSearch"
60
92
  @keydown="handleKeyDown"
93
+ @focusin="handleFocus"
61
94
  aria-label="통합 검색"
95
+ :aria-expanded="panelOpen"
96
+ aria-haspopup="listbox"
97
+ />
98
+
99
+ <QuickSearchPanel
100
+ v-if="panelOpen"
101
+ :query="keyword.trim()"
102
+ :anchor-el="wrapRef"
103
+ @close="handleClose"
104
+ @select-keyword="handleSelectKeyword"
62
105
  />
63
- </span>
106
+ </div>
64
107
  </template>
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, onMounted, onUnmounted, ref } from 'vue';
3
- import { Blocks, Settings, ChevronDown, Check } from 'lucide-vue-next';
3
+ import { Blocks, Settings, ChevronDown, Check, LogOut } 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';
@@ -75,6 +75,9 @@ function goSystemDisplay() {
75
75
  modeDropdownOpen.value = false;
76
76
  router.push('/system/display');
77
77
  }
78
+ function logout() {
79
+ router.push('/login');
80
+ }
78
81
  </script>
79
82
 
80
83
  <template>
@@ -128,6 +131,10 @@ function goSystemDisplay() {
128
131
  <Settings :size="15" :stroke-width="1.8" />
129
132
  </button>
130
133
 
134
+ <button type="button" :class="styles.iconBtn" title="로그아웃" @click="logout">
135
+ <LogOut :size="15" :stroke-width="1.8" />
136
+ </button>
137
+
131
138
  <div :class="styles.account">
132
139
  <div :class="styles.accountAvatar">관</div>
133
140
  <span :class="styles.accountName">관리자</span>