aegon-lv38 1.0.0

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 (2) hide show
  1. package/greenMortgage.vue +1395 -0
  2. package/package.json +12 -0
@@ -0,0 +1,1395 @@
1
+ <template>
2
+ <div class="green-mortgage-page">
3
+ <el-card class="green-mortgage" shadow="never">
4
+ <header class="green-mortgage__header">
5
+ <h1 class="green-mortgage__title">
6
+ Residential Properties that hold valid Gold/Platinum ratings under BEAM Plus New
7
+ Buildings (NB) / Existing Buildings (EB)
8
+ </h1>
9
+ <p class="green-mortgage__subtitle">
10
+ 持有有效的綠建環評新建建築 (NB) / 既有建築 (EB) 金級/鉑金級的住宅物業
11
+ </p>
12
+ <p class="green-mortgage__meta">Data Update Date: {{ dataUpdateDate || 'YYYY-MM-DD' }}</p>
13
+ <p class="green-mortgage__meta">Data Download Status: {{ dataDownloadStatus || 'Automatically Downloaded' }}</p>
14
+ </header>
15
+
16
+ <section class="green-mortgage__filters">
17
+ <div class="green-mortgage__filter-row">
18
+ <div class="green-mortgage__filter-group">
19
+ <label class="green-mortgage__filter-label" for="gm-rating-select">Select Rating(s)</label>
20
+ <el-select
21
+ id="gm-rating-select"
22
+ v-model="selectedRatings"
23
+ multiple
24
+ collapse-tags
25
+ collapse-tags-tooltip
26
+ placeholder=""
27
+ class="green-mortgage__rating-select"
28
+ clearable
29
+ >
30
+ <el-option
31
+ v-for="item in ratingOptions"
32
+ :key="item"
33
+ :label="item"
34
+ :value="item"
35
+ />
36
+ </el-select>
37
+ </div>
38
+
39
+ <div class="green-mortgage__filter-group green-mortgage__filter-group--period">
40
+ <span class="green-mortgage__filter-label">Certification period:</span>
41
+ <div class="green-mortgage__period-controls">
42
+ <el-date-picker
43
+ v-model="startYear"
44
+ type="date"
45
+ format="YYYY-MM-DD"
46
+ value-format="YYYY-MM-DD"
47
+ placeholder=""
48
+ class="green-mortgage__date-input"
49
+ clearable
50
+ @clear="startYear = ''"
51
+ />
52
+ <span class="green-mortgage__to-text">to</span>
53
+ <el-date-picker
54
+ v-model="endYear"
55
+ type="date"
56
+ format="YYYY-MM-DD"
57
+ value-format="YYYY-MM-DD"
58
+ placeholder=""
59
+ class="green-mortgage__date-input"
60
+ clearable
61
+ @clear="endYear = ''"
62
+ />
63
+ <el-button class="green-mortgage__refresh-btn" @click="handleRefresh">
64
+ Refresh
65
+ </el-button>
66
+ </div>
67
+ </div>
68
+ </div>
69
+
70
+ <div class="green-mortgage__action-row">
71
+ <el-button class="green-mortgage__export-btn" @click="handleExportExcel">
72
+ Export to Excel
73
+ </el-button>
74
+
75
+ <div class="green-mortgage__search">
76
+ <label class="green-mortgage__search-label" for="gm-search">Search:</label>
77
+ <el-input
78
+ id="gm-search"
79
+ v-model="searchKeyword"
80
+ class="green-mortgage__search-input"
81
+ clearable
82
+ />
83
+ </div>
84
+ </div>
85
+ </section>
86
+
87
+ <section class="green-mortgage__table-wrap">
88
+ <div
89
+ ref="tableRootRef"
90
+ class="green-mortgage__table-shell"
91
+ :class="{ 'is-scrollbar-visible': showScrollbars }"
92
+ @mouseenter="onTableShellEnter"
93
+ @mouseleave="onTableShellLeave"
94
+ >
95
+ <el-table
96
+ ref="tableRef"
97
+ v-loading="tableLoading"
98
+ :data="paginatedRows"
99
+ border
100
+ stripe
101
+ class="green-mortgage__table"
102
+ empty-text=" "
103
+ height="100%"
104
+ @header-dragend="onHeaderDragEnd"
105
+ >
106
+ <el-table-column
107
+ prop="propertyNameEng"
108
+ :width="columnWidths.propertyNameEng"
109
+ sortable
110
+ :sort-method="sortByPropertyName"
111
+ show-overflow-tooltip
112
+ >
113
+ <template #header>
114
+ <div class="green-mortgage__th green-mortgage__th--single">
115
+ <span>Property Name</span>
116
+ </div>
117
+ </template>
118
+ </el-table-column>
119
+
120
+ <el-table-column
121
+ prop="propertyNameChi"
122
+ :width="columnWidths.propertyNameChi"
123
+ show-overflow-tooltip
124
+ >
125
+ <template #header>
126
+ <div class="green-mortgage__th green-mortgage__th--single">
127
+ <span>物業名稱</span>
128
+ </div>
129
+ </template>
130
+ </el-table-column>
131
+
132
+ <el-table-column
133
+ prop="propertyAddress"
134
+ :width="columnWidths.propertyAddress"
135
+ sortable
136
+ :sort-method="sortByPropertyAddress"
137
+ show-overflow-tooltip
138
+ >
139
+ <template #header>
140
+ <div class="green-mortgage__th green-mortgage__th--inline">
141
+ <span>Property Address</span>
142
+ <span>物業地址</span>
143
+ </div>
144
+ </template>
145
+ </el-table-column>
146
+
147
+ <el-table-column
148
+ prop="owner"
149
+ :width="columnWidths.owner"
150
+ sortable
151
+ :sort-method="sortByOwner"
152
+ show-overflow-tooltip
153
+ >
154
+ <template #header>
155
+ <div class="green-mortgage__th">
156
+ <span>Owner/Developer Co. Name</span>
157
+ <span>項目持有人/發展商名稱</span>
158
+ </div>
159
+ </template>
160
+ </el-table-column>
161
+
162
+ <el-table-column
163
+ prop="district"
164
+ :width="columnWidths.district"
165
+ sortable
166
+ :sort-method="sortByDistrict"
167
+ show-overflow-tooltip
168
+ >
169
+ <template #header>
170
+ <div class="green-mortgage__th green-mortgage__th--inline">
171
+ <span>District</span>
172
+ <span>地區</span>
173
+ </div>
174
+ </template>
175
+ </el-table-column>
176
+
177
+ <el-table-column
178
+ prop="tool"
179
+ :width="columnWidths.tool"
180
+ sortable
181
+ :sort-method="sortByTool"
182
+ show-overflow-tooltip
183
+ >
184
+ <template #header>
185
+ <div class="green-mortgage__th green-mortgage__th--inline">
186
+ <span>Tool</span>
187
+ <span>評估工具</span>
188
+ </div>
189
+ </template>
190
+ </el-table-column>
191
+
192
+ <el-table-column
193
+ prop="projectType"
194
+ :width="columnWidths.projectType"
195
+ sortable
196
+ :sort-method="sortByProjectType"
197
+ show-overflow-tooltip
198
+ >
199
+ <template #header>
200
+ <div class="green-mortgage__th">
201
+ <span>Project Type</span>
202
+ <span>項目類型</span>
203
+ </div>
204
+ </template>
205
+ </el-table-column>
206
+
207
+ <el-table-column
208
+ prop="achievedRating"
209
+ :width="columnWidths.achievedRating"
210
+ sortable
211
+ :sort-method="sortByAchievedRating"
212
+ show-overflow-tooltip
213
+ >
214
+ <template #header>
215
+ <div class="green-mortgage__th">
216
+ <span>Achieved Rating</span>
217
+ <span>獲得評級</span>
218
+ </div>
219
+ </template>
220
+ </el-table-column>
221
+
222
+ <el-table-column
223
+ prop="year"
224
+ :width="columnWidths.year"
225
+ align="center"
226
+ sortable
227
+ :sort-method="sortByYear"
228
+ >
229
+ <template #header>
230
+ <div class="green-mortgage__th green-mortgage__th--single">
231
+ <span>Year</span>
232
+ </div>
233
+ </template>
234
+ </el-table-column>
235
+
236
+ <el-table-column
237
+ prop="month"
238
+ :width="columnWidths.month"
239
+ align="center"
240
+ sortable
241
+ :sort-method="sortByMonth"
242
+ >
243
+ <template #header>
244
+ <div class="green-mortgage__th green-mortgage__th--single">
245
+ <span>Month</span>
246
+ </div>
247
+ </template>
248
+ </el-table-column>
249
+
250
+ <el-table-column
251
+ prop="gbCertLabelNo"
252
+ :min-width="columnWidths.gbCertLabelNo"
253
+ sortable
254
+ :sort-method="sortByGbCertLabelNo"
255
+ show-overflow-tooltip
256
+ >
257
+ <template #header>
258
+ <div class="green-mortgage__th">
259
+ <span>GB Cert Label No</span>
260
+ <span>售樓書上的標籤號碼</span>
261
+ </div>
262
+ </template>
263
+ </el-table-column>
264
+ </el-table>
265
+
266
+ <!-- 外側豎向滑塊:默認隱藏,移入表格且可滾動時顯示 -->
267
+ <div
268
+ v-show="scrollCan && showScrollbars"
269
+ class="green-mortgage__vbar"
270
+ :style="vbarTrackStyle"
271
+ aria-hidden="true"
272
+ >
273
+ <div
274
+ class="green-mortgage__vthumb"
275
+ :style="vbarThumbStyle"
276
+ @mousedown.prevent="onThumbMouseDown"
277
+ />
278
+ </div>
279
+ </div>
280
+
281
+ <div class="green-mortgage__pagination">
282
+ <el-pagination
283
+ v-model:current-page="currentPage"
284
+ v-model:page-size="pageSize"
285
+ :total="filteredRows.length"
286
+ :page-sizes="[10, 20, 50, 100]"
287
+ :pager-count="pagerCount"
288
+ background
289
+ :layout="paginationLayout"
290
+ />
291
+ </div>
292
+ </section>
293
+ </el-card>
294
+ </div>
295
+ </template>
296
+
297
+ <script setup lang="ts">
298
+ import greenMortgageApi from '@/api/modules/industry/green_mortgage'
299
+ import { industryExportToExcel } from '@/utils/function'
300
+ import { lockDocumentScroll } from '@/views/gen-ai/common/utils/scroll'
301
+ import type { TableInstance } from 'element-plus'
302
+ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
303
+
304
+ interface GreenMortgageRow {
305
+ propertyNameEng: string
306
+ propertyNameChi: string
307
+ propertyAddress: string
308
+ owner: string
309
+ district: string
310
+ tool: string
311
+ projectType: string
312
+ achievedRating: string
313
+ year: string
314
+ month: string
315
+ gbCertLabelNo: string
316
+ }
317
+
318
+ const EXPORT_TABLE_HEADERS = [
319
+ 'Property Name',
320
+ '物業名稱',
321
+ 'Property Address',
322
+ 'Owner/Developer Co. Name',
323
+ 'District',
324
+ 'Tool',
325
+ 'Project Type',
326
+ 'Achieved Rating',
327
+ 'Year',
328
+ 'Month',
329
+ 'GB Cert Label No',
330
+ ] as const
331
+
332
+ const COLUMN_WIDTH_STORAGE_KEY = 'green-mortgage-table-column-widths'
333
+
334
+ const DEFAULT_COLUMN_WIDTHS: Record<string, number> = {
335
+ propertyNameEng: 120,
336
+ propertyNameChi: 120,
337
+ propertyAddress: 170,
338
+ owner: 210,
339
+ district: 90,
340
+ tool: 90,
341
+ projectType: 110,
342
+ achievedRating: 120,
343
+ year: 72,
344
+ month: 72,
345
+ gbCertLabelNo: 150,
346
+ }
347
+
348
+ function loadColumnWidths(): Record<string, number> {
349
+ try {
350
+ const raw = localStorage.getItem(COLUMN_WIDTH_STORAGE_KEY)
351
+ if (!raw) return { ...DEFAULT_COLUMN_WIDTHS }
352
+ const parsed = JSON.parse(raw) as Record<string, unknown>
353
+ const next = { ...DEFAULT_COLUMN_WIDTHS }
354
+ for (const key of Object.keys(DEFAULT_COLUMN_WIDTHS)) {
355
+ const value = Number(parsed[key])
356
+ if (Number.isFinite(value) && value >= 48) next[key] = Math.round(value)
357
+ }
358
+ return next
359
+ } catch {
360
+ return { ...DEFAULT_COLUMN_WIDTHS }
361
+ }
362
+ }
363
+
364
+ const columnWidths = ref<Record<string, number>>(loadColumnWidths())
365
+
366
+ function persistColumnWidths() {
367
+ localStorage.setItem(COLUMN_WIDTH_STORAGE_KEY, JSON.stringify(columnWidths.value))
368
+ }
369
+
370
+ function onHeaderDragEnd(
371
+ newWidth: number,
372
+ _oldWidth: number,
373
+ column: { property?: string },
374
+ ) {
375
+ const prop = column?.property
376
+ if (!prop || !(prop in DEFAULT_COLUMN_WIDTHS)) return
377
+ columnWidths.value = {
378
+ ...columnWidths.value,
379
+ [prop]: Math.max(48, Math.round(newWidth)),
380
+ }
381
+ persistColumnWidths()
382
+ void nextTick(() => refreshTableScrollUi())
383
+ }
384
+
385
+ const tableLoading = ref(false)
386
+ const dataUpdateDate = ref('')
387
+ const dataDownloadStatus = ref('')
388
+ const ratingOptions = ref<string[]>([])
389
+ const selectedRatings = ref<string[]>([])
390
+ const startYear = ref<string | null>('')
391
+ const endYear = ref<string | null>('')
392
+ const searchKeyword = ref('')
393
+ const tableRows = ref<GreenMortgageRow[]>([])
394
+ const currentPage = ref(1)
395
+ const pageSize = ref(10)
396
+ const viewportWidth = ref(typeof window !== 'undefined' ? window.innerWidth : 1440)
397
+
398
+ const isMobile = computed(() => viewportWidth.value < 768)
399
+ const isTablet = computed(() => viewportWidth.value >= 768 && viewportWidth.value < 1200)
400
+
401
+ const paginationLayout = computed(() => {
402
+ if (isMobile.value) return 'total, prev, pager, next'
403
+ if (isTablet.value) return 'total, sizes, prev, pager, next'
404
+ return 'total, sizes, prev, pager, next, jumper'
405
+ })
406
+
407
+ const pagerCount = computed(() => (isMobile.value ? 5 : 7))
408
+
409
+ const tableRootRef = ref<HTMLElement | null>(null)
410
+ const tableRef = ref<TableInstance>()
411
+
412
+ /** 外側豎向滑塊(對齊 RecentReportsTable) */
413
+ const THUMB_PX = 50
414
+ const SCROLL_THUMB_COLOR = '#c1c1c1'
415
+ /** 判定可豎滾的溢出閾值(避免 10 條內仍誤顯滾動條) */
416
+ const V_OVERFLOW_THRESHOLD = 8
417
+ /** 底部為橫向滾動條預留,避免豎條被裁切 */
418
+ const V_BAR_BOTTOM_RESERVE = 12
419
+ const scrollCan = ref(false)
420
+ const thumbHeight = ref(THUMB_PX)
421
+ const thumbOffset = ref(0)
422
+ const trackTop = ref(0)
423
+ const trackHeight = ref(0)
424
+ const tableHovered = ref(false)
425
+ const thumbDragging = ref(false)
426
+
427
+ const showScrollbars = computed(() => tableHovered.value || thumbDragging.value)
428
+
429
+ const vbarTrackStyle = computed(() => ({
430
+ top: `${trackTop.value}px`,
431
+ height: `${trackHeight.value}px`,
432
+ }))
433
+
434
+ const vbarThumbStyle = computed(() => ({
435
+ height: `${thumbHeight.value}px`,
436
+ transform: `translateY(${thumbOffset.value}px)`,
437
+ backgroundColor: SCROLL_THUMB_COLOR,
438
+ }))
439
+
440
+ function onTableShellEnter() {
441
+ tableHovered.value = true
442
+ }
443
+
444
+ function onTableShellLeave() {
445
+ if (thumbDragging.value) return
446
+ tableHovered.value = false
447
+ }
448
+
449
+ function getScrollWrap(): HTMLElement | null {
450
+ return (
451
+ (tableRootRef.value?.querySelector(
452
+ '.el-table__body-wrapper .el-scrollbar__wrap',
453
+ ) as HTMLElement | null) ?? null
454
+ )
455
+ }
456
+
457
+ function refreshOuterBar() {
458
+ const root = tableRootRef.value
459
+ const wrap = getScrollWrap()
460
+ if (!root || !wrap || paginatedRows.value.length === 0) {
461
+ scrollCan.value = false
462
+ return
463
+ }
464
+
465
+ const { scrollTop, scrollHeight, clientHeight } = wrap
466
+ const overflow = scrollHeight - clientHeight
467
+ const can = overflow > V_OVERFLOW_THRESHOLD
468
+ scrollCan.value = can
469
+ if (!can) return
470
+
471
+ const header = root.querySelector('.el-table__header-wrapper') as HTMLElement | null
472
+ const bodyWrapper = root.querySelector('.el-table__body-wrapper') as HTMLElement | null
473
+ const headerH = header?.offsetHeight ?? 0
474
+ const bodyH = bodyWrapper?.clientHeight ?? clientHeight
475
+
476
+ trackTop.value = headerH + 2
477
+ // 軌道高度限制在表體可見區內,底部預留橫條空間,避免被遮擋
478
+ const available = Math.min(clientHeight, bodyH, root.clientHeight - trackTop.value)
479
+ trackHeight.value = Math.max(0, available - V_BAR_BOTTOM_RESERVE)
480
+
481
+ const trackH = trackHeight.value
482
+ if (trackH <= 0) {
483
+ scrollCan.value = false
484
+ return
485
+ }
486
+
487
+ thumbHeight.value = Math.min(trackH, THUMB_PX)
488
+ const maxTravel = Math.max(0, trackH - thumbHeight.value)
489
+ const scrollMax = Math.max(0, overflow)
490
+ thumbOffset.value = scrollMax > 0 ? Math.round((scrollTop / scrollMax) * maxTravel) : 0
491
+ }
492
+
493
+ function onWrapScroll() {
494
+ refreshOuterBar()
495
+ }
496
+
497
+ let dragCleanup: (() => void) | undefined
498
+ let boundWrap: HTMLElement | null = null
499
+ let tableResizeObserver: ResizeObserver | undefined
500
+
501
+ function onThumbMouseDown(event: MouseEvent) {
502
+ const wrapEl = getScrollWrap()
503
+ if (!wrapEl || !scrollCan.value) return
504
+ const scrollEl: HTMLElement = wrapEl
505
+
506
+ thumbDragging.value = true
507
+ const startY = event.clientY
508
+ const startTop = thumbOffset.value
509
+ const trackH = trackHeight.value
510
+ const maxTravel = Math.max(0, trackH - thumbHeight.value)
511
+ const scrollMax = scrollEl.scrollHeight - scrollEl.clientHeight
512
+
513
+ function onMove(e: MouseEvent) {
514
+ if (maxTravel <= 0 || scrollMax <= 0) return
515
+ const delta = e.clientY - startY
516
+ const nextOffset = Math.min(maxTravel, Math.max(0, startTop + delta))
517
+ thumbOffset.value = nextOffset
518
+ scrollEl.scrollTop = (nextOffset / maxTravel) * scrollMax
519
+ }
520
+
521
+ function onUp() {
522
+ thumbDragging.value = false
523
+ const root = tableRootRef.value
524
+ if (!root?.matches(':hover')) tableHovered.value = false
525
+ dragCleanup?.()
526
+ dragCleanup = undefined
527
+ }
528
+
529
+ window.addEventListener('mousemove', onMove)
530
+ window.addEventListener('mouseup', onUp, { once: true })
531
+ dragCleanup = () => {
532
+ window.removeEventListener('mousemove', onMove)
533
+ window.removeEventListener('mouseup', onUp)
534
+ }
535
+ }
536
+
537
+ function bindWrapScroll() {
538
+ unbindWrapScroll()
539
+ const wrap = getScrollWrap()
540
+ if (!wrap) return
541
+ wrap.addEventListener('scroll', onWrapScroll, { passive: true })
542
+ boundWrap = wrap
543
+ }
544
+
545
+ function unbindWrapScroll() {
546
+ boundWrap?.removeEventListener('scroll', onWrapScroll)
547
+ boundWrap = null
548
+ }
549
+
550
+ function refreshTableScrollUi() {
551
+ tableRef.value?.doLayout()
552
+ void nextTick(() => {
553
+ bindWrapScroll()
554
+ refreshOuterBar()
555
+ })
556
+ }
557
+
558
+ function syncViewportWidth() {
559
+ viewportWidth.value = window.innerWidth
560
+ refreshTableScrollUi()
561
+ }
562
+
563
+ const filteredRows = computed(() => filterRowsByCurrentCriteria(tableRows.value))
564
+
565
+ const paginatedRows = computed(() => {
566
+ const start = (currentPage.value - 1) * pageSize.value
567
+ return filteredRows.value.slice(start, start + pageSize.value)
568
+ })
569
+
570
+ watch([searchKeyword, selectedRatings, startYear, endYear], () => {
571
+ currentPage.value = 1
572
+ })
573
+
574
+ watch(
575
+ () => tableRows.value.length,
576
+ () => {
577
+ currentPage.value = 1
578
+ },
579
+ )
580
+
581
+ watch(paginatedRows, () => {
582
+ void nextTick(() => refreshTableScrollUi())
583
+ })
584
+
585
+ watch([currentPage, pageSize], () => {
586
+ void nextTick(() => refreshTableScrollUi())
587
+ })
588
+
589
+ function pickString(record: Record<string, unknown>, keys: string[]) {
590
+ for (const key of keys) {
591
+ const value = record[key]
592
+ if (value != null && value !== '') return String(value)
593
+ }
594
+ return ''
595
+ }
596
+
597
+ function normalizeRow(item: unknown): GreenMortgageRow {
598
+ const record = item && typeof item === 'object' ? (item as Record<string, unknown>) : {}
599
+ return {
600
+ propertyNameEng: pickString(record, ['propertyNameEng', 'PROPERTY_NAME_ENG']),
601
+ propertyNameChi: pickString(record, ['propertyNameChi', 'PROPERTY_NAME_CHI']),
602
+ propertyAddress: pickString(record, ['propertyAddress', 'PROPERTY_ADDRESS']),
603
+ owner: pickString(record, ['owner', 'OWNER']),
604
+ district: pickString(record, ['district', 'DISTRICT']),
605
+ tool: pickString(record, ['tool', 'TOOL']),
606
+ projectType: pickString(record, ['projectType', 'PROJECT_TYPE']),
607
+ achievedRating: pickString(record, ['achievedRating', 'ACHIEVED_RATING']),
608
+ year: pickString(record, ['year', 'YEAR']),
609
+ month: pickString(record, ['month', 'MONTH']),
610
+ gbCertLabelNo: pickString(record, ['gbCertLabelNo', 'GB_CERT_LABEL_NO']),
611
+ }
612
+ }
613
+
614
+ function parseQueryResponse(data: unknown) {
615
+ if (!data || typeof data !== 'object') {
616
+ return { rows: [] as GreenMortgageRow[], dataUpdateDate: '', dataDownloadStatus: '' }
617
+ }
618
+
619
+ const record = data as Record<string, unknown>
620
+ const statusVO =
621
+ record.riskModelDataStatusVO && typeof record.riskModelDataStatusVO === 'object'
622
+ ? (record.riskModelDataStatusVO as Record<string, unknown>)
623
+ : {}
624
+
625
+ const list = record.riskModelDataVOList
626
+ const rows = Array.isArray(list) ? list.map(normalizeRow) : []
627
+
628
+ return {
629
+ rows,
630
+ dataUpdateDate: pickString(statusVO, ['dataUpdateDate', 'DataUpdateDate']),
631
+ dataDownloadStatus: pickString(statusVO, ['dataDownloadStatus', 'DataDownloadStatus']),
632
+ }
633
+ }
634
+
635
+ function parseRatingsResponse(data: unknown): string[] {
636
+ if (!Array.isArray(data)) return []
637
+ return data.map((item) => String(item).trim()).filter(Boolean)
638
+ }
639
+
640
+ function compareLexicographic(a: unknown, b: unknown) {
641
+ return String(a ?? '').localeCompare(String(b ?? ''), undefined, {
642
+ sensitivity: 'base',
643
+ })
644
+ }
645
+
646
+ function compareText(a: unknown, b: unknown) {
647
+ return String(a ?? '').localeCompare(String(b ?? ''), undefined, {
648
+ numeric: true,
649
+ sensitivity: 'base',
650
+ })
651
+ }
652
+
653
+ function compareAlphabeticalEmptyFirst(a: unknown, b: unknown) {
654
+ const strA = String(a ?? '').trim()
655
+ const strB = String(b ?? '').trim()
656
+ const emptyA = strA === ''
657
+ const emptyB = strB === ''
658
+
659
+ if (emptyA && emptyB) return 0
660
+ if (emptyA) return -1
661
+ if (emptyB) return 1
662
+
663
+ return strA.localeCompare(strB, undefined, { sensitivity: 'base' })
664
+ }
665
+
666
+ function createAlphabeticalEmptyFirstSorter(key: keyof GreenMortgageRow) {
667
+ return (a: GreenMortgageRow, b: GreenMortgageRow) =>
668
+ compareAlphabeticalEmptyFirst(a[key], b[key])
669
+ }
670
+
671
+ /** 首字元:數字 0 → 字母 1 → 其他 2 */
672
+ function getFirstCharSortCategory(text: string) {
673
+ const trimmed = text.trim()
674
+ if (!trimmed) return 2
675
+ const first = trimmed.charAt(0)
676
+ if (/\d/.test(first)) return 0
677
+ if (/[a-zA-Z]/.test(first)) return 1
678
+ return 2
679
+ }
680
+
681
+ function getPropertyNameSortKey(row: GreenMortgageRow) {
682
+ return (row.propertyNameEng || '').trim()
683
+ }
684
+
685
+ function getPropertyNameLeadingNumber(name: string) {
686
+ const matched = name.trim().match(/^(\d+)/)
687
+ return matched ? Number(matched[1]) : Number.NaN
688
+ }
689
+
690
+ const sortByPropertyName = (a: GreenMortgageRow, b: GreenMortgageRow) => {
691
+ const nameA = getPropertyNameSortKey(a)
692
+ const nameB = getPropertyNameSortKey(b)
693
+ const categoryA = getFirstCharSortCategory(nameA)
694
+ const categoryB = getFirstCharSortCategory(nameB)
695
+
696
+ if (categoryA !== categoryB) return categoryA - categoryB
697
+
698
+ if (categoryA === 0) {
699
+ const leadingA = getPropertyNameLeadingNumber(nameA)
700
+ const leadingB = getPropertyNameLeadingNumber(nameB)
701
+ if (!Number.isNaN(leadingA) && !Number.isNaN(leadingB) && leadingA !== leadingB) {
702
+ return leadingA - leadingB
703
+ }
704
+ }
705
+
706
+ return nameA.localeCompare(nameB, undefined, { sensitivity: 'base' })
707
+ }
708
+
709
+ const sortByPropertyAddress = (a: GreenMortgageRow, b: GreenMortgageRow) => {
710
+ const addressA = (a.propertyAddress || '').trim()
711
+ const addressB = (b.propertyAddress || '').trim()
712
+ const categoryA = getFirstCharSortCategory(addressA)
713
+ const categoryB = getFirstCharSortCategory(addressB)
714
+
715
+ if (categoryA !== categoryB) return categoryA - categoryB
716
+
717
+ return compareLexicographic(addressA, addressB)
718
+ }
719
+ const sortByOwner = (a: GreenMortgageRow, b: GreenMortgageRow) => compareText(a.owner, b.owner)
720
+ const sortByDistrict = createAlphabeticalEmptyFirstSorter('district')
721
+ const sortByTool = createAlphabeticalEmptyFirstSorter('tool')
722
+ const sortByProjectType = createAlphabeticalEmptyFirstSorter('projectType')
723
+ const sortByAchievedRating = createAlphabeticalEmptyFirstSorter('achievedRating')
724
+ const sortByYear = createAlphabeticalEmptyFirstSorter('year')
725
+ const sortByMonth = createAlphabeticalEmptyFirstSorter('month')
726
+ const sortByGbCertLabelNo = createAlphabeticalEmptyFirstSorter('gbCertLabelNo')
727
+
728
+ async function fetchGreenMortgageRatings() {
729
+ try {
730
+ const res = await greenMortgageApi.getGreenMortgageRatings()
731
+ ratingOptions.value = parseRatingsResponse(res?.data)
732
+ } catch (error) {
733
+ console.error('getGreenMortgageRatings failed', error)
734
+ ratingOptions.value = []
735
+ }
736
+ }
737
+
738
+ function asTrimmedText(value: string | null | undefined) {
739
+ return String(value ?? '').trim()
740
+ }
741
+
742
+ function filterRowsByKeyword(rows: GreenMortgageRow[]) {
743
+ const keyword = asTrimmedText(searchKeyword.value).toLowerCase()
744
+ if (!keyword) return rows
745
+
746
+ return rows.filter((row) =>
747
+ Object.values(row).some((value) => String(value).toLowerCase().includes(keyword)),
748
+ )
749
+ }
750
+
751
+ /** 從 Certification period 取值中提取年份(相容 YYYY / YYYY-MM-DD) */
752
+ function getYearFromPeriod(value: string | null | undefined): number | null {
753
+ const trimmed = asTrimmedText(value)
754
+ if (!trimmed) return null
755
+ const matched = trimmed.match(/^(\d{4})/)
756
+ if (!matched) return null
757
+ const year = Number(matched[1])
758
+ return Number.isNaN(year) ? null : year
759
+ }
760
+
761
+ /**
762
+ * 當前頁篩選:Select Rating(s) + Certification period + Search
763
+ * 進頁無篩選條件時返回全部資料
764
+ */
765
+ function filterRowsByCurrentCriteria(rows: GreenMortgageRow[]) {
766
+ let result = rows
767
+
768
+ if (selectedRatings.value.length > 0) {
769
+ const ratingSet = new Set(selectedRatings.value)
770
+ result = result.filter((row) => ratingSet.has(row.achievedRating))
771
+ }
772
+
773
+ const start = getYearFromPeriod(startYear.value)
774
+ const end = getYearFromPeriod(endYear.value)
775
+ if (start != null || end != null) {
776
+ result = result.filter((row) => {
777
+ const year = Number(row.year)
778
+ if (Number.isNaN(year)) return false
779
+ if (start != null && year < start) return false
780
+ if (end != null && year > end) return false
781
+ return true
782
+ })
783
+ }
784
+
785
+ return filterRowsByKeyword(result)
786
+ }
787
+
788
+ function buildWorksheetData(rows: GreenMortgageRow[]) {
789
+ return [
790
+ [...EXPORT_TABLE_HEADERS],
791
+ ...rows.map((row) => [
792
+ row.propertyNameEng,
793
+ row.propertyNameChi,
794
+ row.propertyAddress,
795
+ row.owner,
796
+ row.district,
797
+ row.tool,
798
+ row.projectType,
799
+ row.achievedRating,
800
+ row.year,
801
+ row.month,
802
+ row.gbCertLabelNo,
803
+ ]),
804
+ ]
805
+ }
806
+
807
+ /** 進頁預設空條件 = 查詢全部;Refresh / 匯出請求共用 */
808
+ function getGreenMortgageQueryParams() {
809
+ return {
810
+ endYear: asTrimmedText(endYear.value),
811
+ ratingList: [...selectedRatings.value],
812
+ startYear: asTrimmedText(startYear.value),
813
+ }
814
+ }
815
+
816
+ async function fetchGreenMortgageQuery() {
817
+ tableLoading.value = true
818
+ try {
819
+ const res = await greenMortgageApi.getGreenMortgageQuery(getGreenMortgageQueryParams())
820
+
821
+ if (res.data) {
822
+ const parsed = parseQueryResponse(res.data)
823
+ tableRows.value = parsed.rows
824
+ if (parsed.dataUpdateDate) dataUpdateDate.value = parsed.dataUpdateDate
825
+ if (parsed.dataDownloadStatus) dataDownloadStatus.value = parsed.dataDownloadStatus
826
+ } else {
827
+ tableRows.value = []
828
+ }
829
+ } catch (error) {
830
+ console.error('getGreenMortgageQuery failed', error)
831
+ } finally {
832
+ tableLoading.value = false
833
+ }
834
+ }
835
+
836
+ function handleRefresh() {
837
+ fetchGreenMortgageQuery()
838
+ }
839
+
840
+ /** 匯出當前 Select Rating(s) + Certification period + Search 篩選後的資料(格式不變) */
841
+ async function handleExportExcel() {
842
+ try {
843
+ const res = await greenMortgageApi.getGreenMortgageQuery(getGreenMortgageQueryParams())
844
+ if (!res?.data) return
845
+
846
+ const parsed = parseQueryResponse(res.data)
847
+ const rows = filterRowsByCurrentCriteria(parsed.rows)
848
+ if (!rows.length) return
849
+
850
+ const worksheetData = buildWorksheetData(rows)
851
+ await industryExportToExcel(worksheetData, 'Green Mortgage', 'Green_Mortgage')
852
+ } catch (error) {
853
+ console.error('handleExportExcel failed', error)
854
+ }
855
+ }
856
+
857
+ /** 匯出當前頁已篩選資料(Select Rating / Certification period / Search 過濾後的結果) */
858
+ async function handleExportExcel2() {
859
+ try {
860
+ const rows = filteredRows.value
861
+ if (!rows.length) return
862
+
863
+ const worksheetData = buildWorksheetData(rows)
864
+ await industryExportToExcel(worksheetData, 'Green Mortgage', 'Green_Mortgage')
865
+ } catch (error) {
866
+ console.error('handleExportExcel2 failed', error)
867
+ }
868
+ }
869
+
870
+ /** 鎖定 document 原有滾動條 cleanup */
871
+ let restoreDocScroll: (() => void) | null = null
872
+
873
+ onMounted(async () => {
874
+ syncViewportWidth()
875
+ window.addEventListener('resize', syncViewportWidth)
876
+ void nextTick(() => {
877
+ restoreDocScroll?.()
878
+ restoreDocScroll = lockDocumentScroll()
879
+ refreshTableScrollUi()
880
+ const root = tableRootRef.value
881
+ if (root) {
882
+ tableResizeObserver = new ResizeObserver(() => {
883
+ void nextTick(() => refreshTableScrollUi())
884
+ })
885
+ tableResizeObserver.observe(root)
886
+ }
887
+ })
888
+ await fetchGreenMortgageRatings()
889
+ await fetchGreenMortgageQuery()
890
+ void nextTick(() => refreshTableScrollUi())
891
+ })
892
+
893
+ onUnmounted(() => {
894
+ restoreDocScroll?.()
895
+ restoreDocScroll = null
896
+ window.removeEventListener('resize', syncViewportWidth)
897
+ tableResizeObserver?.disconnect()
898
+ unbindWrapScroll()
899
+ dragCleanup?.()
900
+ })
901
+ </script>
902
+
903
+ <style lang="scss" scoped>
904
+ .green-mortgage-page {
905
+ box-sizing: border-box;
906
+ width: 100%;
907
+ height: 100%;
908
+ min-height: 0;
909
+ }
910
+
911
+ .green-mortgage {
912
+ box-sizing: border-box;
913
+ display: flex;
914
+ flex-direction: column;
915
+ width: 100%;
916
+ height: 100%;
917
+ min-height: 0;
918
+ overflow: hidden;
919
+ border: none;
920
+ border-radius: 0;
921
+ box-shadow: none;
922
+ background: #fff;
923
+ }
924
+
925
+ .green-mortgage :deep(.el-card__body) {
926
+ box-sizing: border-box;
927
+ display: flex;
928
+ flex-direction: column;
929
+ height: 100%;
930
+ min-height: 0;
931
+ padding: 20px;
932
+ }
933
+
934
+ .green-mortgage__header {
935
+ flex-shrink: 0;
936
+ margin-bottom: 14px;
937
+ }
938
+
939
+ .green-mortgage__title,
940
+ .green-mortgage__subtitle {
941
+ margin: 0;
942
+ color: #c0504d;
943
+ font-family: 'Times New Roman', Times, serif;
944
+ font-size: 18px;
945
+ font-weight: 400;
946
+ line-height: 1.35;
947
+ word-break: break-word;
948
+ }
949
+
950
+ .green-mortgage__meta {
951
+ margin: 4px 0 0;
952
+ font-size: 14px;
953
+ line-height: 1.4;
954
+ color: var(--el-text-color-primary);
955
+ word-break: break-word;
956
+ }
957
+
958
+ .green-mortgage__filters {
959
+ flex-shrink: 0;
960
+ margin-bottom: 10px;
961
+ }
962
+
963
+ .green-mortgage__filter-row {
964
+ display: flex;
965
+ flex-wrap: wrap;
966
+ align-items: center;
967
+ gap: 10px 16px;
968
+ margin-bottom: 10px;
969
+ }
970
+
971
+ .green-mortgage__filter-group {
972
+ display: flex;
973
+ flex-wrap: wrap;
974
+ align-items: center;
975
+ gap: 8px 10px;
976
+ min-width: 0;
977
+ }
978
+
979
+ .green-mortgage__filter-group--period {
980
+ flex: 1 1 auto;
981
+ }
982
+
983
+ .green-mortgage__period-controls {
984
+ display: flex;
985
+ flex-wrap: wrap;
986
+ align-items: center;
987
+ gap: 8px 10px;
988
+ min-width: 0;
989
+ }
990
+
991
+ .green-mortgage__filter-label {
992
+ font-size: 14px;
993
+ white-space: nowrap;
994
+ }
995
+
996
+ .green-mortgage__rating-select {
997
+ width: 220px;
998
+ max-width: 100%;
999
+ }
1000
+
1001
+ .green-mortgage__date-input {
1002
+ width: 150px;
1003
+ max-width: 100%;
1004
+ }
1005
+
1006
+ .green-mortgage__date-input.el-date-editor {
1007
+ --el-date-editor-width: 150px;
1008
+ }
1009
+
1010
+ .green-mortgage__to-text {
1011
+ font-size: 14px;
1012
+ }
1013
+
1014
+ .green-mortgage__refresh-btn,
1015
+ .green-mortgage__export-btn {
1016
+ border-radius: 4px;
1017
+ }
1018
+
1019
+ .green-mortgage__action-row {
1020
+ display: flex;
1021
+ flex-wrap: wrap;
1022
+ align-items: center;
1023
+ justify-content: space-between;
1024
+ gap: 12px 16px;
1025
+ }
1026
+
1027
+ .green-mortgage__search {
1028
+ display: flex;
1029
+ flex-wrap: wrap;
1030
+ align-items: center;
1031
+ gap: 8px;
1032
+ min-width: 0;
1033
+ }
1034
+
1035
+ .green-mortgage__search-label {
1036
+ font-size: 14px;
1037
+ white-space: nowrap;
1038
+ }
1039
+
1040
+ .green-mortgage__search-input {
1041
+ width: 220px;
1042
+ max-width: 100%;
1043
+ }
1044
+
1045
+ /* 有值時滑鼠移入顯示清空 x */
1046
+ .green-mortgage__rating-select :deep(.el-select__clear),
1047
+ .green-mortgage__date-input :deep(.el-input__clear),
1048
+ .green-mortgage__search-input :deep(.el-input__clear) {
1049
+ display: inline-flex !important;
1050
+ opacity: 0;
1051
+ transition: opacity 0.15s ease;
1052
+ cursor: pointer;
1053
+ }
1054
+
1055
+ .green-mortgage__rating-select:hover :deep(.el-select__clear),
1056
+ .green-mortgage__rating-select :deep(.el-select__wrapper.is-focused .el-select__clear),
1057
+ .green-mortgage__date-input:hover :deep(.el-input__clear),
1058
+ .green-mortgage__date-input :deep(.el-input__wrapper.is-focus .el-input__clear),
1059
+ .green-mortgage__search-input:hover :deep(.el-input__clear),
1060
+ .green-mortgage__search-input :deep(.el-input__wrapper.is-focus .el-input__clear) {
1061
+ opacity: 1;
1062
+ }
1063
+
1064
+ .green-mortgage__table-wrap {
1065
+ flex: 1;
1066
+ min-height: 0;
1067
+ display: flex;
1068
+ flex-direction: column;
1069
+ }
1070
+
1071
+ .green-mortgage__table-shell {
1072
+ --gm-scroll-thumb: #c1c1c1;
1073
+ position: relative;
1074
+ flex: 1;
1075
+ min-height: 240px;
1076
+ min-width: 0;
1077
+ box-sizing: border-box;
1078
+ overflow: visible;
1079
+ }
1080
+
1081
+ .green-mortgage__table-shell :deep(.el-table) {
1082
+ overflow: hidden;
1083
+ }
1084
+
1085
+ .green-mortgage__table-shell :deep(.el-table__inner-wrapper) {
1086
+ height: 100%;
1087
+ }
1088
+
1089
+ .green-mortgage__pagination {
1090
+ flex-shrink: 0;
1091
+ display: flex;
1092
+ justify-content: flex-end;
1093
+ flex-wrap: wrap;
1094
+ gap: 8px;
1095
+ margin-top: 12px;
1096
+ }
1097
+
1098
+ .green-mortgage__pagination :deep(.el-pagination) {
1099
+ flex-wrap: wrap;
1100
+ row-gap: 8px;
1101
+ justify-content: flex-end;
1102
+ }
1103
+
1104
+ .green-mortgage__th {
1105
+ display: flex;
1106
+ flex-direction: column;
1107
+ align-items: center;
1108
+ justify-content: center;
1109
+ gap: 2px;
1110
+ font-size: 13px;
1111
+ line-height: 1.25;
1112
+ text-align: center;
1113
+ white-space: normal;
1114
+ }
1115
+
1116
+ .green-mortgage__th--single {
1117
+ min-height: 34px;
1118
+ }
1119
+
1120
+ .green-mortgage__th--inline {
1121
+ flex-direction: row;
1122
+ flex-wrap: wrap;
1123
+ gap: 4px;
1124
+ }
1125
+
1126
+ .green-mortgage__table {
1127
+ width: 100%;
1128
+ height: 100%;
1129
+ }
1130
+
1131
+ .green-mortgage__table :deep(.el-table__header-wrapper),
1132
+ .green-mortgage__table :deep(.el-table__header) {
1133
+ background: #556b7d;
1134
+ }
1135
+
1136
+ .green-mortgage__table :deep(.el-table__header table) {
1137
+ width: 100% !important;
1138
+ }
1139
+
1140
+ .green-mortgage__table :deep(.el-table__header-wrapper th.el-table__cell) {
1141
+ padding: 6px 0;
1142
+ background: #556b7d;
1143
+ color: #fff;
1144
+ border-color: #f5f7fa;
1145
+ }
1146
+
1147
+ /* 豎向滾動預留 gutter:與表頭同色,避免右側留白 */
1148
+ .green-mortgage__table :deep(.el-table__header th.el-table__cell.gutter) {
1149
+ background: #556b7d !important;
1150
+ border-bottom-color: #556b7d !important;
1151
+ }
1152
+
1153
+ .green-mortgage__table :deep(.el-table__header-wrapper th.el-table__cell .cell) {
1154
+ padding: 0 8px;
1155
+ color: #fff;
1156
+ }
1157
+
1158
+ .green-mortgage__table :deep(.el-table__body td.el-table__cell) {
1159
+ border-color: #f5f7fa;
1160
+ }
1161
+
1162
+ .green-mortgage__table :deep(.el-table--border::before),
1163
+ .green-mortgage__table :deep(.el-table--border::after),
1164
+ .green-mortgage__table :deep(.el-table__inner-wrapper::after),
1165
+ .green-mortgage__table :deep(.el-table__border-left-patch) {
1166
+ background-color: #f5f7fa;
1167
+ }
1168
+
1169
+ /* 隱藏 table 內建豎向滑塊;橫向滑塊樣式對齊 RecentReportsTable */
1170
+ .green-mortgage__table-shell :deep(.el-scrollbar__bar.is-vertical) {
1171
+ display: none !important;
1172
+ }
1173
+
1174
+ .green-mortgage__table-shell :deep(.el-scrollbar__wrap) {
1175
+ scrollbar-width: none !important;
1176
+ -ms-overflow-style: none !important;
1177
+ }
1178
+
1179
+ .green-mortgage__table-shell :deep(.el-scrollbar__wrap::-webkit-scrollbar) {
1180
+ width: 0 !important;
1181
+ height: 0 !important;
1182
+ display: none !important;
1183
+ }
1184
+
1185
+ .green-mortgage__table-shell :deep(.el-scrollbar__bar.is-horizontal) {
1186
+ display: block !important;
1187
+ height: 8px !important;
1188
+ left: 2px !important;
1189
+ right: 2px !important;
1190
+ bottom: 0 !important;
1191
+ opacity: 0 !important;
1192
+ pointer-events: none;
1193
+ z-index: 10;
1194
+ transition: opacity 0.15s ease;
1195
+ }
1196
+
1197
+ .green-mortgage__table-shell.is-scrollbar-visible :deep(.el-scrollbar__bar.is-horizontal) {
1198
+ opacity: 1 !important;
1199
+ pointer-events: auto;
1200
+ }
1201
+
1202
+ .green-mortgage__table-shell :deep(.el-scrollbar__bar.is-horizontal .el-scrollbar__thumb) {
1203
+ display: block !important;
1204
+ height: 8px !important;
1205
+ width: 50px !important;
1206
+ min-width: 50px !important;
1207
+ border-radius: 4px !important;
1208
+ background-color: var(--gm-scroll-thumb, #c1c1c1) !important;
1209
+ opacity: 1 !important;
1210
+ cursor: grab;
1211
+ }
1212
+
1213
+ .green-mortgage__table-shell :deep(.el-scrollbar__bar.is-horizontal .el-scrollbar__thumb:active) {
1214
+ cursor: grabbing;
1215
+ background-color: #a8a8a8 !important;
1216
+ }
1217
+
1218
+ .green-mortgage__vbar {
1219
+ position: absolute;
1220
+ right: 4px;
1221
+ width: 8px;
1222
+ z-index: 12;
1223
+ pointer-events: none;
1224
+ overflow: visible;
1225
+ }
1226
+
1227
+ .green-mortgage__vthumb {
1228
+ width: 8px;
1229
+ height: 50px;
1230
+ min-height: 50px;
1231
+ border-radius: 4px;
1232
+ background-color: var(--gm-scroll-thumb, #c1c1c1);
1233
+ cursor: grab;
1234
+ pointer-events: auto;
1235
+ will-change: transform;
1236
+ }
1237
+
1238
+ .green-mortgage__vthumb:active {
1239
+ cursor: grabbing;
1240
+ background-color: #a8a8a8;
1241
+ }
1242
+
1243
+ /* 排序圖示:預設隱藏,懸停/已排序時顯示;顏色沿用 el-table 主題 */
1244
+ .green-mortgage__table :deep(.caret-wrapper) {
1245
+ position: absolute;
1246
+ top: 50%;
1247
+ right: 6px;
1248
+ transform: translateY(-50%);
1249
+ opacity: 0;
1250
+ visibility: hidden;
1251
+ transition: opacity 0.15s ease;
1252
+ }
1253
+
1254
+ .green-mortgage__table :deep(.el-table__header-wrapper th.el-table__cell.is-sortable:hover .caret-wrapper) {
1255
+ opacity: 1;
1256
+ visibility: visible;
1257
+ }
1258
+
1259
+ .green-mortgage__table :deep(.el-table__header-wrapper th.el-table__cell.ascending .caret-wrapper),
1260
+ .green-mortgage__table :deep(.el-table__header-wrapper th.el-table__cell.descending .caret-wrapper) {
1261
+ right: 2px;
1262
+ opacity: 1;
1263
+ visibility: visible;
1264
+ }
1265
+
1266
+ .green-mortgage__table :deep(.sort-caret.ascending) {
1267
+ border-bottom-color: #fff;
1268
+ }
1269
+
1270
+ .green-mortgage__table :deep(.sort-caret.descending) {
1271
+ border-top-color: #fff;
1272
+ }
1273
+
1274
+ .green-mortgage__table :deep(.el-table__header-wrapper th.el-table__cell.is-sortable .cell) {
1275
+ position: relative;
1276
+ padding-right: 24px;
1277
+ }
1278
+
1279
+ /* 大屏 */
1280
+ @media (min-width: 1200px) {
1281
+ .green-mortgage__title,
1282
+ .green-mortgage__subtitle {
1283
+ font-size: 18px;
1284
+ }
1285
+
1286
+ .green-mortgage__table {
1287
+ min-width: 100%;
1288
+ }
1289
+ }
1290
+
1291
+ /* 平板 */
1292
+ @media (max-width: 1199px) {
1293
+ .green-mortgage__title,
1294
+ .green-mortgage__subtitle {
1295
+ font-size: 16px;
1296
+ }
1297
+
1298
+ .green-mortgage__rating-select,
1299
+ .green-mortgage__search-input {
1300
+ width: 200px;
1301
+ }
1302
+
1303
+ .green-mortgage__date-input {
1304
+ width: 140px;
1305
+ }
1306
+
1307
+ .green-mortgage__date-input.el-date-editor {
1308
+ --el-date-editor-width: 140px;
1309
+ }
1310
+
1311
+ .green-mortgage__th {
1312
+ font-size: 12px;
1313
+ }
1314
+ }
1315
+
1316
+ /* 手機 */
1317
+ @media (max-width: 767px) {
1318
+ .green-mortgage {
1319
+ overflow: auto;
1320
+ }
1321
+
1322
+ .green-mortgage :deep(.el-card__body) {
1323
+ padding: 20px;
1324
+ }
1325
+
1326
+ .green-mortgage__header {
1327
+ margin-bottom: 10px;
1328
+ }
1329
+
1330
+ .green-mortgage__title,
1331
+ .green-mortgage__subtitle {
1332
+ font-size: 14px;
1333
+ }
1334
+
1335
+ .green-mortgage__meta {
1336
+ font-size: 12px;
1337
+ }
1338
+
1339
+ .green-mortgage__filter-row,
1340
+ .green-mortgage__action-row {
1341
+ flex-direction: column;
1342
+ align-items: stretch;
1343
+ gap: 10px;
1344
+ }
1345
+
1346
+ .green-mortgage__filter-group,
1347
+ .green-mortgage__filter-group--period,
1348
+ .green-mortgage__period-controls,
1349
+ .green-mortgage__search {
1350
+ width: 100%;
1351
+ }
1352
+
1353
+ .green-mortgage__filter-label,
1354
+ .green-mortgage__search-label {
1355
+ white-space: normal;
1356
+ }
1357
+
1358
+ .green-mortgage__rating-select,
1359
+ .green-mortgage__search-input,
1360
+ .green-mortgage__date-input {
1361
+ width: 100%;
1362
+ }
1363
+
1364
+ .green-mortgage__date-input.el-date-editor {
1365
+ --el-date-editor-width: 100%;
1366
+ }
1367
+
1368
+ .green-mortgage__period-controls {
1369
+ display: grid;
1370
+ grid-template-columns: 1fr auto 1fr;
1371
+ align-items: center;
1372
+ }
1373
+
1374
+ .green-mortgage__period-controls .green-mortgage__refresh-btn {
1375
+ grid-column: 1 / -1;
1376
+ width: 100%;
1377
+ }
1378
+
1379
+ .green-mortgage__export-btn {
1380
+ width: 100%;
1381
+ }
1382
+
1383
+ .green-mortgage__table-shell {
1384
+ min-height: 280px;
1385
+ }
1386
+
1387
+ .green-mortgage__pagination {
1388
+ justify-content: center;
1389
+ }
1390
+
1391
+ .green-mortgage__pagination :deep(.el-pagination) {
1392
+ justify-content: center;
1393
+ }
1394
+ }
1395
+ </style>
package/package.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "aegon-lv38",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC"
12
+ }