vue2-client 1.9.72 → 1.9.74

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,6 +1,6 @@
1
1
  {
2
2
  "name": "vue2-client",
3
- "version": "1.9.72",
3
+ "version": "1.9.74",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint",
@@ -0,0 +1,159 @@
1
+ <template>
2
+ <div>
3
+ <button @click="startRecording" :disabled="isRecording">开始录音</button>
4
+ <button @click="stopRecording" :disabled="!isRecording">停止录音</button>
5
+ <!-- <audio v-if="audioUrl" :src="audioUrl" controls></audio>-->
6
+ </div>
7
+ </template>
8
+
9
+ <script>
10
+ import { getConfigByName } from '@vue2-client/services/api/common'
11
+ export default {
12
+ name: 'PersonSetting',
13
+ data () {
14
+ return {
15
+ isRecording: false, // 标识是否正在录音
16
+ mediaRecorder: null, // MediaRecorder 实例
17
+ audioChunks: [], // 存储录音数据块
18
+ audioUrl: null, // 录音生成的 URL
19
+ inputData: '',
20
+ config: {}
21
+ }
22
+ },
23
+ props: {
24
+ },
25
+ watch: {},
26
+ components: {},
27
+ created () {
28
+ getConfigByName('V4RecodingConfig', undefined, (x) => {
29
+ Object.assign(this.config, x)
30
+ })
31
+ },
32
+ methods: {
33
+ async startRecording () {
34
+ this.inputData = ''
35
+ const url = this.config.webSocketURL // WebSocket地址
36
+ this.setupWebSocket(url)
37
+
38
+ // 等待 WebSocket 连接成功
39
+ await this.waitForWebSocketConnection()
40
+
41
+ this.isRecording = true
42
+
43
+ // 初始化 AudioContext
44
+ this.audioContext = new (window.AudioContext || window.webkitAudioContext)()
45
+
46
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
47
+ throw new Error(
48
+ '录音软件不支持您的浏览器!请使用现代浏览器或确保您正在使用 HTTPS。'
49
+ )
50
+ }
51
+
52
+ // 获取麦克风输入流
53
+ this.audioStream = await navigator.mediaDevices.getUserMedia({ audio: true })
54
+
55
+ // 设置音频处理流程
56
+ this.setupAudioProcessing()
57
+ },
58
+ setupWebSocket (url) {
59
+ this.ws = new WebSocket(url)
60
+
61
+ this.ws.onmessage = (event) => {
62
+ const data = JSON.parse(event.data)
63
+ console.log('Result:', data.result)
64
+ if (data.result !== undefined) {
65
+ this.inputData += data.result
66
+ }
67
+ }
68
+
69
+ this.ws.onclose = () => console.log('WebSocket closed.')
70
+ this.ws.onerror = (error) => console.error('WebSocket error:', error)
71
+ },
72
+
73
+ waitForWebSocketConnection () {
74
+ return new Promise((resolve, reject) => {
75
+ this.ws.onopen = () => {
76
+ console.log('WebSocket 连接成功')
77
+ resolve()
78
+ }
79
+
80
+ this.ws.onerror = (error) => {
81
+ reject(new Error('WebSocket 连接失败: ' + error.message))
82
+ }
83
+ })
84
+ },
85
+
86
+ setupAudioProcessing () {
87
+ try {
88
+ const source = this.audioContext.createMediaStreamSource(this.audioStream)
89
+ const processor = this.audioContext.createScriptProcessor(4096, 1, 1)
90
+
91
+ processor.onaudioprocess = (e) => {
92
+ if (!this.isRecording) return
93
+
94
+ const inputData = e.inputBuffer.getChannelData(0)
95
+
96
+ // Resample to 16000 Hz if needed
97
+ const resampledData = this.resample(inputData, this.audioContext.sampleRate, 16000)
98
+
99
+ // Convert to 16-bit PCM and send via WebSocket
100
+ const byteArray = this.convertTo16BitPCM(resampledData)
101
+ this.ws.send(byteArray)
102
+ }
103
+
104
+ source.connect(processor)
105
+ processor.connect(this.audioContext.destination)
106
+ } catch (error) {
107
+ console.error('Error setting up audio processing:', error)
108
+ throw error
109
+ }
110
+ },
111
+ stopRecording () {
112
+ this.isRecording = false
113
+ if (this.ws) {
114
+ // 可以添加一个事件处理器来处理WebSocket关闭
115
+ this.ws.onclose = () => {
116
+ console.log('WebSocket closed.')
117
+ // 这里可以添加WebSocket关闭后的清理工作
118
+ }
119
+ // 关闭WebSocket连接
120
+ this.ws.close()
121
+ }
122
+ if (this.audioStream) {
123
+ this.audioStream.getTracks().forEach(track => track.stop())
124
+ }
125
+ if (this.audioContext) {
126
+ this.audioContext.close()
127
+ }
128
+ },
129
+ resample (data, inputSampleRate, outputSampleRate) {
130
+ if (inputSampleRate === outputSampleRate) {
131
+ return data
132
+ }
133
+ const sampleRateRatio = inputSampleRate / outputSampleRate
134
+ const newLength = Math.round(data.length / sampleRateRatio)
135
+ const resampledData = new Float32Array(newLength)
136
+ for (let i = 0; i < newLength; i++) {
137
+ const index = Math.round(i * sampleRateRatio)
138
+ resampledData[i] = data[index]
139
+ }
140
+ return resampledData
141
+ },
142
+ convertTo16BitPCM (data) {
143
+ const output = new Int16Array(data.length)
144
+ for (let i = 0; i < data.length; i++) {
145
+ output[i] = Math.max(-1, Math.min(1, data[i])) * 0x7FFF
146
+ }
147
+ return new Uint8Array(output.buffer)
148
+ },
149
+ getRecordingData () {
150
+ return this.inputData
151
+ }
152
+ }
153
+ }
154
+
155
+ </script>
156
+
157
+ <style lang="less" scoped>
158
+
159
+ </style>
@@ -0,0 +1,3 @@
1
+ import Recoding from './Recoding.vue'
2
+
3
+ export default Recoding
@@ -0,0 +1,140 @@
1
+ <template>
2
+ <div>
3
+ <div class="chat-container" v-if="loading">
4
+ <!-- 对话展示区域 -->
5
+ <div class="chat-content">
6
+ <div
7
+ v-for="(message, index) in messages"
8
+ :key="index"
9
+ :class="['chat-message', message.type]"
10
+ >
11
+ <span class="chat-avatar">{{ message.type === 'user' ? '👤' : '🤖' }}</span>
12
+ <div class="chat-text">{{ message.text }}</div>
13
+ </div>
14
+ </div>
15
+
16
+ <!-- 输入框和发送按钮 -->
17
+ <div class="chat-input">
18
+ <a-input
19
+ v-model="inputMessage"
20
+ placeholder="Type your message..."
21
+ @pressEnter="sendMessage"
22
+ />
23
+ <a-button type="primary" @click="sendMessage">Send</a-button>
24
+ </div>
25
+ </div>
26
+ <a-spin :spinning="!loading" />
27
+ </div>
28
+ </template>
29
+
30
+ <script>
31
+ import { runLogic } from '@vue2-client/services/api/common'
32
+ export default {
33
+ data () {
34
+ return {
35
+ serviceName: undefined,
36
+ // 组件加载状态
37
+ loading: false,
38
+ // 配置内容
39
+ renderConfig: undefined,
40
+ inputMessage: '', // 用户输入的内容
41
+ additionalInfo: {},
42
+ messages: [ // 消息列表
43
+ { type: 'bot', text: 'Hello! How can I assist you today?' },
44
+ ],
45
+ }
46
+ },
47
+ methods: {
48
+ setAddtionalInfo (params) {
49
+ this.additionalInfo = params
50
+ },
51
+ init (params) {
52
+ const {
53
+ serviceName,
54
+ // 配置内容
55
+ value
56
+ } = params
57
+ this.renderConfig = value
58
+ this.loading = true
59
+ this.serviceName = serviceName
60
+ },
61
+ async sendMessage () {
62
+ if (!this.inputMessage.trim()) return
63
+
64
+ // 添加用户消息
65
+ this.messages.push({ type: 'user', text: this.inputMessage })
66
+
67
+ // 清空输入框
68
+ const userMessage = this.inputMessage
69
+ this.inputMessage = ''
70
+
71
+ // 模拟机器人的回复
72
+ const response = await runLogic(this.renderConfig.logicName, {
73
+ question: userMessage,
74
+ additionalInfo: this.additionalInfo
75
+ }, this.serviceName)
76
+ this.messages.push({ type: 'bot', text: response })
77
+ },
78
+ },
79
+ }
80
+ </script>
81
+
82
+ <style scoped>
83
+ .chat-container {
84
+ display: flex;
85
+ flex-direction: column;
86
+ height: 500px;
87
+ border: 1px solid #d9d9d9;
88
+ border-radius: 4px;
89
+ overflow: hidden;
90
+ }
91
+
92
+ .chat-content {
93
+ flex: 1;
94
+ padding: 16px;
95
+ overflow-y: auto;
96
+ background: #f5f5f5;
97
+ }
98
+
99
+ .chat-message {
100
+ display: flex;
101
+ align-items: flex-start;
102
+ margin-bottom: 10px;
103
+ }
104
+
105
+ .chat-message.user {
106
+ justify-content: flex-end;
107
+ }
108
+
109
+ .chat-message.bot {
110
+ justify-content: flex-start;
111
+ }
112
+
113
+ .chat-avatar {
114
+ margin: 0 8px;
115
+ }
116
+
117
+ .chat-text {
118
+ max-width: 70%;
119
+ padding: 8px 12px;
120
+ border-radius: 4px;
121
+ background-color: #fff;
122
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
123
+ }
124
+
125
+ .chat-message.user .chat-text {
126
+ background-color: #e6f7ff;
127
+ }
128
+
129
+ .chat-input {
130
+ display: flex;
131
+ padding: 8px;
132
+ background: #fff;
133
+ border-top: 1px solid #d9d9d9;
134
+ }
135
+
136
+ .chat-input a-input {
137
+ flex: 1;
138
+ margin-right: 8px;
139
+ }
140
+ </style>
@@ -0,0 +1,28 @@
1
+ <template>
2
+ <div>
3
+ <h1>对话示例</h1>
4
+ <XConversation ref="XConversation"/>
5
+ </div>
6
+ </template>
7
+
8
+ <script>
9
+ import XConversation from './XConversation.vue' // 请根据实际路径调整
10
+
11
+ export default {
12
+ name: 'ConversationDemo',
13
+ components: {
14
+ XConversation
15
+ },
16
+ mounted () {
17
+ this.$refs.XConversation.init({
18
+ value: { logicName: 'submitMessage' },
19
+ serviceName: 'af-his'
20
+ })
21
+ this.$refs.XConversation.setAddtionalInfo({ patient_id: 37 })
22
+ }
23
+ }
24
+ </script>
25
+
26
+ <style scoped>
27
+ /* 这里可以添加你的样式 */
28
+ </style>
@@ -611,6 +611,15 @@
611
611
  ></x-license-plate>
612
612
  </a-form-model-item>
613
613
  </x-form-col>
614
+ <!-- 录音 -->
615
+ <x-form-col
616
+ v-else-if="attr.type === 'recording' && show"
617
+ :flex="attr.flex">
618
+ <recoding
619
+ ref="recording"
620
+ >
621
+ </recoding>
622
+ </x-form-col>
614
623
  </template>
615
624
  <script>
616
625
  import { debounce } from 'ant-design-vue/lib/vc-table/src/utils'
@@ -633,6 +642,7 @@ export default {
633
642
  name: 'XFormItem',
634
643
  components: {
635
644
  XFormTable: () => import('@vue2-client/base-client/components/common/XFormTable/XFormTable.vue'),
645
+ Recoding: () => import('@vue2-client/base-client/components/common/Recording/Recoding.vue'),
636
646
  XLicensePlate,
637
647
  XTreeSelect,
638
648
  XFormCol,
@@ -1136,6 +1146,10 @@ export default {
1136
1146
  },
1137
1147
  rowChooseSearchAfterQuery () {
1138
1148
  this.searching = false
1149
+ },
1150
+ // 获取 recording 转换后的数据
1151
+ getRecodingData () {
1152
+ return this.$refs.recording.getRecordingData()
1139
1153
  }
1140
1154
  }
1141
1155
  }
@@ -88,15 +88,7 @@ export default {
88
88
  } else {
89
89
  if (this.mode === '查询') {
90
90
  const values = []
91
- for (const item of this.option) {
92
- if (value.includes(item.value)) {
93
- values.push(item.value)
94
- if (item.children && item.children.length) {
95
- this.getValues(item.children, value, values)
96
- }
97
- }
98
- }
99
- this.$emit('onChange', values)
91
+ this.getValues(option, value, values)
100
92
  } else {
101
93
  this.value = value
102
94
  }
@@ -109,11 +101,14 @@ export default {
109
101
  this.$emit('mounted', this)
110
102
  this.loaded = true
111
103
  },
112
- getValues (data, value, values) {
113
- for (const item of data) {
114
- values.push(item.value)
104
+ getValues (option, value, values, flag) {
105
+ for (const item of option) {
106
+ if (value.includes(item.value)) {
107
+ values.push(item.value)
108
+ flag = true
109
+ }
115
110
  if (item.children && item.children.length) {
116
- this.getValues(item.children, value, values)
111
+ this.getValues(item.children, value, values, flag)
117
112
  }
118
113
  }
119
114
  },
@@ -205,7 +200,7 @@ export default {
205
200
  for (const item of data) {
206
201
  values.push(item.node.key)
207
202
  if (item.children && item.children.length) {
208
- this.getValues(item.children, value, values)
203
+ this.getNodeValues(item.children, value, values)
209
204
  }
210
205
  }
211
206
  },
@@ -40,7 +40,7 @@ export default {
40
40
  data () {
41
41
  return {
42
42
  // 查询配置文件名
43
- queryParamsName: 'ChargeQueryCRUD',
43
+ queryParamsName: 'ceshiCRUD',
44
44
  // 查询配置左侧tree
45
45
  // xTreeConfigName: 'addressType',
46
46
  // 新增表单固定值
@@ -89,6 +89,9 @@ module.exports = {
89
89
  {
90
90
  label: '人员选择框', key: 'personSetting'
91
91
  },
92
+ {
93
+ label: '录音按钮', key: 'recording'
94
+ }
92
95
  ],
93
96
  // 文件上传类型
94
97
  fileType: [
@@ -84,13 +84,15 @@ routerResource.example = {
84
84
  path: 'default',
85
85
  name: '示例页面',
86
86
  // component: () => import('@vue2-client/base-client/components/common/XAddNativeForm/demo.vue'),
87
- component: () => import('@vue2-client/base-client/components/common/XFormGroup/demo.vue'),
87
+ // component: () => import('@vue2-client/base-client/components/common/XFormGroup/demo.vue'),
88
+ // component: () => import('@vue2-client/base-client/components/common/XFormGroup/demo.vue'),
88
89
  // component: () => import('@vue2-client/base-client/components/common/XReport/XReportDemo.vue'),
89
90
  // component: () => import('@vue2-client/base-client/components/common/XFormTable/demo.vue'),
90
91
  // component: () => import('@vue2-client/base-client/components/common/XTab/XTabDemo.vue'),
91
92
  // component: () => import('@vue2-client/base-client/components/common/XReportGrid/XReportDemo.vue'),
92
93
  // component: () => import('@vue2-client/pages/WorkflowDetail/WorkFlowDemo.vue'),
93
- component: () => import('@vue2-client/base-client/components/common/XButtons/XButtonDemo.vue'),
94
+ component: () => import('@vue2-client/base-client/components/common/XConversation/XConversationDemo.vue'),
95
+ // component: () => import('@vue2-client/base-client/components/common/XButtons/XButtonDemo.vue'),
94
96
  meta: {
95
97
  // 菜单中不显示
96
98
  invisible: true,