aegon-lv82 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.
- package/package.json +12 -0
- package/src/App.vue +44 -0
- package/src/api/index.ts +19 -0
- package/src/api/modules/build-report/reportEditor.ts +526 -0
- package/src/api/modules/build-report/reportPermission.ts +64 -0
- package/src/api/modules/index.ts +69 -0
- package/src/api/modules/industry/green_mortgage.ts +42 -0
- package/src/api/modules/user/index.ts +4 -0
- package/src/api/request.ts +124 -0
- package/src/assets/sample-access-icon.png +0 -0
- package/src/assets/sample-pie-chart.png +0 -0
- package/src/assets/vue.svg +1 -0
- package/src/components/CapsuleScrollbar.vue +7 -0
- package/src/components/Export/ExcelExport.vue +592 -0
- package/src/components/Export/ExcelExport2.vue +494 -0
- package/src/components/Export/ExcelExport3.vue +342 -0
- package/src/components/Export/ExcelExport4.vue +665 -0
- package/src/components/Export/excelExport.js +547 -0
- package/src/components/Export/excelExport.ts +551 -0
- package/src/components/GEN-AI/index.vue +140 -0
- package/src/components/GEN-AI/index1.vue +422 -0
- package/src/components/GEN-AI/index10.vue +5 -0
- package/src/components/GEN-AI/index2.vue +548 -0
- package/src/components/GEN-AI/index3.vue +605 -0
- package/src/components/GEN-AI/index4.vue +609 -0
- package/src/components/GEN-AI/index5.vue +558 -0
- package/src/components/GEN-AI/index6.vue +634 -0
- package/src/components/GEN-AI/index7.vue +688 -0
- package/src/components/GEN-AI/index8.vue +458 -0
- package/src/components/GEN-AI/index9.vue +1217 -0
- package/src/components/GEN-AI/types.ts +12 -0
- package/src/components/GEN-AI/utils.ts +42 -0
- package/src/components/HelloWorld.vue +41 -0
- package/src/components/PageCard.vue +124 -0
- package/src/components/PageHeader.vue +32 -0
- package/src/components/backup/index5 copy.vue +556 -0
- package/src/components/backup/index5.vue +620 -0
- package/src/components/backup/index9 copy.vue +1029 -0
- package/src/components/backup/index9-pro.vue +1065 -0
- package/src/components/backup/index9.vue +1057 -0
- package/src/directives/btnLoading.ts +455 -0
- package/src/directives/debounce copy.ts +670 -0
- package/src/directives/debounce.ts +98 -0
- package/src/directives/index.ts +25 -0
- package/src/layouts/MainLayout.vue +129 -0
- package/src/main.ts +76 -0
- package/src/router/index.ts +142 -0
- package/src/router/menus.ts +28 -0
- package/src/store/modules/gen-ai/index.ts +1 -0
- package/src/store/modules/gen-ai/reportBuild.ts +37 -0
- package/src/style.css +79 -0
- package/src/styles/report-config-popover.css +39 -0
- package/src/styles/report-icon-btn.css +19 -0
- package/src/test/mock.ts +101 -0
- package/src/test/test1.vue +402 -0
- package/src/test/test2.vue +1689 -0
- package/src/types/gen-ai/build-report/reportEditor.ts +684 -0
- package/src/utils/function.ts +46 -0
- package/src/utils/gen-ai-navigation.ts +1 -0
- package/src/utils/gen-ai-scroll.ts +1 -0
- package/src/utils/openDataLoaderWordExport.ts +33 -0
- package/src/utils/pageScrollbar.ts +115 -0
- package/src/utils/randomTranscode.ts +87 -0
- package/src/utils/reportPdfExport.ts +44 -0
- package/src/views/Blank.vue +68 -0
- package/src/views/Home.vue +29 -0
- package/src/views/TemplateCenter/Knowledge.ts +83 -0
- package/src/views/TemplateCenter/data.d.ts +10 -0
- package/src/views/TemplateCenter/index.vue +1128 -0
- package/src/views/TemplateCenter/service.ts +69 -0
- package/src/views/gen-ai/build-report/chartStreamRefs.ts +174 -0
- package/src/views/gen-ai/build-report/coherence-check/index.vue +559 -0
- package/src/views/gen-ai/build-report/components/ReportChapterWorkspace.vue +4258 -0
- package/src/views/gen-ai/build-report/components/ReportConfigPanel.vue +727 -0
- package/src/views/gen-ai/build-report/components/ReportGenerateSettingsPanel.vue +2361 -0
- package/src/views/gen-ai/build-report/components/ReportGeneratingPanel.vue +1765 -0
- package/src/views/gen-ai/build-report/components/ReportPromptPanel.vue +614 -0
- package/src/views/gen-ai/build-report/components/ReportSourceFileSummary.vue +656 -0
- package/src/views/gen-ai/build-report/components/ReportSourcesPanel.vue +1327 -0
- package/src/views/gen-ai/build-report/components/ReportTemplateDetail.vue +191 -0
- package/src/views/gen-ai/build-report/components/ReportTemplateEditorFormPanel.vue +667 -0
- package/src/views/gen-ai/build-report/components/ReportTemplatePicker.vue +302 -0
- package/src/views/gen-ai/build-report/components/ReportWorkflowSidebar.vue +148 -0
- package/src/views/gen-ai/build-report/components/paramsSetting.vue +886 -0
- package/src/views/gen-ai/build-report/components/reportEdit.vue +2283 -0
- package/src/views/gen-ai/build-report/index.vue +856 -0
- package/src/views/gen-ai/build-report/report-domain.ts +1133 -0
- package/src/views/gen-ai/build-report/report-template-data.ts +295 -0
- package/src/views/gen-ai/build-report/useReportWorkflow.ts +1408 -0
- package/src/views/gen-ai/common/assets/NotoSans-SC.woff2 +0 -0
- package/src/views/gen-ai/common/assets/NotoSans-TC.woff2 +0 -0
- package/src/views/gen-ai/common/assets/NotoSans.woff2 +0 -0
- package/src/views/gen-ai/common/assets/fonts.css +278 -0
- package/src/views/gen-ai/common/assets/pagination.css +163 -0
- package/src/views/gen-ai/common/directives/btnLoading.ts +455 -0
- package/src/views/gen-ai/common/directives/debounce.ts +98 -0
- package/src/views/gen-ai/common/directives/index.ts +30 -0
- package/src/views/gen-ai/common/en.md +417 -0
- package/src/views/gen-ai/common/index.ts +36 -0
- package/src/views/gen-ai/common/types/component-expose.ts +9 -0
- package/src/views/gen-ai/common/utils/api-res-status.ts +7 -0
- package/src/views/gen-ai/common/utils/navigation.ts +59 -0
- package/src/views/gen-ai/common/utils/remote-request.ts +45 -0
- package/src/views/gen-ai/common/utils/scroll.ts +577 -0
- package/src/views/gen-ai/components/AIExport/docx-export.ts +950 -0
- package/src/views/gen-ai/components/AIExport/docxExport.ts +2566 -0
- package/src/views/gen-ai/components/AIExport/index.ts +52 -0
- package/src/views/gen-ai/components/AIExport/parseMarkdownHtml.ts +649 -0
- package/src/views/gen-ai/components/CapsuleScrollbar.vue +149 -0
- package/src/views/gen-ai/components/ChapterTitleScroll.vue +468 -0
- package/src/views/gen-ai/components/EntryHeroNavCards.vue +379 -0
- package/src/views/gen-ai/components/GenAiPagination.vue +52 -0
- package/src/views/gen-ai/components/ModelCard.vue +1114 -0
- package/src/views/gen-ai/components/Preview/FilePreviewPage.vue +947 -0
- package/src/views/gen-ai/components/Preview/PreviewCapsuleScroll.vue +205 -0
- package/src/views/gen-ai/components/Preview/PreviewOutlineTree.vue +148 -0
- package/src/views/gen-ai/components/Preview/index.ts +11 -0
- package/src/views/gen-ai/components/Preview/preview.ts +722 -0
- package/src/views/gen-ai/components/PromptField.vue +505 -0
- package/src/views/gen-ai/components/RecentReportsTable.vue +974 -0
- package/src/views/gen-ai/components/ReportCollapsibleSection.vue +172 -0
- package/src/views/gen-ai/components/ReportHistoryList.vue +637 -0
- package/src/views/gen-ai/components/ReportSectionConfirmPopover.vue +251 -0
- package/src/views/gen-ai/components/editor/ChapterTiptapEditor.vue +3139 -0
- package/src/views/gen-ai/components/editor/StructureBlockView.vue +441 -0
- package/src/views/gen-ai/components/editor/TiptapToolbar.vue +1036 -0
- package/src/views/gen-ai/components/editor/TypewriterDocEditor.vue +398 -0
- package/src/views/gen-ai/components/editor/_StructureOrgNode.vue +326 -0
- package/src/views/gen-ai/components/editor/index.ts +12 -0
- package/src/views/gen-ai/components/editor/parseMarkdownHtml.ts +667 -0
- package/src/views/gen-ai/gen-entry/api/index.ts +32 -0
- package/src/views/gen-ai/gen-entry/index.vue +287 -0
- package/src/views/gen-ai/gen-entry/types/index.ts +4 -0
- package/src/views/gen-ai/i.mark +2 -0
- package/src/views/gen-ai/management-center/index.vue +1815 -0
- package/src/views/gen-ai/management-center/mockData.ts +248 -0
- package/src/views/gen-ai/management-center/types/index.ts +37 -0
- package/src/views/gen-ai/model-manager/TemplateEditorFormPanel.vue +661 -0
- package/src/views/gen-ai/model-manager/api/index.ts +32 -0
- package/src/views/gen-ai/model-manager/api/mockApi.ts +33 -0
- package/src/views/gen-ai/model-manager/index.vue +1583 -0
- package/src/views/gen-ai/model-manager/mockData.ts +177 -0
- package/src/views/gen-ai/model-manager/template-detail-shared.ts +154 -0
- package/src/views/gen-ai/model-manager/template-editor.vue +345 -0
- package/src/views/gen-ai/model-manager/types/index.ts +40 -0
- package/src/views/gen-ai/model-manager/utils.ts +25 -0
- package/src/views/greenMortgage.vue +1834 -0
- package/src/vite-env.d.ts +46 -0
|
@@ -0,0 +1,1834 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="green-mortgage-page">
|
|
3
|
+
<div class="green-mortgage">
|
|
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 Buildings
|
|
7
|
+
(NB) / Existing Buildings (EB)
|
|
8
|
+
</h1>
|
|
9
|
+
<p class="green-mortgage__subtitle">
|
|
10
|
+
持有有效的綠建環評新建建築 (NB) / 既有建築 (EB) 金級/鉑金級的住宅物業
|
|
11
|
+
</p>
|
|
12
|
+
<p class="green-mortgage__meta">
|
|
13
|
+
Data Update Date
|
|
14
|
+
<span class="green-mortgage__meta-value">{{ dataUpdateDate || 'YYYY-MM-DD' }}</span>
|
|
15
|
+
</p>
|
|
16
|
+
<p class="green-mortgage__meta">
|
|
17
|
+
Data Download Status
|
|
18
|
+
<span class="green-mortgage__meta-value">{{
|
|
19
|
+
dataDownloadStatus || 'Automatically Downloaded'
|
|
20
|
+
}}</span>
|
|
21
|
+
</p>
|
|
22
|
+
</header>
|
|
23
|
+
|
|
24
|
+
<section class="green-mortgage__filters">
|
|
25
|
+
<div class="green-mortgage__filter-row">
|
|
26
|
+
<div class="green-mortgage__filter-group">
|
|
27
|
+
<label class="green-mortgage__filter-label" for="gm-rating-select"
|
|
28
|
+
>Select Rating(s)</label
|
|
29
|
+
>
|
|
30
|
+
<el-select
|
|
31
|
+
id="gm-rating-select"
|
|
32
|
+
v-model="selectedRatings"
|
|
33
|
+
multiple
|
|
34
|
+
collapse-tags
|
|
35
|
+
collapse-tags-tooltip
|
|
36
|
+
placeholder=""
|
|
37
|
+
class="green-mortgage__rating-select"
|
|
38
|
+
clearable
|
|
39
|
+
>
|
|
40
|
+
<el-option v-for="item in ratingOptions" :key="item" :label="item" :value="item" />
|
|
41
|
+
</el-select>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div class="green-mortgage__filter-group green-mortgage__filter-group--period">
|
|
45
|
+
<span class="green-mortgage__filter-label">Certification period</span>
|
|
46
|
+
<div class="green-mortgage__period-controls">
|
|
47
|
+
<el-date-picker
|
|
48
|
+
v-model="yearRange"
|
|
49
|
+
type="yearrange"
|
|
50
|
+
format="YYYY"
|
|
51
|
+
value-format="YYYY"
|
|
52
|
+
range-separator="to"
|
|
53
|
+
start-placeholder=""
|
|
54
|
+
end-placeholder=""
|
|
55
|
+
class="green-mortgage__date-input"
|
|
56
|
+
style="width: 320px; --el-date-editor-width: 320px"
|
|
57
|
+
clearable
|
|
58
|
+
/>
|
|
59
|
+
<el-button class="green-mortgage__refresh-btn" @click="handleRefresh">
|
|
60
|
+
Refresh
|
|
61
|
+
</el-button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="green-mortgage__action-row">
|
|
67
|
+
<el-button class="green-mortgage__export-btn" @click="handleExportExcel">
|
|
68
|
+
Export to Excel
|
|
69
|
+
</el-button>
|
|
70
|
+
|
|
71
|
+
<div class="green-mortgage__search">
|
|
72
|
+
<label class="green-mortgage__search-label" for="gm-search">Search:</label>
|
|
73
|
+
<el-input
|
|
74
|
+
id="gm-search"
|
|
75
|
+
v-model="searchKeyword"
|
|
76
|
+
class="green-mortgage__search-input"
|
|
77
|
+
clearable
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</section>
|
|
82
|
+
|
|
83
|
+
<section
|
|
84
|
+
class="green-mortgage__table-wrap"
|
|
85
|
+
@mouseenter="onTableShellEnter"
|
|
86
|
+
@mouseleave="onTableShellLeave"
|
|
87
|
+
>
|
|
88
|
+
<div class="green-mortgage__table-main">
|
|
89
|
+
<div class="green-mortgage__table-row">
|
|
90
|
+
<div
|
|
91
|
+
ref="tableRootRef"
|
|
92
|
+
class="green-mortgage__table-shell"
|
|
93
|
+
:class="{ 'is-scrollbar-visible': showScrollbars }"
|
|
94
|
+
>
|
|
95
|
+
<el-table
|
|
96
|
+
ref="tableRef"
|
|
97
|
+
v-loading="tableLoading"
|
|
98
|
+
:data="filteredRows"
|
|
99
|
+
border
|
|
100
|
+
stripe
|
|
101
|
+
class="green-mortgage__table"
|
|
102
|
+
empty-text=" "
|
|
103
|
+
height="100%"
|
|
104
|
+
:default-sort="{ prop: 'propertyNameEng', order: 'ascending' }"
|
|
105
|
+
@header-dragend="onHeaderDragEnd"
|
|
106
|
+
>
|
|
107
|
+
<el-table-column
|
|
108
|
+
prop="propertyNameEng"
|
|
109
|
+
:width="columnWidths.propertyNameEng"
|
|
110
|
+
sortable
|
|
111
|
+
:sort-method="sortByPropertyName"
|
|
112
|
+
show-overflow-tooltip
|
|
113
|
+
>
|
|
114
|
+
<template #header>
|
|
115
|
+
<div class="green-mortgage__th green-mortgage__th--single">
|
|
116
|
+
<span>Property Name</span>
|
|
117
|
+
</div>
|
|
118
|
+
</template>
|
|
119
|
+
</el-table-column>
|
|
120
|
+
|
|
121
|
+
<el-table-column
|
|
122
|
+
prop="propertyNameChi"
|
|
123
|
+
:width="columnWidths.propertyNameChi"
|
|
124
|
+
show-overflow-tooltip
|
|
125
|
+
>
|
|
126
|
+
<template #header>
|
|
127
|
+
<div class="green-mortgage__th green-mortgage__th--single">
|
|
128
|
+
<span>物業名稱</span>
|
|
129
|
+
</div>
|
|
130
|
+
</template>
|
|
131
|
+
</el-table-column>
|
|
132
|
+
|
|
133
|
+
<el-table-column
|
|
134
|
+
prop="propertyAddress"
|
|
135
|
+
:width="columnWidths.propertyAddress"
|
|
136
|
+
sortable
|
|
137
|
+
:sort-method="sortByPropertyAddress"
|
|
138
|
+
show-overflow-tooltip
|
|
139
|
+
>
|
|
140
|
+
<template #header>
|
|
141
|
+
<div class="green-mortgage__th green-mortgage__th--inline">
|
|
142
|
+
<span>Property Address</span>
|
|
143
|
+
<span>物業地址</span>
|
|
144
|
+
</div>
|
|
145
|
+
</template>
|
|
146
|
+
</el-table-column>
|
|
147
|
+
|
|
148
|
+
<el-table-column
|
|
149
|
+
prop="owner"
|
|
150
|
+
:width="columnWidths.owner"
|
|
151
|
+
sortable
|
|
152
|
+
:sort-method="sortByOwner"
|
|
153
|
+
show-overflow-tooltip
|
|
154
|
+
>
|
|
155
|
+
<template #header>
|
|
156
|
+
<div class="green-mortgage__th">
|
|
157
|
+
<span>Owner/Developer Co. Name</span>
|
|
158
|
+
<span>項目持有人/發展商名稱</span>
|
|
159
|
+
</div>
|
|
160
|
+
</template>
|
|
161
|
+
</el-table-column>
|
|
162
|
+
|
|
163
|
+
<el-table-column
|
|
164
|
+
prop="district"
|
|
165
|
+
:width="columnWidths.district"
|
|
166
|
+
sortable
|
|
167
|
+
:sort-method="sortByDistrict"
|
|
168
|
+
show-overflow-tooltip
|
|
169
|
+
>
|
|
170
|
+
<template #header>
|
|
171
|
+
<div class="green-mortgage__th green-mortgage__th--inline">
|
|
172
|
+
<span>District</span>
|
|
173
|
+
<span>地區</span>
|
|
174
|
+
</div>
|
|
175
|
+
</template>
|
|
176
|
+
</el-table-column>
|
|
177
|
+
|
|
178
|
+
<el-table-column
|
|
179
|
+
prop="tool"
|
|
180
|
+
:width="columnWidths.tool"
|
|
181
|
+
sortable
|
|
182
|
+
:sort-method="sortByTool"
|
|
183
|
+
show-overflow-tooltip
|
|
184
|
+
>
|
|
185
|
+
<template #header>
|
|
186
|
+
<div class="green-mortgage__th green-mortgage__th--inline">
|
|
187
|
+
<span>Tool</span>
|
|
188
|
+
<span>評估工具</span>
|
|
189
|
+
</div>
|
|
190
|
+
</template>
|
|
191
|
+
</el-table-column>
|
|
192
|
+
|
|
193
|
+
<el-table-column
|
|
194
|
+
prop="projectType"
|
|
195
|
+
:width="columnWidths.projectType"
|
|
196
|
+
sortable
|
|
197
|
+
:sort-method="sortByProjectType"
|
|
198
|
+
show-overflow-tooltip
|
|
199
|
+
>
|
|
200
|
+
<template #header>
|
|
201
|
+
<div class="green-mortgage__th">
|
|
202
|
+
<span>Project Type</span>
|
|
203
|
+
<span>項目類型</span>
|
|
204
|
+
</div>
|
|
205
|
+
</template>
|
|
206
|
+
</el-table-column>
|
|
207
|
+
|
|
208
|
+
<el-table-column
|
|
209
|
+
prop="achievedRating"
|
|
210
|
+
:width="columnWidths.achievedRating"
|
|
211
|
+
sortable
|
|
212
|
+
:sort-method="sortByAchievedRating"
|
|
213
|
+
show-overflow-tooltip
|
|
214
|
+
>
|
|
215
|
+
<template #header>
|
|
216
|
+
<div class="green-mortgage__th">
|
|
217
|
+
<span>Achieved Rating</span>
|
|
218
|
+
<span>獲得評級</span>
|
|
219
|
+
</div>
|
|
220
|
+
</template>
|
|
221
|
+
</el-table-column>
|
|
222
|
+
|
|
223
|
+
<el-table-column
|
|
224
|
+
prop="year"
|
|
225
|
+
:width="columnWidths.year"
|
|
226
|
+
align="center"
|
|
227
|
+
sortable
|
|
228
|
+
:sort-method="sortByYear"
|
|
229
|
+
>
|
|
230
|
+
<template #header>
|
|
231
|
+
<div class="green-mortgage__th green-mortgage__th--single">
|
|
232
|
+
<span>Year</span>
|
|
233
|
+
</div>
|
|
234
|
+
</template>
|
|
235
|
+
</el-table-column>
|
|
236
|
+
|
|
237
|
+
<el-table-column
|
|
238
|
+
prop="month"
|
|
239
|
+
:width="columnWidths.month"
|
|
240
|
+
align="center"
|
|
241
|
+
sortable
|
|
242
|
+
:sort-method="sortByMonth"
|
|
243
|
+
>
|
|
244
|
+
<template #header>
|
|
245
|
+
<div class="green-mortgage__th green-mortgage__th--single">
|
|
246
|
+
<span>Month</span>
|
|
247
|
+
</div>
|
|
248
|
+
</template>
|
|
249
|
+
</el-table-column>
|
|
250
|
+
|
|
251
|
+
<el-table-column
|
|
252
|
+
prop="gbCertLabelNo"
|
|
253
|
+
:min-width="columnWidths.gbCertLabelNo"
|
|
254
|
+
sortable
|
|
255
|
+
:sort-method="sortByGbCertLabelNo"
|
|
256
|
+
show-overflow-tooltip
|
|
257
|
+
>
|
|
258
|
+
<template #header>
|
|
259
|
+
<div class="green-mortgage__th">
|
|
260
|
+
<span>GB Cert Label No</span>
|
|
261
|
+
<span>售樓書上的標籤號碼</span>
|
|
262
|
+
</div>
|
|
263
|
+
</template>
|
|
264
|
+
</el-table-column>
|
|
265
|
+
</el-table>
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
<!-- 外側橫向滑塊:表格底邊外側,不佔表高 -->
|
|
270
|
+
<div class="green-mortgage__hbar-row">
|
|
271
|
+
<div
|
|
272
|
+
v-show="hScrollCan"
|
|
273
|
+
ref="hbarRef"
|
|
274
|
+
class="green-mortgage__hbar"
|
|
275
|
+
aria-hidden="true"
|
|
276
|
+
@mousedown.prevent="onHTrackMouseDown"
|
|
277
|
+
>
|
|
278
|
+
<div
|
|
279
|
+
class="green-mortgage__hthumb"
|
|
280
|
+
:class="{ 'is-visible': showScrollbars }"
|
|
281
|
+
:style="hbarThumbStyle"
|
|
282
|
+
@mousedown.stop.prevent="onHThumbMouseDown"
|
|
283
|
+
/>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
<!-- 外側豎向滑塊:表格右邊外側(落在 card 右 padding),不佔列寬 -->
|
|
288
|
+
<div
|
|
289
|
+
v-show="scrollCan"
|
|
290
|
+
class="green-mortgage__vbar"
|
|
291
|
+
:style="vbarTrackStyle"
|
|
292
|
+
@mousedown.prevent="onTrackMouseDown"
|
|
293
|
+
>
|
|
294
|
+
<div
|
|
295
|
+
class="green-mortgage__vthumb"
|
|
296
|
+
:class="{ 'is-visible': showScrollbars }"
|
|
297
|
+
:style="vbarThumbStyle"
|
|
298
|
+
@mousedown.stop.prevent="onThumbMouseDown"
|
|
299
|
+
/>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
<div class="green-mortgage__table-footer">
|
|
304
|
+
<span class="green-mortgage__table-footer-left">
|
|
305
|
+
{{ filteredRows.length }} result{{ filteredRows.length === 1 ? '' : 's' }} 搜索結果
|
|
306
|
+
</span>
|
|
307
|
+
<span class="green-mortgage__table-footer-right">
|
|
308
|
+
excluding student hostels and staff quarters 不包括職員/學生宿舍
|
|
309
|
+
</span>
|
|
310
|
+
</div>
|
|
311
|
+
</section>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
</template>
|
|
315
|
+
|
|
316
|
+
<script setup lang="ts">
|
|
317
|
+
import greenMortgageApi from '@/api/modules/industry/green_mortgage'
|
|
318
|
+
import { industryExportToExcel } from '@/utils/function'
|
|
319
|
+
import { useHiddenLayoutScroll } from '@/utils/gen-ai-scroll'
|
|
320
|
+
import type { TableInstance } from 'element-plus'
|
|
321
|
+
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
322
|
+
|
|
323
|
+
interface GreenMortgageRow {
|
|
324
|
+
propertyNameEng: string
|
|
325
|
+
propertyNameChi: string
|
|
326
|
+
propertyAddress: string
|
|
327
|
+
owner: string
|
|
328
|
+
district: string
|
|
329
|
+
tool: string
|
|
330
|
+
projectType: string
|
|
331
|
+
achievedRating: string
|
|
332
|
+
year: string
|
|
333
|
+
month: string
|
|
334
|
+
gbCertLabelNo: string
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const EXPORT_TABLE_HEADERS = [
|
|
338
|
+
'Property Name',
|
|
339
|
+
'物業名稱',
|
|
340
|
+
'Property Address',
|
|
341
|
+
'Owner/Developer Co. Name',
|
|
342
|
+
'District',
|
|
343
|
+
'Tool',
|
|
344
|
+
'Project Type',
|
|
345
|
+
'Achieved Rating',
|
|
346
|
+
'Year',
|
|
347
|
+
'Month',
|
|
348
|
+
'GB Cert Label No',
|
|
349
|
+
] as const
|
|
350
|
+
|
|
351
|
+
const COLUMN_WIDTH_STORAGE_KEY = 'green-mortgage-table-column-widths'
|
|
352
|
+
|
|
353
|
+
const DEFAULT_COLUMN_WIDTHS: Record<string, number> = {
|
|
354
|
+
propertyNameEng: 120,
|
|
355
|
+
propertyNameChi: 120,
|
|
356
|
+
propertyAddress: 170,
|
|
357
|
+
owner: 210,
|
|
358
|
+
district: 90,
|
|
359
|
+
tool: 90,
|
|
360
|
+
projectType: 110,
|
|
361
|
+
achievedRating: 120,
|
|
362
|
+
year: 72,
|
|
363
|
+
month: 72,
|
|
364
|
+
gbCertLabelNo: 150,
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function loadColumnWidths(): Record<string, number> {
|
|
368
|
+
try {
|
|
369
|
+
const raw = localStorage.getItem(COLUMN_WIDTH_STORAGE_KEY)
|
|
370
|
+
if (!raw) return { ...DEFAULT_COLUMN_WIDTHS }
|
|
371
|
+
const parsed = JSON.parse(raw) as Record<string, unknown>
|
|
372
|
+
const next = { ...DEFAULT_COLUMN_WIDTHS }
|
|
373
|
+
for (const key of Object.keys(DEFAULT_COLUMN_WIDTHS)) {
|
|
374
|
+
const value = Number(parsed[key])
|
|
375
|
+
if (Number.isFinite(value) && value >= 48) next[key] = Math.round(value)
|
|
376
|
+
}
|
|
377
|
+
return next
|
|
378
|
+
} catch {
|
|
379
|
+
return { ...DEFAULT_COLUMN_WIDTHS }
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const columnWidths = ref<Record<string, number>>(loadColumnWidths())
|
|
384
|
+
|
|
385
|
+
function persistColumnWidths() {
|
|
386
|
+
localStorage.setItem(COLUMN_WIDTH_STORAGE_KEY, JSON.stringify(columnWidths.value))
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function onHeaderDragEnd(newWidth: number, _oldWidth: number, column: { property?: string }) {
|
|
390
|
+
const prop = column?.property
|
|
391
|
+
if (!prop || !(prop in DEFAULT_COLUMN_WIDTHS)) return
|
|
392
|
+
columnWidths.value = {
|
|
393
|
+
...columnWidths.value,
|
|
394
|
+
[prop]: Math.max(48, Math.round(newWidth)),
|
|
395
|
+
}
|
|
396
|
+
persistColumnWidths()
|
|
397
|
+
void nextTick(() => refreshTableScrollUi())
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const tableLoading = ref(false)
|
|
401
|
+
const dataUpdateDate = ref('')
|
|
402
|
+
const dataDownloadStatus = ref('')
|
|
403
|
+
const ratingOptions = ref<string[]>([])
|
|
404
|
+
/** 下拉選單當前選值(改動不立刻刷表,需點 Refresh) */
|
|
405
|
+
const selectedRatings = ref<string[]>([])
|
|
406
|
+
/** Year Range:[startYear, endYear],元件保證 end >= start(改動不立刻刷表,需點 Refresh) */
|
|
407
|
+
const yearRange = ref<string[] | null>(null)
|
|
408
|
+
/** 已套用到列表的 Rating / Year(僅進頁預設與 Refresh 時寫入) */
|
|
409
|
+
const appliedRatings = ref<string[]>([])
|
|
410
|
+
const appliedYearRange = ref<string[] | null>(null)
|
|
411
|
+
const searchKeyword = ref('')
|
|
412
|
+
const tableRows = ref<GreenMortgageRow[]>([])
|
|
413
|
+
const viewportWidth = ref(typeof window !== 'undefined' ? window.innerWidth : 1440)
|
|
414
|
+
|
|
415
|
+
const startYear = computed(() => {
|
|
416
|
+
const start = yearRange.value?.[0]
|
|
417
|
+
return start != null ? String(start) : ''
|
|
418
|
+
})
|
|
419
|
+
const endYear = computed(() => {
|
|
420
|
+
const end = yearRange.value?.[1]
|
|
421
|
+
return end != null ? String(end) : ''
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
const tableRootRef = ref<HTMLElement | null>(null)
|
|
425
|
+
const tableRef = ref<TableInstance>()
|
|
426
|
+
const hbarRef = ref<HTMLElement | null>(null)
|
|
427
|
+
|
|
428
|
+
/** 表體內側自繪滾動條 */
|
|
429
|
+
const THUMB_PX = 50
|
|
430
|
+
const H_THUMB_PX = 50
|
|
431
|
+
const SCROLL_THUMB_COLOR = '#c1c1c1'
|
|
432
|
+
/** 判定可豎滾的溢出閾值(避免 10 條內仍誤顯滾動條) */
|
|
433
|
+
const V_OVERFLOW_THRESHOLD = 8
|
|
434
|
+
const H_OVERFLOW_THRESHOLD = 8
|
|
435
|
+
/** 豎軌道底部預留,避免與橫軌道打架 */
|
|
436
|
+
const V_BAR_BOTTOM_RESERVE = 4
|
|
437
|
+
const scrollCan = ref(false)
|
|
438
|
+
const hScrollCan = ref(false)
|
|
439
|
+
const thumbHeight = ref(THUMB_PX)
|
|
440
|
+
const thumbOffset = ref(0)
|
|
441
|
+
const trackTop = ref(0)
|
|
442
|
+
const trackHeight = ref(0)
|
|
443
|
+
const hThumbWidth = ref(H_THUMB_PX)
|
|
444
|
+
const hThumbOffset = ref(0)
|
|
445
|
+
const hTrackWidth = ref(0)
|
|
446
|
+
const tableHovered = ref(false)
|
|
447
|
+
const thumbDragging = ref(false)
|
|
448
|
+
const hThumbDragging = ref(false)
|
|
449
|
+
|
|
450
|
+
const showScrollbars = computed(
|
|
451
|
+
() => tableHovered.value || thumbDragging.value || hThumbDragging.value
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
const vbarTrackStyle = computed(() => ({
|
|
455
|
+
top: `${trackTop.value}px`,
|
|
456
|
+
height: `${trackHeight.value}px`,
|
|
457
|
+
}))
|
|
458
|
+
|
|
459
|
+
const vbarThumbStyle = computed(() => ({
|
|
460
|
+
transform: `translateY(${thumbOffset.value}px)`,
|
|
461
|
+
backgroundColor: SCROLL_THUMB_COLOR,
|
|
462
|
+
}))
|
|
463
|
+
|
|
464
|
+
const hbarThumbStyle = computed(() => ({
|
|
465
|
+
transform: `translateX(${hThumbOffset.value}px)`,
|
|
466
|
+
backgroundColor: SCROLL_THUMB_COLOR,
|
|
467
|
+
}))
|
|
468
|
+
|
|
469
|
+
function onTableShellEnter() {
|
|
470
|
+
tableHovered.value = true
|
|
471
|
+
void nextTick(() => refreshOuterBar())
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function onTableShellLeave() {
|
|
475
|
+
if (thumbDragging.value || hThumbDragging.value) return
|
|
476
|
+
tableHovered.value = false
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** 真正可滾的 wrap(優先 el-scrollbar__wrap) */
|
|
480
|
+
function getScrollWrap(): HTMLElement | null {
|
|
481
|
+
const root = tableRootRef.value
|
|
482
|
+
if (!root) return null
|
|
483
|
+
|
|
484
|
+
const wrap =
|
|
485
|
+
(root.querySelector('.el-table__body-wrapper .el-scrollbar__wrap') as HTMLElement | null) ||
|
|
486
|
+
(root.querySelector('.el-scrollbar__wrap') as HTMLElement | null) ||
|
|
487
|
+
(root.querySelector('.el-table__body-wrapper') as HTMLElement | null)
|
|
488
|
+
|
|
489
|
+
return wrap
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** 橫滾時同步表頭,避免表體動、表頭不動 */
|
|
493
|
+
function syncHeaderScrollLeft(left: number) {
|
|
494
|
+
const root = tableRootRef.value
|
|
495
|
+
if (!root) return
|
|
496
|
+
const headerWrap = root.querySelector('.el-table__header-wrapper') as HTMLElement | null
|
|
497
|
+
if (headerWrap && headerWrap.scrollLeft !== left) headerWrap.scrollLeft = left
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function refreshOuterBar() {
|
|
501
|
+
const root = tableRootRef.value
|
|
502
|
+
const wrap = getScrollWrap()
|
|
503
|
+
if (!root || !wrap) {
|
|
504
|
+
scrollCan.value = false
|
|
505
|
+
hScrollCan.value = false
|
|
506
|
+
return
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const { scrollTop, scrollHeight, clientHeight, scrollLeft, scrollWidth, clientWidth } = wrap
|
|
510
|
+
|
|
511
|
+
// —— 豎向(無資料時不顯示) ——
|
|
512
|
+
const vOverflow = scrollHeight - clientHeight
|
|
513
|
+
const canV = filteredRows.value.length > 0 && vOverflow > V_OVERFLOW_THRESHOLD
|
|
514
|
+
scrollCan.value = canV
|
|
515
|
+
if (canV) {
|
|
516
|
+
const header = root.querySelector('.el-table__header-wrapper') as HTMLElement | null
|
|
517
|
+
const headerH = header?.offsetHeight ?? 0
|
|
518
|
+
trackTop.value = headerH + 2
|
|
519
|
+
trackHeight.value = Math.max(0, clientHeight - V_BAR_BOTTOM_RESERVE)
|
|
520
|
+
const trackH = trackHeight.value
|
|
521
|
+
if (trackH <= 0) {
|
|
522
|
+
scrollCan.value = false
|
|
523
|
+
} else {
|
|
524
|
+
thumbHeight.value = THUMB_PX
|
|
525
|
+
const maxTravel = Math.max(0, trackH - THUMB_PX)
|
|
526
|
+
thumbOffset.value =
|
|
527
|
+
vOverflow > 0 && maxTravel > 0 ? Math.round((scrollTop / vOverflow) * maxTravel) : 0
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// —— 橫向(列寬溢出) ——
|
|
532
|
+
const hOverflow = scrollWidth - clientWidth
|
|
533
|
+
const canH = hOverflow > H_OVERFLOW_THRESHOLD
|
|
534
|
+
hScrollCan.value = canH
|
|
535
|
+
if (canH) {
|
|
536
|
+
// 軌道寬以可見 hbar / 表體為準
|
|
537
|
+
const trackW = Math.max(0, hbarRef.value?.clientWidth || root.clientWidth || clientWidth)
|
|
538
|
+
hTrackWidth.value = trackW
|
|
539
|
+
hThumbWidth.value = H_THUMB_PX
|
|
540
|
+
const maxTravel = Math.max(0, trackW - H_THUMB_PX)
|
|
541
|
+
hThumbOffset.value =
|
|
542
|
+
hOverflow > 0 && maxTravel > 0 ? Math.round((scrollLeft / hOverflow) * maxTravel) : 0
|
|
543
|
+
syncHeaderScrollLeft(scrollLeft)
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function onWrapScroll() {
|
|
548
|
+
if (thumbDragging.value || hThumbDragging.value) return
|
|
549
|
+
const wrap = getScrollWrap()
|
|
550
|
+
if (wrap) syncHeaderScrollLeft(wrap.scrollLeft)
|
|
551
|
+
refreshOuterBar()
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
let dragCleanup: (() => void) | undefined
|
|
555
|
+
let boundWrap: HTMLElement | null = null
|
|
556
|
+
let tableResizeObserver: ResizeObserver | undefined
|
|
557
|
+
|
|
558
|
+
function applyThumbScroll(
|
|
559
|
+
scrollEl: HTMLElement,
|
|
560
|
+
nextOffset: number,
|
|
561
|
+
maxTravel: number,
|
|
562
|
+
scrollMax: number
|
|
563
|
+
) {
|
|
564
|
+
const offset = Math.min(maxTravel, Math.max(0, nextOffset))
|
|
565
|
+
thumbOffset.value = offset
|
|
566
|
+
if (maxTravel <= 0 || scrollMax <= 0) return
|
|
567
|
+
scrollEl.scrollTop = (offset / maxTravel) * scrollMax
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function applyHThumbScroll(
|
|
571
|
+
scrollEl: HTMLElement,
|
|
572
|
+
nextOffset: number,
|
|
573
|
+
maxTravel: number,
|
|
574
|
+
scrollMax: number
|
|
575
|
+
) {
|
|
576
|
+
const offset = Math.min(maxTravel, Math.max(0, nextOffset))
|
|
577
|
+
hThumbOffset.value = offset
|
|
578
|
+
if (maxTravel <= 0 || scrollMax <= 0) return
|
|
579
|
+
const left = (offset / maxTravel) * scrollMax
|
|
580
|
+
scrollEl.scrollLeft = left
|
|
581
|
+
syncHeaderScrollLeft(left)
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function onThumbMouseDown(event: MouseEvent) {
|
|
585
|
+
const wrapEl = getScrollWrap()
|
|
586
|
+
if (!wrapEl || !scrollCan.value) return
|
|
587
|
+
const scrollEl: HTMLElement = wrapEl
|
|
588
|
+
|
|
589
|
+
thumbDragging.value = true
|
|
590
|
+
tableHovered.value = true
|
|
591
|
+
|
|
592
|
+
const startY = event.clientY
|
|
593
|
+
const startTop = thumbOffset.value
|
|
594
|
+
const startScrollTop = scrollEl.scrollTop
|
|
595
|
+
const trackH = trackHeight.value
|
|
596
|
+
const maxTravel = Math.max(0, trackH - thumbHeight.value)
|
|
597
|
+
const scrollMax = Math.max(0, scrollEl.scrollHeight - scrollEl.clientHeight)
|
|
598
|
+
|
|
599
|
+
function onMove(e: MouseEvent) {
|
|
600
|
+
if (scrollMax <= 0 || trackH <= 0) return
|
|
601
|
+
const delta = e.clientY - startY
|
|
602
|
+
if (maxTravel <= 0) {
|
|
603
|
+
scrollEl.scrollTop = Math.min(
|
|
604
|
+
scrollMax,
|
|
605
|
+
Math.max(0, startScrollTop + (delta / trackH) * scrollMax)
|
|
606
|
+
)
|
|
607
|
+
return
|
|
608
|
+
}
|
|
609
|
+
applyThumbScroll(scrollEl, startTop + delta, maxTravel, scrollMax)
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function onUp() {
|
|
613
|
+
thumbDragging.value = false
|
|
614
|
+
const root = tableRootRef.value?.closest('.green-mortgage__table-wrap')
|
|
615
|
+
if (!root?.matches(':hover')) tableHovered.value = false
|
|
616
|
+
dragCleanup?.()
|
|
617
|
+
dragCleanup = undefined
|
|
618
|
+
refreshOuterBar()
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
window.addEventListener('mousemove', onMove, { passive: true })
|
|
622
|
+
window.addEventListener('mouseup', onUp, { once: true })
|
|
623
|
+
dragCleanup = () => {
|
|
624
|
+
window.removeEventListener('mousemove', onMove)
|
|
625
|
+
window.removeEventListener('mouseup', onUp)
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/** 點擊豎軌道空白處跳轉 */
|
|
630
|
+
function onTrackMouseDown(event: MouseEvent) {
|
|
631
|
+
const wrapEl = getScrollWrap()
|
|
632
|
+
if (!wrapEl || !scrollCan.value) return
|
|
633
|
+
const trackH = trackHeight.value
|
|
634
|
+
const maxTravel = Math.max(0, trackH - thumbHeight.value)
|
|
635
|
+
const scrollMax = Math.max(0, wrapEl.scrollHeight - wrapEl.clientHeight)
|
|
636
|
+
if (trackH <= 0 || scrollMax <= 0) return
|
|
637
|
+
|
|
638
|
+
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()
|
|
639
|
+
const y = event.clientY - rect.top - thumbHeight.value / 2
|
|
640
|
+
if (maxTravel <= 0) {
|
|
641
|
+
wrapEl.scrollTop = Math.min(scrollMax, Math.max(0, (y / trackH) * scrollMax))
|
|
642
|
+
} else {
|
|
643
|
+
applyThumbScroll(wrapEl, y, maxTravel, scrollMax)
|
|
644
|
+
}
|
|
645
|
+
onThumbMouseDown(event)
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function onHThumbMouseDown(event: MouseEvent) {
|
|
649
|
+
const wrapEl = getScrollWrap()
|
|
650
|
+
if (!wrapEl || !hScrollCan.value) return
|
|
651
|
+
const scrollEl: HTMLElement = wrapEl
|
|
652
|
+
|
|
653
|
+
hThumbDragging.value = true
|
|
654
|
+
tableHovered.value = true
|
|
655
|
+
|
|
656
|
+
const startX = event.clientX
|
|
657
|
+
const startLeft = hThumbOffset.value
|
|
658
|
+
const startScrollLeft = scrollEl.scrollLeft
|
|
659
|
+
const trackW = Math.max(
|
|
660
|
+
0,
|
|
661
|
+
hTrackWidth.value || hbarRef.value?.clientWidth || scrollEl.clientWidth
|
|
662
|
+
)
|
|
663
|
+
const maxTravel = Math.max(0, trackW - hThumbWidth.value)
|
|
664
|
+
const scrollMax = Math.max(0, scrollEl.scrollWidth - scrollEl.clientWidth)
|
|
665
|
+
|
|
666
|
+
function onMove(e: MouseEvent) {
|
|
667
|
+
if (scrollMax <= 0 || trackW <= 0) return
|
|
668
|
+
const delta = e.clientX - startX
|
|
669
|
+
if (maxTravel <= 0) {
|
|
670
|
+
const left = Math.min(scrollMax, Math.max(0, startScrollLeft + (delta / trackW) * scrollMax))
|
|
671
|
+
scrollEl.scrollLeft = left
|
|
672
|
+
syncHeaderScrollLeft(left)
|
|
673
|
+
return
|
|
674
|
+
}
|
|
675
|
+
applyHThumbScroll(scrollEl, startLeft + delta, maxTravel, scrollMax)
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function onUp() {
|
|
679
|
+
hThumbDragging.value = false
|
|
680
|
+
const root = tableRootRef.value?.closest('.green-mortgage__table-wrap')
|
|
681
|
+
if (!root?.matches(':hover')) tableHovered.value = false
|
|
682
|
+
dragCleanup?.()
|
|
683
|
+
dragCleanup = undefined
|
|
684
|
+
refreshOuterBar()
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
window.addEventListener('mousemove', onMove, { passive: true })
|
|
688
|
+
window.addEventListener('mouseup', onUp, { once: true })
|
|
689
|
+
dragCleanup = () => {
|
|
690
|
+
window.removeEventListener('mousemove', onMove)
|
|
691
|
+
window.removeEventListener('mouseup', onUp)
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function onHTrackMouseDown(event: MouseEvent) {
|
|
696
|
+
const wrapEl = getScrollWrap()
|
|
697
|
+
if (!wrapEl || !hScrollCan.value) return
|
|
698
|
+
const trackW = Math.max(0, hTrackWidth.value || hbarRef.value?.clientWidth || wrapEl.clientWidth)
|
|
699
|
+
const maxTravel = Math.max(0, trackW - hThumbWidth.value)
|
|
700
|
+
const scrollMax = Math.max(0, wrapEl.scrollWidth - wrapEl.clientWidth)
|
|
701
|
+
if (trackW <= 0 || scrollMax <= 0) return
|
|
702
|
+
|
|
703
|
+
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()
|
|
704
|
+
const x = event.clientX - rect.left - hThumbWidth.value / 2
|
|
705
|
+
if (maxTravel <= 0) {
|
|
706
|
+
const left = Math.min(scrollMax, Math.max(0, (x / trackW) * scrollMax))
|
|
707
|
+
wrapEl.scrollLeft = left
|
|
708
|
+
syncHeaderScrollLeft(left)
|
|
709
|
+
} else {
|
|
710
|
+
applyHThumbScroll(wrapEl, x, maxTravel, scrollMax)
|
|
711
|
+
}
|
|
712
|
+
onHThumbMouseDown(event)
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function bindWrapScroll() {
|
|
716
|
+
unbindWrapScroll()
|
|
717
|
+
const wrap = getScrollWrap()
|
|
718
|
+
if (!wrap) return
|
|
719
|
+
wrap.addEventListener('scroll', onWrapScroll, { passive: true })
|
|
720
|
+
boundWrap = wrap
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function unbindWrapScroll() {
|
|
724
|
+
boundWrap?.removeEventListener('scroll', onWrapScroll)
|
|
725
|
+
boundWrap = null
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function refreshTableScrollUi() {
|
|
729
|
+
tableRef.value?.doLayout()
|
|
730
|
+
void nextTick(() => {
|
|
731
|
+
bindWrapScroll()
|
|
732
|
+
refreshOuterBar()
|
|
733
|
+
})
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function syncViewportWidth() {
|
|
737
|
+
viewportWidth.value = window.innerWidth
|
|
738
|
+
refreshTableScrollUi()
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const filteredRows = computed(() => filterRowsByCurrentCriteria(tableRows.value))
|
|
742
|
+
|
|
743
|
+
watch(filteredRows, () => {
|
|
744
|
+
void nextTick(() => refreshTableScrollUi())
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
function pickString(record: Record<string, unknown>, keys: string[]) {
|
|
748
|
+
for (const key of keys) {
|
|
749
|
+
const value = record[key]
|
|
750
|
+
if (value != null && value !== '') return String(value)
|
|
751
|
+
}
|
|
752
|
+
return ''
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function normalizeRow(item: unknown): GreenMortgageRow {
|
|
756
|
+
const record = item && typeof item === 'object' ? (item as Record<string, unknown>) : {}
|
|
757
|
+
return {
|
|
758
|
+
propertyNameEng: pickString(record, ['propertyNameEng', 'PROPERTY_NAME_ENG']),
|
|
759
|
+
propertyNameChi: pickString(record, ['propertyNameChi', 'PROPERTY_NAME_CHI']),
|
|
760
|
+
propertyAddress: pickString(record, ['propertyAddress', 'PROPERTY_ADDRESS']),
|
|
761
|
+
owner: pickString(record, ['owner', 'OWNER']),
|
|
762
|
+
district: pickString(record, ['district', 'DISTRICT']),
|
|
763
|
+
tool: pickString(record, ['tool', 'TOOL']),
|
|
764
|
+
projectType: pickString(record, ['projectType', 'PROJECT_TYPE']),
|
|
765
|
+
achievedRating: pickString(record, ['achievedRating', 'ACHIEVED_RATING']),
|
|
766
|
+
year: pickString(record, ['year', 'YEAR', 'Year']),
|
|
767
|
+
month: pickString(record, ['month', 'MONTH']),
|
|
768
|
+
gbCertLabelNo: pickString(record, ['gbCertLabelNo', 'GB_CERT_LABEL_NO']),
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function parseQueryResponse(data: unknown) {
|
|
773
|
+
if (!data || typeof data !== 'object') {
|
|
774
|
+
return { rows: [] as GreenMortgageRow[], dataUpdateDate: '', dataDownloadStatus: '' }
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const record = data as Record<string, unknown>
|
|
778
|
+
// 兼容 data 再包一層
|
|
779
|
+
const inner =
|
|
780
|
+
record.data && typeof record.data === 'object' && !Array.isArray(record.data)
|
|
781
|
+
? (record.data as Record<string, unknown>)
|
|
782
|
+
: record
|
|
783
|
+
|
|
784
|
+
const statusVO =
|
|
785
|
+
(inner.riskModelDataStatusVO && typeof inner.riskModelDataStatusVO === 'object'
|
|
786
|
+
? (inner.riskModelDataStatusVO as Record<string, unknown>)
|
|
787
|
+
: null) ||
|
|
788
|
+
(record.riskModelDataStatusVO && typeof record.riskModelDataStatusVO === 'object'
|
|
789
|
+
? (record.riskModelDataStatusVO as Record<string, unknown>)
|
|
790
|
+
: {})
|
|
791
|
+
|
|
792
|
+
const list = inner.riskModelDataVOList ?? record.riskModelDataVOList
|
|
793
|
+
const rows = Array.isArray(list) ? list.map(normalizeRow) : []
|
|
794
|
+
|
|
795
|
+
return {
|
|
796
|
+
rows,
|
|
797
|
+
dataUpdateDate: pickString(statusVO, ['dataUpdateDate', 'DataUpdateDate']),
|
|
798
|
+
dataDownloadStatus: pickString(statusVO, ['dataDownloadStatus', 'DataDownloadStatus']),
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function parseRatingsResponse(data: unknown): string[] {
|
|
803
|
+
if (!Array.isArray(data)) return []
|
|
804
|
+
return data.map((item) => String(item).trim()).filter(Boolean)
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function compareLexicographic(a: unknown, b: unknown) {
|
|
808
|
+
return String(a ?? '').localeCompare(String(b ?? ''), undefined, {
|
|
809
|
+
sensitivity: 'base',
|
|
810
|
+
})
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function compareText(a: unknown, b: unknown) {
|
|
814
|
+
return String(a ?? '').localeCompare(String(b ?? ''), undefined, {
|
|
815
|
+
numeric: true,
|
|
816
|
+
sensitivity: 'base',
|
|
817
|
+
})
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function compareAlphabeticalEmptyFirst(a: unknown, b: unknown) {
|
|
821
|
+
const strA = String(a ?? '').trim()
|
|
822
|
+
const strB = String(b ?? '').trim()
|
|
823
|
+
const emptyA = strA === ''
|
|
824
|
+
const emptyB = strB === ''
|
|
825
|
+
|
|
826
|
+
if (emptyA && emptyB) return 0
|
|
827
|
+
if (emptyA) return -1
|
|
828
|
+
if (emptyB) return 1
|
|
829
|
+
|
|
830
|
+
return strA.localeCompare(strB, undefined, { sensitivity: 'base' })
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function createAlphabeticalEmptyFirstSorter(key: keyof GreenMortgageRow) {
|
|
834
|
+
return (a: GreenMortgageRow, b: GreenMortgageRow) => compareAlphabeticalEmptyFirst(a[key], b[key])
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/** 首字元:數字 0 → 字母 1 → 其他 2 */
|
|
838
|
+
function getFirstCharSortCategory(text: string) {
|
|
839
|
+
const trimmed = text.trim()
|
|
840
|
+
if (!trimmed) return 2
|
|
841
|
+
const first = trimmed.charAt(0)
|
|
842
|
+
if (/\d/.test(first)) return 0
|
|
843
|
+
if (/[a-zA-Z]/.test(first)) return 1
|
|
844
|
+
return 2
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function getPropertyNameSortKey(row: GreenMortgageRow) {
|
|
848
|
+
return (row.propertyNameEng || '').trim()
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function getPropertyNameLeadingNumber(name: string) {
|
|
852
|
+
const matched = name.trim().match(/^(\d+)/)
|
|
853
|
+
return matched ? Number(matched[1]) : Number.NaN
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const sortByPropertyName = (a: GreenMortgageRow, b: GreenMortgageRow) => {
|
|
857
|
+
const nameA = getPropertyNameSortKey(a)
|
|
858
|
+
const nameB = getPropertyNameSortKey(b)
|
|
859
|
+
const categoryA = getFirstCharSortCategory(nameA)
|
|
860
|
+
const categoryB = getFirstCharSortCategory(nameB)
|
|
861
|
+
|
|
862
|
+
if (categoryA !== categoryB) return categoryA - categoryB
|
|
863
|
+
|
|
864
|
+
if (categoryA === 0) {
|
|
865
|
+
const leadingA = getPropertyNameLeadingNumber(nameA)
|
|
866
|
+
const leadingB = getPropertyNameLeadingNumber(nameB)
|
|
867
|
+
if (!Number.isNaN(leadingA) && !Number.isNaN(leadingB) && leadingA !== leadingB) {
|
|
868
|
+
return leadingA - leadingB
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
return nameA.localeCompare(nameB, undefined, { sensitivity: 'base' })
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
const sortByPropertyAddress = (a: GreenMortgageRow, b: GreenMortgageRow) => {
|
|
876
|
+
const addressA = (a.propertyAddress || '').trim()
|
|
877
|
+
const addressB = (b.propertyAddress || '').trim()
|
|
878
|
+
const categoryA = getFirstCharSortCategory(addressA)
|
|
879
|
+
const categoryB = getFirstCharSortCategory(addressB)
|
|
880
|
+
|
|
881
|
+
if (categoryA !== categoryB) return categoryA - categoryB
|
|
882
|
+
|
|
883
|
+
return compareLexicographic(addressA, addressB)
|
|
884
|
+
}
|
|
885
|
+
const sortByOwner = (a: GreenMortgageRow, b: GreenMortgageRow) => compareText(a.owner, b.owner)
|
|
886
|
+
const sortByDistrict = createAlphabeticalEmptyFirstSorter('district')
|
|
887
|
+
const sortByTool = createAlphabeticalEmptyFirstSorter('tool')
|
|
888
|
+
const sortByProjectType = createAlphabeticalEmptyFirstSorter('projectType')
|
|
889
|
+
const sortByAchievedRating = createAlphabeticalEmptyFirstSorter('achievedRating')
|
|
890
|
+
const sortByYear = createAlphabeticalEmptyFirstSorter('year')
|
|
891
|
+
/** Month:按 1–12 月份數值排序(空值靠前),非字母序 */
|
|
892
|
+
const sortByMonth = (a: GreenMortgageRow, b: GreenMortgageRow) => {
|
|
893
|
+
const monthA = parseMonthNumber(a.month)
|
|
894
|
+
const monthB = parseMonthNumber(b.month)
|
|
895
|
+
const emptyA = monthA == null
|
|
896
|
+
const emptyB = monthB == null
|
|
897
|
+
if (emptyA && emptyB) return 0
|
|
898
|
+
if (emptyA) return -1
|
|
899
|
+
if (emptyB) return 1
|
|
900
|
+
return monthA - monthB
|
|
901
|
+
}
|
|
902
|
+
const sortByGbCertLabelNo = createAlphabeticalEmptyFirstSorter('gbCertLabelNo')
|
|
903
|
+
|
|
904
|
+
function parseMonthNumber(value: unknown): number | null {
|
|
905
|
+
const raw = String(value ?? '').trim()
|
|
906
|
+
if (!raw) return null
|
|
907
|
+
const direct = Number(raw)
|
|
908
|
+
if (Number.isFinite(direct) && direct >= 1 && direct <= 12) return Math.trunc(direct)
|
|
909
|
+
const matched = raw.match(/(\d{1,2})/)
|
|
910
|
+
if (matched?.[1]) {
|
|
911
|
+
const n = Number(matched[1])
|
|
912
|
+
if (Number.isFinite(n) && n >= 1 && n <= 12) return Math.trunc(n)
|
|
913
|
+
}
|
|
914
|
+
const key = raw.toLowerCase().slice(0, 3)
|
|
915
|
+
const nameMap: Record<string, number> = {
|
|
916
|
+
jan: 1,
|
|
917
|
+
feb: 2,
|
|
918
|
+
mar: 3,
|
|
919
|
+
apr: 4,
|
|
920
|
+
may: 5,
|
|
921
|
+
jun: 6,
|
|
922
|
+
jul: 7,
|
|
923
|
+
aug: 8,
|
|
924
|
+
sep: 9,
|
|
925
|
+
oct: 10,
|
|
926
|
+
nov: 11,
|
|
927
|
+
dec: 12,
|
|
928
|
+
}
|
|
929
|
+
return nameMap[key] ?? null
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
async function fetchGreenMortgageRatings() {
|
|
933
|
+
try {
|
|
934
|
+
const res = await greenMortgageApi.getGreenMortgageRatings()
|
|
935
|
+
ratingOptions.value = parseRatingsResponse(res?.data)
|
|
936
|
+
selectedRatings.value = [...ratingOptions.value]
|
|
937
|
+
} catch (error) {
|
|
938
|
+
console.error('getGreenMortgageRatings failed', error)
|
|
939
|
+
ratingOptions.value = []
|
|
940
|
+
selectedRatings.value = []
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
function asTrimmedText(value: string | null | undefined) {
|
|
945
|
+
return String(value ?? '').trim()
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
function filterRowsByKeyword(rows: GreenMortgageRow[]) {
|
|
949
|
+
const keyword = asTrimmedText(searchKeyword.value).toLowerCase()
|
|
950
|
+
if (!keyword) return rows
|
|
951
|
+
|
|
952
|
+
return rows.filter((row) =>
|
|
953
|
+
Object.values(row).some((value) => String(value).toLowerCase().includes(keyword))
|
|
954
|
+
)
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/** 從 Certification period / Year 欄取值中提取年份(相容 YYYY、YYYY-MM-DD、夾雜文字) */
|
|
958
|
+
function getYearFromPeriod(value: string | null | undefined): number | null {
|
|
959
|
+
const trimmed = asTrimmedText(value)
|
|
960
|
+
if (!trimmed) return null
|
|
961
|
+
const matched = trimmed.match(/(\d{4})/)
|
|
962
|
+
if (!matched?.[1]) return null
|
|
963
|
+
const year = Number(matched[1])
|
|
964
|
+
if (Number.isNaN(year) || year < 1900 || year > 2100) return null
|
|
965
|
+
return year
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/** 接口返回後:遍歷列表 Year 欄,寫入最小 / 最大年份到 yearRange(進頁可見預設值) */
|
|
969
|
+
function applyYearRangeFromTableYearColumn(rows: GreenMortgageRow[]) {
|
|
970
|
+
let min = Infinity
|
|
971
|
+
let max = -Infinity
|
|
972
|
+
for (const row of rows) {
|
|
973
|
+
const year = getYearFromPeriod(row.year)
|
|
974
|
+
if (year == null) continue
|
|
975
|
+
if (year < min) min = year
|
|
976
|
+
if (year > max) max = year
|
|
977
|
+
}
|
|
978
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) {
|
|
979
|
+
yearRange.value = null
|
|
980
|
+
return
|
|
981
|
+
}
|
|
982
|
+
// 新建陣列,確保 yearrange 能偵測到變更並顯示
|
|
983
|
+
yearRange.value = [String(min), String(max)]
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* 列表篩選:已套用的 Select Rating(s) + Certification period + 即時 Search
|
|
988
|
+
* Rating / Year 僅在進頁預設與點 Refresh 後生效
|
|
989
|
+
*/
|
|
990
|
+
function filterRowsByCurrentCriteria(rows: GreenMortgageRow[]) {
|
|
991
|
+
let result = rows
|
|
992
|
+
|
|
993
|
+
if (appliedRatings.value.length > 0) {
|
|
994
|
+
const ratingSet = new Set(appliedRatings.value)
|
|
995
|
+
result = result.filter((row) => ratingSet.has(row.achievedRating))
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
const start = getYearFromPeriod(appliedYearRange.value?.[0])
|
|
999
|
+
const end = getYearFromPeriod(appliedYearRange.value?.[1])
|
|
1000
|
+
if (start != null || end != null) {
|
|
1001
|
+
result = result.filter((row) => {
|
|
1002
|
+
const raw = row.year
|
|
1003
|
+
if (raw == null || String(raw).trim() === '') return true
|
|
1004
|
+
const year = Number(raw)
|
|
1005
|
+
if (Number.isNaN(year)) return true
|
|
1006
|
+
if (start != null && year < start) return false
|
|
1007
|
+
if (end != null && year > end) return false
|
|
1008
|
+
return true
|
|
1009
|
+
})
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
return filterRowsByKeyword(result)
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/** 將當前下拉 / 日期選值套用到列表篩選 */
|
|
1016
|
+
function commitFilterCriteria() {
|
|
1017
|
+
appliedRatings.value = [...selectedRatings.value]
|
|
1018
|
+
appliedYearRange.value = yearRange.value?.length ? [...yearRange.value] : null
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
function buildWorksheetData(rows: GreenMortgageRow[]) {
|
|
1022
|
+
return [
|
|
1023
|
+
[...EXPORT_TABLE_HEADERS],
|
|
1024
|
+
...rows.map((row) => [
|
|
1025
|
+
row.propertyNameEng,
|
|
1026
|
+
row.propertyNameChi,
|
|
1027
|
+
row.propertyAddress,
|
|
1028
|
+
row.owner,
|
|
1029
|
+
row.district,
|
|
1030
|
+
row.tool,
|
|
1031
|
+
row.projectType,
|
|
1032
|
+
row.achievedRating,
|
|
1033
|
+
row.year,
|
|
1034
|
+
row.month,
|
|
1035
|
+
row.gbCertLabelNo,
|
|
1036
|
+
]),
|
|
1037
|
+
]
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
/** 進頁預設空條件 = 查詢全部;Refresh 用當前下拉選值;匯出用已套用條件 */
|
|
1041
|
+
function getGreenMortgageQueryParams(source: 'draft' | 'applied' = 'draft') {
|
|
1042
|
+
if (source === 'applied') {
|
|
1043
|
+
return {
|
|
1044
|
+
endYear: asTrimmedText(appliedYearRange.value?.[1]),
|
|
1045
|
+
ratingList: [...appliedRatings.value],
|
|
1046
|
+
startYear: asTrimmedText(appliedYearRange.value?.[0]),
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return {
|
|
1050
|
+
endYear: asTrimmedText(endYear.value),
|
|
1051
|
+
ratingList: [...selectedRatings.value],
|
|
1052
|
+
startYear: asTrimmedText(startYear.value),
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
async function fetchGreenMortgageQuery(options?: { applyYearDefaults?: boolean }) {
|
|
1057
|
+
tableLoading.value = true
|
|
1058
|
+
try {
|
|
1059
|
+
const res = await greenMortgageApi.getGreenMortgageQuery(getGreenMortgageQueryParams())
|
|
1060
|
+
// 兼容 { data: {...} } / 直接 {...}
|
|
1061
|
+
const payload =
|
|
1062
|
+
res && typeof res === 'object' && 'data' in res && (res as { data?: unknown }).data != null
|
|
1063
|
+
? (res as { data: unknown }).data
|
|
1064
|
+
: res
|
|
1065
|
+
|
|
1066
|
+
if (payload) {
|
|
1067
|
+
const parsed = parseQueryResponse(payload)
|
|
1068
|
+
tableRows.value = parsed.rows
|
|
1069
|
+
if (parsed.dataUpdateDate) dataUpdateDate.value = parsed.dataUpdateDate
|
|
1070
|
+
if (parsed.dataDownloadStatus) dataDownloadStatus.value = parsed.dataDownloadStatus
|
|
1071
|
+
// 進頁:列表 Year 回來後立刻寫入 Certification period,刷新頁面可見預設區間
|
|
1072
|
+
if (options?.applyYearDefaults) {
|
|
1073
|
+
applyYearRangeFromTableYearColumn(parsed.rows)
|
|
1074
|
+
await nextTick()
|
|
1075
|
+
}
|
|
1076
|
+
} else {
|
|
1077
|
+
tableRows.value = []
|
|
1078
|
+
if (options?.applyYearDefaults) yearRange.value = null
|
|
1079
|
+
}
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
console.error('getGreenMortgageQuery failed', error)
|
|
1082
|
+
} finally {
|
|
1083
|
+
tableLoading.value = false
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
function handleRefresh() {
|
|
1088
|
+
commitFilterCriteria()
|
|
1089
|
+
fetchGreenMortgageQuery()
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/** 匯出已套用的 Select Rating(s) + Certification period + Search 篩選後的資料(格式不變) */
|
|
1093
|
+
async function handleExportExcel() {
|
|
1094
|
+
try {
|
|
1095
|
+
const res = await greenMortgageApi.getGreenMortgageQuery(getGreenMortgageQueryParams('applied'))
|
|
1096
|
+
if (!res?.data) return
|
|
1097
|
+
|
|
1098
|
+
const parsed = parseQueryResponse(res.data)
|
|
1099
|
+
const rows = filterRowsByCurrentCriteria(parsed.rows)
|
|
1100
|
+
if (!rows.length) return
|
|
1101
|
+
|
|
1102
|
+
const worksheetData = buildWorksheetData(rows)
|
|
1103
|
+
await industryExportToExcel(worksheetData, 'Green Mortgage', 'Green_Mortgage')
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
console.error('handleExportExcel failed', error)
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/** 匯出當前頁已篩選資料(Select Rating / Certification period / Search 過濾後的結果) */
|
|
1110
|
+
async function handleExportExcel2() {
|
|
1111
|
+
try {
|
|
1112
|
+
const rows = filteredRows.value
|
|
1113
|
+
if (!rows.length) return
|
|
1114
|
+
|
|
1115
|
+
const worksheetData = buildWorksheetData(rows)
|
|
1116
|
+
await industryExportToExcel(worksheetData, 'Green Mortgage', 'Green_Mortgage')
|
|
1117
|
+
} catch (error) {
|
|
1118
|
+
console.error('handleExportExcel2 failed', error)
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
/** 備用導出入口:與 handleExportExcel 二選一替換按鈕 @click 即可 */
|
|
1123
|
+
defineExpose({ handleExportExcel2 })
|
|
1124
|
+
|
|
1125
|
+
// 鎖定 .layout 外層滾動(進/離頁自動處理)
|
|
1126
|
+
useHiddenLayoutScroll()
|
|
1127
|
+
|
|
1128
|
+
onMounted(async () => {
|
|
1129
|
+
// 以下僅本頁自繪表內滾動條:resize / 軌道同步(與 layout 鎖定無關)
|
|
1130
|
+
syncViewportWidth()
|
|
1131
|
+
window.addEventListener('resize', syncViewportWidth)
|
|
1132
|
+
void nextTick(() => {
|
|
1133
|
+
refreshTableScrollUi()
|
|
1134
|
+
const root = tableRootRef.value
|
|
1135
|
+
if (root) {
|
|
1136
|
+
tableResizeObserver = new ResizeObserver(() => {
|
|
1137
|
+
void nextTick(() => refreshTableScrollUi())
|
|
1138
|
+
})
|
|
1139
|
+
tableResizeObserver.observe(root)
|
|
1140
|
+
}
|
|
1141
|
+
})
|
|
1142
|
+
await fetchGreenMortgageRatings()
|
|
1143
|
+
await fetchGreenMortgageQuery({ applyYearDefaults: true })
|
|
1144
|
+
// 保險:若首次寫入未生效,再依 tableRows.Year 補一次
|
|
1145
|
+
if (!yearRange.value?.length && tableRows.value.length) {
|
|
1146
|
+
applyYearRangeFromTableYearColumn(tableRows.value)
|
|
1147
|
+
}
|
|
1148
|
+
// 進頁預設值寫入後立刻套用,之後改下拉/日期需點 Refresh 才更新列表
|
|
1149
|
+
commitFilterCriteria()
|
|
1150
|
+
await nextTick()
|
|
1151
|
+
tableRef.value?.sort('propertyNameEng', 'ascending')
|
|
1152
|
+
void nextTick(() => refreshTableScrollUi())
|
|
1153
|
+
})
|
|
1154
|
+
|
|
1155
|
+
onUnmounted(() => {
|
|
1156
|
+
window.removeEventListener('resize', syncViewportWidth)
|
|
1157
|
+
tableResizeObserver?.disconnect()
|
|
1158
|
+
unbindWrapScroll()
|
|
1159
|
+
dragCleanup?.()
|
|
1160
|
+
})
|
|
1161
|
+
</script>
|
|
1162
|
+
|
|
1163
|
+
<style lang="scss" scoped>
|
|
1164
|
+
.green-mortgage-page {
|
|
1165
|
+
box-sizing: border-box;
|
|
1166
|
+
width: 100%;
|
|
1167
|
+
height: 100%;
|
|
1168
|
+
min-height: 0;
|
|
1169
|
+
padding: 0;
|
|
1170
|
+
background: transparent;
|
|
1171
|
+
/* 允許豎條伸入 el-card__body 右 padding */
|
|
1172
|
+
overflow: visible;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
.green-mortgage {
|
|
1176
|
+
/* 與 el-card__body 的 --gm-card-padding 對齊,供豎條外側定位 */
|
|
1177
|
+
--gm-card-padding: 20px;
|
|
1178
|
+
--gm-h-scroll-gutter: 14px;
|
|
1179
|
+
/* 豎條在右 padding 內居中:不改動其他邊距 */
|
|
1180
|
+
--gm-v-scroll-thumb: 8px;
|
|
1181
|
+
/* 篩選區控件高度與 el-button 默認 32px 對齊 */
|
|
1182
|
+
--gm-control-height: 32px;
|
|
1183
|
+
--el-component-size: var(--gm-control-height);
|
|
1184
|
+
--el-color-primary: #c53355;
|
|
1185
|
+
--el-color-primary-light-3: #d66b82;
|
|
1186
|
+
--el-color-primary-light-5: #e299a9;
|
|
1187
|
+
--el-color-primary-light-7: #efc6cf;
|
|
1188
|
+
--el-color-primary-light-9: #fce8ec;
|
|
1189
|
+
box-sizing: border-box;
|
|
1190
|
+
display: flex;
|
|
1191
|
+
flex-direction: column;
|
|
1192
|
+
width: 100%;
|
|
1193
|
+
height: 100%;
|
|
1194
|
+
min-height: 0;
|
|
1195
|
+
/* 勿裁切伸入 card 右 padding 的豎條 */
|
|
1196
|
+
overflow: visible;
|
|
1197
|
+
border: none;
|
|
1198
|
+
border-radius: 0;
|
|
1199
|
+
box-shadow: none;
|
|
1200
|
+
background: transparent;
|
|
1201
|
+
padding: 0;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
.green-mortgage__header {
|
|
1205
|
+
flex-shrink: 0;
|
|
1206
|
+
margin-bottom: 14px;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
.green-mortgage__title,
|
|
1210
|
+
.green-mortgage__subtitle {
|
|
1211
|
+
margin: 0;
|
|
1212
|
+
color: #c0504d;
|
|
1213
|
+
font-family: 'Times New Roman', Times, serif;
|
|
1214
|
+
font-size: 18px;
|
|
1215
|
+
font-weight: 400;
|
|
1216
|
+
line-height: 1.35;
|
|
1217
|
+
word-break: break-word;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
.green-mortgage__meta {
|
|
1221
|
+
margin: 4px 0 0;
|
|
1222
|
+
font-size: 14px;
|
|
1223
|
+
line-height: 1.4;
|
|
1224
|
+
color: var(--el-text-color-primary);
|
|
1225
|
+
word-break: break-word;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
.green-mortgage__meta-value {
|
|
1229
|
+
font-size: 16px;
|
|
1230
|
+
font-weight: 600;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
.green-mortgage__filters {
|
|
1234
|
+
flex-shrink: 0;
|
|
1235
|
+
margin-bottom: 10px;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
.green-mortgage__filter-row {
|
|
1239
|
+
display: flex;
|
|
1240
|
+
flex-wrap: wrap;
|
|
1241
|
+
align-items: center;
|
|
1242
|
+
gap: 10px 16px;
|
|
1243
|
+
margin-bottom: 10px;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
.green-mortgage__filter-group {
|
|
1247
|
+
display: flex;
|
|
1248
|
+
flex-wrap: wrap;
|
|
1249
|
+
align-items: center;
|
|
1250
|
+
gap: 8px 10px;
|
|
1251
|
+
min-width: 0;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
.green-mortgage__filter-group--period {
|
|
1255
|
+
flex: 1 1 auto;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
.green-mortgage__period-controls {
|
|
1259
|
+
display: flex;
|
|
1260
|
+
flex-wrap: wrap;
|
|
1261
|
+
align-items: center;
|
|
1262
|
+
gap: 8px 10px;
|
|
1263
|
+
min-width: 0;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
.green-mortgage__filter-label {
|
|
1267
|
+
font-size: 14px;
|
|
1268
|
+
white-space: nowrap;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
.green-mortgage__rating-select {
|
|
1272
|
+
width: 220px;
|
|
1273
|
+
max-width: 100%;
|
|
1274
|
+
height: var(--gm-control-height);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
.green-mortgage__rating-select :deep(.el-select__wrapper) {
|
|
1278
|
+
min-height: var(--gm-control-height);
|
|
1279
|
+
height: var(--gm-control-height);
|
|
1280
|
+
padding-top: 0;
|
|
1281
|
+
padding-bottom: 0;
|
|
1282
|
+
box-sizing: border-box;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
.green-mortgage__rating-select :deep(.el-select__selection) {
|
|
1286
|
+
gap: 4px;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
.green-mortgage__date-input {
|
|
1290
|
+
width: 320px !important;
|
|
1291
|
+
max-width: 320px;
|
|
1292
|
+
height: var(--gm-control-height);
|
|
1293
|
+
--el-input-height: var(--gm-control-height);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
.green-mortgage__date-input.el-date-editor {
|
|
1297
|
+
--el-date-editor-width: 320px;
|
|
1298
|
+
width: 320px !important;
|
|
1299
|
+
height: var(--gm-control-height);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
.green-mortgage__date-input :deep(.el-input__wrapper),
|
|
1303
|
+
.green-mortgage__date-input.el-input__wrapper {
|
|
1304
|
+
min-height: var(--gm-control-height);
|
|
1305
|
+
height: var(--gm-control-height);
|
|
1306
|
+
padding-top: 0;
|
|
1307
|
+
padding-bottom: 0;
|
|
1308
|
+
box-sizing: border-box;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
.green-mortgage__refresh-btn,
|
|
1312
|
+
.green-mortgage__export-btn {
|
|
1313
|
+
height: var(--gm-control-height);
|
|
1314
|
+
min-height: var(--gm-control-height);
|
|
1315
|
+
padding: 0 15px;
|
|
1316
|
+
border-radius: 4px;
|
|
1317
|
+
box-sizing: border-box;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
/* Refresh:默認主色底 */
|
|
1321
|
+
.green-mortgage__refresh-btn {
|
|
1322
|
+
--el-button-bg-color: #c53355;
|
|
1323
|
+
--el-button-border-color: #c53355;
|
|
1324
|
+
--el-button-text-color: #fff;
|
|
1325
|
+
--el-button-hover-bg-color: #d14a68;
|
|
1326
|
+
--el-button-hover-border-color: #d14a68;
|
|
1327
|
+
--el-button-hover-text-color: #fff;
|
|
1328
|
+
--el-button-active-bg-color: #a82b48;
|
|
1329
|
+
--el-button-active-border-color: #a82b48;
|
|
1330
|
+
--el-button-active-text-color: #fff;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/* Export to Excel:移入主色描邊/文字 */
|
|
1334
|
+
.green-mortgage__export-btn {
|
|
1335
|
+
--el-button-hover-text-color: #c53355;
|
|
1336
|
+
--el-button-hover-bg-color: var(--el-color-primary-light-9, #fce8ec);
|
|
1337
|
+
--el-button-hover-border-color: #c53355;
|
|
1338
|
+
--el-button-active-text-color: #c53355;
|
|
1339
|
+
--el-button-active-border-color: #c53355;
|
|
1340
|
+
--el-button-active-bg-color: var(--el-color-primary-light-9, #fce8ec);
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
.green-mortgage__action-row {
|
|
1344
|
+
display: flex;
|
|
1345
|
+
flex-wrap: wrap;
|
|
1346
|
+
align-items: center;
|
|
1347
|
+
justify-content: space-between;
|
|
1348
|
+
gap: 12px 16px;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
.green-mortgage__search {
|
|
1352
|
+
display: flex;
|
|
1353
|
+
flex-wrap: wrap;
|
|
1354
|
+
align-items: center;
|
|
1355
|
+
gap: 8px;
|
|
1356
|
+
min-width: 0;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
.green-mortgage__search-label {
|
|
1360
|
+
font-size: 14px;
|
|
1361
|
+
white-space: nowrap;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
.green-mortgage__search-input {
|
|
1365
|
+
width: 220px;
|
|
1366
|
+
max-width: 100%;
|
|
1367
|
+
height: var(--gm-control-height);
|
|
1368
|
+
--el-input-height: var(--gm-control-height);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
.green-mortgage__search-input :deep(.el-input__wrapper) {
|
|
1372
|
+
min-height: var(--gm-control-height);
|
|
1373
|
+
height: var(--gm-control-height);
|
|
1374
|
+
padding-top: 0;
|
|
1375
|
+
padding-bottom: 0;
|
|
1376
|
+
box-sizing: border-box;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
/* 有值時滑鼠移入顯示清空 x */
|
|
1380
|
+
.green-mortgage__rating-select :deep(.el-select__clear),
|
|
1381
|
+
.green-mortgage__date-input :deep(.el-input__clear),
|
|
1382
|
+
.green-mortgage__search-input :deep(.el-input__clear) {
|
|
1383
|
+
display: inline-flex !important;
|
|
1384
|
+
opacity: 0;
|
|
1385
|
+
transition: opacity 0.15s ease;
|
|
1386
|
+
cursor: pointer;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
.green-mortgage__rating-select:hover :deep(.el-select__clear),
|
|
1390
|
+
.green-mortgage__rating-select :deep(.el-select__wrapper.is-focused .el-select__clear),
|
|
1391
|
+
.green-mortgage__date-input:hover :deep(.el-input__clear),
|
|
1392
|
+
.green-mortgage__date-input :deep(.el-input__wrapper.is-focus .el-input__clear),
|
|
1393
|
+
.green-mortgage__search-input:hover :deep(.el-input__clear),
|
|
1394
|
+
.green-mortgage__search-input :deep(.el-input__wrapper.is-focus .el-input__clear) {
|
|
1395
|
+
opacity: 1;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
.green-mortgage__table-wrap {
|
|
1399
|
+
flex: 1;
|
|
1400
|
+
min-height: 0;
|
|
1401
|
+
display: flex;
|
|
1402
|
+
flex-direction: column;
|
|
1403
|
+
position: relative;
|
|
1404
|
+
box-sizing: border-box;
|
|
1405
|
+
/* 伸入 el-card__body 既有右 padding,表寬不變;豎條放在這段 padding 裡 */
|
|
1406
|
+
margin-right: calc(-1 * var(--gm-card-padding));
|
|
1407
|
+
padding-right: var(--gm-card-padding);
|
|
1408
|
+
overflow: visible;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
/* 列表底部:左側結果數、右側排除說明 */
|
|
1412
|
+
.green-mortgage__table-footer {
|
|
1413
|
+
flex-shrink: 0;
|
|
1414
|
+
display: flex;
|
|
1415
|
+
flex-wrap: wrap;
|
|
1416
|
+
align-items: baseline;
|
|
1417
|
+
justify-content: space-between;
|
|
1418
|
+
gap: 8px 16px;
|
|
1419
|
+
margin-top: 10px;
|
|
1420
|
+
padding: 0 2px;
|
|
1421
|
+
font-size: 14px;
|
|
1422
|
+
line-height: 1.4;
|
|
1423
|
+
color: #333;
|
|
1424
|
+
font-family: 'Times New Roman', Times, serif;
|
|
1425
|
+
box-sizing: border-box;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
.green-mortgage__table-footer-left,
|
|
1429
|
+
.green-mortgage__table-footer-right {
|
|
1430
|
+
min-width: 0;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
.green-mortgage__table-footer-right {
|
|
1434
|
+
text-align: right;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
/* 上:table;下:橫條(底邊外側);豎條絕對定位在右邊外側 */
|
|
1438
|
+
.green-mortgage__table-main {
|
|
1439
|
+
flex: 1;
|
|
1440
|
+
min-height: 0;
|
|
1441
|
+
min-width: 0;
|
|
1442
|
+
display: flex;
|
|
1443
|
+
flex-direction: column;
|
|
1444
|
+
position: relative;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
.green-mortgage__table-row {
|
|
1448
|
+
flex: 1;
|
|
1449
|
+
min-height: 0;
|
|
1450
|
+
min-width: 0;
|
|
1451
|
+
display: flex;
|
|
1452
|
+
flex-direction: row;
|
|
1453
|
+
align-items: stretch;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
.green-mortgage__table-shell {
|
|
1457
|
+
--gm-scroll-thumb: #c1c1c1;
|
|
1458
|
+
position: relative;
|
|
1459
|
+
flex: 1;
|
|
1460
|
+
min-height: 240px;
|
|
1461
|
+
min-width: 0;
|
|
1462
|
+
width: 100%;
|
|
1463
|
+
padding: 0;
|
|
1464
|
+
box-sizing: border-box;
|
|
1465
|
+
overflow: hidden;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
.green-mortgage__table-shell :deep(.el-table) {
|
|
1469
|
+
overflow: hidden;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
.green-mortgage__table-shell :deep(.el-table__inner-wrapper) {
|
|
1473
|
+
height: 100%;
|
|
1474
|
+
border-radius: 4px;
|
|
1475
|
+
overflow: hidden;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
.green-mortgage__table-shell :deep(.el-table__body-wrapper),
|
|
1479
|
+
.green-mortgage__table-shell :deep(.el-scrollbar__wrap) {
|
|
1480
|
+
overflow-x: auto !important;
|
|
1481
|
+
overflow-y: auto !important;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
.green-mortgage__th {
|
|
1485
|
+
display: flex;
|
|
1486
|
+
flex-direction: column;
|
|
1487
|
+
align-items: center;
|
|
1488
|
+
justify-content: center;
|
|
1489
|
+
gap: 2px;
|
|
1490
|
+
font-size: 13px;
|
|
1491
|
+
line-height: 1.25;
|
|
1492
|
+
text-align: center;
|
|
1493
|
+
white-space: normal;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
.green-mortgage__th--single {
|
|
1497
|
+
min-height: 34px;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
.green-mortgage__th--inline {
|
|
1501
|
+
flex-direction: row;
|
|
1502
|
+
flex-wrap: wrap;
|
|
1503
|
+
gap: 4px;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
.green-mortgage__table {
|
|
1507
|
+
width: 100%;
|
|
1508
|
+
height: 100%;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
/* 勿強制表體 width:100%,否則列寬總和無法溢出,橫向滾動條永久失效 */
|
|
1512
|
+
.green-mortgage__table :deep(.el-table) {
|
|
1513
|
+
width: 100%;
|
|
1514
|
+
--el-table-row-hover-bg-color: #edf2fa;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
.green-mortgage__table :deep(.el-table__body tr.el-table__row:hover > td.el-table__cell) {
|
|
1518
|
+
background-color: #edf2fa;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
.green-mortgage__table :deep(.el-table__body tr.el-table__row--striped td.el-table__cell) {
|
|
1522
|
+
background-color: #f2f2f2;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
.green-mortgage__table :deep(.el-table__header-wrapper),
|
|
1526
|
+
.green-mortgage__table :deep(.el-table__header) {
|
|
1527
|
+
background: #556b7d;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
/* 去掉 EP 內建 gutter 空白列(豎條已疊加在表內,避免表頭多一截空白) */
|
|
1531
|
+
.green-mortgage__table :deep(.el-table__header th.gutter),
|
|
1532
|
+
.green-mortgage__table :deep(.el-table__body td.gutter),
|
|
1533
|
+
.green-mortgage__table :deep(col[name='gutter']) {
|
|
1534
|
+
display: none !important;
|
|
1535
|
+
width: 0 !important;
|
|
1536
|
+
min-width: 0 !important;
|
|
1537
|
+
padding: 0 !important;
|
|
1538
|
+
border: none !important;
|
|
1539
|
+
overflow: hidden !important;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
.green-mortgage__table :deep(.el-table__header-wrapper th.el-table__cell) {
|
|
1543
|
+
position: relative;
|
|
1544
|
+
padding: 6px 0;
|
|
1545
|
+
background: #556b7d;
|
|
1546
|
+
color: #fff;
|
|
1547
|
+
border-color: #f5f7fa;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
/* 列寬拖拽縫:懸停 / 拖動指示條用系統主色 */
|
|
1551
|
+
.green-mortgage__table :deep(.el-table__header-wrapper th.el-table__cell::after) {
|
|
1552
|
+
content: '';
|
|
1553
|
+
position: absolute;
|
|
1554
|
+
top: 0;
|
|
1555
|
+
right: -2px;
|
|
1556
|
+
width: 4px;
|
|
1557
|
+
height: 100%;
|
|
1558
|
+
background: transparent;
|
|
1559
|
+
z-index: 3;
|
|
1560
|
+
pointer-events: none;
|
|
1561
|
+
transition: background-color 0.12s ease;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
.green-mortgage__table :deep(.el-table__header-wrapper th.el-table__cell:hover::after) {
|
|
1565
|
+
background: var(--el-color-primary, #c53355);
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
.green-mortgage__table :deep(.el-table__column-resize-proxy) {
|
|
1569
|
+
border-left: 2px solid var(--el-color-primary, #c53355) !important;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
.green-mortgage__table :deep(.el-table__header-wrapper th.el-table__cell .cell) {
|
|
1573
|
+
padding: 0 8px;
|
|
1574
|
+
color: #fff;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
.green-mortgage__table :deep(.el-table__body td.el-table__cell) {
|
|
1578
|
+
border-color: #f5f7fa;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
.green-mortgage__table :deep(.el-table--border::before),
|
|
1582
|
+
.green-mortgage__table :deep(.el-table--border::after),
|
|
1583
|
+
.green-mortgage__table :deep(.el-table__inner-wrapper::after),
|
|
1584
|
+
.green-mortgage__table :deep(.el-table__border-left-patch) {
|
|
1585
|
+
background-color: #f5f7fa;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
/* 隱藏 table 內建橫/豎滑塊(改外側自繪) */
|
|
1589
|
+
.green-mortgage__table-shell :deep(.el-scrollbar__bar.is-vertical),
|
|
1590
|
+
.green-mortgage__table-shell :deep(.el-scrollbar__bar.is-horizontal) {
|
|
1591
|
+
display: none !important;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
.green-mortgage__table-shell :deep(.el-scrollbar__wrap) {
|
|
1595
|
+
scrollbar-width: none !important;
|
|
1596
|
+
-ms-overflow-style: none !important;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
.green-mortgage__table-shell :deep(.el-scrollbar__wrap::-webkit-scrollbar) {
|
|
1600
|
+
width: 0 !important;
|
|
1601
|
+
height: 0 !important;
|
|
1602
|
+
display: none !important;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
/* 橫向滑塊:表格底邊外側(不擠壓表體) */
|
|
1606
|
+
.green-mortgage__hbar-row {
|
|
1607
|
+
flex-shrink: 0;
|
|
1608
|
+
display: flex;
|
|
1609
|
+
flex-direction: row;
|
|
1610
|
+
align-items: stretch;
|
|
1611
|
+
min-height: 0;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
.green-mortgage__hbar {
|
|
1615
|
+
flex: 1;
|
|
1616
|
+
min-width: 0;
|
|
1617
|
+
position: relative;
|
|
1618
|
+
height: var(--gm-h-scroll-gutter);
|
|
1619
|
+
z-index: 12;
|
|
1620
|
+
pointer-events: auto;
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
.green-mortgage__hthumb {
|
|
1624
|
+
position: absolute;
|
|
1625
|
+
top: 3px;
|
|
1626
|
+
left: 0;
|
|
1627
|
+
width: 50px;
|
|
1628
|
+
min-width: 50px;
|
|
1629
|
+
max-width: 50px;
|
|
1630
|
+
height: 8px;
|
|
1631
|
+
border-radius: 4px;
|
|
1632
|
+
background-color: var(--gm-scroll-thumb, #c1c1c1);
|
|
1633
|
+
cursor: grab;
|
|
1634
|
+
pointer-events: auto;
|
|
1635
|
+
will-change: transform;
|
|
1636
|
+
opacity: 0;
|
|
1637
|
+
visibility: hidden;
|
|
1638
|
+
transition: opacity 0.15s ease;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
.green-mortgage__hthumb.is-visible {
|
|
1642
|
+
opacity: 1;
|
|
1643
|
+
visibility: visible;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
.green-mortgage__hthumb:active {
|
|
1647
|
+
cursor: grabbing;
|
|
1648
|
+
background-color: #a8a8a8;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
/* 豎向滑塊:只移到表格右外側(card 既有 padding 內),其他邊距不變 */
|
|
1652
|
+
.green-mortgage__vbar {
|
|
1653
|
+
position: absolute;
|
|
1654
|
+
top: 0;
|
|
1655
|
+
right: calc((var(--gm-card-padding) - var(--gm-v-scroll-thumb)) / 2);
|
|
1656
|
+
left: auto;
|
|
1657
|
+
transform: none;
|
|
1658
|
+
width: var(--gm-v-scroll-thumb);
|
|
1659
|
+
z-index: 12;
|
|
1660
|
+
pointer-events: none;
|
|
1661
|
+
overflow: visible;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
.green-mortgage__vthumb {
|
|
1665
|
+
width: 8px;
|
|
1666
|
+
min-width: 8px;
|
|
1667
|
+
max-width: 8px;
|
|
1668
|
+
height: 50px;
|
|
1669
|
+
min-height: 50px;
|
|
1670
|
+
max-height: 50px;
|
|
1671
|
+
border-radius: 9999px;
|
|
1672
|
+
background-color: var(--gm-scroll-thumb, #c1c1c1);
|
|
1673
|
+
cursor: grab;
|
|
1674
|
+
pointer-events: auto;
|
|
1675
|
+
will-change: transform;
|
|
1676
|
+
opacity: 0;
|
|
1677
|
+
visibility: hidden;
|
|
1678
|
+
transition: opacity 0.15s ease;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
.green-mortgage__vthumb.is-visible {
|
|
1682
|
+
opacity: 1;
|
|
1683
|
+
visibility: visible;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
.green-mortgage__vthumb:active {
|
|
1687
|
+
cursor: grabbing;
|
|
1688
|
+
background-color: #a8a8a8;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
.green-mortgage__table :deep(.caret-wrapper) {
|
|
1692
|
+
position: absolute;
|
|
1693
|
+
top: 50%;
|
|
1694
|
+
right: 6px;
|
|
1695
|
+
transform: translateY(-50%);
|
|
1696
|
+
opacity: 1;
|
|
1697
|
+
visibility: visible;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
.green-mortgage__table :deep(.el-table__header-wrapper th.el-table__cell.ascending .caret-wrapper),
|
|
1701
|
+
.green-mortgage__table
|
|
1702
|
+
:deep(.el-table__header-wrapper th.el-table__cell.descending .caret-wrapper) {
|
|
1703
|
+
right: 2px;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
.green-mortgage__table :deep(.sort-caret.ascending) {
|
|
1707
|
+
border-bottom-color: #c0c4cc;
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
.green-mortgage__table :deep(.sort-caret.descending) {
|
|
1711
|
+
border-top-color: #c0c4cc;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
.green-mortgage__table
|
|
1715
|
+
:deep(.el-table__header-wrapper th.el-table__cell.ascending .sort-caret.ascending) {
|
|
1716
|
+
border-bottom-color: #c53355;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
.green-mortgage__table
|
|
1720
|
+
:deep(.el-table__header-wrapper th.el-table__cell.descending .sort-caret.descending) {
|
|
1721
|
+
border-top-color: #c53355;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
.green-mortgage__table :deep(.el-table__header-wrapper th.el-table__cell.is-sortable .cell) {
|
|
1725
|
+
position: relative;
|
|
1726
|
+
padding-right: 24px;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
/* 大屏 */
|
|
1730
|
+
@media (min-width: 1200px) {
|
|
1731
|
+
.green-mortgage__title,
|
|
1732
|
+
.green-mortgage__subtitle {
|
|
1733
|
+
font-size: 18px;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
.green-mortgage__table {
|
|
1737
|
+
min-width: 100%;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
/* 平板 */
|
|
1742
|
+
@media (max-width: 1199px) {
|
|
1743
|
+
.green-mortgage__title,
|
|
1744
|
+
.green-mortgage__subtitle {
|
|
1745
|
+
font-size: 16px;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
.green-mortgage__rating-select,
|
|
1749
|
+
.green-mortgage__search-input {
|
|
1750
|
+
width: 200px;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
.green-mortgage__date-input {
|
|
1754
|
+
width: 320px;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
.green-mortgage__date-input.el-date-editor {
|
|
1758
|
+
--el-date-editor-width: 320px;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
.green-mortgage__th {
|
|
1762
|
+
font-size: 12px;
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
/* 手機 */
|
|
1767
|
+
@media (max-width: 767px) {
|
|
1768
|
+
.green-mortgage {
|
|
1769
|
+
overflow: auto;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
.green-mortgage__header {
|
|
1773
|
+
margin-bottom: 10px;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
.green-mortgage__title,
|
|
1777
|
+
.green-mortgage__subtitle {
|
|
1778
|
+
font-size: 14px;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
.green-mortgage__meta {
|
|
1782
|
+
font-size: 12px;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
.green-mortgage__filter-row,
|
|
1786
|
+
.green-mortgage__action-row {
|
|
1787
|
+
flex-direction: column;
|
|
1788
|
+
align-items: stretch;
|
|
1789
|
+
gap: 10px;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
.green-mortgage__filter-group,
|
|
1793
|
+
.green-mortgage__filter-group--period,
|
|
1794
|
+
.green-mortgage__period-controls,
|
|
1795
|
+
.green-mortgage__search {
|
|
1796
|
+
width: 100%;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
.green-mortgage__filter-label,
|
|
1800
|
+
.green-mortgage__search-label {
|
|
1801
|
+
white-space: normal;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
.green-mortgage__rating-select,
|
|
1805
|
+
.green-mortgage__search-input {
|
|
1806
|
+
width: 100%;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
.green-mortgage__date-input,
|
|
1810
|
+
.green-mortgage__date-input.el-date-editor {
|
|
1811
|
+
width: 320px !important;
|
|
1812
|
+
max-width: 100%;
|
|
1813
|
+
--el-date-editor-width: 320px;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
.green-mortgage__period-controls {
|
|
1817
|
+
display: flex;
|
|
1818
|
+
flex-direction: column;
|
|
1819
|
+
align-items: stretch;
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
.green-mortgage__period-controls .green-mortgage__refresh-btn {
|
|
1823
|
+
width: 100%;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
.green-mortgage__export-btn {
|
|
1827
|
+
width: 100%;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
.green-mortgage__table-shell {
|
|
1831
|
+
min-height: 280px;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
</style>
|