1+ <template >
2+ <div class =" pivot-model-example" >
3+ <h1 >PivotModel 양방향 바인딩 사용 예제</h1 >
4+
5+ <!-- 기본 사용법 -->
6+ <section class =" basic-usage" >
7+ <h2 >기본 사용법</h2 >
8+ <div class =" controls" >
9+ <button @click =" resetModel" >모델 초기화</button >
10+ <button @click =" saveToLocalStorage" >로컬 저장</button >
11+ <button @click =" loadFromLocalStorage" >로컬 로드</button >
12+ </div >
13+
14+ <div class =" model-status" >
15+ <h3 >현재 PivotModel 상태:</h3 >
16+ <pre >{{ JSON.stringify(pivotModel, null, 2) }}</pre >
17+ </div >
18+
19+ <VPivottableUi
20+ v-model:pivot-model =" pivotModel"
21+ :data =" salesData"
22+ @change =" onPivotChange"
23+ />
24+ </section >
25+
26+ <!-- 히스토리 관리 예제 -->
27+ <section class =" history-example" >
28+ <h2 >상태 히스토리 관리</h2 >
29+ <div class =" history-controls" >
30+ <button
31+ @click =" undo"
32+ :disabled =" !canUndo"
33+ >
34+ ← 실행 취소
35+ </button >
36+ <span >{{ currentIndex + 1 }} / {{ history.length }}</span >
37+ <button
38+ @click =" redo"
39+ :disabled =" !canRedo"
40+ >
41+ 다시 실행 →
42+ </button >
43+ </div >
44+
45+ <VPivottableUi
46+ v-model:pivot-model =" historyModel"
47+ :data =" salesData"
48+ />
49+ </section >
50+
51+ <!-- URL 동기화 예제 -->
52+ <section class =" url-sync-example" >
53+ <h2 >URL 파라미터 동기화</h2 >
54+ <div class =" url-controls" >
55+ <button @click =" syncToUrl" >URL에 저장</button >
56+ <button @click =" loadFromUrl" >URL에서 로드</button >
57+ <button @click =" shareUrl" >공유 링크 생성</button >
58+ </div >
59+
60+ <div
61+ v-if =" shareLink"
62+ class =" share-link"
63+ >
64+ <p >공유 링크:</p >
65+ <code >{{ shareLink }}</code >
66+ </div >
67+
68+ <VPivottableUi
69+ v-model:pivot-model =" urlModel"
70+ :data =" salesData"
71+ />
72+ </section >
73+
74+ <!-- 다중 피벗테이블 동기화 -->
75+ <section class =" sync-example" >
76+ <h2 >다중 피벗테이블 동기화</h2 >
77+ <div class =" sync-controls" >
78+ <label >
79+ <input
80+ type =" checkbox"
81+ v-model =" syncEnabled"
82+ />
83+ 동기화 활성화
84+ </label >
85+ </div >
86+
87+ <div class =" pivot-grid" >
88+ <div class =" pivot-item" >
89+ <h3 >매출 분석</h3 >
90+ <VPivottableUi
91+ v-model:pivot-model =" masterModel"
92+ :data =" salesData"
93+ @change =" onMasterChange"
94+ />
95+ </div >
96+
97+ <div class =" pivot-item" >
98+ <h3 >고객 분석 (동기화됨)</h3 >
99+ <VPivottableUi
100+ v-model:pivot-model =" slaveModel"
101+ :data =" customerData"
102+ />
103+ </div >
104+ </div >
105+ </section >
106+ </div >
107+ </template >
108+
109+ <script setup lang="ts">
110+ import { ref , onMounted } from ' vue'
111+ import { VPivottableUi } from ' @/components'
112+ import { PivotModelInterface } from ' @/types'
113+ import { createPivotModel } from ' @/utils/pivotModel'
114+ import { PivotModelSerializer } from ' @/utils/pivotModelSerializer'
115+ import { usePivotModelHistory } from ' @/composables/usePivotModelHistory'
116+
117+ // 샘플 데이터
118+ const salesData = ref ([
119+ { region: ' North' , quarter: ' Q1' , product: ' A' , sales: 100 , quantity: 10 },
120+ { region: ' North' , quarter: ' Q1' , product: ' B' , sales: 150 , quantity: 15 },
121+ { region: ' North' , quarter: ' Q2' , product: ' A' , sales: 120 , quantity: 12 },
122+ { region: ' North' , quarter: ' Q2' , product: ' B' , sales: 180 , quantity: 18 },
123+ { region: ' South' , quarter: ' Q1' , product: ' A' , sales: 200 , quantity: 20 },
124+ { region: ' South' , quarter: ' Q1' , product: ' B' , sales: 250 , quantity: 25 },
125+ { region: ' South' , quarter: ' Q2' , product: ' A' , sales: 220 , quantity: 22 },
126+ { region: ' South' , quarter: ' Q2' , product: ' B' , sales: 280 , quantity: 28 },
127+ { region: ' East' , quarter: ' Q1' , product: ' A' , sales: 150 , quantity: 15 },
128+ { region: ' East' , quarter: ' Q1' , product: ' B' , sales: 200 , quantity: 20 },
129+ { region: ' East' , quarter: ' Q2' , product: ' A' , sales: 170 , quantity: 17 },
130+ { region: ' East' , quarter: ' Q2' , product: ' B' , sales: 230 , quantity: 23 }
131+ ])
132+
133+ const customerData = ref ([
134+ { region: ' North' , quarter: ' Q1' , segment: ' Enterprise' , customers: 50 },
135+ { region: ' North' , quarter: ' Q1' , segment: ' SMB' , customers: 100 },
136+ { region: ' North' , quarter: ' Q2' , segment: ' Enterprise' , customers: 55 },
137+ { region: ' North' , quarter: ' Q2' , segment: ' SMB' , customers: 110 },
138+ { region: ' South' , quarter: ' Q1' , segment: ' Enterprise' , customers: 40 },
139+ { region: ' South' , quarter: ' Q1' , segment: ' SMB' , customers: 80 },
140+ { region: ' South' , quarter: ' Q2' , segment: ' Enterprise' , customers: 45 },
141+ { region: ' South' , quarter: ' Q2' , segment: ' SMB' , customers: 90 }
142+ ])
143+
144+ // 1. 기본 사용법
145+ const pivotModel = ref <PivotModelInterface >(createPivotModel ({
146+ rows: [' region' ],
147+ cols: [' quarter' ],
148+ vals: [' sales' ],
149+ aggregatorName: ' Sum' ,
150+ rendererName: ' Table'
151+ }))
152+
153+ const onPivotChange = (newModel : PivotModelInterface ) => {
154+ console .log (' 피벗 모델 변경:' , newModel )
155+ // 여기서 서버로 상태를 저장하거나 다른 작업을 수행할 수 있습니다
156+ }
157+
158+ const resetModel = () => {
159+ pivotModel .value = createPivotModel ()
160+ }
161+
162+ const saveToLocalStorage = () => {
163+ PivotModelSerializer .saveToLocalStorage (' myPivotModel' , pivotModel .value )
164+ alert (' 로컬 스토리지에 저장되었습니다!' )
165+ }
166+
167+ const loadFromLocalStorage = () => {
168+ const loaded = PivotModelSerializer .loadFromLocalStorage (' myPivotModel' )
169+ if (loaded ) {
170+ pivotModel .value = loaded
171+ alert (' 로컬 스토리지에서 로드되었습니다!' )
172+ } else {
173+ alert (' 저장된 모델이 없습니다.' )
174+ }
175+ }
176+
177+ // 2. 히스토리 관리
178+ const historyModel = ref <PivotModelInterface >(createPivotModel ({
179+ rows: [' product' ],
180+ cols: [' region' ],
181+ vals: [' quantity' ],
182+ aggregatorName: ' Average'
183+ }))
184+
185+ const {
186+ history,
187+ currentIndex,
188+ canUndo,
189+ canRedo,
190+ undo,
191+ redo
192+ } = usePivotModelHistory (historyModel )
193+
194+ // 3. URL 동기화
195+ const urlModel = ref <PivotModelInterface >(createPivotModel ())
196+ const shareLink = ref <string >(' ' )
197+
198+ const syncToUrl = () => {
199+ const params = PivotModelSerializer .toUrlParams (urlModel .value )
200+ const newUrl = ` ${window .location .pathname }?${params .toString ()} `
201+ window .history .pushState ({}, ' ' , newUrl )
202+ alert (' URL에 저장되었습니다!' )
203+ }
204+
205+ const loadFromUrl = () => {
206+ const params = new URLSearchParams (window .location .search )
207+ const loaded = PivotModelSerializer .fromUrlParams (params )
208+ if (Object .keys (loaded ).length > 0 ) {
209+ urlModel .value = createPivotModel (loaded )
210+ alert (' URL에서 로드되었습니다!' )
211+ } else {
212+ alert (' URL에 저장된 모델이 없습니다.' )
213+ }
214+ }
215+
216+ const shareUrl = () => {
217+ const params = PivotModelSerializer .toUrlParams (urlModel .value )
218+ shareLink .value = ` ${window .location .origin }${window .location .pathname }?${params .toString ()} `
219+ }
220+
221+ // 4. 다중 피벗테이블 동기화
222+ const syncEnabled = ref (false )
223+ const masterModel = ref <PivotModelInterface >(createPivotModel ({
224+ rows: [' region' ],
225+ cols: [' quarter' ],
226+ vals: [' sales' ]
227+ }))
228+ const slaveModel = ref <PivotModelInterface >(createPivotModel ({
229+ rows: [' region' ],
230+ cols: [' quarter' ],
231+ vals: [' customers' ]
232+ }))
233+
234+ const onMasterChange = (newModel : PivotModelInterface ) => {
235+ if (syncEnabled .value ) {
236+ // 구조만 동기화하고 vals는 유지
237+ slaveModel .value = {
238+ ... slaveModel .value ,
239+ rows: newModel .rows ,
240+ cols: newModel .cols ,
241+ aggregatorName: newModel .aggregatorName ,
242+ rowOrder: newModel .rowOrder ,
243+ colOrder: newModel .colOrder ,
244+ valueFilter: newModel .valueFilter
245+ }
246+ }
247+ }
248+
249+ // 페이지 로드 시 URL에서 상태 복원
250+ onMounted (() => {
251+ loadFromUrl ()
252+ })
253+ </script >
254+
255+ <style scoped>
256+ .pivot-model-example {
257+ max-width : 1400px ;
258+ margin : 0 auto ;
259+ padding : 20px ;
260+ }
261+
262+ section {
263+ margin-bottom : 60px ;
264+ padding : 20px ;
265+ border : 1px solid #e0e0e0 ;
266+ border-radius : 8px ;
267+ }
268+
269+ h1 , h2 , h3 {
270+ margin-bottom : 20px ;
271+ }
272+
273+ .controls , .history-controls , .url-controls , .sync-controls {
274+ margin-bottom : 20px ;
275+ display : flex ;
276+ gap : 10px ;
277+ align-items : center ;
278+ }
279+
280+ button {
281+ padding : 8px 16px ;
282+ border : 1px solid #ddd ;
283+ border-radius : 4px ;
284+ background : white ;
285+ cursor : pointer ;
286+ transition : all 0.3s ;
287+ }
288+
289+ button :hover:not (:disabled ) {
290+ background : #f0f0f0 ;
291+ }
292+
293+ button :disabled {
294+ opacity : 0.5 ;
295+ cursor : not-allowed ;
296+ }
297+
298+ .model-status {
299+ margin-bottom : 20px ;
300+ padding : 15px ;
301+ background : #f5f5f5 ;
302+ border-radius : 4px ;
303+ }
304+
305+ .model-status pre {
306+ margin : 0 ;
307+ font-size : 12px ;
308+ max-height : 200px ;
309+ overflow-y : auto ;
310+ }
311+
312+ .share-link {
313+ margin-bottom : 20px ;
314+ padding : 15px ;
315+ background : #e8f5e9 ;
316+ border-radius : 4px ;
317+ }
318+
319+ .share-link code {
320+ display : block ;
321+ padding : 10px ;
322+ background : white ;
323+ border-radius : 4px ;
324+ word-break : break-all ;
325+ font-size : 12px ;
326+ }
327+
328+ .pivot-grid {
329+ display : grid ;
330+ grid-template-columns : 1fr 1fr ;
331+ gap : 20px ;
332+ }
333+
334+ .pivot-item {
335+ border : 1px solid #e0e0e0 ;
336+ padding : 15px ;
337+ border-radius : 4px ;
338+ }
339+
340+ .pivot-item h3 {
341+ margin-bottom : 15px ;
342+ color : #333 ;
343+ }
344+
345+ label {
346+ display : flex ;
347+ align-items : center ;
348+ gap : 8px ;
349+ cursor : pointer ;
350+ }
351+
352+ input [type = " checkbox" ] {
353+ width : 18px ;
354+ height : 18px ;
355+ cursor : pointer ;
356+ }
357+ </style >
0 commit comments