af-mobile-client-vue3 1.4.53 → 1.4.55

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.
Files changed (58) hide show
  1. package/build/vite/optimize.ts +36 -36
  2. package/package.json +1 -1
  3. package/public/favicon.svg +4 -4
  4. package/scripts/verifyCommit.js +19 -19
  5. package/src/App.vue +14 -2
  6. package/src/components/common/MateChat/apiService.ts +285 -0
  7. package/src/components/common/MateChat/assets/035-avatar-13.svg +1 -0
  8. package/src/components/common/MateChat/components/MateChatContent.vue +281 -0
  9. package/src/components/common/MateChat/components/MateChatHeader.vue +298 -0
  10. package/src/components/common/MateChat/components/PasswordDialog.vue +97 -0
  11. package/src/components/common/MateChat/components/PromptList/PromptList.vue +189 -0
  12. package/src/components/common/MateChat/components/PromptList/index.ts +1 -0
  13. package/src/components/common/MateChat/composables/useChatHistoryCache.ts +117 -0
  14. package/src/components/common/MateChat/composables/useChatMessagesCache.ts +72 -0
  15. package/src/components/common/MateChat/composables/useMateChat.ts +372 -0
  16. package/src/components/common/MateChat/composables/usePasswordManager.ts +38 -0
  17. package/src/components/common/MateChat/index.vue +429 -0
  18. package/src/components/common/MateChat/types.ts +236 -0
  19. package/src/components/common/otherCharge/ChargePrintSelectorAndRemarks.vue +137 -137
  20. package/src/components/common/otherCharge/CodePayment.vue +357 -357
  21. package/src/components/common/otherCharge/FileUploader.vue +602 -602
  22. package/src/components/common/otherCharge/GridFileUploader.vue +846 -846
  23. package/src/components/common/otherCharge/PaymentMethodSelector.vue +202 -202
  24. package/src/components/common/otherCharge/PaymentMethodSelectorCard.vue +45 -45
  25. package/src/components/common/otherCharge/ReceiptModal.vue +273 -273
  26. package/src/components/common/otherCharge/index.ts +43 -43
  27. package/src/components/data/OtherCharge/OtherChargeItemModal.vue +547 -547
  28. package/src/components/data/UserDetail/types.ts +1 -1
  29. package/src/components/data/XReportGrid/XAddReport/index.ts +1 -1
  30. package/src/components/data/XReportGrid/XReportDrawer/index.ts +1 -1
  31. package/src/components/data/XTag/index.vue +10 -10
  32. package/src/components/layout/TabBarLayout/index.vue +40 -40
  33. package/src/hooks/useCommon.ts +9 -9
  34. package/src/plugins/AppData.ts +38 -38
  35. package/src/router/invoiceRoutes.ts +33 -33
  36. package/src/services/api/common.ts +109 -109
  37. package/src/services/api/manage.ts +8 -8
  38. package/src/services/api/search.ts +16 -16
  39. package/src/services/restTools.ts +56 -56
  40. package/src/utils/authority-utils.ts +84 -84
  41. package/src/utils/crypto.ts +39 -39
  42. package/src/utils/queryFormDefaultRangePicker.ts +57 -57
  43. package/src/utils/runEvalFunction.ts +13 -13
  44. package/src/views/component/EvaluateRecordView/index.vue +40 -40
  45. package/src/views/component/MateChat/MateChatView.vue +10 -254
  46. package/src/views/component/XCellDetailView/index.vue +217 -217
  47. package/src/views/component/XCellListView/index.vue +107 -138
  48. package/src/views/component/XFormGroupView/index.vue +78 -82
  49. package/src/views/component/XFormView/index.vue +41 -46
  50. package/src/views/component/XReportFormIframeView/index.vue +47 -47
  51. package/src/views/component/XReportFormView/index.vue +13 -13
  52. package/src/views/component/XSignatureView/index.vue +50 -50
  53. package/src/views/component/notice.vue +46 -46
  54. package/src/views/component/topNav.vue +36 -36
  55. package/src/views/invoiceShow/index.vue +61 -61
  56. package/src/views/user/login/index.vue +22 -22
  57. package/vite.config.ts +2 -1
  58. package/src/views/component/MateChat/apiService.ts +0 -104
@@ -0,0 +1,281 @@
1
+ <script setup lang="ts">
2
+ import type { MateChatConfig } from '@af-mobile-client-vue3/components/common/MateChat/types'
3
+ import liuliLogo from '@af-mobile-client-vue3/assets/img/component/liuli.png'
4
+ import userAvatarImg from '@af-mobile-client-vue3/components/common/MateChat/assets/035-avatar-13.svg'
5
+ import MateChatHeader from '@af-mobile-client-vue3/components/common/MateChat/components/MateChatHeader.vue'
6
+ import { PromptList } from '@af-mobile-client-vue3/components/common/MateChat/components/PromptList'
7
+ import { useMateChat } from '@af-mobile-client-vue3/components/common/MateChat/composables/useMateChat'
8
+ import { useDebounceFn } from '@vueuse/core'
9
+ import { Button as VanButton } from 'vant'
10
+ import { computed, nextTick, ref, watch } from 'vue'
11
+ import 'vant/es/image-preview/style'
12
+
13
+ interface MateChatContentProps {
14
+ /**
15
+ * 完整的配置对象
16
+ */
17
+ config: MateChatConfig
18
+ }
19
+
20
+ const props = defineProps<MateChatContentProps>()
21
+
22
+ // 从配置中获取的值
23
+ const description = computed(() => props.config.description || [])
24
+ const introPrompt = computed(() => props.config.introPrompt || [])
25
+ const simplePrompt = computed(() => props.config.simplePrompt || [])
26
+ const serviceName = computed(() => props.config.serviceName || '智能客服')
27
+ const useStream = computed(() => props.config.useStream || false)
28
+
29
+ // 初始化 useMateChat
30
+ const mateChatInstance = useMateChat(props.config)
31
+
32
+ const startPage = computed(() => mateChatInstance.startPage.value)
33
+ const inputValue = computed({
34
+ get: () => mateChatInstance.inputValue.value,
35
+ set: (val: string) => {
36
+ mateChatInstance.inputValue.value = val
37
+ },
38
+ })
39
+ const messages = computed(() => mateChatInstance.messages.value)
40
+ const newConversation = () => mateChatInstance.newConversation()
41
+ const onSubmit = (evt: string) => mateChatInstance.onSubmit(evt)
42
+
43
+ // 消息容器的 ref
44
+ const messageContainerRef = ref<HTMLElement | null>(null)
45
+ // 滚动锚点的 ref
46
+ const scrollAnchorRef = ref<HTMLElement | null>(null)
47
+
48
+ /**
49
+ * 滚动到底部
50
+ */
51
+ function scrollToBottom() {
52
+ nextTick(() => {
53
+ // 优先使用滚动锚点元素滚动到底部(最可靠)
54
+ if (scrollAnchorRef.value) {
55
+ scrollAnchorRef.value.scrollIntoView({ behavior: 'smooth', block: 'end' })
56
+ return
57
+ }
58
+
59
+ // 使用 ref 获取容器并滚动
60
+ const container = messageContainerRef.value
61
+ if (container instanceof HTMLElement) {
62
+ container.scrollTo({
63
+ top: container.scrollHeight,
64
+ behavior: 'smooth',
65
+ })
66
+ }
67
+ })
68
+ }
69
+
70
+ // 使用防抖处理滚动函数,延迟 100ms
71
+ const debouncedScrollToBottom = useDebounceFn(scrollToBottom, 100)
72
+
73
+ // 计算最后一条消息的长度
74
+ const lastMessageLength = computed(() => {
75
+ const msgList = messages.value
76
+ if (msgList.length === 0) {
77
+ return 0
78
+ }
79
+ const lastMsg = msgList[msgList.length - 1]
80
+ return lastMsg?.content?.length || 0
81
+ })
82
+
83
+ // 监听消息数量和最后一条消息的长度变化,自动滚动到底部
84
+ // 使用 immediate: false 避免初始化时滚动
85
+ watch(
86
+ [() => messages.value.length, lastMessageLength],
87
+ () => {
88
+ debouncedScrollToBottom()
89
+ },
90
+ { flush: 'post' },
91
+ )
92
+
93
+ /**
94
+ * 处理历史会话选择
95
+ */
96
+ function handleSelectSession(session: { chatId: string, title: string, lastTime: string }) {
97
+ mateChatInstance.loadHistoryMessages(session.chatId)
98
+ // 加载历史消息后也需要滚动到底部
99
+ // 使用防抖版本,避免频繁滚动
100
+ debouncedScrollToBottom()
101
+ }
102
+ </script>
103
+
104
+ <template>
105
+ <McLayout class="chat-card">
106
+ <MateChatHeader
107
+ :show-title="!startPage"
108
+ :logo-image="liuliLogo"
109
+ :title="serviceName"
110
+ :app-id="props.config.appId"
111
+ :app-key="props.config.appKey"
112
+ @select-session="handleSelectSession"
113
+ />
114
+ <McLayoutContent
115
+ v-if="startPage"
116
+ style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px"
117
+ >
118
+ <McIntroduction
119
+ v-if="description && description.length"
120
+ :logo-img="liuliLogo"
121
+ :title="serviceName"
122
+ :sub-title="`Hi,我是${serviceName}`"
123
+ :description="description"
124
+ class="McIntroduction"
125
+ />
126
+ <PromptList
127
+ v-if="introPrompt && introPrompt.length"
128
+ :list="introPrompt"
129
+ @item-click="onSubmit($event.label)"
130
+ />
131
+ </McLayoutContent>
132
+ <McLayoutContent v-else ref="messageContainerRef" class="content-container">
133
+ <template v-for="(msg, idx) in messages" :key="idx">
134
+ <McBubble
135
+ v-if="msg.from === 'user'"
136
+ :content="msg.content"
137
+ align="right"
138
+ :avatar-config="{ imgSrc: userAvatarImg }"
139
+ />
140
+ <McBubble
141
+ v-else-if="msg.from === 'service'"
142
+ :content="msg.content"
143
+ :avatar-config="{ imgSrc: userAvatarImg }"
144
+ />
145
+ <McBubble v-else :content="msg.content" :avatar-config="{ imgSrc: liuliLogo }" :loading="msg.loading">
146
+ <McMarkdownCard
147
+ :content="msg.content"
148
+ :typing="!useStream"
149
+ />
150
+ </McBubble>
151
+ </template>
152
+ <!-- 滚动锚点 -->
153
+ <div ref="scrollAnchorRef" class="scroll-anchor" />
154
+ </McLayoutContent>
155
+ <div class="shortcut" style="display: flex; align-items: center; gap: 8px">
156
+ <PromptList
157
+ v-if="!startPage && simplePrompt && simplePrompt.length"
158
+ :list="simplePrompt"
159
+ direction="horizontal"
160
+ class="shortcut-prompt"
161
+ style="flex: 1"
162
+ @item-click="onSubmit($event.label)"
163
+ />
164
+ <VanButton
165
+ style="margin-left: auto"
166
+ icon="add"
167
+ title="新建对话"
168
+ size="small"
169
+ round
170
+ @click="newConversation"
171
+ />
172
+ </div>
173
+ <McLayoutSender>
174
+ <McInput
175
+ :value="inputValue"
176
+ placeholder="请输入您的问题,我会为您解答"
177
+ :max-length="2000"
178
+ @change="(e) => (inputValue = e)"
179
+ @submit="onSubmit"
180
+ />
181
+ </McLayoutSender>
182
+ </McLayout>
183
+ </template>
184
+
185
+ <style scoped lang="less">
186
+ .chat-card {
187
+ width: 100%;
188
+ max-width: 1200px;
189
+ height: calc(100vh - 40px);
190
+ background: #ffffff;
191
+ border-radius: 24px;
192
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
193
+ overflow: hidden;
194
+ display: flex;
195
+ flex-direction: column;
196
+ transition: all 0.3s ease;
197
+ padding: 20px;
198
+ gap: 8px;
199
+ }
200
+
201
+ .chat-card:hover {
202
+ box-shadow: 0 25px 70px rgba(0, 0, 0, 0.2);
203
+ }
204
+
205
+ /* 移动端适配 */
206
+ @media (max-width: 768px) {
207
+ .chat-card {
208
+ height: calc(100vh - 16px);
209
+ border-radius: 16px;
210
+ padding: 8px;
211
+ }
212
+ }
213
+
214
+ .content-container {
215
+ display: flex;
216
+ flex-direction: column;
217
+ gap: 8px;
218
+ overflow: auto;
219
+ }
220
+
221
+ .scroll-anchor {
222
+ height: 1px;
223
+ width: 100%;
224
+ flex-shrink: 0;
225
+ }
226
+
227
+ .input-foot-wrapper {
228
+ display: flex;
229
+ justify-content: space-between;
230
+ align-items: center;
231
+ width: 100%;
232
+ height: 100%;
233
+ margin-right: 8px;
234
+
235
+ .input-foot-left {
236
+ display: flex;
237
+ align-items: center;
238
+ gap: 8px;
239
+
240
+ span {
241
+ font-size: 14px;
242
+ line-height: 18px;
243
+ color: #252b3a;
244
+ cursor: pointer;
245
+ }
246
+
247
+ .input-foot-dividing-line {
248
+ width: 1px;
249
+ height: 14px;
250
+ background-color: #d7d8da;
251
+ }
252
+
253
+ .input-foot-maxlength {
254
+ font-size: 14px;
255
+ color: #71757f;
256
+ }
257
+ }
258
+
259
+ .input-foot-right {
260
+ .demo-button-content {
261
+ font-size: 14px;
262
+ }
263
+
264
+ & > *:not(:first-child) {
265
+ margin-left: 8px;
266
+ }
267
+ }
268
+ }
269
+
270
+ :deep(.mc-header-logo-container img) {
271
+ width: 32px;
272
+ height: 32px;
273
+ }
274
+
275
+ .McIntroduction {
276
+ :deep(.mc-introduction-logo-container img) {
277
+ width: 64px;
278
+ height: 64px;
279
+ }
280
+ }
281
+ </style>
@@ -0,0 +1,298 @@
1
+ <script setup lang="ts">
2
+ import type { ChatHistoryItem } from '@af-mobile-client-vue3/components/common/MateChat/types'
3
+ import { getHistories } from '@af-mobile-client-vue3/components/common/MateChat/apiService'
4
+ import { useChatHistoryCache } from '@af-mobile-client-vue3/components/common/MateChat/composables/useChatHistoryCache'
5
+ import { Empty, Icon, Loading, Popup } from 'vant'
6
+ import { ref } from 'vue'
7
+ import 'vant/es/popup/style'
8
+ import 'vant/es/empty/style'
9
+ import 'vant/es/loading/style'
10
+ import 'vant/es/icon/style'
11
+
12
+ interface Props {
13
+ /**
14
+ * 头部左侧 Logo 图片
15
+ */
16
+ logoImage: string
17
+ /**
18
+ * 头部标题文案
19
+ */
20
+ title: string
21
+ /**
22
+ * 历史对话弹层标题
23
+ */
24
+ historyTitle?: string
25
+ /**
26
+ * FastGPT 应用 ID
27
+ */
28
+ appId: string
29
+ /**
30
+ * FastGPT API Key
31
+ */
32
+ appKey: string
33
+ /**
34
+ * 是否显示标题
35
+ */
36
+ showTitle: boolean
37
+ }
38
+
39
+ interface SessionItem {
40
+ chatId: string
41
+ title: string
42
+ lastTime: string
43
+ }
44
+
45
+ interface Emits {
46
+ /**
47
+ * 选择某条历史会话
48
+ */
49
+ (e: 'selectSession', session: SessionItem): void
50
+ }
51
+
52
+ const props = withDefaults(defineProps<Props>(), {
53
+ historyTitle: '对话历史',
54
+ })
55
+
56
+ const emit = defineEmits<Emits>()
57
+
58
+ const showHistory = ref(false)
59
+ const isLoading = ref(false)
60
+ const sessionList = ref<SessionItem[]>([])
61
+
62
+ // 使用历史会话缓存
63
+ const { getCachedHistory, setCachedHistory } = useChatHistoryCache()
64
+
65
+ function handleOpenHistory() {
66
+ showHistory.value = true
67
+ fetchSessions()
68
+ }
69
+
70
+ function handleSelectSession(session: SessionItem) {
71
+ emit('selectSession', session)
72
+ showHistory.value = false
73
+ }
74
+
75
+ /**
76
+ * 格式化时间字符串为日期时间字符串
77
+ */
78
+ function formatDateTime(timeString: string): string {
79
+ try {
80
+ const date = new Date(timeString)
81
+ const year = date.getFullYear()
82
+ const month = String(date.getMonth() + 1).padStart(2, '0')
83
+ const day = String(date.getDate()).padStart(2, '0')
84
+ const hours = String(date.getHours()).padStart(2, '0')
85
+ const minutes = String(date.getMinutes()).padStart(2, '0')
86
+ return `${year}-${month}-${day} ${hours}:${minutes}`
87
+ }
88
+ catch {
89
+ return timeString
90
+ }
91
+ }
92
+
93
+ /**
94
+ * 将接口返回的历史会话数据转换为组件需要的格式
95
+ */
96
+ function transformHistoryItem(item: ChatHistoryItem): SessionItem {
97
+ return {
98
+ chatId: item.chatId || '',
99
+ title: item.title || item.customTitle || '未命名会话',
100
+ lastTime: item.updateTime ? formatDateTime(item.updateTime) : '',
101
+ }
102
+ }
103
+
104
+ async function fetchSessions() {
105
+ // 先检查缓存
106
+ const cachedList = getCachedHistory(props.appId, props.appKey)
107
+ if (cachedList) {
108
+ // 使用缓存数据
109
+ sessionList.value = cachedList.map(transformHistoryItem)
110
+ return
111
+ }
112
+
113
+ // 缓存不存在或已过期,从服务器获取
114
+ isLoading.value = true
115
+ try {
116
+ const response = await getHistories(props.appId, props.appKey, 0, 5)
117
+
118
+ // 转换数据格式
119
+ if (response.data?.list && Array.isArray(response.data.list)) {
120
+ // 缓存数据
121
+ setCachedHistory(props.appId, props.appKey, response.data.list)
122
+ sessionList.value = response.data.list.map(transformHistoryItem)
123
+ }
124
+ else {
125
+ sessionList.value = []
126
+ // 缓存空数组,避免重复请求
127
+ setCachedHistory(props.appId, props.appKey, [])
128
+ }
129
+ }
130
+ catch (error) {
131
+ console.error('获取历史会话失败:', error)
132
+ sessionList.value = []
133
+ }
134
+ finally {
135
+ isLoading.value = false
136
+ }
137
+ }
138
+ </script>
139
+
140
+ <template>
141
+ <div class="matechat-header-wrapper">
142
+ <McHeader :logo-img="showTitle ? props.logoImage : ''" :title="showTitle ? props.title : ''" :logo-clickable="false">
143
+ <template #operationArea>
144
+ <div class="matechat-header-history-btn" @click="handleOpenHistory">
145
+ <Icon name="clock-o" size="16" />
146
+ </div>
147
+ </template>
148
+ </McHeader>
149
+
150
+ <Popup
151
+ v-model:show="showHistory"
152
+ position="center"
153
+ round
154
+ :overlay="true"
155
+ class="matechat-history-popup"
156
+ >
157
+ <div class="matechat-history-card">
158
+ <div class="matechat-history-card__header">
159
+ <span class="matechat-history-card__title">
160
+ {{ props.historyTitle }}
161
+ </span>
162
+ </div>
163
+ <div class="matechat-history-card__content">
164
+ <div v-if="isLoading" class="matechat-history-card__loading">
165
+ <Loading size="24px" />
166
+ </div>
167
+ <template v-else>
168
+ <div
169
+ v-if="sessionList.length"
170
+ class="matechat-history-card__list"
171
+ >
172
+ <div
173
+ v-for="session in sessionList"
174
+ :key="session.chatId"
175
+ class="matechat-history-card__item"
176
+ @click="handleSelectSession(session)"
177
+ >
178
+ <div class="matechat-history-card__item-title">
179
+ {{ session.title }}
180
+ </div>
181
+ <div class="matechat-history-card__item-time">
182
+ {{ session.lastTime }}
183
+ </div>
184
+ </div>
185
+ </div>
186
+ <div v-else class="matechat-history-card__empty">
187
+ <Empty description="无数据" />
188
+ </div>
189
+ </template>
190
+ </div>
191
+ </div>
192
+ </Popup>
193
+ </div>
194
+ </template>
195
+
196
+ <style scoped lang="less">
197
+ .matechat-header-wrapper {
198
+ position: relative;
199
+ }
200
+
201
+ .matechat-header-history-btn {
202
+ display: inline-flex;
203
+ align-items: center;
204
+ justify-content: center;
205
+ width: 32px;
206
+ height: 32px;
207
+ border-radius: 50%;
208
+ background-color: rgba(255, 255, 255, 0.8);
209
+ cursor: pointer;
210
+ transition: all 0.2s ease;
211
+
212
+ &:hover {
213
+ background-color: rgba(255, 255, 255, 1);
214
+ transform: translateY(-1px);
215
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
216
+ }
217
+ }
218
+
219
+ .matechat-history-popup {
220
+ .matechat-history-card {
221
+ width: 320px;
222
+ max-width: 80vw;
223
+ background-color: #ffffff;
224
+ border-radius: 16px;
225
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.15);
226
+ padding: 16px 16px 12px;
227
+ box-sizing: border-box;
228
+
229
+ &__header {
230
+ display: flex;
231
+ align-items: center;
232
+ justify-content: space-between;
233
+ margin-bottom: 12px;
234
+ }
235
+
236
+ &__title {
237
+ font-size: 16px;
238
+ font-weight: 600;
239
+ color: #252b3a;
240
+ }
241
+
242
+ &__content {
243
+ min-height: 120px;
244
+ max-height: 260px;
245
+ overflow: auto;
246
+ }
247
+
248
+ &__loading {
249
+ display: flex;
250
+ align-items: center;
251
+ justify-content: center;
252
+ padding: 24px 0;
253
+ }
254
+
255
+ &__list {
256
+ display: flex;
257
+ flex-direction: column;
258
+ gap: 8px;
259
+ }
260
+
261
+ &__item {
262
+ padding: 10px 8px;
263
+ border-radius: 8px;
264
+ cursor: pointer;
265
+ transition: all 0.2s ease;
266
+
267
+ &:hover {
268
+ background-color: #f5f6f9;
269
+ }
270
+ }
271
+
272
+ &__item-title {
273
+ font-size: 14px;
274
+ color: #252b3a;
275
+ margin-bottom: 2px;
276
+ }
277
+
278
+ &__item-time {
279
+ font-size: 12px;
280
+ color: #a0a4af;
281
+ }
282
+
283
+ &__empty {
284
+ padding: 16px 0 8px;
285
+
286
+ :deep(.van-empty__image) {
287
+ width: 80px;
288
+ height: 80px;
289
+ }
290
+
291
+ :deep(.van-empty__description) {
292
+ font-size: 13px;
293
+ color: #a0a4af;
294
+ }
295
+ }
296
+ }
297
+ }
298
+ </style>
@@ -0,0 +1,97 @@
1
+ <script setup lang="ts">
2
+ import { Dialog as VanDialog, Field as VanField, Icon as VanIcon } from 'vant'
3
+ import { ref, watch } from 'vue'
4
+
5
+ interface PasswordDialogProps {
6
+ visible: boolean
7
+ }
8
+
9
+ const props = defineProps<PasswordDialogProps>()
10
+
11
+ const emit = defineEmits<{
12
+ (event: 'confirm', password: string): void
13
+ (event: 'cancel'): void
14
+ }>()
15
+
16
+ const password = ref('')
17
+ const showPassword = ref(false)
18
+ const errorMessage = ref('')
19
+
20
+ watch(() => props.visible, (newVal) => {
21
+ if (newVal) {
22
+ password.value = ''
23
+ errorMessage.value = ''
24
+ showPassword.value = false
25
+ }
26
+ })
27
+
28
+ function handleConfirm() {
29
+ if (!password.value.trim()) {
30
+ errorMessage.value = '请输入密码'
31
+ return
32
+ }
33
+ emit('confirm', password.value.trim())
34
+ }
35
+
36
+ function handleCancel() {
37
+ emit('cancel')
38
+ }
39
+
40
+ function togglePasswordVisibility() {
41
+ showPassword.value = !showPassword.value
42
+ }
43
+
44
+ /**
45
+ * 清空密码输入框和错误信息
46
+ */
47
+ function clearPassword() {
48
+ password.value = ''
49
+ errorMessage.value = ''
50
+ showPassword.value = false
51
+ }
52
+
53
+ /**
54
+ * 设置错误信息
55
+ */
56
+ function setError(message: string) {
57
+ errorMessage.value = message
58
+ }
59
+
60
+ // 暴露方法供父组件调用
61
+ defineExpose({
62
+ clearPassword,
63
+ setError,
64
+ })
65
+ </script>
66
+
67
+ <template>
68
+ <VanDialog
69
+ :show="visible"
70
+ title="请输入密码"
71
+ show-cancel-button
72
+ confirm-button-text="确定"
73
+ cancel-button-text="取消"
74
+ @confirm="handleConfirm"
75
+ @cancel="handleCancel"
76
+ @update:show="(val: boolean) => emit('cancel')"
77
+ >
78
+ <div style="padding: 16px 0">
79
+ <VanField
80
+ v-model="password"
81
+ :type="showPassword ? 'text' : 'password'"
82
+ placeholder="请输入密码"
83
+ :error="!!errorMessage"
84
+ :error-message="errorMessage"
85
+ @update:model-value="errorMessage = ''"
86
+ @keyup.enter="handleConfirm"
87
+ >
88
+ <template #right-icon>
89
+ <VanIcon
90
+ :name="showPassword ? 'eye-o' : 'closed-eye'"
91
+ @click="togglePasswordVisibility"
92
+ />
93
+ </template>
94
+ </VanField>
95
+ </div>
96
+ </VanDialog>
97
+ </template>