@liujitcn/kratos-uni-app-system 0.0.1

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.
@@ -0,0 +1,308 @@
1
+ <script setup lang="ts">
2
+ import type { AiSession } from '../../../../rpc/base/v1/ai_session'
3
+
4
+ type PressPoint = {
5
+ clientX?: number
6
+ clientY?: number
7
+ pageX?: number
8
+ pageY?: number
9
+ x?: number
10
+ y?: number
11
+ }
12
+
13
+ type PressEvent = {
14
+ detail?: {
15
+ x?: number
16
+ y?: number
17
+ }
18
+ touches?: PressPoint[]
19
+ changedTouches?: PressPoint[]
20
+ }
21
+
22
+ type InputEventValue = {
23
+ detail: {
24
+ value: string
25
+ }
26
+ }
27
+
28
+ defineProps<{
29
+ open: boolean
30
+ topPadding: string
31
+ keyword: string
32
+ loading: boolean
33
+ sessions: AiSession[]
34
+ activeSessionId: string
35
+ }>()
36
+
37
+ const emit = defineEmits<{
38
+ close: []
39
+ create: []
40
+ select: [sessionID: string]
41
+ action: [session: AiSession, event?: PressEvent]
42
+ 'update:keyword': [value: string]
43
+ }>()
44
+
45
+ function handleKeywordInput(event: Event) {
46
+ emit('update:keyword', ((event as unknown as InputEventValue).detail?.value || '').toString())
47
+ }
48
+
49
+ function formatSessionTime(session: AiSession) {
50
+ const seconds = Number(session.updated_at?.seconds ?? 0)
51
+ const nanos = Number(session.updated_at?.nanos ?? 0)
52
+ const timestamp = seconds * 1000 + Math.floor(nanos / 1_000_000)
53
+ if (!timestamp) {
54
+ return ''
55
+ }
56
+ const date = new Date(timestamp)
57
+ const now = new Date()
58
+ const isToday =
59
+ date.getFullYear() === now.getFullYear() &&
60
+ date.getMonth() === now.getMonth() &&
61
+ date.getDate() === now.getDate()
62
+ const month = `${date.getMonth() + 1}`.padStart(2, '0')
63
+ const day = `${date.getDate()}`.padStart(2, '0')
64
+ const hour = `${date.getHours()}`.padStart(2, '0')
65
+ const minute = `${date.getMinutes()}`.padStart(2, '0')
66
+ return isToday ? `${hour}:${minute}` : `${month}-${day}`
67
+ }
68
+ </script>
69
+
70
+ <template>
71
+ <view v-if="open" class="session-mask" @tap="emit('close')"></view>
72
+ <view class="session-drawer" :class="{ 'is-open': open }" :style="{ paddingTop: topPadding }">
73
+ <view class="session-drawer__head">
74
+ <view class="session-drawer__title">历史会话</view>
75
+ <button class="session-create" hover-class="none" @tap="emit('create')">
76
+ <uni-icons type="plusempty" size="16" color="#27ba9b" />
77
+ <text>新建</text>
78
+ </button>
79
+ </view>
80
+ <view class="session-search">
81
+ <uni-icons type="search" size="16" color="#898b94" />
82
+ <input
83
+ class="session-search-input"
84
+ confirm-type="search"
85
+ placeholder="搜索会话"
86
+ placeholder-class="session-search-placeholder"
87
+ :value="keyword"
88
+ @input="handleKeywordInput"
89
+ />
90
+ </view>
91
+ <scroll-view class="session-list" scroll-y :show-scrollbar="false">
92
+ <view v-if="loading" class="session-empty">正在加载会话...</view>
93
+ <view
94
+ v-for="session in sessions"
95
+ :key="session.id"
96
+ class="session-item"
97
+ :class="{ 'is-active': session.id === activeSessionId }"
98
+ @tap="emit('select', session.id)"
99
+ @longpress="emit('action', session, $event)"
100
+ >
101
+ <view class="session-content">
102
+ <view class="session-row">
103
+ <view class="session-title">{{ session.title }}</view>
104
+ <view class="session-time">{{ formatSessionTime(session) }}</view>
105
+ </view>
106
+ <view class="session-summary">{{ session.summary || '暂无摘要' }}</view>
107
+ </view>
108
+ <button class="session-more" hover-class="none" @tap.stop="emit('action', session, $event)">
109
+ <view></view>
110
+ <view></view>
111
+ <view></view>
112
+ </button>
113
+ </view>
114
+ <view v-if="!loading && !sessions.length" class="session-empty">没有匹配的会话</view>
115
+ </scroll-view>
116
+ </view>
117
+ </template>
118
+
119
+ <style lang="scss" scoped>
120
+ .session-create,
121
+ .session-more {
122
+ padding: 0;
123
+ margin: 0;
124
+ border-radius: 0;
125
+ background: transparent;
126
+ line-height: normal;
127
+
128
+ &::after {
129
+ border: none;
130
+ }
131
+ }
132
+
133
+ .session-mask {
134
+ position: absolute;
135
+ top: 0;
136
+ right: 0;
137
+ bottom: 0;
138
+ left: 560rpx;
139
+ z-index: 20;
140
+ background-color: rgba(0, 0, 0, 0.18);
141
+ }
142
+
143
+ .session-drawer {
144
+ display: flex;
145
+ flex-direction: column;
146
+ position: absolute;
147
+ top: 0;
148
+ left: 0;
149
+ bottom: 0;
150
+ z-index: 21;
151
+ width: 560rpx;
152
+ padding: 0 24rpx 36rpx;
153
+ background-color: #fff;
154
+ box-shadow: none;
155
+ box-sizing: border-box;
156
+ transform: translateX(-100%);
157
+ transition: transform 0.2s ease;
158
+ }
159
+
160
+ .session-drawer.is-open {
161
+ box-shadow: 24rpx 0 60rpx rgba(0, 0, 0, 0.12);
162
+ transform: translateX(0);
163
+ }
164
+
165
+ .session-drawer__head {
166
+ flex-shrink: 0;
167
+ display: flex;
168
+ align-items: center;
169
+ justify-content: space-between;
170
+ min-height: 76rpx;
171
+ margin-bottom: 18rpx;
172
+ }
173
+
174
+ .session-drawer__title {
175
+ color: #333;
176
+ font-size: 32rpx;
177
+ font-weight: 700;
178
+ line-height: 40rpx;
179
+ }
180
+
181
+ .session-create {
182
+ display: flex;
183
+ align-items: center;
184
+ justify-content: center;
185
+ gap: 4rpx;
186
+ width: 112rpx;
187
+ height: 52rpx;
188
+ border-radius: 8rpx;
189
+ color: #27ba9b;
190
+ font-size: 24rpx;
191
+ line-height: 52rpx;
192
+ background-color: #e8f8f4;
193
+ }
194
+
195
+ .session-search {
196
+ flex-shrink: 0;
197
+ display: flex;
198
+ align-items: center;
199
+ gap: 10rpx;
200
+ height: 64rpx;
201
+ padding: 0 18rpx;
202
+ margin-bottom: 18rpx;
203
+ border-radius: 10rpx;
204
+ background-color: #f6f7f9;
205
+ box-sizing: border-box;
206
+ }
207
+
208
+ .session-search-input {
209
+ flex: 1;
210
+ min-width: 0;
211
+ color: #333;
212
+ font-size: 24rpx;
213
+ }
214
+
215
+ .session-search-placeholder {
216
+ color: #b8bcc5;
217
+ }
218
+
219
+ .session-list {
220
+ flex: 1;
221
+ min-height: 0;
222
+ }
223
+
224
+ .session-item {
225
+ display: flex;
226
+ align-items: center;
227
+ gap: 12rpx;
228
+ padding: 22rpx 16rpx 22rpx 22rpx;
229
+ border: 1rpx solid #eee;
230
+ border-radius: 10rpx;
231
+ background-color: #fff;
232
+ }
233
+
234
+ .session-item + .session-item {
235
+ margin-top: 18rpx;
236
+ }
237
+
238
+ .session-item.is-active {
239
+ border-color: #c7eee4;
240
+ background-color: #e8f8f4;
241
+ }
242
+
243
+ .session-content {
244
+ flex: 1;
245
+ min-width: 0;
246
+ }
247
+
248
+ .session-row {
249
+ display: flex;
250
+ align-items: center;
251
+ gap: 14rpx;
252
+ }
253
+
254
+ .session-title {
255
+ flex: 1;
256
+ min-width: 0;
257
+ overflow: hidden;
258
+ text-overflow: ellipsis;
259
+ white-space: nowrap;
260
+ color: #333;
261
+ font-size: 26rpx;
262
+ font-weight: 600;
263
+ line-height: 34rpx;
264
+ }
265
+
266
+ .session-time {
267
+ flex-shrink: 0;
268
+ color: #a6abb3;
269
+ font-size: 20rpx;
270
+ line-height: 30rpx;
271
+ }
272
+
273
+ .session-summary {
274
+ margin-top: 10rpx;
275
+ overflow: hidden;
276
+ text-overflow: ellipsis;
277
+ white-space: nowrap;
278
+ color: #777;
279
+ font-size: 22rpx;
280
+ line-height: 32rpx;
281
+ }
282
+
283
+ .session-more {
284
+ flex-shrink: 0;
285
+ display: flex;
286
+ align-items: center;
287
+ justify-content: center;
288
+ gap: 5rpx;
289
+ width: 56rpx;
290
+ height: 56rpx;
291
+ border-radius: 50%;
292
+ background-color: transparent;
293
+ }
294
+
295
+ .session-more view {
296
+ width: 6rpx;
297
+ height: 6rpx;
298
+ border-radius: 50%;
299
+ background-color: #9ca3af;
300
+ }
301
+
302
+ .session-empty {
303
+ padding: 60rpx 0;
304
+ color: #999;
305
+ font-size: 24rpx;
306
+ text-align: center;
307
+ }
308
+ </style>