af-mobile-client-vue3 1.3.13 → 1.3.14

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "af-mobile-client-vue3",
3
3
  "type": "module",
4
- "version": "1.3.13",
4
+ "version": "1.3.14",
5
5
  "packageManager": "pnpm@10.13.1",
6
6
  "description": "Vue + Vite component lib",
7
7
  "engines": {
@@ -1,9 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import type { RecordEntry } from './recordEntries'
3
3
  import type { BaseUser, ConfigItem } from './types'
4
- import useBoolean from '@af-mobile-client-vue3/hooks/useBoolean'
4
+ import useLoading from '@af-mobile-client-vue3/hooks/useLoading'
5
5
  import { mobileUtil } from '@af-mobile-client-vue3/utils/mobileUtil'
6
- import { Button as VanButton } from 'vant'
6
+ import { Button as VanButton, Empty as VanEmpty, Icon as VanIcon, Loading as VanLoading } from 'vant'
7
7
  import { computed, ref, watch } from 'vue'
8
8
  import { useRouter } from 'vue-router'
9
9
  import InfoDisplay from '../InfoDisplay/index.vue'
@@ -16,6 +16,8 @@ interface Props {
16
16
  recordEntries?: RecordEntry[] // 记录入口配置(可选)
17
17
  businessButtonText?: string // 业务办理按钮文本,默认"业务办理"
18
18
  showBottomButtons?: boolean // 是否显示底部按钮区域,默认false
19
+ showHeader?: boolean // 是否显示头部导航栏,默认false
20
+ headerTitle?: string // 头部标题,默认"用户档案详情"
19
21
  getUserDetailApi?: (id: string) => Promise<BaseUser> // 自定义获取用户详情接口
20
22
  getRecentTimeApi?: (id: string) => Promise<Record<string, string>> // 自定义获取历史时间接口
21
23
  }
@@ -31,6 +33,8 @@ const props = withDefaults(defineProps<Props>(), {
31
33
  showRecentTime: false,
32
34
  businessButtonText: '业务办理',
33
35
  showBottomButtons: false,
36
+ showHeader: false,
37
+ headerTitle: '用户档案详情',
34
38
  recordEntries: () => defaultRecordEntries,
35
39
  })
36
40
 
@@ -39,14 +43,15 @@ const router = useRouter()
39
43
 
40
44
  // 用户数据
41
45
  const user = ref<BaseUser | null>(null)
42
- const userLoading = useBoolean(true)
46
+ const { loading: userLoading, startLoading: startUserLoading, endLoading: endUserLoading } = useLoading(false)
47
+ const userError = ref<string | null>(null)
43
48
 
44
49
  // 控制用户信息展开收起
45
50
  const showUserInfo = ref(false)
46
51
 
47
52
  // 最近记录
48
53
  const recentRecord = ref<Record<string, string>>({})
49
- const { bool: recentRecordLoading, setTrue: openRecentRecordLoading, setFalse: closeRecentRecordLoading } = useBoolean(false)
54
+ const { loading: recentRecordLoading, startLoading: startRecentRecordLoading, endLoading: endRecentRecordLoading } = useLoading(false)
50
55
 
51
56
  // 状态类名
52
57
  const statusClass = computed(() => {
@@ -122,25 +127,33 @@ async function fetchUserDetail() {
122
127
  return
123
128
 
124
129
  try {
125
- userLoading.setTrue()
130
+ startUserLoading()
131
+ userError.value = null
126
132
  const api = props.getUserDetailApi || getCacheUserDetail
127
133
  user.value = await api(props.userInfoId)
128
134
  }
129
135
  catch (error) {
130
136
  console.error('获取用户详情失败:', error)
137
+ userError.value = error instanceof Error ? error.message : '获取用户详情失败'
138
+ user.value = null
131
139
  }
132
140
  finally {
133
- userLoading.setFalse()
141
+ endUserLoading()
134
142
  }
135
143
  }
136
144
 
145
+ // 重试获取用户详情
146
+ function retryFetchUserDetail() {
147
+ fetchUserDetail()
148
+ }
149
+
137
150
  // 获取最近记录
138
151
  async function fetchRecentRecord() {
139
152
  if (!props.showRecentTime || !props.userInfoId)
140
153
  return
141
154
 
142
155
  try {
143
- openRecentRecordLoading()
156
+ startRecentRecordLoading()
144
157
  const api = props.getRecentTimeApi || getRecentBusinessTime
145
158
  recentRecord.value = await api(props.userInfoId)
146
159
  }
@@ -149,7 +162,7 @@ async function fetchRecentRecord() {
149
162
  recentRecord.value = {}
150
163
  }
151
164
  finally {
152
- closeRecentRecordLoading()
165
+ endRecentRecordLoading()
153
166
  }
154
167
  }
155
168
 
@@ -214,8 +227,40 @@ watch(() => props.userInfoId, async (newId) => {
214
227
  </script>
215
228
 
216
229
  <template>
217
- <div v-if="user" class="user-detail">
218
- <div class="user-detail__content">
230
+ <div class="user-detail" :class="{ 'has-header': props.showHeader }">
231
+ <!-- 头部导航栏 -->
232
+ <div v-if="props.showHeader" class="user-detail__header">
233
+ <div class="header-left">
234
+ <button type="button" class="back-button" @click="emit('close')">
235
+ <VanIcon name="arrow-left" />
236
+ </button>
237
+ <h3 class="header-title">
238
+ {{ props.headerTitle }}
239
+ </h3>
240
+ </div>
241
+ </div>
242
+
243
+ <!-- 加载状态 -->
244
+ <div v-if="userLoading" class="loading-container">
245
+ <VanLoading size="24px" vertical>
246
+ 加载中...
247
+ </VanLoading>
248
+ </div>
249
+
250
+ <!-- 错误状态 -->
251
+ <div v-else-if="userError" class="error-container">
252
+ <VanEmpty
253
+ image="error"
254
+ :description="userError"
255
+ >
256
+ <VanButton type="primary" @click="retryFetchUserDetail">
257
+ 重试
258
+ </VanButton>
259
+ </VanEmpty>
260
+ </div>
261
+
262
+ <!-- 用户详情内容 -->
263
+ <div v-else-if="user" class="user-detail__content">
219
264
  <!-- 用户基本信息卡片 -->
220
265
  <div class="user-info-card">
221
266
  <div
@@ -306,7 +351,7 @@ watch(() => props.userInfoId, async (newId) => {
306
351
  .user-detail {
307
352
  position: relative;
308
353
  background-color: #f5f7fa;
309
-
354
+ min-height: 100vh;
310
355
  padding-bottom: 70px;
311
356
 
312
357
  &__header {
@@ -326,6 +371,42 @@ watch(() => props.userInfoId, async (newId) => {
326
371
  &__content {
327
372
  padding: 16px;
328
373
  }
374
+
375
+ // 当有 header 时,为 content 腾出空间
376
+ &.has-header &__content {
377
+ padding-top: calc(16px + 46px); // 16px原有padding + 56px header高度
378
+ }
379
+ }
380
+
381
+ // 加载状态
382
+ .loading-container {
383
+ display: flex;
384
+ justify-content: center;
385
+ align-items: center;
386
+ height: 300px;
387
+ background-color: #fff;
388
+ margin: 16px;
389
+ border-radius: 8px;
390
+ }
391
+
392
+ // 错误状态
393
+ .error-container {
394
+ display: flex;
395
+ justify-content: center;
396
+ align-items: center;
397
+ min-height: 300px;
398
+ background-color: #fff;
399
+ margin: 16px;
400
+ border-radius: 8px;
401
+ padding: 32px 16px;
402
+ }
403
+
404
+ // 当有 header 时,调整加载和错误状态的位置
405
+ .has-header {
406
+ .loading-container,
407
+ .error-container {
408
+ margin-top: calc(16px + 56px); // header高度 + 原有margin
409
+ }
329
410
  }
330
411
 
331
412
  .header-left {
@@ -439,7 +520,7 @@ watch(() => props.userInfoId, async (newId) => {
439
520
  }
440
521
 
441
522
  .user-info-body {
442
- padding: 0 16px 16px;
523
+ padding: 16px 16px;
443
524
  border-top: 1px solid #f0f0f0;
444
525
  }
445
526
 
@@ -0,0 +1,16 @@
1
+ import useBoolean from './useBoolean'
2
+
3
+ /**
4
+ * 加载状态管理 Hook
5
+ * @param initValue 初始加载状态,默认为 false
6
+ * @returns {Object} 包含 loading 状态和控制方法
7
+ */
8
+ export default function useLoading(initValue = false) {
9
+ const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(initValue)
10
+
11
+ return {
12
+ loading,
13
+ startLoading,
14
+ endLoading,
15
+ }
16
+ }
@@ -104,6 +104,16 @@ function switchUser() {
104
104
  <span class="prop-type">boolean</span>
105
105
  <span class="prop-desc">是否显示底部按钮区域</span>
106
106
  </div>
107
+ <div class="props-row">
108
+ <span class="prop-name">showHeader</span>
109
+ <span class="prop-type">boolean</span>
110
+ <span class="prop-desc">是否显示头部导航栏</span>
111
+ </div>
112
+ <div class="props-row">
113
+ <span class="prop-name">headerTitle</span>
114
+ <span class="prop-type">string</span>
115
+ <span class="prop-desc">头部标题文本</span>
116
+ </div>
107
117
  <div class="props-row">
108
118
  <span class="prop-name">#bottom</span>
109
119
  <span class="prop-type">slot</span>
@@ -1,8 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import XCellList from '@af-mobile-client-vue3/components/data/XCellList/index.vue'
3
+ import XExpandDetail from '@af-mobile-client-vue3/views/userRecords/operateRecordDetail/index.vue'
3
4
  import { computed, ref } from 'vue'
4
5
  import { useRoute } from 'vue-router'
5
- import XExpandDetail from '@/views/userRecords/operateRecordDetail/index.vue'
6
6
  // 简易crud表单测试——变更记录
7
7
  const configName = 'mobile_operateRecordCRUD'
8
8
  const serviceName = 'af-revenue'