gis-view-webgl2 0.1.0 → 0.1.2
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/README.md +40 -8
- package/dist/gis-view.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1036 -249
- package/dist/index.js +1 -1
- package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +194 -0
- package/docs/GisView.md +9 -8
- package/docs/GisView/345/256/236/347/216/260/345/216/237/347/220/206.md +2 -2
- package/docs/ai/06-/344/274/232/350/257/235/347/264/242/345/274/225/344/270/216/345/244/215/346/237/245/346/226/271/346/263/225.md +172 -0
- package/docs/api/index.html +545 -59
- package/docs/demos/BackendSyncDemo.vue +10 -8
- package/docs/demos/BasicDemo.vue +101 -101
- package/docs/demos/BatchAddDemo.vue +5 -5
- package/docs/demos/ChannelDemo.vue +617 -10
- package/docs/demos/ConnectDemo.vue +392 -0
- package/docs/demos/CopyMergeDemo.vue +980 -929
- package/docs/demos/DecorationDraftDemo.vue +1300 -0
- package/docs/demos/DemoShell.vue +569 -240
- package/docs/demos/DragDropDemo.vue +11 -11
- package/docs/demos/EffectsDemo.vue +6 -6
- package/docs/demos/EventDemo.vue +327 -327
- package/docs/demos/ExtensionDemo.vue +458 -0
- package/docs/demos/GisViewDemo.vue +61 -48
- package/docs/demos/GraphicDemo.vue +338 -0
- package/docs/demos/LabelDemo.vue +553 -497
- package/docs/demos/MinimapDemo.vue +358 -0
- package/docs/demos/PerfDemo.vue +29 -0
- package/docs/demos/SearchFilterDemo.vue +412 -0
- package/docs/demos/StyleDemo.vue +121 -121
- package/docs/demos/api-index.js +98 -77
- package/docs/demos/demos.js +159 -135
- package/docs/demos/shared/demoData.js +107 -107
- package/docs/demos/shared/demoStyle.css +249 -249
- package/docs/demos/shared/fakeBackend.js +93 -6
- package/docs/format/03-/346/213/220/347/202/271/350/243/205/351/245/260/344/270/216/346/240/267/345/274/217.md +28 -8
- package/docs/format/04-/351/200/232/351/201/223/344/270/216/346/240/207/347/255/276.md +5 -4
- package/docs/format/06-/345/257/274/345/205/245/345/257/274/345/207/272/344/270/216/346/240/207/345/207/206/346/226/207/346/241/243.md +8 -6
- package/docs/format/07-/344/272/213/344/273/266/350/275/275/350/215/267/344/270/216/347/244/272/344/276/213/346/225/260/346/215/256.md +6 -6
- package/docs/guide/sample-document.html +4 -4
- package/docs/index.html +180 -9
- package/docs/internals/11-/350/246/206/347/233/226/345/261/202/346/240/207/347/255/276/344/270/216/346/217/220/347/244/272.md +1 -1
- package/docs/internals/13-/351/271/260/347/234/274/345/233/276.md +3 -2
- package/docs/internals/15-03-/351/200/220/350/275/256/350/256/260/345/275/225-/346/200/247/350/203/275/344/270/216/344/277/256/345/244/215-/344/270/213.md +16 -0
- package/docs/internals/16-03-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/213.md +11 -0
- package/docs/internals/17-/350/260/203/350/257/225/344/270/216/346/211/251/345/261/225/345/273/272/350/256/256.md +1 -1
- package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +18 -4
- package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +110 -25
- package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +13 -8
- package/docs/manual/06-/346/225/260/346/215/256/344/270/216/345/234/272/346/231/257/346/233/264/346/226/260.md +111 -10
- package/docs/manual/07-/346/214/207/351/222/210/344/270/216/350/246/201/347/264/240/347/274/226/350/276/221.md +47 -9
- package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +17 -15
- package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +61 -11
- package/docs/manual/10-/351/200/232/351/201/223/347/274/226/350/276/221/344/270/216/346/226/260/345/242/236.md +74 -7
- package/docs/manual/11-/346/240/267/345/274/217/344/270/216/350/247/204/345/210/231.md +3 -2
- package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +260 -3
- package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +6 -5
- package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +27 -14
- package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +142 -49
- package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +31 -29
- package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +55 -39
- package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +189 -64
- package/docs/manual/19-/350/257/273/346/225/260/344/270/216/346/213/226/346/213/275/351/242/204/350/247/210.md +4 -4
- package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +42 -2
- package/docs/manual/21-/350/257/264/346/230/216/344/270/216/345/270/270/350/247/201/351/227/256/351/242/230.md +22 -6
- package/docs/playground/index.html +5 -5
- package/docs/steps/README.md +14 -10
- package/docs/steps/check-doc-truth.mjs +1 -1
- package/docs/steps/selftest-actions.html +10 -10
- package/docs/steps/selftest-data.html +96 -14
- package/docs/steps/selftest-decoration.html +577 -0
- package/docs/steps/selftest-demo.html +6 -5
- package/docs/steps/selftest-demos.html +2201 -1612
- package/docs/steps/selftest-docs.html +89 -16
- package/docs/steps/selftest-effects.html +5 -5
- package/docs/steps/selftest-features.html +757 -86
- package/docs/steps/selftest-full.html +583 -583
- package/docs/steps/selftest-labels.html +1034 -652
- package/docs/steps/selftest-perf.html +1899 -1844
- package/docs/steps/selftest-scale.html +117 -7
- package/docs/steps/selftest-select.html +24 -17
- package/docs/steps/step-10-full.js +1 -1
- package/docs//346/216/245/345/217/243/345/215/217/350/256/256.md +167 -17
- package/docs//346/225/260/346/215/256/346/240/274/345/274/217.md +1 -1
- package/package.json +43 -43
- package/docs/demos/HistoryDemo.vue +0 -230
- package/docs/demos/InterfaceDemo.vue +0 -309
- package/docs/demos/SampleDemo.vue +0 -283
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
/**
|
|
3
|
+
* 搜索与筛选演示:
|
|
4
|
+
* 上半区把 keyword / scope / fields / limit 与 search / searchAsync 摆出来,
|
|
5
|
+
* 下半区用 setFilter 对比「隐藏数据」和「只找出来」的差别。
|
|
6
|
+
*
|
|
7
|
+
* 数据里额外塞了 props.owner 与 symbol 两个字段:前者演示业务字段路径,
|
|
8
|
+
* 后者演示默认全字段模式会跳过、显式 fields 点名时又能命中的边界口径。
|
|
9
|
+
*/
|
|
10
|
+
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
11
|
+
import { createGisView } from '../../src/index.js'
|
|
12
|
+
import { buildDemoData } from './shared/demoData.js'
|
|
13
|
+
import './shared/demoStyle.css'
|
|
14
|
+
|
|
15
|
+
const emit = defineEmits(['notice'])
|
|
16
|
+
const container = ref(null)
|
|
17
|
+
|
|
18
|
+
const OWNERS = ['北城运检', '南城运检', '西城运检']
|
|
19
|
+
const baseData = buildDemoData({ count: 24, channels: 2 })
|
|
20
|
+
const dataset = {
|
|
21
|
+
...baseData,
|
|
22
|
+
points: baseData.points.map((point, index) => ({
|
|
23
|
+
...point,
|
|
24
|
+
props: {
|
|
25
|
+
owner: OWNERS[index % OWNERS.length],
|
|
26
|
+
tag: `BJ-${String(index + 1).padStart(3, '0')}`
|
|
27
|
+
},
|
|
28
|
+
symbol: index % 3 === 0 ? 'square' : 'circle'
|
|
29
|
+
})),
|
|
30
|
+
lines: baseData.lines.map((line, index) => ({
|
|
31
|
+
...line,
|
|
32
|
+
props: { owner: OWNERS[index % OWNERS.length] }
|
|
33
|
+
}))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const keyword = ref('变电站')
|
|
37
|
+
const scope = ref('all')
|
|
38
|
+
const fieldMode = ref('all')
|
|
39
|
+
const limit = ref(3)
|
|
40
|
+
const useWorker = ref(false)
|
|
41
|
+
const actionText = ref('输入关键字,点同步或异步搜索')
|
|
42
|
+
const filterAction = ref('当前没有筛选条件')
|
|
43
|
+
const searchResult = ref(null)
|
|
44
|
+
const filterResult = ref(null)
|
|
45
|
+
const visibleDataText = ref('未读取')
|
|
46
|
+
const activeFilter = ref('none')
|
|
47
|
+
let view = null
|
|
48
|
+
let requestSeq = 0
|
|
49
|
+
|
|
50
|
+
const SCOPES = [
|
|
51
|
+
{ id: 'all', label: '全部三类' },
|
|
52
|
+
{ id: 'points', label: '只看节点' },
|
|
53
|
+
{ id: 'lines', label: '只看线路' },
|
|
54
|
+
{ id: 'channels', label: '只看通道' }
|
|
55
|
+
]
|
|
56
|
+
const FIELD_MODES = [
|
|
57
|
+
{ id: 'all', label: '全部字段', fields: null },
|
|
58
|
+
{ id: 'name', label: 'name', fields: ['name'] },
|
|
59
|
+
{ id: 'owner', label: 'props.owner', fields: ['props.owner'] },
|
|
60
|
+
{ id: 'symbol', label: '显式 symbol', fields: ['symbol'] }
|
|
61
|
+
]
|
|
62
|
+
const LIMITS = [3, 10, 20, 0]
|
|
63
|
+
const PRESETS = [
|
|
64
|
+
{ id: 'name', demo: 'search-filter-preset-name', label: '变电站(limit 3)', keyword: '变电站', scope: 'points', field: 'all', limit: 3 },
|
|
65
|
+
{ id: 'owner', demo: 'search-filter-preset-owner', label: '北城运检(props.owner)', keyword: '北城运检', scope: 'points', field: 'owner', limit: 10 },
|
|
66
|
+
{ id: 'channel', demo: 'search-filter-preset-channel', label: '甲通道(scope=channels)', keyword: '甲通道', scope: 'channels', field: 'all', limit: 10 },
|
|
67
|
+
{ id: 'symbol', demo: 'search-filter-preset-symbol', label: 'square(显式 symbol)', keyword: 'square', scope: 'points', field: 'symbol', limit: 10 }
|
|
68
|
+
]
|
|
69
|
+
const FILTERS = [
|
|
70
|
+
{ id: 'none', demo: 'search-filter-filter-clear', label: '清空筛选', condition: null },
|
|
71
|
+
{ id: 'voltage', demo: 'search-filter-filter-voltage', label: '节点电压 ≥ 525 kV', condition: { points: { vn_kv: { gte: 525 } }, cascade: 'binding' } },
|
|
72
|
+
{ id: 'zone', demo: 'search-filter-filter-zone', label: '分区 2 / 4', condition: { points: { zone_name: { in: ['分区 2', '分区 4'] } }, cascade: 'binding' } },
|
|
73
|
+
{ id: 'loading', demo: 'search-filter-filter-loading', label: '线路负载 ≥ 1', condition: { lines: { max_loading_rate: { gte: 1 } } } }
|
|
74
|
+
]
|
|
75
|
+
const KIND_LABELS = { point: '节点', line: '线路', channel: '通道' }
|
|
76
|
+
|
|
77
|
+
const scopeLabel = computed(() => SCOPES.find(item => item.id === scope.value)?.label ?? '全部三类')
|
|
78
|
+
const searchSummary = computed(() => {
|
|
79
|
+
const result = searchResult.value
|
|
80
|
+
if (!result) {
|
|
81
|
+
return '还没有搜索'
|
|
82
|
+
}
|
|
83
|
+
return `命中 ${result.total} 条:节点 ${result.points.length} / 线路 ${result.lines.length} / 通道 ${result.channels.length}`
|
|
84
|
+
})
|
|
85
|
+
const searchMeta = computed(() => {
|
|
86
|
+
const result = searchResult.value
|
|
87
|
+
if (!result) {
|
|
88
|
+
return `limit=${limit.value} · 还没跑搜索`
|
|
89
|
+
}
|
|
90
|
+
const via = result.via === 'async' ? 'searchAsync' : 'search'
|
|
91
|
+
const worker = result.via === 'async' ? ` · worker=${result.worker ? '是' : '否'}` : ''
|
|
92
|
+
const stale = result.stale ? ' · stale=true(这一份已作废)' : ''
|
|
93
|
+
return `${via}${worker} · limit=${limit.value} · truncated=${result.truncated ? '是' : '否'} · ${result.durationMs} ms${stale}`
|
|
94
|
+
})
|
|
95
|
+
const hitRows = computed(() => (searchResult.value?.hits ?? []).slice(0, 6).map((hit, index) => ({
|
|
96
|
+
key: `${hit.kind}-${hit.index}-${index}`,
|
|
97
|
+
demo: `search-filter-hit-${index}`,
|
|
98
|
+
title: `${KIND_LABELS[hit.kind] ?? hit.kind} · ${hit.id ?? `#${hit.index}`}`,
|
|
99
|
+
detail: `${hit.field} = ${hit.value}`,
|
|
100
|
+
hit
|
|
101
|
+
})))
|
|
102
|
+
const filterSummary = computed(() => {
|
|
103
|
+
const result = filterResult.value
|
|
104
|
+
if (!result) {
|
|
105
|
+
return 'getFilterResult:当前没有筛选条件'
|
|
106
|
+
}
|
|
107
|
+
const hiddenPoints = result.hiddenPoints?.length ?? 0
|
|
108
|
+
const hiddenLines = result.hiddenLines?.length ?? 0
|
|
109
|
+
return `getFilterResult:隐藏 ${hiddenPoints} 节点 / ${hiddenLines} 线路 · 可见 ${result.visiblePoints ?? 0} 节点 / ${result.visibleLines ?? 0} 线路`
|
|
110
|
+
})
|
|
111
|
+
const chipClass = active => (active ? 'dv-chip--active' : '')
|
|
112
|
+
|
|
113
|
+
const buildSearchRequest = () => {
|
|
114
|
+
const mode = FIELD_MODES.find(item => item.id === fieldMode.value) ?? FIELD_MODES[0]
|
|
115
|
+
const request = { keyword: keyword.value, limit: limit.value }
|
|
116
|
+
if (scope.value !== 'all') {
|
|
117
|
+
request.scope = scope.value
|
|
118
|
+
}
|
|
119
|
+
if (mode.fields) {
|
|
120
|
+
request.fields = [...mode.fields]
|
|
121
|
+
}
|
|
122
|
+
return request
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const runSearch = async (mode = 'sync') => {
|
|
126
|
+
if (!view) {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
const seq = (requestSeq += 1)
|
|
130
|
+
const request = buildSearchRequest()
|
|
131
|
+
if (mode === 'async') {
|
|
132
|
+
actionText.value = 'searchAsync 正在计算…'
|
|
133
|
+
try {
|
|
134
|
+
const result = await view.searchAsync(request)
|
|
135
|
+
if (seq !== requestSeq) {
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
searchResult.value = { ...result, via: 'async' }
|
|
139
|
+
actionText.value = result.stale ? '异步搜索返回 stale:这一份结果已作废' : '异步搜索完成:数据量大时这段计算在 Worker 里'
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (seq === requestSeq) {
|
|
143
|
+
actionText.value = `searchAsync 失败:${error?.message ?? error}`
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
const result = view.search(request)
|
|
149
|
+
if (seq !== requestSeq) {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
searchResult.value = { ...result, via: 'sync' }
|
|
153
|
+
actionText.value = '同步搜索完成:只返回命中,不改筛选与视角'
|
|
154
|
+
}
|
|
155
|
+
const runSync = () => runSearch('sync')
|
|
156
|
+
const runAsync = () => runSearch('async')
|
|
157
|
+
|
|
158
|
+
const choosePreset = (preset) => {
|
|
159
|
+
keyword.value = preset.keyword
|
|
160
|
+
scope.value = preset.scope
|
|
161
|
+
fieldMode.value = preset.field
|
|
162
|
+
limit.value = preset.limit
|
|
163
|
+
runSearch('sync')
|
|
164
|
+
}
|
|
165
|
+
const applyWorker = () => {
|
|
166
|
+
if (!view) {
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
view.updateOptions({ searchWorker: useWorker.value ? { threshold: 30 } : false })
|
|
170
|
+
actionText.value = useWorker.value
|
|
171
|
+
? 'searchWorker.threshold=30:这份小数据也会走 Worker'
|
|
172
|
+
: 'searchWorker=false:searchAsync 退回主线程同步计算'
|
|
173
|
+
}
|
|
174
|
+
const focusHit = (hit) => {
|
|
175
|
+
view?.focusOn(hit, { select: true })
|
|
176
|
+
actionText.value = `focusOn(${KIND_LABELS[hit.kind] ?? hit.kind} #${hit.index}, { select: true })`
|
|
177
|
+
}
|
|
178
|
+
const focusAllHits = () => {
|
|
179
|
+
const hits = searchResult.value?.hits ?? []
|
|
180
|
+
if (!hits.length) {
|
|
181
|
+
actionText.value = '当前没有可定位的命中'
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
view?.focusOn(hits, { select: true })
|
|
185
|
+
actionText.value = `focusOn(hits, { select: true }):${hits.length} 条命中一起进包围盒并选中`
|
|
186
|
+
}
|
|
187
|
+
const readVisibleData = () => {
|
|
188
|
+
if (!view) {
|
|
189
|
+
return null
|
|
190
|
+
}
|
|
191
|
+
const data = view.getVisibleData()
|
|
192
|
+
visibleDataText.value = `getVisibleData:${data.points.length} 节点 / ${data.lines.length} 线路`
|
|
193
|
+
return data
|
|
194
|
+
}
|
|
195
|
+
const syncFilterReadout = (result) => {
|
|
196
|
+
filterResult.value = result
|
|
197
|
+
nextTick(readVisibleData)
|
|
198
|
+
}
|
|
199
|
+
const applyFilter = (filter) => {
|
|
200
|
+
if (!view) {
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
activeFilter.value = filter.id
|
|
204
|
+
if (filter.condition) {
|
|
205
|
+
view.setFilter(filter.condition)
|
|
206
|
+
filterAction.value = `setFilter:${filter.label}`
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
view.setFilter(null)
|
|
210
|
+
filterAction.value = 'setFilter(null):筛选条件已清空'
|
|
211
|
+
}
|
|
212
|
+
filterResult.value = view.getFilterResult()
|
|
213
|
+
readVisibleData()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
onMounted(() => {
|
|
217
|
+
view = createGisView(container.value, {
|
|
218
|
+
points: dataset.points,
|
|
219
|
+
lines: dataset.lines,
|
|
220
|
+
channels: dataset.channels,
|
|
221
|
+
channelWidth: 2.2,
|
|
222
|
+
channelInset: 1.6,
|
|
223
|
+
pointStyle: { size: 8, color: '#ffffff', strokeColor: '#4c8dff', strokeWidth: 1.6 },
|
|
224
|
+
lineStyle: { width: 1.2, color: 'rgba(120, 168, 226, 0.43)' },
|
|
225
|
+
showGrid: true,
|
|
226
|
+
gridColor: 'rgba(126, 168, 214, 0.18)',
|
|
227
|
+
fitPadding: 62,
|
|
228
|
+
features: 'view',
|
|
229
|
+
// 默认阈值 20000;这份 demo 只有几十条,勾选后降到 30 才看得到 Worker 路径
|
|
230
|
+
searchWorker: useWorker.value ? { threshold: 30 } : false,
|
|
231
|
+
label: {
|
|
232
|
+
enabled: true,
|
|
233
|
+
content: '{name}',
|
|
234
|
+
position: 'top',
|
|
235
|
+
gap: 6,
|
|
236
|
+
maxCount: 20,
|
|
237
|
+
style: { color: '#24415f', fontSize: '10px', background: 'rgba(255, 255, 255, 0.72)' }
|
|
238
|
+
},
|
|
239
|
+
onReady: () => {
|
|
240
|
+
emit('notice', `${dataset.points.length} 节点 / ${dataset.lines.length} 线路,搜索与筛选都是只读口径`)
|
|
241
|
+
},
|
|
242
|
+
onFilter: result => syncFilterReadout(result),
|
|
243
|
+
onWarning: text => {
|
|
244
|
+
actionText.value = `warning:${text}`
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
runSearch('sync')
|
|
248
|
+
readVisibleData()
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
onBeforeUnmount(() => {
|
|
252
|
+
requestSeq += 1
|
|
253
|
+
view?.destroy()
|
|
254
|
+
view = null
|
|
255
|
+
})
|
|
256
|
+
</script>
|
|
257
|
+
|
|
258
|
+
<template>
|
|
259
|
+
<section class='dv-demo'>
|
|
260
|
+
<div class='dv-stage'>
|
|
261
|
+
<div ref='container' class='dv-canvas'></div>
|
|
262
|
+
<div class='dv-toolbar'>
|
|
263
|
+
<button type='button' @click='view?.fitView(62)'>适应全部</button>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
<aside class='dv-panel'>
|
|
267
|
+
<div class='dv-group'>
|
|
268
|
+
<h3>搜索<span class='dv-chip'>{{ scopeLabel }}</span></h3>
|
|
269
|
+
<div class='dv-row'>
|
|
270
|
+
<label class='sf-keyword'>关键字
|
|
271
|
+
<input v-model='keyword' class='sf-input' data-demo='search-filter-keyword' type='text' @keydown.enter.prevent='runSync' />
|
|
272
|
+
</label>
|
|
273
|
+
</div>
|
|
274
|
+
<div class='dv-row'>
|
|
275
|
+
<label>范围
|
|
276
|
+
<select v-model='scope' data-demo='search-filter-scope'>
|
|
277
|
+
<option v-for='item in SCOPES' :key='item.id' :value='item.id'>{{ item.label }}</option>
|
|
278
|
+
</select>
|
|
279
|
+
</label>
|
|
280
|
+
<label>字段
|
|
281
|
+
<select v-model='fieldMode' data-demo='search-filter-fields'>
|
|
282
|
+
<option v-for='item in FIELD_MODES' :key='item.id' :value='item.id'>{{ item.label }}</option>
|
|
283
|
+
</select>
|
|
284
|
+
</label>
|
|
285
|
+
<label>limit
|
|
286
|
+
<select v-model.number='limit' data-demo='search-filter-limit'>
|
|
287
|
+
<option v-for='value in LIMITS' :key='value' :value='value'>{{ value === 0 ? '0(不限)' : value }}</option>
|
|
288
|
+
</select>
|
|
289
|
+
</label>
|
|
290
|
+
</div>
|
|
291
|
+
<div class='dv-row sf-actions'>
|
|
292
|
+
<button type='button' class='dv-button dv-button--primary' data-demo='search-filter-run' @click='runSync'>同步 search</button>
|
|
293
|
+
<button type='button' class='dv-button' data-demo='search-filter-run-async' @click='runAsync'>异步 searchAsync</button>
|
|
294
|
+
<label><input v-model='useWorker' data-demo='search-filter-worker' type='checkbox' @change='applyWorker' /> 低阈值 Worker(30 条)</label>
|
|
295
|
+
</div>
|
|
296
|
+
<div class='dv-chips'>
|
|
297
|
+
<button v-for='preset in PRESETS' :key='preset.id' type='button' class='dv-chip' :data-demo='preset.demo' @click='choosePreset(preset)'>{{ preset.label }}</button>
|
|
298
|
+
</div>
|
|
299
|
+
<p class='dv-hint'>显式 fields 能搜默认会跳过的 symbol;搜索基于全量数据,不隐藏、也不自动移动视角。</p>
|
|
300
|
+
<div class='dv-row'>
|
|
301
|
+
<button type='button' class='dv-button' data-demo='search-filter-focus-all' @click='focusAllHits'>聚焦并选中全部命中</button>
|
|
302
|
+
</div>
|
|
303
|
+
<p class='dv-readout' data-demo='search-filter-summary'>{{ searchSummary }}</p>
|
|
304
|
+
<p class='dv-readout' data-demo='search-filter-meta'>{{ searchMeta }}</p>
|
|
305
|
+
<p class='dv-hint' data-demo='search-filter-action'>{{ actionText }}</p>
|
|
306
|
+
<ul v-if='hitRows.length' class='sf-hits'>
|
|
307
|
+
<li v-for='row in hitRows' :key='row.key'>
|
|
308
|
+
<button type='button' class='sf-hit' :data-demo='row.demo' @click='focusHit(row.hit)'>
|
|
309
|
+
<strong>{{ row.title }}</strong>
|
|
310
|
+
<span>{{ row.detail }}</span>
|
|
311
|
+
</button>
|
|
312
|
+
</li>
|
|
313
|
+
</ul>
|
|
314
|
+
<p v-else class='dv-hint'>当前没有命中,换一个关键字或字段再试。</p>
|
|
315
|
+
</div>
|
|
316
|
+
<div class='dv-group'>
|
|
317
|
+
<h3>筛选<span class='dv-chip'>getFilterResult</span></h3>
|
|
318
|
+
<div class='dv-chips'>
|
|
319
|
+
<button
|
|
320
|
+
v-for='filter in FILTERS'
|
|
321
|
+
:key='filter.id'
|
|
322
|
+
type='button'
|
|
323
|
+
class='dv-chip'
|
|
324
|
+
:class='chipClass(activeFilter === filter.id)'
|
|
325
|
+
:data-demo='filter.demo'
|
|
326
|
+
@click='applyFilter(filter)'
|
|
327
|
+
>{{ filter.label }}</button>
|
|
328
|
+
</div>
|
|
329
|
+
<p class='dv-readout' data-demo='search-filter-filter'>{{ filterSummary }}</p>
|
|
330
|
+
<p class='dv-readout' data-demo='search-filter-visible'>{{ visibleDataText }}</p>
|
|
331
|
+
<div class='dv-row'>
|
|
332
|
+
<button type='button' class='dv-button' data-demo='search-filter-read-visible' @click='readVisibleData'>读取 getVisibleData()</button>
|
|
333
|
+
</div>
|
|
334
|
+
<p class='dv-hint' data-demo='search-filter-filter-action'>{{ filterAction }}</p>
|
|
335
|
+
</div>
|
|
336
|
+
<div class='dv-group'>
|
|
337
|
+
<h3>两件事别混用</h3>
|
|
338
|
+
<p class='dv-hint'>search 只把要素找出来,结果带 bounds;要居中就再调 focusOn。setFilter 才会隐藏要素,并从 getVisibleData / listPoints 的可见集合里消失。</p>
|
|
339
|
+
</div>
|
|
340
|
+
</aside>
|
|
341
|
+
</section>
|
|
342
|
+
</template>
|
|
343
|
+
|
|
344
|
+
<style scoped>
|
|
345
|
+
.sf-keyword {
|
|
346
|
+
flex: 1 1 220px;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.sf-keyword .sf-input {
|
|
350
|
+
flex: 1;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.sf-input {
|
|
354
|
+
width: 100%;
|
|
355
|
+
min-width: 150px;
|
|
356
|
+
padding: 5px 8px;
|
|
357
|
+
border: 1px solid #d6e0ec;
|
|
358
|
+
border-radius: 6px;
|
|
359
|
+
color: #1f2a44;
|
|
360
|
+
font-size: 12px;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.sf-actions {
|
|
364
|
+
align-items: center;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.sf-hits {
|
|
368
|
+
max-height: 196px;
|
|
369
|
+
margin: 8px 0 0;
|
|
370
|
+
padding: 0;
|
|
371
|
+
overflow: auto;
|
|
372
|
+
list-style: none;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.sf-hits li + li {
|
|
376
|
+
margin-top: 5px;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.sf-hit {
|
|
380
|
+
display: flex;
|
|
381
|
+
align-items: baseline;
|
|
382
|
+
gap: 7px;
|
|
383
|
+
width: 100%;
|
|
384
|
+
padding: 6px 8px;
|
|
385
|
+
border: 1px solid #e2e9f2;
|
|
386
|
+
border-radius: 7px;
|
|
387
|
+
background: #f8fbff;
|
|
388
|
+
text-align: left;
|
|
389
|
+
cursor: pointer;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.sf-hit:hover {
|
|
393
|
+
border-color: #b7d2ee;
|
|
394
|
+
background: #eef4fc;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.sf-hit strong {
|
|
398
|
+
flex: none;
|
|
399
|
+
color: #2f5e94;
|
|
400
|
+
font-size: 11.5px;
|
|
401
|
+
font-weight: 600;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.sf-hit span {
|
|
405
|
+
min-width: 0;
|
|
406
|
+
overflow: hidden;
|
|
407
|
+
color: #71809d;
|
|
408
|
+
font-size: 11px;
|
|
409
|
+
text-overflow: ellipsis;
|
|
410
|
+
white-space: nowrap;
|
|
411
|
+
}
|
|
412
|
+
</style>
|