openclaw-openagent 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/index.ts +114 -0
- package/openclaw.plugin.json +159 -0
- package/package.json +79 -0
- package/skills/clawlink/SKILL.md +145 -0
- package/skills/clawlink/SKILL.md.bak +165 -0
- package/src/app/channel-tools.ts +249 -0
- package/src/app/discovery-tools.ts +273 -0
- package/src/app/hooks.ts +60 -0
- package/src/app/index.ts +78 -0
- package/src/app/messaging-tools.ts +79 -0
- package/src/app/ops-tools.ts +155 -0
- package/src/app/remote-agent-tool.ts +476 -0
- package/src/app/types.ts +67 -0
- package/src/app/verbose-preflight.ts +190 -0
- package/src/auth/config.ts +197 -0
- package/src/auth/credential-manager.ts +146 -0
- package/src/auth/index.ts +24 -0
- package/src/auth/verify.ts +99 -0
- package/src/channel.ts +565 -0
- package/src/compat.ts +82 -0
- package/src/config/config-schema.ts +39 -0
- package/src/messaging/aggregator.ts +120 -0
- package/src/messaging/collector.ts +89 -0
- package/src/messaging/executor.ts +72 -0
- package/src/messaging/inbound.ts +150 -0
- package/src/messaging/index.ts +11 -0
- package/src/messaging/mention-protocol.ts +94 -0
- package/src/messaging/process-c2c-request.ts +564 -0
- package/src/messaging/process-message.ts +373 -0
- package/src/messaging/scheduler.ts +55 -0
- package/src/messaging/types.ts +38 -0
- package/src/plugin-ui/assets/agentbook-icon.svg +5 -0
- package/src/plugin-ui/assets/magic.svg +5 -0
- package/src/plugin-ui/assets/openagent-override.js +9329 -0
- package/src/plugin-ui/build.cjs +175 -0
- package/src/plugin-ui/index.ts +18 -0
- package/src/plugin-ui/modules/agent-book/panel/agent-book.js +458 -0
- package/src/plugin-ui/modules/agent-book/panel/agent-card.js +154 -0
- package/src/plugin-ui/modules/agent-book/panel/agent-data.js +644 -0
- package/src/plugin-ui/modules/agent-book/panel/inject-ui.js +456 -0
- package/src/plugin-ui/modules/agent-book/panel/mention-state.js +206 -0
- package/src/plugin-ui/modules/agent-book/panel/styles.js +670 -0
- package/src/plugin-ui/modules/agent-book/remote-agent-tool/components-core.js +293 -0
- package/src/plugin-ui/modules/agent-book/remote-agent-tool/thought-chain-card.js +208 -0
- package/src/plugin-ui/modules/agent-book/scanner.js +119 -0
- package/src/plugin-ui/modules/agent-book/travelcard/travel-cards.js +500 -0
- package/src/plugin-ui/modules/agent-book/travelcard/travel-engine.js +652 -0
- package/src/plugin-ui/modules/agent-book/travelcard/travel-styles.js +251 -0
- package/src/plugin-ui/modules/loader/bootstrap.js +38 -0
- package/src/plugin-ui/modules/loader/shared-state.js +560 -0
- package/src/plugin-ui/modules/loader/ws-intercept.js +199 -0
- package/src/plugin-ui/modules/remote-agent/chunk-parser.js +161 -0
- package/src/plugin-ui/modules/remote-agent/execution-card.js +269 -0
- package/src/plugin-ui/modules/remote-agent/markdown-renderer.js +256 -0
- package/src/plugin-ui/modules/remote-agent/native-style-adapter.js +146 -0
- package/src/plugin-ui/modules/remote-agent/output-card.js +259 -0
- package/src/plugin-ui/modules/remote-agent/progress-store.js +363 -0
- package/src/plugin-ui/modules/remote-agent/render-hooks.js +1609 -0
- package/src/plugin-ui/modules/remote-agent/styles.js +668 -0
- package/src/plugin-ui/modules/remote-agent/tool-card-model.js +56 -0
- package/src/plugin-ui/ui-extension-loader/backup.ts +92 -0
- package/src/plugin-ui/ui-extension-loader/index.ts +276 -0
- package/src/plugin-ui/ui-extension-loader/locator.ts +152 -0
- package/src/plugin-ui/ui-extension-loader/manifest.ts +57 -0
- package/src/plugin-ui/ui-extension-loader/registry-regex.ts +729 -0
- package/src/plugin-ui/ui-extension-loader/removed-extensions.ts +70 -0
- package/src/plugin-ui/ui-extension-loader/types.ts +68 -0
- package/src/proxy/auth-proxy.ts +356 -0
- package/src/runtime/account.ts +572 -0
- package/src/runtime/index.ts +7 -0
- package/src/runtime/plugin-runtime.ts +94 -0
- package/src/runtime/registry.ts +71 -0
- package/src/sdk/CLASS_MAP.md +143 -0
- package/src/sdk/index.d.ts +126 -0
- package/src/sdk/index.js +23990 -0
- package/src/sdk/modules/cloud-search-module.js +1117 -0
- package/src/sdk/modules/follow-module.js +1069 -0
- package/src/sdk/modules/group-module.js +7397 -0
- package/src/sdk/modules/relationship-module.js +2269 -0
- package/src/sdk/modules/signaling-module.js +1468 -0
- package/src/sdk/modules/tim-upload-plugin.js +730 -0
- package/src/sdk/node-env/http-request.js +90 -0
- package/src/sdk/node-env/index.js +57 -0
- package/src/sdk/node-env/storage.js +114 -0
- package/src/sdk/package.json +10 -0
- package/src/sdk/tsconfig.json +16 -0
- package/src/state/pending-invocation-store.ts +43 -0
- package/src/state/store.ts +676 -0
- package/src/tim/c2c.ts +451 -0
- package/src/tim/channels.ts +364 -0
- package/src/tim/client.ts +330 -0
- package/src/tim/index.ts +18 -0
- package/src/tim/messages.ts +166 -0
- package/src/tim/sdk-logger-init.ts +50 -0
- package/src/tools.ts +10 -0
- package/src/transport/factory.ts +95 -0
- package/src/transport/oasn/index.ts +17 -0
- package/src/transport/oasn/oasn-agent-card.ts +111 -0
- package/src/transport/oasn/oasn-discovery.ts +108 -0
- package/src/transport/oasn/oasn-files.ts +210 -0
- package/src/transport/oasn/oasn-http.ts +483 -0
- package/src/transport/oasn/oasn-invocation.ts +527 -0
- package/src/transport/oasn/oasn-normalize.ts +159 -0
- package/src/transport/oasn/oasn-register.ts +106 -0
- package/src/transport/oasn/oasn-transport.ts +341 -0
- package/src/transport/oasn/oasn-types.ts +353 -0
- package/src/transport/tim/index.ts +8 -0
- package/src/transport/tim/tim-transport.ts +515 -0
- package/src/transport/types.ts +541 -0
- package/src/types/openclaw.d.ts +97 -0
- package/src/types/tencentcloud-chat.d.ts +15 -0
- package/src/util/http.ts +113 -0
- package/src/util/logger.ts +131 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
console.log('[openagent] ✅ Trip card CSS injected');
|
|
2
|
+
|
|
3
|
+
// ── 2. 辅助函数 ──
|
|
4
|
+
|
|
5
|
+
// _el / _text 已提升到文件顶部工具函数区(全局共用)
|
|
6
|
+
|
|
7
|
+
function _formatTime(s) {
|
|
8
|
+
if (!s) return '';
|
|
9
|
+
const p = s.split(' ');
|
|
10
|
+
return p.length >= 2 ? p[1].slice(0, 5) : '';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Traffic icon unicode map
|
|
14
|
+
const _TRAFFIC_ICONS = {
|
|
15
|
+
FLIGHT: String.fromCodePoint(0xF11C9),
|
|
16
|
+
TRAIN: String.fromCodePoint(0xF11CA),
|
|
17
|
+
DRIVE: String.fromCodePoint(0xF1BBE),
|
|
18
|
+
BUS: String.fromCodePoint(0xF1BBD),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// ── 3. convertTrafficPlanInfo (translated from commons.js _m.OC) ──
|
|
22
|
+
|
|
23
|
+
function _convertTPI(tpi) {
|
|
24
|
+
if (!tpi) return null;
|
|
25
|
+
const summary = tpi.summary || {};
|
|
26
|
+
const dep = tpi.departure || {};
|
|
27
|
+
const arr = tpi.arrival || {};
|
|
28
|
+
const detailList = tpi.detailList || [];
|
|
29
|
+
const mainDetail = detailList.find(d => d.type && d.type !== 'DRIVE') || detailList[0];
|
|
30
|
+
const getStation = ep => ep?.trafficHub?.name || '';
|
|
31
|
+
const getDistrict = ep => ep?.trafficHub?.districtInfo?.districtName || '';
|
|
32
|
+
return {
|
|
33
|
+
startStationName: getStation(dep),
|
|
34
|
+
endStationName: getStation(arr),
|
|
35
|
+
startDistrictName: getDistrict(dep),
|
|
36
|
+
endDistrictName: getDistrict(arr),
|
|
37
|
+
startCode: dep?.trafficHub?.terminalCode,
|
|
38
|
+
endCode: arr?.trafficHub?.terminalCode,
|
|
39
|
+
startId: dep?.trafficHub?.districtInfo?.districtId,
|
|
40
|
+
endId: arr?.trafficHub?.districtInfo?.districtId,
|
|
41
|
+
travelTime: summary.transportTime,
|
|
42
|
+
startDateTime: summary.startDateTime,
|
|
43
|
+
endDateTime: summary.endDateTime,
|
|
44
|
+
endDateTimeSuperscript: summary.endDateTimeSuperscript,
|
|
45
|
+
startDateTimeFormatted: summary.startDateTimeFormatted,
|
|
46
|
+
shiftNumber: mainDetail?.basicInfo?.transportNumber,
|
|
47
|
+
nearbyText: summary.description,
|
|
48
|
+
isRecommended: summary.isRecommended,
|
|
49
|
+
isTransfer: summary.isTransfer,
|
|
50
|
+
trafficType: mainDetail?.type,
|
|
51
|
+
transferCount: summary.transferCount || 0,
|
|
52
|
+
description: mainDetail?.basicInfo?.description,
|
|
53
|
+
tagList: summary.tagList,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── 4. Card Renderers ──
|
|
58
|
+
|
|
59
|
+
/** TransportCard — 1:1 from TransportCard.jsx */
|
|
60
|
+
function _renderTransportCard(uc) {
|
|
61
|
+
if (!uc || uc.type !== 'transportCard') return null;
|
|
62
|
+
const units = uc.unitList || [];
|
|
63
|
+
if (units.length === 0) return null;
|
|
64
|
+
const unit = units[0];
|
|
65
|
+
if (!unit.trafficPlanInfo) return null;
|
|
66
|
+
const X = _convertTPI(unit.trafficPlanInfo);
|
|
67
|
+
if (!X) return null;
|
|
68
|
+
|
|
69
|
+
const url = unit.trafficPlanInfo.summary?.url;
|
|
70
|
+
|
|
71
|
+
const card = _el('div', 'trip-transport-card');
|
|
72
|
+
if (url?.type === 'NORMAL_URL' && url.url) {
|
|
73
|
+
card.style.cursor = 'pointer';
|
|
74
|
+
card.onclick = () => window.open(url.url, '_blank');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const inner = _el('div', 'trip-tc-inner');
|
|
78
|
+
card.appendChild(inner);
|
|
79
|
+
|
|
80
|
+
// StationAndTime
|
|
81
|
+
const w = X.startDateTime ? _formatTime(X.startDateTime) : '';
|
|
82
|
+
const b = X.endDateTime ? _formatTime(X.endDateTime) : '';
|
|
83
|
+
let H = w, z = b;
|
|
84
|
+
const U = !!(w && b);
|
|
85
|
+
if (!U) { H = X.startDistrictName; z = X.endDistrictName; }
|
|
86
|
+
const Y = !!(H && z);
|
|
87
|
+
if (!Y) { H = X.startStationName; z = X.endStationName; }
|
|
88
|
+
|
|
89
|
+
const stationRow = _el('div', 'trip-tc-station-row');
|
|
90
|
+
inner.appendChild(stationRow);
|
|
91
|
+
|
|
92
|
+
// Left side
|
|
93
|
+
const left = _el('div', 'trip-tc-left');
|
|
94
|
+
stationRow.appendChild(left);
|
|
95
|
+
|
|
96
|
+
const leftTop = _el('div', 'trip-tc-left-top');
|
|
97
|
+
left.appendChild(leftTop);
|
|
98
|
+
|
|
99
|
+
// Start time/city
|
|
100
|
+
leftTop.appendChild(_text('span', 'trip-tc-time', H));
|
|
101
|
+
|
|
102
|
+
// Arrow area
|
|
103
|
+
const arrowArea = _el('div', 'trip-tc-arrow-area');
|
|
104
|
+
leftTop.appendChild(arrowArea);
|
|
105
|
+
|
|
106
|
+
// SVG arrow
|
|
107
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
108
|
+
svg.setAttribute('width', '100%');
|
|
109
|
+
svg.setAttribute('height', '20');
|
|
110
|
+
svg.setAttribute('viewBox', '0 0 100 20');
|
|
111
|
+
svg.setAttribute('preserveAspectRatio', 'none');
|
|
112
|
+
svg.style.display = 'block';
|
|
113
|
+
svg.style.flex = '1';
|
|
114
|
+
svg.style.minWidth = '40px';
|
|
115
|
+
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
116
|
+
line.setAttribute('x1', '0'); line.setAttribute('y1', '10');
|
|
117
|
+
line.setAttribute('x2', '90'); line.setAttribute('y2', '10');
|
|
118
|
+
line.setAttribute('stroke', '#C9CDD4'); line.setAttribute('stroke-width', '1');
|
|
119
|
+
svg.appendChild(line);
|
|
120
|
+
const polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
|
|
121
|
+
polygon.setAttribute('points', '90,6 98,10 90,14');
|
|
122
|
+
polygon.setAttribute('fill', '#C9CDD4');
|
|
123
|
+
svg.appendChild(polygon);
|
|
124
|
+
arrowArea.appendChild(svg);
|
|
125
|
+
|
|
126
|
+
// Duration label
|
|
127
|
+
const dur = _el('div', 'trip-tc-duration');
|
|
128
|
+
if (X.travelTime) dur.appendChild(_text('span', '', X.travelTime));
|
|
129
|
+
if (X.transferCount > 0) dur.appendChild(_text('span', '', ` ${X.transferCount}次中转`));
|
|
130
|
+
arrowArea.appendChild(dur);
|
|
131
|
+
|
|
132
|
+
// Start station name
|
|
133
|
+
const startCode = (X.startId === X.startCode) ? undefined : X.startCode;
|
|
134
|
+
if (Y) {
|
|
135
|
+
left.appendChild(_text('span', 'trip-tc-station',
|
|
136
|
+
X.startStationName + (startCode ? ` ${startCode}` : '')));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Right side
|
|
140
|
+
const right = _el('div', 'trip-tc-right');
|
|
141
|
+
stationRow.appendChild(right);
|
|
142
|
+
|
|
143
|
+
const endTime = _text('span', 'trip-tc-time', z);
|
|
144
|
+
right.appendChild(endTime);
|
|
145
|
+
if (X.endDateTimeSuperscript) {
|
|
146
|
+
endTime.appendChild(_text('span', 'trip-tc-superscript', X.endDateTimeSuperscript));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const endCode = (X.endId === X.endCode) ? undefined : X.endCode;
|
|
150
|
+
if (Y) {
|
|
151
|
+
right.appendChild(_text('span', 'trip-tc-station',
|
|
152
|
+
X.endStationName + (endCode ? ` ${endCode}` : '')));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// DescBox
|
|
156
|
+
const desc = _el('div', 'trip-tc-desc');
|
|
157
|
+
inner.appendChild(desc);
|
|
158
|
+
const descParts = [];
|
|
159
|
+
|
|
160
|
+
// Recommend tag
|
|
161
|
+
const recTag = (X.tagList || []).find(t => t.tagType === 'recommend' || t.tagType === 'book' || t.tagType === 'TRAFFIC_PLAN');
|
|
162
|
+
if (recTag?.tagValue) {
|
|
163
|
+
descParts.push(_text('span', 'trip-tc-desc-tag trip-tc-desc-tag-positive', recTag.tagValue));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Flight number — 始终显示"参考班次:"前缀
|
|
167
|
+
if (!X.isTransfer && X.shiftNumber) {
|
|
168
|
+
descParts.push(_text('span', 'trip-tc-desc-tag', '参考班次:' + X.shiftNumber));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Date — 不显示日期(参考图一)
|
|
172
|
+
|
|
173
|
+
// Join with dividers
|
|
174
|
+
descParts.forEach((part, i) => {
|
|
175
|
+
if (i > 0) desc.appendChild(_text('span', 'trip-tc-desc-divider', '|'));
|
|
176
|
+
desc.appendChild(part);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
return card;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** ImageCard — POI 图片横向滚动 */
|
|
183
|
+
function _renderImageCard(uc) {
|
|
184
|
+
if (!uc || uc.type !== 'imageCard') return null;
|
|
185
|
+
const units = (uc.unitList || []).filter(u => u.unitType === 'poi' && u.poiInfo);
|
|
186
|
+
if (units.length === 0) return null;
|
|
187
|
+
|
|
188
|
+
const container = _el('div', 'trip-img-card');
|
|
189
|
+
|
|
190
|
+
units.forEach(u => {
|
|
191
|
+
const poi = u.poiInfo;
|
|
192
|
+
const imgs = poi.imgs || [];
|
|
193
|
+
const name = poi.name || poi.poiName || '';
|
|
194
|
+
const score = poi.hotScore;
|
|
195
|
+
const desc = poi.description
|
|
196
|
+
|| poi.summary
|
|
197
|
+
|| poi.basicInfo?.description
|
|
198
|
+
|| poi.basicInfo?.summary
|
|
199
|
+
|| poi.rankName
|
|
200
|
+
|| '';
|
|
201
|
+
|
|
202
|
+
// Only create items for POIs with images
|
|
203
|
+
imgs.slice(0, 3).forEach(imgUrl => {
|
|
204
|
+
const item = _el('div', 'trip-img-item');
|
|
205
|
+
|
|
206
|
+
const img = document.createElement('img');
|
|
207
|
+
img.src = imgUrl;
|
|
208
|
+
img.alt = name;
|
|
209
|
+
img.loading = 'lazy';
|
|
210
|
+
item.appendChild(img);
|
|
211
|
+
|
|
212
|
+
// Overlay with name
|
|
213
|
+
const overlay = _el('div', 'trip-img-overlay');
|
|
214
|
+
overlay.appendChild(_text('span', 'trip-img-name', name));
|
|
215
|
+
item.appendChild(overlay);
|
|
216
|
+
|
|
217
|
+
// Score badge
|
|
218
|
+
if (score) {
|
|
219
|
+
const scoreBadge = _el('div', 'trip-img-score');
|
|
220
|
+
scoreBadge.appendChild(_text('span', 'trip-img-score-icon', '🔥'));
|
|
221
|
+
scoreBadge.appendChild(_text('span', 'trip-img-score-text', String(score)));
|
|
222
|
+
item.appendChild(scoreBadge);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
container.appendChild(item);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// If only markdown fallback data is available, still render the useful text
|
|
229
|
+
// instead of collapsing to an empty-looking image placeholder.
|
|
230
|
+
if (imgs.length === 0 && name) {
|
|
231
|
+
const item = _el('div', 'trip-img-item trip-img-fallback-item');
|
|
232
|
+
item.appendChild(_text('span', 'trip-img-fallback-title', name));
|
|
233
|
+
if (desc) {
|
|
234
|
+
item.appendChild(_text('span', 'trip-img-fallback-desc', desc));
|
|
235
|
+
}
|
|
236
|
+
if (score) {
|
|
237
|
+
item.appendChild(_text('span', 'trip-img-fallback-score', String(score)));
|
|
238
|
+
}
|
|
239
|
+
container.appendChild(item);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
return container.children.length > 0 ? container : null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** TimeDataCard — Day 分隔符 */
|
|
247
|
+
function _renderTimeDataCard(content) {
|
|
248
|
+
// 蓝色第N天 badge 已移除,参考图二不需要此组件
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** ShoppingCard — 线路概览表格 */
|
|
253
|
+
function _renderShoppingCard(dayList) {
|
|
254
|
+
if (!dayList || dayList.length === 0) return null;
|
|
255
|
+
|
|
256
|
+
const card = _el('div', 'trip-sh-card');
|
|
257
|
+
card.appendChild(_text('span', 'trip-sh-title', '线路概览'));
|
|
258
|
+
|
|
259
|
+
const table = _el('div', 'trip-sh-table');
|
|
260
|
+
card.appendChild(table);
|
|
261
|
+
|
|
262
|
+
dayList.forEach((day, idx) => {
|
|
263
|
+
const row = _el('div', 'trip-sh-row');
|
|
264
|
+
table.appendChild(row);
|
|
265
|
+
|
|
266
|
+
// Day cell
|
|
267
|
+
const dayCell = _el('div', 'trip-sh-day-cell');
|
|
268
|
+
dayCell.appendChild(_text('span', 'trip-sh-day-title', 'DAY'));
|
|
269
|
+
dayCell.appendChild(_text('span', 'trip-sh-day-num', String(idx + 1).padStart(2, '0')));
|
|
270
|
+
row.appendChild(dayCell);
|
|
271
|
+
|
|
272
|
+
// Detail cell
|
|
273
|
+
const detail = _el('div', 'trip-sh-detail');
|
|
274
|
+
row.appendChild(detail);
|
|
275
|
+
|
|
276
|
+
const wrapper = _el('div', '');
|
|
277
|
+
detail.appendChild(wrapper);
|
|
278
|
+
|
|
279
|
+
// Districts
|
|
280
|
+
const districts = day.districtInfos || [];
|
|
281
|
+
if (districts.length > 0) {
|
|
282
|
+
const dv = _el('div', 'trip-sh-districts');
|
|
283
|
+
wrapper.appendChild(dv);
|
|
284
|
+
districts.forEach((d, di) => {
|
|
285
|
+
if (di > 0) dv.appendChild(_text('span', 'trip-sh-district-arrow', '→'));
|
|
286
|
+
dv.appendChild(_text('span', 'trip-sh-district', d.districtName || ''));
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// POIs
|
|
291
|
+
const units = day.itineraryUnitList || [];
|
|
292
|
+
const poiNames = units.map(u => u.poiInfo?.basicInfo?.poiName || u.poiInfo?.name || '').filter(Boolean);
|
|
293
|
+
if (poiNames.length > 0) {
|
|
294
|
+
const pv = _el('div', 'trip-sh-pois');
|
|
295
|
+
wrapper.appendChild(pv);
|
|
296
|
+
poiNames.forEach((name, pi) => {
|
|
297
|
+
if (pi > 0) pv.appendChild(_text('span', 'trip-sh-poi-arrow', '→'));
|
|
298
|
+
pv.appendChild(_text('span', 'trip-sh-poi', name));
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
return card;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** HotelAreaCard */
|
|
307
|
+
function _renderHotelCard(uc) {
|
|
308
|
+
if (!uc || uc.type !== 'hotelAreaCard') return null;
|
|
309
|
+
const unit = (uc.unitList || [])[0];
|
|
310
|
+
const areaList = unit?.hotelAreaList || [];
|
|
311
|
+
const area = unit?.hotelArea || areaList.find(a => a && a.recommendArea) || areaList[0];
|
|
312
|
+
if (!area) return null;
|
|
313
|
+
|
|
314
|
+
const card = _el('div', 'trip-hotel-card');
|
|
315
|
+
const header = _el('div', 'trip-hotel-header');
|
|
316
|
+
card.appendChild(header);
|
|
317
|
+
|
|
318
|
+
// Hotel icon (using emoji as fallback for icon font)
|
|
319
|
+
header.appendChild(_text('span', 'trip-hotel-icon', String.fromCodePoint(0xF178F)));
|
|
320
|
+
header.appendChild(_text('span', 'trip-hotel-title', `推荐住在 · ${area.name || ''}`));
|
|
321
|
+
const desc = area.description
|
|
322
|
+
|| area.recommendation
|
|
323
|
+
|| (area.chosenRate ? `${Math.round(Number(area.chosenRate) * 100)}%的游客选择住在这里` : '');
|
|
324
|
+
if (desc) {
|
|
325
|
+
card.appendChild(_text('div', 'trip-hotel-desc', desc));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return card;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** FoodCard */
|
|
332
|
+
function _renderFoodCard(foodData) {
|
|
333
|
+
if (!foodData) return null;
|
|
334
|
+
const mt = foodData.mustTasteResult || foodData;
|
|
335
|
+
const foods = (mt.hotFoodInfos || []).slice(0, 4);
|
|
336
|
+
const rests = (mt.hotRestInfos || []).filter(r => r.name || r.restName).slice(0, 3);
|
|
337
|
+
if (foods.length === 0 && rests.length === 0) return null;
|
|
338
|
+
|
|
339
|
+
const card = _el('div', '', { margin: '16px 0' });
|
|
340
|
+
|
|
341
|
+
// 标题
|
|
342
|
+
var title = document.createElement('h3');
|
|
343
|
+
title.textContent = '美食推荐';
|
|
344
|
+
title.style.cssText = 'font-size:18px;font-weight:600;color:#111;margin:0 0 8px 0;line-height:26px;';
|
|
345
|
+
card.appendChild(title);
|
|
346
|
+
|
|
347
|
+
// 介绍段落:"当地特色美食有寿司、鳗鱼饭、天妇罗等,推荐口碑榜餐厅作为参考:"
|
|
348
|
+
if (foods.length > 0) {
|
|
349
|
+
var foodNames = foods.map(function(f) { return f.name || ''; }).filter(Boolean).join('、');
|
|
350
|
+
var intro = document.createElement('p');
|
|
351
|
+
intro.textContent = '当地特色美食有' + foodNames + '等,推荐口碑榜餐厅作为参考:';
|
|
352
|
+
intro.style.cssText = 'font-size:15px;color:#333;line-height:1.7;margin:0 0 12px 0;';
|
|
353
|
+
card.appendChild(intro);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// 餐厅列表(bullet list 样式,匹配图一)
|
|
357
|
+
if (rests.length > 0) {
|
|
358
|
+
var ul = document.createElement('ul');
|
|
359
|
+
ul.style.cssText = 'list-style:disc;padding-left:20px;margin:0;';
|
|
360
|
+
rests.forEach(function(r) {
|
|
361
|
+
var li = document.createElement('li');
|
|
362
|
+
li.style.cssText = 'margin-bottom:10px;font-size:15px;line-height:1.7;color:#333;';
|
|
363
|
+
|
|
364
|
+
var nameEl = document.createElement('strong');
|
|
365
|
+
nameEl.textContent = r.name || r.restName || '';
|
|
366
|
+
li.appendChild(nameEl);
|
|
367
|
+
|
|
368
|
+
if (r.rankTitle) {
|
|
369
|
+
var desc = document.createTextNode(':' + r.rankTitle);
|
|
370
|
+
li.appendChild(desc);
|
|
371
|
+
}
|
|
372
|
+
ul.appendChild(li);
|
|
373
|
+
});
|
|
374
|
+
card.appendChild(ul);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return card;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** BudgetCard */
|
|
381
|
+
function _renderBudgetCard(budgetData) {
|
|
382
|
+
if (!budgetData) return null;
|
|
383
|
+
const result = budgetData.result || budgetData;
|
|
384
|
+
const budget = result.budget || result.budgetData || result;
|
|
385
|
+
if (!budget) return null;
|
|
386
|
+
|
|
387
|
+
function _readBudgetNumber(obj, fields) {
|
|
388
|
+
for (var i = 0; i < fields.length; i++) {
|
|
389
|
+
var value = obj && obj[fields[i]];
|
|
390
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
391
|
+
if (typeof value === 'string') {
|
|
392
|
+
var parsed = Number(value.replace(/[^\d.-]/g, ''));
|
|
393
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return 0;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function _readBudgetText(obj, fields) {
|
|
400
|
+
for (var i = 0; i < fields.length; i++) {
|
|
401
|
+
var value = obj && obj[fields[i]];
|
|
402
|
+
if (typeof value === 'string' && value.trim()) return value.trim();
|
|
403
|
+
}
|
|
404
|
+
return '';
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const totalPrice = _readBudgetNumber(budget, [
|
|
408
|
+
'totalPrice', 'totalAmount', 'totalCost', 'total', 'amount', 'price', 'cost', 'value'
|
|
409
|
+
]);
|
|
410
|
+
const totalFormatted = _readBudgetText(budget, [
|
|
411
|
+
'totalPriceFormatted', 'totalAmountFormatted', 'totalCostFormatted',
|
|
412
|
+
'totalFormatted', 'amountFormatted', 'priceFormatted', 'formattedPrice', 'formatted'
|
|
413
|
+
]);
|
|
414
|
+
|
|
415
|
+
const rawParts = budget.budgetParts || budget.parts || budget.items || budget.categories || budget.detailList || [];
|
|
416
|
+
const description = budget.description || budget.desc || budget.summary || budget.remark || '';
|
|
417
|
+
|
|
418
|
+
if (!totalPrice && !totalFormatted && (!rawParts || rawParts.length === 0) && !description) return null;
|
|
419
|
+
|
|
420
|
+
const PART_COLORS = {
|
|
421
|
+
TRAFFIC: { color: '#7585F8', bg: 'rgba(117,133,248,0.08)', title: '交通', icon: '✈️' },
|
|
422
|
+
TICKETS: { color: '#1FC496', bg: 'rgba(31,196,150,0.08)', title: '景点', icon: '🎫' },
|
|
423
|
+
STAY: { color: '#3290FA', bg: 'rgba(50,144,250,0.08)', title: '住宿', icon: '🏨' },
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
const card = _el('div', 'trip-budget-card');
|
|
427
|
+
card.appendChild(_text('div', 'trip-budget-title', '费用预估'));
|
|
428
|
+
if (description) {
|
|
429
|
+
// 描述用 bullet list 格式(匹配图一)
|
|
430
|
+
const descUl = document.createElement('ul');
|
|
431
|
+
descUl.style.cssText = 'list-style:disc;padding-left:20px;margin:6px 0 0 0;';
|
|
432
|
+
const descLi = document.createElement('li');
|
|
433
|
+
descLi.className = 'trip-budget-desc';
|
|
434
|
+
descLi.textContent = description;
|
|
435
|
+
descUl.appendChild(descLi);
|
|
436
|
+
card.appendChild(descUl);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const chart = _el('div', 'trip-budget-chart');
|
|
440
|
+
card.appendChild(chart);
|
|
441
|
+
|
|
442
|
+
// Header
|
|
443
|
+
const header = _el('div', 'trip-budget-header');
|
|
444
|
+
chart.appendChild(header);
|
|
445
|
+
const person = (budget.person || 1) > 1 ? `${budget.person}人总价` : '1人总价';
|
|
446
|
+
header.appendChild(_text('span', 'trip-budget-person', person));
|
|
447
|
+
header.appendChild(_text('span', 'trip-budget-total',
|
|
448
|
+
totalFormatted || (totalPrice ? `≈ ¥${totalPrice.toLocaleString()}` : '费用待估算')));
|
|
449
|
+
|
|
450
|
+
// Parts
|
|
451
|
+
const parts = (rawParts || []).map(p => {
|
|
452
|
+
const type = p.type || p.category || p.key || p.name || p.title || 'OTHER';
|
|
453
|
+
const price = _readBudgetNumber(p, [
|
|
454
|
+
'totalPrice', 'totalAmount', 'totalCost', 'total', 'amount', 'price', 'cost', 'value'
|
|
455
|
+
]);
|
|
456
|
+
const formatted = _readBudgetText(p, [
|
|
457
|
+
'totalPriceFormatted', 'totalAmountFormatted', 'totalCostFormatted',
|
|
458
|
+
'totalFormatted', 'amountFormatted', 'priceFormatted', 'formattedPrice', 'formatted'
|
|
459
|
+
]);
|
|
460
|
+
return {
|
|
461
|
+
raw: p,
|
|
462
|
+
type: String(type || 'OTHER').toUpperCase(),
|
|
463
|
+
label: p.title || p.name || p.label || p.categoryName || '',
|
|
464
|
+
totalPrice: price,
|
|
465
|
+
totalPriceFormatted: formatted,
|
|
466
|
+
};
|
|
467
|
+
}).sort((a, b) => {
|
|
468
|
+
const order = { TRAFFIC: 0, STAY: 1, TICKETS: 2 };
|
|
469
|
+
return (order[a.type] ?? 99) - (order[b.type] ?? 99);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
if (parts.length > 0) {
|
|
473
|
+
const partsDiv = _el('div', 'trip-budget-parts');
|
|
474
|
+
chart.appendChild(partsDiv);
|
|
475
|
+
|
|
476
|
+
parts.forEach(p => {
|
|
477
|
+
const cfg = PART_COLORS[p.type] || { color: '#ccc', bg: '#f5f5f5', title: p.label || p.type, icon: '📦' };
|
|
478
|
+
const ratio = totalPrice > 0 ? (p.totalPrice || 0) / totalPrice : 0;
|
|
479
|
+
|
|
480
|
+
const part = _el('div', 'trip-budget-part');
|
|
481
|
+
partsDiv.appendChild(part);
|
|
482
|
+
|
|
483
|
+
part.appendChild(_text('span', 'trip-budget-part-icon', cfg.icon));
|
|
484
|
+
part.appendChild(_text('span', 'trip-budget-part-name', cfg.title));
|
|
485
|
+
|
|
486
|
+
const bar = _el('div', 'trip-budget-bar', { backgroundColor: cfg.bg });
|
|
487
|
+
const fill = _el('div', 'trip-budget-bar-fill', {
|
|
488
|
+
width: `${Math.max(100 * ratio, 4)}%`,
|
|
489
|
+
backgroundColor: cfg.color,
|
|
490
|
+
});
|
|
491
|
+
bar.appendChild(fill);
|
|
492
|
+
part.appendChild(bar);
|
|
493
|
+
|
|
494
|
+
part.appendChild(_text('span', 'trip-budget-part-price',
|
|
495
|
+
p.totalPriceFormatted || (p.totalPrice ? `¥${p.totalPrice.toLocaleString()}` : '待估算')));
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return card;
|
|
500
|
+
}
|