@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,238 @@
1
+ <script setup lang="ts">
2
+ import { useUserStore } from '@liujitcn/kratos-uni-app-core/stores'
3
+ import { computed } from 'vue'
4
+ import { formatSrc } from '@liujitcn/kratos-uni-app-core/utils/index'
5
+ import { navigateToLogin } from '@liujitcn/kratos-uni-app-core/utils/navigation'
6
+ import { navigateAppRoute } from '@liujitcn/kratos-uni-app-core'
7
+ import defaultAvatar from '@liujitcn/kratos-uni-app-core/static/images/avatar.png'
8
+ import centerBackground from '@liujitcn/kratos-uni-app-core/static/images/center_bg.png'
9
+
10
+ // 获取会员信息
11
+ const userStore = useUserStore()
12
+ const isLoggedIn = computed(() => userStore.isAuthenticated())
13
+ const profile = computed(() => userStore.userInfo)
14
+
15
+ /** 打开移动端 AI 助手静态页,未登录时先进入登录流程。 */
16
+ const navigateToAi = () => {
17
+ console.log('[DEBUG-login-click] ai')
18
+ if (!userStore.ensureAuthenticated()) {
19
+ navigateToLogin()
20
+ return
21
+ }
22
+ navigateAppRoute('app/ai')
23
+ }
24
+
25
+ /** 打开设置页,未登录时先进入登录流程。 */
26
+ const navigateToSettings = () => {
27
+ console.log('[DEBUG-login-click] settings')
28
+ if (!userStore.ensureAuthenticated()) {
29
+ navigateToLogin()
30
+ return
31
+ }
32
+ navigateAppRoute('app/settings')
33
+ }
34
+
35
+ /** 打开当前用户资料。 */
36
+ const navigateToProfile = () => navigateAppRoute('app/profile')
37
+ </script>
38
+
39
+ <template>
40
+ <scroll-view
41
+ enable-back-to-top
42
+ class="viewport"
43
+ scroll-y
44
+ :style="{ backgroundImage: `url(${centerBackground})` }"
45
+ >
46
+ <!-- 个人资料 -->
47
+ <view class="profile">
48
+ <!-- 情况1:已登录 -->
49
+ <view class="overview" v-if="isLoggedIn && profile">
50
+ <view @tap="navigateToProfile">
51
+ <image
52
+ v-if="profile.avatar"
53
+ class="avatar"
54
+ :src="formatSrc(profile.avatar)"
55
+ mode="aspectFill"
56
+ ></image>
57
+ <image v-else class="avatar" :src="defaultAvatar" mode="aspectFill"></image>
58
+ </view>
59
+ <view class="meta">
60
+ <view class="nickname">
61
+ {{ profile.nick_name }}
62
+ </view>
63
+ <view class="extra" @tap="navigateToProfile">
64
+ <text class="update">更新头像昵称</text>
65
+ </view>
66
+ </view>
67
+ </view>
68
+ <!-- 情况2:未登录 -->
69
+ <view class="overview" v-else>
70
+ <view @tap="navigateToLogin">
71
+ <image class="avatar gray" mode="aspectFill" :src="defaultAvatar"></image>
72
+ </view>
73
+ <view class="meta">
74
+ <view @tap="navigateToLogin" class="nickname"> 未登录 </view>
75
+ <view class="extra">
76
+ <text class="tips">点击登录账号</text>
77
+ </view>
78
+ </view>
79
+ </view>
80
+ <view class="settings" @tap="navigateToSettings">设置</view>
81
+ </view>
82
+ <!-- AI 助手入口 -->
83
+ <view class="ai-entry" @tap="navigateToAi">
84
+ <view class="ai-entry__icon">AI</view>
85
+ <view class="ai-entry__content">
86
+ <view class="ai-entry__title">智能助手</view>
87
+ <view class="ai-entry__desc">帮你整理信息、回答问题并处理日常任务</view>
88
+ </view>
89
+ <view class="ai-entry__action">去提问</view>
90
+ </view>
91
+ </scroll-view>
92
+ </template>
93
+
94
+ <style lang="scss">
95
+ page {
96
+ height: 100%;
97
+ overflow: hidden;
98
+ background-color: #f7f7f8;
99
+ }
100
+
101
+ /* AI 助手入口 */
102
+ .ai-entry {
103
+ position: relative;
104
+ z-index: 99;
105
+ display: flex;
106
+ align-items: center;
107
+ margin: 20rpx 20rpx 0;
108
+ padding: 26rpx 24rpx;
109
+ border-radius: 10rpx;
110
+ background-color: #fff;
111
+ box-shadow: 0 4rpx 6rpx rgba(240, 240, 240, 0.6);
112
+ }
113
+
114
+ .ai-entry__icon {
115
+ flex-shrink: 0;
116
+ width: 92rpx;
117
+ height: 92rpx;
118
+ border-radius: 24rpx;
119
+ color: #fff;
120
+ font-size: 28rpx;
121
+ font-weight: 700;
122
+ line-height: 92rpx;
123
+ text-align: center;
124
+ background-color: #27ba9b;
125
+ }
126
+
127
+ .ai-entry__content {
128
+ flex: 1;
129
+ min-width: 0;
130
+ margin-left: 18rpx;
131
+ }
132
+
133
+ .ai-entry__title {
134
+ color: #1e1e1e;
135
+ font-size: 30rpx;
136
+ font-weight: 600;
137
+ line-height: 40rpx;
138
+ }
139
+
140
+ .ai-entry__desc {
141
+ overflow: hidden;
142
+ text-overflow: ellipsis;
143
+ white-space: nowrap;
144
+ margin-top: 8rpx;
145
+ color: #747f7c;
146
+ font-size: 24rpx;
147
+ line-height: 32rpx;
148
+ }
149
+
150
+ .ai-entry__action {
151
+ flex-shrink: 0;
152
+ margin-left: 16rpx;
153
+ padding: 10rpx 18rpx;
154
+ border-radius: 999rpx;
155
+ color: #16806d;
156
+ font-size: 24rpx;
157
+ background-color: #e8f8f4;
158
+ }
159
+
160
+ .viewport {
161
+ height: 100%;
162
+ background-repeat: no-repeat;
163
+ background-size: 100% auto;
164
+ }
165
+
166
+ /* 用户信息 */
167
+ .profile {
168
+ margin-top: 30rpx;
169
+ position: relative;
170
+
171
+ /* #ifdef MP-WEIXIN */
172
+ padding-top: 44px;
173
+ /* #endif */
174
+
175
+ .overview {
176
+ display: flex;
177
+ height: 120rpx;
178
+ padding: 0 36rpx;
179
+ color: #fff;
180
+ }
181
+
182
+ .avatar {
183
+ width: 120rpx;
184
+ height: 120rpx;
185
+ border-radius: 50%;
186
+ background-color: #eee;
187
+ }
188
+
189
+ .gray {
190
+ filter: grayscale(100%);
191
+ }
192
+
193
+ .meta {
194
+ display: flex;
195
+ flex-direction: column;
196
+ justify-content: center;
197
+ align-items: flex-start;
198
+ line-height: 30rpx;
199
+ padding: 16rpx 0;
200
+ margin-left: 20rpx;
201
+ }
202
+
203
+ .nickname {
204
+ max-width: 180rpx;
205
+ margin-bottom: 16rpx;
206
+ font-size: 30rpx;
207
+
208
+ overflow: hidden;
209
+ text-overflow: ellipsis;
210
+ white-space: nowrap;
211
+ }
212
+
213
+ .extra {
214
+ display: flex;
215
+ font-size: 20rpx;
216
+ }
217
+
218
+ .tips {
219
+ font-size: 22rpx;
220
+ }
221
+
222
+ .update {
223
+ padding: 3rpx 10rpx 1rpx;
224
+ color: rgba(255, 255, 255, 0.8);
225
+ border: 1rpx solid rgba(255, 255, 255, 0.8);
226
+ margin-right: 10rpx;
227
+ border-radius: 30rpx;
228
+ }
229
+
230
+ .settings {
231
+ position: absolute;
232
+ bottom: 0;
233
+ right: 40rpx;
234
+ font-size: 30rpx;
235
+ color: #fff;
236
+ }
237
+ }
238
+ </style>
@@ -0,0 +1,219 @@
1
+ <script setup lang="ts">
2
+ import type { AiAttachment } from '../../../../rpc/base/v1/ai_session'
3
+
4
+ type InputEventValue = {
5
+ detail: {
6
+ value: string
7
+ }
8
+ }
9
+
10
+ defineProps<{
11
+ modelValue: string
12
+ attachments: AiAttachment[]
13
+ placeholder: string
14
+ bottom: string
15
+ recording: boolean
16
+ sending: boolean
17
+ disabled: boolean
18
+ }>()
19
+
20
+ const emit = defineEmits<{
21
+ 'update:modelValue': [value: string]
22
+ attach: []
23
+ record: []
24
+ send: []
25
+ 'remove-attachment': [attachment: AiAttachment]
26
+ }>()
27
+
28
+ function handleInput(event: Event) {
29
+ emit('update:modelValue', ((event as unknown as InputEventValue).detail?.value || '').toString())
30
+ }
31
+ </script>
32
+
33
+ <template>
34
+ <view class="composer" :style="{ paddingBottom: bottom }">
35
+ <view class="composer-main">
36
+ <button class="attach-button" hover-class="none" @tap="emit('attach')">
37
+ <uni-icons type="plusempty" size="30" color="#111" />
38
+ </button>
39
+ <view class="composer-card">
40
+ <view v-if="attachments.length" class="composer-attachments">
41
+ <view
42
+ v-for="attachment in attachments"
43
+ :key="attachment.id || attachment.url || attachment.name"
44
+ class="composer-attachment"
45
+ @tap="emit('remove-attachment', attachment)"
46
+ >
47
+ {{ attachment.name }} ×
48
+ </view>
49
+ </view>
50
+ <textarea
51
+ class="composer-input"
52
+ auto-height
53
+ :maxlength="500"
54
+ :value="modelValue"
55
+ :placeholder="placeholder"
56
+ placeholder-class="composer-placeholder"
57
+ @input="handleInput"
58
+ />
59
+ <button
60
+ class="voice-button"
61
+ :class="{ active: recording }"
62
+ hover-class="none"
63
+ @tap="emit('record')"
64
+ >
65
+ <uni-icons type="mic" size="28" :color="recording ? '#00a96b' : '#111'" />
66
+ </button>
67
+ </view>
68
+ <button
69
+ class="send-button"
70
+ :class="{ 'is-disabled': disabled, 'is-sending': sending }"
71
+ :disabled="disabled"
72
+ hover-class="none"
73
+ @tap="emit('send')"
74
+ >
75
+ <uni-icons type="paperplane" size="28" :color="disabled ? '#111' : '#00a96b'" />
76
+ </button>
77
+ </view>
78
+ </view>
79
+ </template>
80
+
81
+ <style lang="scss" scoped>
82
+ .attach-button,
83
+ .voice-button,
84
+ .send-button {
85
+ padding: 0;
86
+ margin: 0;
87
+ border-radius: 0;
88
+ background: transparent;
89
+ line-height: normal;
90
+
91
+ &::after {
92
+ border: none;
93
+ }
94
+ }
95
+
96
+ .composer {
97
+ flex-shrink: 0;
98
+ width: 100%;
99
+ padding: 14rpx 24rpx 18rpx;
100
+ overflow: hidden;
101
+ background-color: transparent;
102
+ box-sizing: border-box;
103
+ }
104
+
105
+ .composer-main {
106
+ display: flex;
107
+ align-items: center;
108
+ gap: 16rpx;
109
+ min-height: 124rpx;
110
+ padding: 22rpx 18rpx;
111
+ border-radius: 24rpx;
112
+ background-color: #fff;
113
+ box-shadow: 0 12rpx 34rpx rgba(15, 23, 42, 0.08);
114
+ box-sizing: border-box;
115
+ }
116
+
117
+ .attach-button {
118
+ flex-shrink: 0;
119
+ display: flex;
120
+ align-items: center;
121
+ justify-content: center;
122
+ width: 72rpx;
123
+ height: 72rpx;
124
+ border: 2rpx solid #d6dae2;
125
+ border-radius: 50%;
126
+ background-color: #fff;
127
+ box-shadow: none;
128
+ box-sizing: border-box;
129
+ }
130
+
131
+ .composer-card {
132
+ flex: 1;
133
+ min-width: 0;
134
+ display: flex;
135
+ align-items: center;
136
+ flex-wrap: wrap;
137
+ gap: 10rpx;
138
+ min-height: 72rpx;
139
+ padding: 0 12rpx 0 28rpx;
140
+ border: 2rpx solid #d6dae2;
141
+ border-radius: 38rpx;
142
+ background-color: #fff;
143
+ box-shadow: none;
144
+ box-sizing: border-box;
145
+ }
146
+
147
+ .composer-attachments {
148
+ display: flex;
149
+ flex-wrap: wrap;
150
+ gap: 8rpx;
151
+ width: 100%;
152
+ padding-top: 4rpx;
153
+ }
154
+
155
+ .composer-attachment {
156
+ max-width: 240rpx;
157
+ overflow: hidden;
158
+ text-overflow: ellipsis;
159
+ white-space: nowrap;
160
+ padding: 6rpx 12rpx;
161
+ border-radius: 8rpx;
162
+ color: #16806d;
163
+ font-size: 20rpx;
164
+ line-height: 28rpx;
165
+ background-color: #e8f8f4;
166
+ }
167
+
168
+ .composer-input {
169
+ flex: 1;
170
+ min-width: 0;
171
+ min-height: 42rpx;
172
+ max-height: 126rpx;
173
+ padding: 14rpx 0;
174
+ box-sizing: border-box;
175
+ color: #333;
176
+ font-size: 29rpx;
177
+ line-height: 42rpx;
178
+ overflow-y: auto;
179
+ background-color: transparent;
180
+ }
181
+
182
+ .composer-placeholder {
183
+ color: #8d929c;
184
+ font-size: 29rpx;
185
+ }
186
+
187
+ .voice-button,
188
+ .send-button {
189
+ flex-shrink: 0;
190
+ display: flex;
191
+ align-items: center;
192
+ justify-content: center;
193
+ border-radius: 50%;
194
+ }
195
+
196
+ .voice-button {
197
+ width: 60rpx;
198
+ height: 60rpx;
199
+ background-color: transparent;
200
+ }
201
+
202
+ .voice-button.active {
203
+ background-color: #e8f8f4;
204
+ }
205
+
206
+ .send-button {
207
+ width: 74rpx;
208
+ height: 74rpx;
209
+ background-color: #f2f2f2;
210
+ }
211
+
212
+ .send-button.is-disabled {
213
+ background-color: #f2f2f2;
214
+ }
215
+
216
+ .send-button:not(.is-disabled) {
217
+ background-color: #e7f7f2;
218
+ }
219
+ </style>