@liujitcn/kratos-uni-app-system 0.0.78 → 0.0.80

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.
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { defAuthService } from '@liujitcn/kratos-uni-app-core/api/system/app/v1/auth'
3
- import { useUserStore } from '@liujitcn/kratos-uni-app-core/stores'
3
+ import { useThemeStore, useUserStore } from '@liujitcn/kratos-uni-app-core/stores'
4
4
  import type { UserProfileForm } from '@liujitcn/kratos-uni-app-core/rpc/system/app/v1/auth'
5
5
  import { onLoad } from '@dcloudio/uni-app'
6
6
  import { computed, onBeforeUnmount, ref } from 'vue'
@@ -11,14 +11,14 @@ import { formatSrc } from '@liujitcn/kratos-uni-app-core/utils/index'
11
11
  import { uploadFile } from '@liujitcn/kratos-uni-app-core/utils/file'
12
12
  import { navigateToLogin } from '@liujitcn/kratos-uni-app-core/utils/navigation'
13
13
  import defaultAvatar from '@liujitcn/kratos-uni-app-core/static/images/avatar.png'
14
- import navigatorBackground from '@liujitcn/kratos-uni-app-core/static/images/navigator_bg.png'
15
14
  import { useI18n } from '@liujitcn/kratos-uni-app-core'
16
15
 
17
16
  const userStore = useUserStore()
17
+ const themeStore = useThemeStore()
18
18
  const { t } = useI18n()
19
19
 
20
- // 获取屏幕边界到安全区域距离
21
- const { safeAreaInsets } = uni.getSystemInfoSync()
20
+ /** 返回上一页。 */
21
+ const onBack = () => uni.navigateBack()
22
22
 
23
23
  const imgMaxSize = ref(1024 * 1024)
24
24
  const submitting = ref(false)
@@ -248,254 +248,195 @@ const onSubmit = async () => {
248
248
  </script>
249
249
 
250
250
  <template>
251
- <view class="viewport" :style="{ backgroundImage: `url(${navigatorBackground})` }">
252
- <!-- 导航栏 -->
253
- <view class="navbar" :style="{ paddingTop: safeAreaInsets?.top + 'px' }">
254
- <navigator open-type="navigateBack" class="back icon-left" hover-class="none"></navigator>
255
- <view class="title">{{ t('system.profile.title') }}</view>
256
- </view>
257
- <view class="avatar">
258
- <view @tap="onAvatarChange" class="avatar-content">
259
- <image
260
- v-if="userInfo?.avatar"
261
- class="image"
262
- :src="formatSrc(userInfo?.avatar)"
263
- mode="aspectFill"
264
- />
265
- <image v-else class="image" :src="defaultAvatar" mode="aspectFill"></image>
266
- <text class="text">{{ t('system.profile.avatar_change') }}</text>
251
+ <view class="profile-viewport" :class="themeStore.themeClass">
252
+ <view class="profile-content">
253
+ <!-- 导航栏:统一子页头(单一返回 + 标题) -->
254
+ <view class="k-page-nav">
255
+ <view class="k-page-nav__back" :aria-label="t('common.action.back')" @tap="onBack"></view>
256
+ <view class="k-page-nav__title">{{ t('system.profile.title') }}</view>
257
+ <view class="k-page-nav__side" aria-hidden="true"></view>
267
258
  </view>
268
- </view>
269
- <!-- 表单 -->
270
- <view class="form">
271
- <!-- 表单内容 -->
272
- <view class="form-content">
273
- <view class="form-item" v-if="userInfo?.user_name">
274
- <text class="label">{{ t('system.profile.account') }}</text>
275
- <text class="account placeholder">{{ userInfo?.user_name }}</text>
259
+
260
+ <!-- 头像卡:点击更换头像 -->
261
+ <view class="k-glass k-glass--lit k-pfx profile-id" @tap="onAvatarChange">
262
+ <view class="k-cnr k-cnr--tl" aria-hidden="true"></view>
263
+ <view class="k-cnr k-cnr--br" aria-hidden="true"></view>
264
+ <view class="profile-id__main">
265
+ <view class="k-pfx__av">
266
+ <image
267
+ class="profile-av-img"
268
+ :src="userInfo?.avatar ? formatSrc(userInfo?.avatar) : defaultAvatar"
269
+ mode="aspectFill"
270
+ />
271
+ </view>
272
+ <view class="profile-id__meta">
273
+ <view class="k-pfx__nm">{{ userInfo?.nick_name || t('system.profile.title') }}</view>
274
+ <view class="k-pfx__ac">{{ userInfo?.user_name }}</view>
275
+ </view>
276
+ </view>
277
+ <view class="k-pfx__ent">{{ t('system.profile.avatar_change') }}</view>
278
+ </view>
279
+
280
+ <!-- 表单 -->
281
+ <view class="k-glass profile-form">
282
+ <view class="k-phd">
283
+ <view class="k-phd__title">{{ t('system.profile.title') }}</view>
284
+ </view>
285
+
286
+ <view class="k-field" v-if="userInfo?.user_name">
287
+ <text class="k-field__k">{{ t('system.profile.account') }}</text>
288
+ <text class="k-field__v">{{ userInfo?.user_name }}</text>
276
289
  </view>
277
290
  <!-- #ifdef MP-WEIXIN -->
278
291
  <!-- 手机号 -->
279
- <view class="form-item">
280
- <text class="label">{{ t('system.profile.mobile') }}</text>
281
- <view class="input">
282
- <text v-if="userInfo.phone" class="account">{{ userInfo.phone }}</text>
283
- <button
284
- v-else
285
- class="auth-button"
286
- open-type="getPhoneNumber"
287
- @getphonenumber="onGetPhoneNumber"
288
- >
289
- {{ t('system.profile.phone_authorization') }}
290
- </button>
291
- </view>
292
+ <view class="k-field">
293
+ <text class="k-field__k">{{ t('system.profile.mobile') }}</text>
294
+ <text v-if="userInfo.phone" class="k-field__v">{{ userInfo.phone }}</text>
295
+ <button
296
+ v-else
297
+ class="profile-auth-button"
298
+ open-type="getPhoneNumber"
299
+ @getphonenumber="onGetPhoneNumber"
300
+ >
301
+ {{ t('system.profile.phone_authorization') }}
302
+ </button>
292
303
  </view>
293
304
  <!-- #endif -->
294
- <view class="form-item">
295
- <text class="label">{{ t('system.profile.nick_name') }}</text>
305
+ <view class="k-field">
306
+ <text class="k-field__k">{{ t('system.profile.nick_name') }}</text>
296
307
  <input
297
- class="input"
308
+ class="k-field__v profile-input"
298
309
  type="text"
299
310
  :placeholder="t('system.profile.nick_name_placeholder')"
300
311
  v-model="userInfo.nick_name"
301
312
  />
302
313
  </view>
303
- <view class="form-item">
304
- <text class="label">{{ t('system.profile.email') }}</text>
314
+ <view class="k-field">
315
+ <text class="k-field__k">{{ t('system.profile.email') }}</text>
305
316
  <input
306
- class="input"
317
+ class="k-field__v profile-input"
307
318
  type="text"
308
319
  :placeholder="t('system.profile.email_placeholder')"
309
320
  v-model="userInfo.email"
310
321
  />
311
322
  </view>
312
- <view class="form-item">
313
- <text class="label">{{ t('system.profile.id_type') }}</text>
314
- <picker
315
- class="picker"
316
- mode="selector"
317
- :range="idTypeOptions"
318
- range-key="label"
319
- :value="idTypeIndex"
320
- @change="onIdTypeChange"
321
- >
322
- <view class="input">{{ idTypeLabel }}</view>
323
- </picker>
324
- </view>
325
- <view class="form-item">
326
- <text class="label">{{ t('system.profile.id_code') }}</text>
323
+ <picker
324
+ mode="selector"
325
+ :range="idTypeOptions"
326
+ range-key="label"
327
+ :value="idTypeIndex"
328
+ @change="onIdTypeChange"
329
+ >
330
+ <view class="k-field profile-field--sub">
331
+ <text class="k-field__k">{{ t('system.profile.id_type') }}</text>
332
+ <view class="k-field__v">{{ idTypeLabel }}</view>
333
+ </view>
334
+ </picker>
335
+ <view class="k-field">
336
+ <text class="k-field__k">{{ t('system.profile.id_code') }}</text>
327
337
  <input
328
- class="input"
338
+ class="k-field__v profile-input"
329
339
  type="text"
330
340
  :placeholder="t('system.profile.id_code_placeholder')"
331
341
  v-model="userInfo.id_code"
332
342
  />
333
343
  </view>
334
- <view class="form-item">
335
- <text class="label">{{ t('system.profile.gender') }}</text>
336
- <radio-group @change="onGenderChange">
337
- <label class="radio" v-for="(item, index) in genderList" :key="index">
344
+ <view class="k-field">
345
+ <text class="k-field__k">{{ t('system.profile.gender') }}</text>
346
+ <radio-group class="profile-radios" @change="onGenderChange">
347
+ <label class="profile-radio" v-for="(item, index) in genderList" :key="index">
338
348
  <radio
339
349
  :value="item.value"
340
- color="#27ba9b"
350
+ :color="themeStore.theme === 'light' ? '#039e8c' : '#28f0d4'"
341
351
  :checked="userInfo?.gender === Number(item.value)"
342
352
  />
343
353
  {{ item.label }}
344
354
  </label>
345
355
  </radio-group>
346
356
  </view>
357
+
358
+ <!-- 提交按钮 -->
359
+ <button @tap="onSubmit" class="k-save">{{ t('common.action.save') }}</button>
347
360
  </view>
348
- <!-- 提交按钮 -->
349
- <button @tap="onSubmit" class="form-button">{{ t('common.action.save') }}</button>
350
361
  </view>
351
362
  </view>
352
363
  </template>
353
364
 
354
365
  <style lang="scss">
355
- page {
356
- background-color: #f4f4f4;
357
- }
358
-
359
- .viewport {
360
- display: flex;
361
- flex-direction: column;
362
- height: 100%;
366
+ .profile-viewport {
363
367
  min-height: 100vh;
364
368
  box-sizing: border-box;
365
- background-color: #f4f4f4;
366
- background-size: auto 420rpx;
367
- background-repeat: no-repeat;
369
+ background: var(--kratos-bg-gradient);
368
370
  }
369
371
 
370
- // 导航栏
371
- .navbar {
372
- position: relative;
373
-
374
- .title {
375
- height: 40px;
376
- display: flex;
377
- justify-content: center;
378
- align-items: center;
379
- font-size: 16px;
380
- font-weight: 500;
381
- color: #fff;
382
- }
372
+ /* k-page-nav 自带安全区顶部内边距,这里只补水平与底部 */
373
+ .profile-content {
374
+ padding: 0 32rpx calc(40rpx + env(safe-area-inset-bottom));
375
+ }
383
376
 
384
- .back {
385
- position: absolute;
386
- height: 40px;
387
- width: 40px;
388
- left: 0;
389
- font-size: 20px;
390
- color: #fff;
391
- display: flex;
392
- justify-content: center;
393
- align-items: center;
394
-
395
- &::before {
396
- content: '‹';
397
- }
398
- }
377
+ /* 头像卡 */
378
+ .profile-id {
379
+ margin-top: 8rpx;
380
+ padding: 36rpx 30rpx;
399
381
  }
400
382
 
401
- // 头像
402
- .avatar {
403
- text-align: center;
404
- width: 100%;
405
- height: 260rpx;
383
+ .profile-id__main {
406
384
  display: flex;
407
- flex-direction: column;
408
- justify-content: center;
409
385
  align-items: center;
386
+ gap: 24rpx;
387
+ min-width: 0;
388
+ }
410
389
 
411
- .image {
412
- width: 160rpx;
413
- height: 160rpx;
414
- border-radius: 50%;
415
- background-color: #eee;
416
- }
390
+ .profile-id__meta {
391
+ min-width: 0;
392
+ }
417
393
 
418
- .text {
419
- display: block;
420
- padding-top: 20rpx;
421
- line-height: 1;
422
- font-size: 26rpx;
423
- color: #fff;
424
- }
394
+ .profile-av-img {
395
+ width: 100%;
396
+ height: 100%;
397
+ border-radius: 22rpx;
425
398
  }
426
399
 
427
- // 表单
428
- .form {
429
- background-color: #f4f4f4;
400
+ /* 表单卡 */
401
+ .profile-form {
402
+ margin-top: 24rpx;
403
+ padding: 10rpx 28rpx 28rpx;
404
+ }
430
405
 
431
- &-content {
432
- margin: 20rpx 20rpx 0;
433
- padding: 0 20rpx;
434
- border-radius: 10rpx;
435
- background-color: #fff;
436
- }
406
+ /* 子项字段包在 picker 中会命中 :first-child,恢复分隔线 */
407
+ .profile-field--sub {
408
+ border-top: 1rpx solid var(--kratos-color-border);
409
+ }
437
410
 
438
- &-item {
439
- display: flex;
440
- height: 96rpx;
441
- line-height: 46rpx;
442
- padding: 25rpx 10rpx;
443
- background-color: #fff;
444
- font-size: 28rpx;
445
- border-bottom: 1rpx solid #ddd;
446
-
447
- &:last-child {
448
- border: none;
449
- }
450
-
451
- .label {
452
- width: 180rpx;
453
- color: #333;
454
- }
455
-
456
- .account {
457
- color: #666;
458
- }
459
-
460
- .input {
461
- flex: 1;
462
- display: block;
463
- height: 46rpx;
464
- }
465
-
466
- .radio {
467
- margin-right: 20rpx;
468
- }
469
-
470
- .picker {
471
- flex: 1;
472
- }
473
- .placeholder {
474
- color: #808080;
475
- }
476
- }
411
+ .profile-input {
412
+ flex: 1;
413
+ height: 46rpx;
414
+ }
477
415
 
478
- &-button {
479
- height: 80rpx;
480
- text-align: center;
481
- line-height: 80rpx;
482
- margin: 30rpx 20rpx;
483
- color: #fff;
484
- border-radius: 80rpx;
485
- font-size: 30rpx;
486
- background-color: #27ba9b;
487
- }
416
+ .profile-radios {
417
+ display: flex;
418
+ flex-wrap: wrap;
419
+ justify-content: flex-end;
420
+ gap: 24rpx;
488
421
  }
489
- .auth-button {
422
+
423
+ .profile-radio {
424
+ display: flex;
425
+ align-items: center;
426
+ color: var(--kratos-color-text);
427
+ font-size: 25rpx;
428
+ }
429
+
430
+ .profile-auth-button {
490
431
  height: 60rpx;
491
- line-height: 60rpx;
492
432
  margin: 0;
493
- padding: 0 20rpx;
494
- font-size: 26rpx;
495
- color: #27ba9b;
496
- border: 1rpx solid #27ba9b;
497
- border-radius: 30rpx;
498
- background: none;
433
+ padding: 0 24rpx;
434
+ color: var(--kratos-color-primary);
435
+ font-size: 24rpx;
436
+ line-height: 58rpx;
437
+ border: 1rpx solid var(--kratos-color-border-accent);
438
+ border-radius: 999rpx;
439
+ background: var(--kratos-color-primary-soft);
499
440
 
500
441
  &::after {
501
442
  border: none;