af-mobile-client-vue3 1.4.46 → 1.4.48

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.
@@ -1283,6 +1283,7 @@ function scanCodeOrNfc(attr) {
1283
1283
  :attr="attr"
1284
1284
  :mode="props.mode"
1285
1285
  :readonly="readonly"
1286
+ :service-name="serviceName"
1286
1287
  :is-async-upload="isAsyncUpload"
1287
1288
  @update-file-list="updateFile"
1288
1289
  />
@@ -188,4 +188,4 @@ export interface PolygonLayerConfig {
188
188
  onClick?: (polygon: PolygonData, event: any) => void
189
189
  /** 多边形数据提供者 */
190
190
  dataProvider?: () => PolygonData[] | Promise<PolygonData[]>
191
- }
191
+ }
package/src/main.ts CHANGED
@@ -4,15 +4,17 @@ import Plugins from '@af-mobile-client-vue3/plugins'
4
4
  import router from '@af-mobile-client-vue3/router'
5
5
  import pinia from '@af-mobile-client-vue3/stores'
6
6
  import { i18n } from '@af-mobile-client-vue3/utils/i18n'
7
+ // MateChat ai 对话相关
8
+ import MateChat from '@matechat/core'
7
9
  import { createHead } from '@unhead/vue/client'
8
10
  import { createApp } from 'vue'
9
11
  import 'virtual:uno.css'
10
12
  import '@af-mobile-client-vue3/styles/app.less'
13
+
11
14
  import '@af-mobile-client-vue3/styles/var.less'
12
15
 
13
16
  // Vant 桌面端适配
14
17
  import '@vant/touch-emulator'
15
-
16
18
  /* --------------------------------
17
19
  Vant 中有个别组件是以函数的形式提供的,
18
20
  包括 Toast,Dialog,Notify 和 ImagePreview 组件。
@@ -22,7 +24,6 @@ Vant 中有个别组件是以函数的形式提供的,
22
24
  import 'vant/es/toast/style'
23
25
  import 'vant/es/dialog/style'
24
26
  import 'vant/es/notify/style'
25
- import 'vant/es/image-preview/style'
26
27
 
27
28
  (async () => {
28
29
  const app = createApp(App)
@@ -33,6 +34,7 @@ import 'vant/es/image-preview/style'
33
34
  app.use(pinia)
34
35
  app.use(i18n)
35
36
  app.use(Plugins)
37
+ app.use(MateChat)
36
38
 
37
39
  await bootstrap(router)
38
40
  app.mount('#system-app')
@@ -10,6 +10,7 @@ import NotFound from '@af-mobile-client-vue3/views/common/NotFound.vue'
10
10
  import EvaluateRecordView from '@af-mobile-client-vue3/views/component/EvaluateRecordView/index.vue'
11
11
  import IconifyView from '@af-mobile-client-vue3/views/component/IconifyView/index.vue'
12
12
  import ComponentView from '@af-mobile-client-vue3/views/component/index.vue'
13
+ import MateChatView from '@af-mobile-client-vue3/views/component/MateChat/MateChatView.vue'
13
14
  import OtherChargeForm from '@af-mobile-client-vue3/views/component/OtherCharge/index.vue'
14
15
  import UserDetailView from '@af-mobile-client-vue3/views/component/UserDetailView/index.vue'
15
16
  import UserDetailPage from '@af-mobile-client-vue3/views/component/UserDetailView/UserDetailPage.vue'
@@ -211,6 +212,11 @@ const routes: Array<RouteRecordRaw> = [
211
212
  component: UserDetailPage,
212
213
  meta: { title: '用户详情' },
213
214
  },
215
+ {
216
+ path: '/Component/MateChatView',
217
+ name: 'MateChatView',
218
+ component: MateChatView,
219
+ },
214
220
  ],
215
221
  },
216
222
 
@@ -5,16 +5,16 @@ import wx from 'weixin-js-sdk'
5
5
  export interface WechatConfig {
6
6
  debug: boolean
7
7
  appId: string
8
- timestamp: string
8
+ timestamp: number
9
9
  nonceStr: string
10
10
  signature: string
11
- jsApiList: string[]
11
+ jsApiList: wx.ApiMethod[]
12
12
  }
13
13
 
14
14
  // 微信扫码配置
15
15
  export interface WechatScanConfig {
16
16
  needResult: 0 | 1
17
- scanType: string[]
17
+ scanType: wx.scanType[]
18
18
  }
19
19
 
20
20
  // 微信扫码结果
@@ -92,7 +92,7 @@ export function scanQRCode(config: WechatScanConfig): Promise<WechatScanResult>
92
92
  // 获取微信签名(模拟)
93
93
  export async function getWechatSignature(url: string): Promise<{
94
94
  appId: string
95
- timestamp: string
95
+ timestamp: number
96
96
  nonceStr: string
97
97
  signature: string
98
98
  }> {
@@ -0,0 +1,256 @@
1
+ <script setup lang="ts">
2
+ import liuliLogo from '@af-mobile-client-vue3/assets/img/component/liuli.png'
3
+ import { showToast } from 'vant'
4
+ import { ref } from 'vue'
5
+ import { Button } from 'vue-devui/button'
6
+ import { chatCompletions } from './apiService'
7
+ import 'vue-devui/button/style.css'
8
+ import 'vant/es/image-preview/style'
9
+ import '@devui-design/icons/icomoon/devui-icon.css'
10
+
11
+ const description = [
12
+ '我是【奥枫天然气公司】官方公众号专属 AI 客服小璃,可以为您提供专业、高效、易懂的天然气相关咨询服务。',
13
+ ]
14
+ const introPrompt = {
15
+ direction: 'horizontal',
16
+ list: [
17
+ {
18
+ value: 'howToPay',
19
+ label: '如何缴纳燃气费?',
20
+ iconConfig: { name: 'icon-info-o', color: '#5e7ce0' },
21
+ desc: '了解线上缴费方式和操作步骤',
22
+ },
23
+ {
24
+ value: 'howToRepair',
25
+ label: '燃气故障如何报修?',
26
+ iconConfig: { name: 'icon-star', color: 'rgb(255, 215, 0)' },
27
+ desc: '遇到燃气问题时的处理流程',
28
+ },
29
+ {
30
+ value: 'safetyTips',
31
+ label: '安全使用注意事项',
32
+ iconConfig: { name: 'icon-priority', color: '#3ac295' },
33
+ desc: '了解燃气安全使用常识',
34
+ },
35
+ ],
36
+ }
37
+ const simplePrompt = [
38
+ {
39
+ value: 'howToPay',
40
+ iconConfig: { name: 'icon-info-o', color: '#5e7ce0' },
41
+ label: '如何缴费',
42
+ },
43
+ {
44
+ value: 'howToRepair',
45
+ iconConfig: { name: 'icon-star', color: 'rgb(255, 215, 0)' },
46
+ label: '如何报修',
47
+ },
48
+ ]
49
+ const startPage = ref(true)
50
+ const inputValue = ref('')
51
+
52
+ const messages = ref<any[]>([])
53
+
54
+ function newConversation() {
55
+ startPage.value = true
56
+ messages.value = []
57
+ }
58
+
59
+ async function onSubmit(evt: string) {
60
+ if (!evt.trim()) {
61
+ return
62
+ }
63
+
64
+ inputValue.value = ''
65
+ startPage.value = false
66
+
67
+ // 用户发送消息
68
+ messages.value.push({
69
+ from: 'user',
70
+ content: evt,
71
+ })
72
+
73
+ // 添加 loading 状态的 model 消息
74
+ const loadingMessageIndex = messages.value.length
75
+ messages.value.push({
76
+ from: 'model',
77
+ content: '',
78
+ loading: true,
79
+ })
80
+
81
+ try {
82
+ // 调用 API
83
+ const response = await chatCompletions(evt)
84
+
85
+ // 更新 model 消息内容
86
+ if (response.choices && response.choices.length > 0) {
87
+ const content = response.choices[0].message.content
88
+
89
+ // 检测是否为转人工标识
90
+ try {
91
+ const parsedContent = JSON.parse(content)
92
+ if (parsedContent.msgType === 'transfer') {
93
+ // 移除 loading 消息
94
+ messages.value.splice(loadingMessageIndex, 1)
95
+ // 添加人工客服消息
96
+ messages.value.push({
97
+ from: 'service',
98
+ content: '您好,客服xxx工号xxx为你服务',
99
+ })
100
+ }
101
+ else {
102
+ // 正常消息
103
+ messages.value[loadingMessageIndex] = {
104
+ from: 'model',
105
+ content,
106
+ loading: false,
107
+ }
108
+ }
109
+ }
110
+ catch {
111
+ // 如果不是 JSON,按正常消息处理
112
+ messages.value[loadingMessageIndex] = {
113
+ from: 'model',
114
+ content,
115
+ loading: false,
116
+ }
117
+ }
118
+ }
119
+ else {
120
+ throw new Error('响应数据格式错误')
121
+ }
122
+ }
123
+ catch (error: any) {
124
+ // 处理错误
125
+ console.error('聊天请求失败:', error)
126
+ messages.value[loadingMessageIndex] = {
127
+ from: 'model',
128
+ content: '抱歉,服务暂时不可用,请稍后再试。',
129
+ loading: false,
130
+ }
131
+ showToast(error?.message || '请求失败,请稍后再试')
132
+ }
133
+ }
134
+ </script>
135
+
136
+ <template>
137
+ <McLayout class="container">
138
+ <McLayoutContent
139
+ v-if="startPage"
140
+ style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px"
141
+ >
142
+ <McIntroduction
143
+ :logo-img="liuliLogo"
144
+ title="小璃"
145
+ sub-title="Hi,我是小璃"
146
+ :description="description"
147
+ />
148
+ <McPrompt
149
+ :list="introPrompt.list"
150
+ :direction="introPrompt.direction"
151
+ @item-click="onSubmit($event.label)"
152
+ />
153
+ </McLayoutContent>
154
+ <McLayoutContent v-else class="content-container">
155
+ <template v-for="(msg, idx) in messages" :key="idx">
156
+ <McBubble
157
+ v-if="msg.from === 'user'"
158
+ :content="msg.content"
159
+ align="right"
160
+ :avatar-config="{ imgSrc: 'https://matechat.gitcode.com/png/demo/userAvatar.svg' }"
161
+ />
162
+ <McBubble
163
+ v-else-if="msg.from === 'service'"
164
+ :content="msg.content"
165
+ :avatar-config="{ imgSrc: 'https://matechat.gitcode.com/png/demo/userAvatar.svg' }"
166
+ />
167
+ <McBubble v-else :content="msg.content" :avatar-config="{ imgSrc: liuliLogo }" :loading="msg.loading">
168
+ <McMarkdownCard
169
+ :content="msg.content" :typing="true"
170
+ />
171
+ </McBubble>
172
+ </template>
173
+ </McLayoutContent>
174
+ <div class="shortcut" style="display: flex; align-items: center; gap: 8px">
175
+ <McPrompt
176
+ v-if="!startPage"
177
+ :list="simplePrompt"
178
+ direction="horizontal"
179
+ class="shortcut-prompt"
180
+ style="flex: 1"
181
+ @item-click="onSubmit($event.label)"
182
+ />
183
+ <Button
184
+ style="margin-left: auto"
185
+ icon="add"
186
+ shape="circle"
187
+ title="新建对话"
188
+ size="md"
189
+ @click="newConversation"
190
+ />
191
+ </div>
192
+ <McLayoutSender>
193
+ <McInput :value="inputValue" :autofocus="true" :max-length="2000" @change="(e) => (inputValue = e)" @submit="onSubmit" />
194
+ </McLayoutSender>
195
+ </McLayout>
196
+ </template>
197
+
198
+ <style>
199
+ .container {
200
+ width: 100%;
201
+ height: 100vh;
202
+ padding: 20px;
203
+ gap: 8px;
204
+ background: #fff;
205
+ }
206
+
207
+ .content-container {
208
+ display: flex;
209
+ flex-direction: column;
210
+ gap: 8px;
211
+ overflow: auto;
212
+ }
213
+
214
+ .input-foot-wrapper {
215
+ display: flex;
216
+ justify-content: space-between;
217
+ align-items: center;
218
+ width: 100%;
219
+ height: 100%;
220
+ margin-right: 8px;
221
+
222
+ .input-foot-left {
223
+ display: flex;
224
+ align-items: center;
225
+ gap: 8px;
226
+
227
+ span {
228
+ font-size: 14px;
229
+ line-height: 18px;
230
+ color: #252b3a;
231
+ cursor: pointer;
232
+ }
233
+
234
+ .input-foot-dividing-line {
235
+ width: 1px;
236
+ height: 14px;
237
+ background-color: #d7d8da;
238
+ }
239
+
240
+ .input-foot-maxlength {
241
+ font-size: 14px;
242
+ color: #71757f;
243
+ }
244
+ }
245
+
246
+ .input-foot-right {
247
+ .demo-button-content {
248
+ font-size: 14px;
249
+ }
250
+
251
+ & > *:not(:first-child) {
252
+ margin-left: 8px;
253
+ }
254
+ }
255
+ }
256
+ </style>
@@ -0,0 +1,104 @@
1
+ import type { AxiosInstance } from 'axios'
2
+ import axios from 'axios'
3
+
4
+ /**
5
+ * 聊天消息接口
6
+ */
7
+ export interface ChatMessage {
8
+ role: 'user' | 'assistant' | 'system'
9
+ content: string
10
+ }
11
+
12
+ /**
13
+ * 聊天请求参数接口
14
+ */
15
+ export interface ChatCompletionsRequest {
16
+ chatId: string
17
+ stream: boolean
18
+ detail: boolean
19
+ variables: {
20
+ uid: string
21
+ name: string
22
+ }
23
+ messages: ChatMessage[]
24
+ }
25
+
26
+ /**
27
+ * 聊天响应使用情况接口
28
+ */
29
+ export interface ChatUsage {
30
+ prompt_tokens: number
31
+ completion_tokens: number
32
+ total_tokens: number
33
+ }
34
+
35
+ /**
36
+ * 聊天响应选择项接口
37
+ */
38
+ export interface ChatChoice {
39
+ message: {
40
+ role: 'assistant'
41
+ content: string
42
+ }
43
+ finish_reason: string
44
+ index: number
45
+ }
46
+
47
+ /**
48
+ * 聊天响应接口
49
+ */
50
+ export interface ChatCompletionsResponse {
51
+ id: string
52
+ model: string
53
+ usage: ChatUsage
54
+ choices: ChatChoice[]
55
+ }
56
+
57
+ /**
58
+ * 创建独立的 axios 实例,不经过拦截器
59
+ */
60
+ const chatAxiosInstance: AxiosInstance = axios.create({
61
+ baseURL: import.meta.env.VITE_APP_API_BASE_URL || '',
62
+ timeout: 20000,
63
+ headers: {
64
+ 'Content-Type': 'application/json',
65
+ },
66
+ })
67
+
68
+ /**
69
+ * 发送聊天请求
70
+ * @param content 用户输入的内容
71
+ * @returns Promise<ChatCompletionsResponse>
72
+ */
73
+ /**
74
+ * 发送聊天请求
75
+ * @param content 用户输入的内容
76
+ * @returns Promise<ChatCompletionsResponse>
77
+ */
78
+ export function chatCompletions(content: string): Promise<ChatCompletionsResponse> {
79
+ const requestData: ChatCompletionsRequest = {
80
+ chatId: 'chatId13333113',
81
+ stream: false,
82
+ detail: false,
83
+ variables: {
84
+ uid: 'asdfadsfasfd2323',
85
+ name: '张三',
86
+ },
87
+ messages: [
88
+ {
89
+ role: 'user',
90
+ content,
91
+ },
92
+ ],
93
+ }
94
+
95
+ return chatAxiosInstance.post<ChatCompletionsResponse>(
96
+ '/v1/chat/completions',
97
+ requestData,
98
+ {
99
+ headers: {
100
+ Authorization: 'Bearer fastgpt-cmS6DpSmnrAWfH23iz3J4Dz9witLSKVmNgaRbh5fcVWpWG4dDJYgmGlW',
101
+ },
102
+ },
103
+ ).then(response => response.data)
104
+ }
@@ -13,7 +13,83 @@ const router = useRouter()
13
13
 
14
14
  // 简易crud表单测试
15
15
  const configName = ref('ceshiCRUD')
16
- const serviceName = ref('af-linepatrol')
16
+ const serviceName = ref('af-safecheck')
17
+
18
+ // 资源权限测试
19
+ // const configName = ref('crud_sources_test')
20
+ // const serviceName = ref('af-system')
21
+
22
+ // 实际业务测试
23
+ // const configName = ref('lngChargeAuditMobileCRUD')
24
+ // const serviceName = ref('af-gaslink')
25
+
26
+ // 跳转到详情页面
27
+ // function toDetail(item) {
28
+ // router.push({
29
+ // name: 'XCellDetailView',
30
+ // params: { id: item[idKey.value] }, // 如果使用命名路由,推荐使用路由参数而不是直接构建 URL
31
+ // query: {
32
+ // operName: item[operNameKey.value],
33
+ // method:item[methodKey.value],
34
+ // requestMethod:item[requestMethodKey.value],
35
+ // operatorType:item[operatorTypeKey.value],
36
+ // operUrl:item[operUrlKey.value],
37
+ // operIp:item[operIpKey.value],
38
+ // costTime:item[costTimeKey.value],
39
+ // operTime:item[operTimeKey.value],
40
+ //
41
+ // title: item[titleKey.value],
42
+ // businessType: item[businessTypeKey.value],
43
+ // status:item[statusKey.value]
44
+ // }
45
+ // })
46
+ // }
47
+
48
+ // 跳转到表单——以表单组来渲染纯表单
49
+ // function toDetail(item) {
50
+ // router.push({
51
+ // name: 'XFormGroupView',
52
+ // query: {
53
+ // id: item[idKey.value],
54
+ // // id: item.rr_id,
55
+ // // o_id: item.o_id,
56
+ // },
57
+ // })
58
+ // }
59
+
60
+ // 新增功能
61
+ // function addOption(totalCount) {
62
+ // router.push({
63
+ // name: 'XFormView',
64
+ // params: { id: totalCount, openid: totalCount },
65
+ // query: {
66
+ // configName: configName.value,
67
+ // serviceName: serviceName.value,
68
+ // mode: '新增',
69
+ // },
70
+ // })
71
+ // }
72
+
73
+ // 修改功能
74
+ // function updateRow(result) {
75
+ // router.push({
76
+ // name: 'XFormView',
77
+ // params: { id: result.o_id, openid: result.o_id },
78
+ // query: {
79
+ // configName: configName.value,
80
+ // serviceName: serviceName.value,
81
+ // mode: '修改',
82
+ // },
83
+ // })
84
+ // }
85
+
86
+ // 删除功能
87
+ // function deleteRow(result) {
88
+ // emit('deleteRow', result.o_id)
89
+ // }
90
+ // const fixQueryForm = ref({
91
+ // f_operator_id: '487184754014158848',
92
+ // })
17
93
  </script>
18
94
 
19
95
  <template>
@@ -21,7 +97,7 @@ const serviceName = ref('af-linepatrol')
21
97
  <template #layout_content>
22
98
  <XCellList
23
99
  :config-name="configName"
24
- :show-filter="false"
100
+ :service-name="serviceName"
25
101
  />
26
102
  </template>
27
103
  </NormalDataLayout>
@@ -1,26 +1,41 @@
1
1
  <script setup lang="ts">
2
2
  import XForm from '@af-mobile-client-vue3/components/data/XForm/index.vue'
3
+ import NormalDataLayout from '@af-mobile-client-vue3/components/layout/NormalDataLayout/index.vue'
3
4
  import { ref } from 'vue'
4
5
 
5
- const configName = ref('HiddenDangersPhotoForm')
6
- const serviceName = ref('af-wechat')
6
+ const configName = ref('ceshiForm')
7
+ const serviceName = ref('af-system')
7
8
 
8
- const formHiddenDangersPhoto = ref(null)
9
- function submit(result: any) {
10
- console.log('>>>> 提交: ', JSON.stringify(result))
9
+ const formGroupAddConstruction = ref(null)
10
+ const formData = ref({
11
+ 'YYYY': '2025-09-08 16:03:22',
12
+ 'YYYY-MM': '2025-09-08 16:03:22',
13
+ 'YYYY-MM-DD': '2025-09-08 16:03:22',
14
+ 'YYYY-MM-DDHH': '2025-09-08 16:03:22',
15
+ 'YYYY-MM-DDHHMM': '2025-09-08 16:03:22',
16
+ 'YYYY-MM-DDHHMMSS': '2025-09-08 16:03:22',
17
+ })
18
+
19
+ function onSubmit(data: any) {
20
+ console.warn('提交表单', data)
11
21
  }
12
22
  </script>
13
23
 
14
24
  <template>
15
- <XForm
16
- ref="formHiddenDangersPhoto"
17
- mode="新增"
18
- :config-name="configName"
19
- :service-name="serviceName"
20
- label-align="top"
21
- @on-submit="submit"
22
- />
25
+ <NormalDataLayout id="XFormGroupView" title="纯表单">
26
+ <template #layout_content>
27
+ <XForm
28
+ ref="formGroupAddConstruction"
29
+ mode="修改"
30
+ :config-name="configName"
31
+ :service-name="serviceName"
32
+ :form-data="formData"
33
+ @on-submit="onSubmit"
34
+ />
35
+ </template>
36
+ </NormalDataLayout>
23
37
  </template>
24
38
 
25
39
  <style scoped lang="less">
40
+
26
41
  </style>