vue2-client 1.9.83 → 1.9.85
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 +2 -2
- package/src/base-client/components/common/XAddNativeForm/XAddNativeForm.vue +18 -0
- package/src/base-client/components/common/XConversation/XConversation.vue +7 -3
- package/src/base-client/components/common/XForm/XFormItem.vue +75 -7
- package/src/base-client/components/common/XForm/XStatusButton.vue +54 -0
- package/src/base-client/components/common/XReportGrid/XReportDemo.vue +1 -1
- package/src/base-client/components/common/XTable/XTable.vue +2 -0
- package/src/base-client/plugins/Recording.js +210 -0
- package/src/base-client/plugins/index.js +23 -21
- package/src/router/async/router.map.js +2 -2
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vue2-client",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.85",
|
|
4
4
|
"private": false,
|
|
5
5
|
"scripts": {
|
|
6
6
|
"serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint",
|
|
7
7
|
"serve:gaslink": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint --mode gaslink",
|
|
8
8
|
"serve:revenue": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint --mode revenue",
|
|
9
9
|
"serve:his": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint --mode his",
|
|
10
|
-
"mac-serve": "vue-cli-service serve --no-eslint --mode
|
|
10
|
+
"mac-serve": "vue-cli-service serve --no-eslint --mode his",
|
|
11
11
|
"build": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build",
|
|
12
12
|
"test:unit": "vue-cli-service test:unit",
|
|
13
13
|
"lint": "vue-cli-service lint",
|
|
@@ -217,6 +217,12 @@ export default {
|
|
|
217
217
|
},
|
|
218
218
|
...mapState('account', { currUser: 'user' })
|
|
219
219
|
},
|
|
220
|
+
provide () {
|
|
221
|
+
return {
|
|
222
|
+
getComponentByName: this.getComponentByName,
|
|
223
|
+
registerComponent: this.registerComponent
|
|
224
|
+
}
|
|
225
|
+
},
|
|
220
226
|
methods: {
|
|
221
227
|
runLogic,
|
|
222
228
|
getConfigByNameAsync,
|
|
@@ -298,6 +304,17 @@ export default {
|
|
|
298
304
|
}
|
|
299
305
|
this.loaded = true
|
|
300
306
|
},
|
|
307
|
+
registerComponent (componentName, component) {
|
|
308
|
+
console.log('内部注册', this.$options.name, componentName)
|
|
309
|
+
this.$refs[componentName] = component
|
|
310
|
+
console.log('内部注册完成', this.$refs)
|
|
311
|
+
},
|
|
312
|
+
// 根据名字从注册到组件中获取组件
|
|
313
|
+
getComponentByName (componentName) {
|
|
314
|
+
console.log('内部取组件', this.$options.name, componentName)
|
|
315
|
+
console.log('内部组件内容', this.$refs)
|
|
316
|
+
return this.$refs[componentName]
|
|
317
|
+
},
|
|
301
318
|
// 兼容需要省略 传递 [formItems: res.formJson ] 可以使用 ...res 展开运算符 直接转递
|
|
302
319
|
getFromItem (formItems, formJson) {
|
|
303
320
|
const _formItems = formItems || formJson
|
|
@@ -747,6 +764,7 @@ export default {
|
|
|
747
764
|
}).catch(e => {
|
|
748
765
|
this.$message.error(this.businessType + '失败:' + e)
|
|
749
766
|
}).finally(() => {
|
|
767
|
+
this.loading = false
|
|
750
768
|
if (callback) {
|
|
751
769
|
callback()
|
|
752
770
|
}
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
<script>
|
|
31
31
|
import { runLogic } from '@vue2-client/services/api/common'
|
|
32
32
|
export default {
|
|
33
|
+
inject: ['getSelectedId', 'getSelectedData', 'getMixinData', 'getOutEnv', 'currUser'],
|
|
33
34
|
data () {
|
|
34
35
|
return {
|
|
35
36
|
serviceName: undefined,
|
|
@@ -52,10 +53,10 @@ export default {
|
|
|
52
53
|
const {
|
|
53
54
|
serviceName,
|
|
54
55
|
// 配置内容
|
|
55
|
-
|
|
56
|
+
logicName
|
|
56
57
|
} = params
|
|
57
|
-
console.log('
|
|
58
|
-
this.renderConfig =
|
|
58
|
+
console.log('调用会话框init方法', params)
|
|
59
|
+
this.renderConfig = { logicName: logicName }
|
|
59
60
|
this.loading = true
|
|
60
61
|
this.serviceName = serviceName
|
|
61
62
|
},
|
|
@@ -77,6 +78,9 @@ export default {
|
|
|
77
78
|
this.messages.push({ type: 'bot', text: response.value })
|
|
78
79
|
},
|
|
79
80
|
},
|
|
81
|
+
mounted () {
|
|
82
|
+
this.setAddtionalInfo(this.getMixinData())
|
|
83
|
+
},
|
|
80
84
|
}
|
|
81
85
|
</script>
|
|
82
86
|
|
|
@@ -29,12 +29,19 @@
|
|
|
29
29
|
:placeholder="attr.placeholder ? attr.placeholder : '请输入'+attr.name.replace(/\s*/g, '')"
|
|
30
30
|
:ref="`${attr.model}input`"/>
|
|
31
31
|
<a-button
|
|
32
|
-
v-if="attr.inputOnAfterName && attr.inputOnAfterFunc"
|
|
32
|
+
v-if="attr.inputOnAfterName && attr.inputOnAfterFunc && !attr.inputOnAfterName.includes('|')"
|
|
33
33
|
style="flex: 1; width: auto; min-width: 4rem;max-width: 6rem"
|
|
34
34
|
type="primary"
|
|
35
35
|
@click="emitFunc(attr.inputOnAfterFunc,attr)">
|
|
36
36
|
{{ attr.inputOnAfterName }}
|
|
37
37
|
</a-button>
|
|
38
|
+
<!-- 状态按钮 -->
|
|
39
|
+
<x-status-button
|
|
40
|
+
v-else
|
|
41
|
+
:states="parseStates(attr.inputOnAfterName, attr.inputOnAfterFunc)"
|
|
42
|
+
v-on="generateDynamicEvents(attr.inputOnAfterFunc, attr)"
|
|
43
|
+
style="flex: 1; width: auto; min-width: 4rem; max-width: 6rem"
|
|
44
|
+
/>
|
|
38
45
|
<!-- 仅可以配置 一个按钮 以及 一个图标插槽 -->
|
|
39
46
|
<a-button
|
|
40
47
|
style="width: 2rem; flex-shrink: 0;"
|
|
@@ -621,6 +628,26 @@
|
|
|
621
628
|
>
|
|
622
629
|
</recording>
|
|
623
630
|
</x-form-col>
|
|
631
|
+
<!-- 表格录入 -->
|
|
632
|
+
<x-form-col
|
|
633
|
+
v-else-if="attr.type === 'rowEdit' && show"
|
|
634
|
+
:flex="attr.flex">
|
|
635
|
+
<a-form-model-item
|
|
636
|
+
:ref="attr.model"
|
|
637
|
+
:label="showLabel?attr.name:undefined"
|
|
638
|
+
:prop="attr.prop ? attr.prop : attr.model">
|
|
639
|
+
<x-form-table
|
|
640
|
+
:key="'childTable_' + attr.model"
|
|
641
|
+
:title="attr.name"
|
|
642
|
+
:queryParamsName="attr.crud"
|
|
643
|
+
:localEditMode="true"
|
|
644
|
+
:fixed-query-form="childTableFixedQueryForm(attr)"
|
|
645
|
+
:service-name="serviceName"
|
|
646
|
+
@hook:mounted="(h)=>onComponentMounted(h, attr)"
|
|
647
|
+
:ref="'childXFormTable_' + attr.model">
|
|
648
|
+
</x-form-table>
|
|
649
|
+
</a-form-model-item>
|
|
650
|
+
</x-form-col>
|
|
624
651
|
</template>
|
|
625
652
|
<script>
|
|
626
653
|
import { debounce } from 'ant-design-vue/lib/vc-table/src/utils'
|
|
@@ -636,8 +663,9 @@ import util from '@vue2-client/utils/util'
|
|
|
636
663
|
import XTreeSelect from '@vue2-client/base-client/components/common/XForm/XTreeSelect'
|
|
637
664
|
import { searchToListOption, searchToOption } from '@vue2-client/services/v3Api'
|
|
638
665
|
import { mapState } from 'vuex'
|
|
639
|
-
import {
|
|
666
|
+
import { executeStrFunctionByContext } from '@vue2-client/utils/runEvalFunction'
|
|
640
667
|
import XLicensePlate from '@vue2-client/base-client/components/common/XLicensePlate/XLicensePlate.vue'
|
|
668
|
+
import XStatusButton from './XStatusButton.vue'
|
|
641
669
|
|
|
642
670
|
export default {
|
|
643
671
|
name: 'XFormItem',
|
|
@@ -651,7 +679,8 @@ export default {
|
|
|
651
679
|
CitySelect,
|
|
652
680
|
PersonSetting,
|
|
653
681
|
AddressSearchCombobox,
|
|
654
|
-
Upload
|
|
682
|
+
Upload,
|
|
683
|
+
XStatusButton
|
|
655
684
|
},
|
|
656
685
|
data () {
|
|
657
686
|
// 检索去抖
|
|
@@ -849,7 +878,46 @@ export default {
|
|
|
849
878
|
deep: true
|
|
850
879
|
}
|
|
851
880
|
},
|
|
881
|
+
inject: ['registerComponent', 'getComponentByName'],
|
|
852
882
|
methods: {
|
|
883
|
+
// 把内部的crud表单录入放到表单中,以便外部可以调用
|
|
884
|
+
onComponentMounted (h, attr) {
|
|
885
|
+
console.log('crud表单', h)
|
|
886
|
+
if (cell.slotRef) {
|
|
887
|
+
this.registerComponent(attr.key, this.$refs["'childXFormTable_' + attr.model"])
|
|
888
|
+
}
|
|
889
|
+
},
|
|
890
|
+
childTableFixedQueryForm (item) {
|
|
891
|
+
console.log('传递的form', this.form)
|
|
892
|
+
if (this.modifyModelData?.primaryKeyData) {
|
|
893
|
+
const fixedForm = {}
|
|
894
|
+
fixedForm[item.childTableForeignKeyName] = Object.values(this.modifyModelData.primaryKeyData)[0]
|
|
895
|
+
return fixedForm
|
|
896
|
+
}
|
|
897
|
+
return null
|
|
898
|
+
},
|
|
899
|
+
// 动态生成事件绑定对象
|
|
900
|
+
generateDynamicEvents (inputOnAfterFunc, attr) {
|
|
901
|
+
const events = {}
|
|
902
|
+
const states = this.parseStates(attr.inputOnAfterName, inputOnAfterFunc)
|
|
903
|
+
|
|
904
|
+
states.forEach((state) => {
|
|
905
|
+
// 动态绑定事件名到 emitFunc
|
|
906
|
+
events[state.event] = () => {
|
|
907
|
+
console.info('事件名', state.event)
|
|
908
|
+
this.emitFunc(state.event, attr)
|
|
909
|
+
}
|
|
910
|
+
})
|
|
911
|
+
|
|
912
|
+
return events // 返回 { state1Event: handler, state2Event: handler, ... }
|
|
913
|
+
},
|
|
914
|
+
parseStates (input, events) {
|
|
915
|
+
const eventNames = events.split('|')
|
|
916
|
+
return input.split('|').map((label, index) => ({
|
|
917
|
+
label,
|
|
918
|
+
event: eventNames[index] // 如果没有提供事件名称,则使用默认值
|
|
919
|
+
}))
|
|
920
|
+
},
|
|
853
921
|
focusInput () {
|
|
854
922
|
if (this.attr.defaultFocus) {
|
|
855
923
|
this.$nextTick(h => {
|
|
@@ -904,17 +972,17 @@ export default {
|
|
|
904
972
|
// js 函数作为数据源
|
|
905
973
|
async updateOptions () {
|
|
906
974
|
if (this.attr.keyName && (this.attr.keyName.indexOf('async ') !== -1 || this.attr.keyName.indexOf('function ') !== -1)) {
|
|
907
|
-
this.optionForFunc = await
|
|
975
|
+
this.optionForFunc = await executeStrFunctionByContext(this, this.attr.keyName, [this.form, runLogic, this.mode, getConfigByNameAsync])
|
|
908
976
|
}
|
|
909
977
|
},
|
|
910
978
|
async dataChangeFunc () {
|
|
911
979
|
if (this.attr.dataChangeFunc) {
|
|
912
|
-
await
|
|
980
|
+
await executeStrFunctionByContext(this, this.attr.dataChangeFunc, [this.form, this.setForm, this.attr, util, this.mode, runLogic, getConfigByNameAsync])
|
|
913
981
|
}
|
|
914
982
|
},
|
|
915
983
|
async showFormItemFunc () {
|
|
916
984
|
if (this.attr.showFormItemFunc) {
|
|
917
|
-
const obj =
|
|
985
|
+
const obj = executeStrFunctionByContext(this, this.attr.showFormItemFunc, [this.form, this.setForm, this.attr, util, this.mode])
|
|
918
986
|
// 判断是 bool 还是 obj 兼容
|
|
919
987
|
if (typeof obj === 'boolean') {
|
|
920
988
|
this.show = obj
|
|
@@ -929,7 +997,7 @@ export default {
|
|
|
929
997
|
},
|
|
930
998
|
async showQueryFormItemFunc () {
|
|
931
999
|
if (this.attr.showQueryFormItemFunc) {
|
|
932
|
-
const obj =
|
|
1000
|
+
const obj = executeStrFunctionByContext(this, this.attr.showQueryFormItemFunc, [this.form, this.setForm, this.attr, util, this.mode])
|
|
933
1001
|
// 判断是 bool 还是 obj 兼容
|
|
934
1002
|
if (typeof obj === 'boolean') {
|
|
935
1003
|
this.show = obj
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<a-button type="primary" @click="handleClick">{{ currentLabel }}</a-button>
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script>
|
|
8
|
+
export default {
|
|
9
|
+
name: 'StateButton',
|
|
10
|
+
props: {
|
|
11
|
+
// 定义状态和对应的显示文本与事件
|
|
12
|
+
states: {
|
|
13
|
+
type: Array,
|
|
14
|
+
required: true,
|
|
15
|
+
default: () => [
|
|
16
|
+
{ label: '状态1', event: 'state1Event' },
|
|
17
|
+
{ label: '状态2', event: 'state2Event' },
|
|
18
|
+
{ label: '状态3', event: 'state3Event' },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
initialIndex: {
|
|
22
|
+
type: Number,
|
|
23
|
+
default: 0,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
data () {
|
|
27
|
+
return {
|
|
28
|
+
currentIndex: this.initialIndex,
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
computed: {
|
|
32
|
+
// 当前显示的按钮文本
|
|
33
|
+
currentLabel () {
|
|
34
|
+
return this.states[this.currentIndex]?.label || '按钮'
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
methods: {
|
|
38
|
+
handleClick () {
|
|
39
|
+
// 触发当前状态的事件
|
|
40
|
+
const currentState = this.states[this.currentIndex]
|
|
41
|
+
if (currentState && currentState.event) {
|
|
42
|
+
this.$emit(currentState.event)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 切换到下一个状态
|
|
46
|
+
this.currentIndex = (this.currentIndex + 1) % this.states.length
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<style scoped>
|
|
53
|
+
/* 添加样式以适应项目需求 */
|
|
54
|
+
</style>
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// recordingPlugin.js
|
|
2
|
+
export default {
|
|
3
|
+
install (Vue) {
|
|
4
|
+
Vue.prototype.$recording = {
|
|
5
|
+
isRecording: false, // 标识是否正在录音
|
|
6
|
+
mediaRecorder: null, // MediaRecorder 实例
|
|
7
|
+
audioChunks: [], // 存储录音数据块
|
|
8
|
+
audioUrl: null, // 录音生成的 URL
|
|
9
|
+
inputData: '',
|
|
10
|
+
audioContext: null,
|
|
11
|
+
audioStream: null,
|
|
12
|
+
ws: null,
|
|
13
|
+
|
|
14
|
+
async startRecording () {
|
|
15
|
+
this.inputData = ''
|
|
16
|
+
this.audioChunks = []
|
|
17
|
+
|
|
18
|
+
const url = 'ws://192.168.50.67:31090/websocket/bash' // WebSocket地址
|
|
19
|
+
this.setupWebSocket(url)
|
|
20
|
+
|
|
21
|
+
// 等待 WebSocket 连接成功
|
|
22
|
+
await this.waitForWebSocketConnection()
|
|
23
|
+
|
|
24
|
+
this.isRecording = true
|
|
25
|
+
|
|
26
|
+
// 初始化 AudioContext
|
|
27
|
+
this.audioContext = new (window.AudioContext || window.webkitAudioContext)()
|
|
28
|
+
|
|
29
|
+
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
'录音软件不支持您的浏览器!请使用现代浏览器或确保您正在使用 HTTPS。'
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 获取麦克风输入流
|
|
36
|
+
this.audioStream = await navigator.mediaDevices.getUserMedia({ audio: true })
|
|
37
|
+
|
|
38
|
+
// 设置音频处理流程
|
|
39
|
+
this.setupAudioProcessing()
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
setupWebSocket (url) {
|
|
43
|
+
this.ws = new WebSocket(url)
|
|
44
|
+
|
|
45
|
+
this.ws.onmessage = (event) => {
|
|
46
|
+
const data = JSON.parse(event.data)
|
|
47
|
+
console.log('Result:', data.result)
|
|
48
|
+
if (data.result !== undefined) {
|
|
49
|
+
this.inputData += data.result
|
|
50
|
+
}
|
|
51
|
+
if (!this.isRecording) {
|
|
52
|
+
this.ws.close()
|
|
53
|
+
this.resRecordingData()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.ws.onclose = () => console.log('WebSocket closed.')
|
|
58
|
+
this.ws.onerror = (error) => console.error('WebSocket error:', error)
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
waitForWebSocketConnection () {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
this.ws.onopen = () => {
|
|
64
|
+
console.log('WebSocket 连接成功')
|
|
65
|
+
resolve()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.ws.onerror = (error) => {
|
|
69
|
+
reject(new Error('WebSocket 连接失败: ' + error.message))
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
setupAudioProcessing () {
|
|
75
|
+
try {
|
|
76
|
+
const source = this.audioContext.createMediaStreamSource(this.audioStream)
|
|
77
|
+
const processor = this.audioContext.createScriptProcessor(4096, 1, 1)
|
|
78
|
+
|
|
79
|
+
processor.onaudioprocess = (e) => {
|
|
80
|
+
if (!this.isRecording) return
|
|
81
|
+
|
|
82
|
+
const inputData = e.inputBuffer.getChannelData(0)
|
|
83
|
+
|
|
84
|
+
// Resample to 16000 Hz if needed
|
|
85
|
+
const resampledData = this.resample(inputData, this.audioContext.sampleRate, 16000)
|
|
86
|
+
|
|
87
|
+
this.audioChunks.push(new Float32Array(resampledData))
|
|
88
|
+
|
|
89
|
+
// Convert to 16-bit PCM and send via WebSocket
|
|
90
|
+
const byteArray = this.convertTo16BitPCM(resampledData)
|
|
91
|
+
this.ws.send(byteArray)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
source.connect(processor)
|
|
95
|
+
processor.connect(this.audioContext.destination)
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error('Error setting up audio processing:', error)
|
|
98
|
+
throw error
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
stopRecording () {
|
|
103
|
+
this.isRecording = false
|
|
104
|
+
|
|
105
|
+
// 停止麦克风流
|
|
106
|
+
if (this.audioStream) {
|
|
107
|
+
this.audioStream.getTracks().forEach((track) => track.stop())
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (this.audioContext) {
|
|
111
|
+
this.audioContext.close()
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
saveAudioFile () {
|
|
116
|
+
const audioBuffer = this.mergeAudioChunks(this.audioChunks)
|
|
117
|
+
|
|
118
|
+
// 转换为 WAV 格式
|
|
119
|
+
const wavBlob = this.encodeWAV(audioBuffer, 16000)
|
|
120
|
+
|
|
121
|
+
// 创建下载链接并触发下载
|
|
122
|
+
const url = URL.createObjectURL(wavBlob)
|
|
123
|
+
const a = document.createElement('a')
|
|
124
|
+
a.style.display = 'none'
|
|
125
|
+
a.href = url
|
|
126
|
+
a.download = 'recording.wav'
|
|
127
|
+
document.body.appendChild(a)
|
|
128
|
+
a.click()
|
|
129
|
+
window.URL.revokeObjectURL(url)
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
mergeAudioChunks (chunks) {
|
|
133
|
+
const length = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
|
|
134
|
+
const result = new Float32Array(length)
|
|
135
|
+
let offset = 0
|
|
136
|
+
for (const chunk of chunks) {
|
|
137
|
+
result.set(chunk, offset)
|
|
138
|
+
offset += chunk.length
|
|
139
|
+
}
|
|
140
|
+
return result
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
encodeWAV (samples, sampleRate) {
|
|
144
|
+
const buffer = new ArrayBuffer(44 + samples.length * 2)
|
|
145
|
+
const view = new DataView(buffer)
|
|
146
|
+
|
|
147
|
+
const writeString = (view, offset, string) => {
|
|
148
|
+
for (let i = 0; i < string.length; i++) {
|
|
149
|
+
view.setUint8(offset + i, string.charCodeAt(i))
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
writeString(view, 0, 'RIFF')
|
|
154
|
+
view.setUint32(4, 36 + samples.length * 2, true)
|
|
155
|
+
writeString(view, 8, 'WAVE')
|
|
156
|
+
|
|
157
|
+
writeString(view, 12, 'fmt ')
|
|
158
|
+
view.setUint32(16, 16, true)
|
|
159
|
+
view.setUint16(20, 1, true)
|
|
160
|
+
view.setUint16(22, 1, true)
|
|
161
|
+
view.setUint32(24, sampleRate, true)
|
|
162
|
+
view.setUint32(28, sampleRate * 2, true)
|
|
163
|
+
view.setUint16(32, 2, true)
|
|
164
|
+
view.setUint16(34, 16, true)
|
|
165
|
+
|
|
166
|
+
writeString(view, 36, 'data')
|
|
167
|
+
view.setUint32(40, samples.length * 2, true)
|
|
168
|
+
|
|
169
|
+
let offset = 44
|
|
170
|
+
for (let i = 0; i < samples.length; i++) {
|
|
171
|
+
const s = Math.max(-1, Math.min(1, samples[i]))
|
|
172
|
+
view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true)
|
|
173
|
+
offset += 2
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return new Blob([buffer], { type: 'audio/wav' })
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
resample (data, inputSampleRate, outputSampleRate) {
|
|
180
|
+
if (inputSampleRate === outputSampleRate) {
|
|
181
|
+
return data
|
|
182
|
+
}
|
|
183
|
+
const sampleRateRatio = inputSampleRate / outputSampleRate
|
|
184
|
+
const newLength = Math.round(data.length / sampleRateRatio)
|
|
185
|
+
const resampledData = new Float32Array(newLength)
|
|
186
|
+
for (let i = 0; i < newLength; i++) {
|
|
187
|
+
const index = Math.round(i * sampleRateRatio)
|
|
188
|
+
resampledData[i] = data[index]
|
|
189
|
+
}
|
|
190
|
+
return resampledData
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
convertTo16BitPCM (data) {
|
|
194
|
+
const output = new Int16Array(data.length)
|
|
195
|
+
for (let i = 0; i < data.length; i++) {
|
|
196
|
+
output[i] = Math.max(-1, Math.min(1, data[i])) * 0x7fff
|
|
197
|
+
}
|
|
198
|
+
return new Uint8Array(output.buffer)
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
getRecordingData () {
|
|
202
|
+
return this.inputData
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
resRecordingData () {
|
|
206
|
+
// Custom logic for emitting recording data
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
import GetLoginInfoService from '@vue2-client/base-client/plugins/GetLoginInfoService'
|
|
2
|
-
import GetAppDataService from '@vue2-client/base-client/plugins/AppData'
|
|
3
|
-
import getConfig from '@vue2-client/base-client/plugins/Config'
|
|
4
|
-
import moment from '@vue2-client/base-client/plugins/moment'
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Vue.use(
|
|
14
|
-
Vue.use(
|
|
15
|
-
Vue.use(
|
|
16
|
-
Vue.use(
|
|
17
|
-
Vue.use(
|
|
18
|
-
Vue.use(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
import GetLoginInfoService from '@vue2-client/base-client/plugins/GetLoginInfoService'
|
|
2
|
+
import GetAppDataService from '@vue2-client/base-client/plugins/AppData'
|
|
3
|
+
import getConfig from '@vue2-client/base-client/plugins/Config'
|
|
4
|
+
import moment from '@vue2-client/base-client/plugins/moment'
|
|
5
|
+
import recording from '@vue2-client/base-client/plugins/Recording'
|
|
6
|
+
|
|
7
|
+
import VueI18nPlugin from './i18n-extend'
|
|
8
|
+
import AuthorityPlugin from './authority-plugin'
|
|
9
|
+
import TabsPagePlugin from './tabs-page-plugin'
|
|
10
|
+
|
|
11
|
+
const Plugins = {
|
|
12
|
+
install: function (Vue) {
|
|
13
|
+
Vue.use(GetLoginInfoService)
|
|
14
|
+
Vue.use(GetAppDataService)
|
|
15
|
+
Vue.use(getConfig)
|
|
16
|
+
Vue.use(moment)
|
|
17
|
+
Vue.use(VueI18nPlugin)
|
|
18
|
+
Vue.use(AuthorityPlugin)
|
|
19
|
+
Vue.use(TabsPagePlugin)
|
|
20
|
+
Vue.use(recording)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export default Plugins
|
|
@@ -87,9 +87,9 @@ routerResource.example = {
|
|
|
87
87
|
// component: () => import('@vue2-client/base-client/components/common/XFormGroup/demo.vue'),
|
|
88
88
|
// component: () => import('@vue2-client/base-client/components/common/XFormGroup/demo.vue'),
|
|
89
89
|
// component: () => import('@vue2-client/base-client/components/common/XReport/XReportDemo.vue'),
|
|
90
|
-
|
|
90
|
+
component: () => import('@vue2-client/base-client/components/common/XFormTable/demo.vue'),
|
|
91
91
|
// component: () => import('@vue2-client/base-client/components/common/XTab/XTabDemo.vue'),
|
|
92
|
-
component: () => import('@vue2-client/base-client/components/common/XReportGrid/XReportDemo.vue'),
|
|
92
|
+
// component: () => import('@vue2-client/base-client/components/common/XReportGrid/XReportDemo.vue'),
|
|
93
93
|
// component: () => import('@vue2-client/pages/WorkflowDetail/WorkFlowDemo.vue'),
|
|
94
94
|
// component: () => import('@vue2-client/base-client/components/common/XConversation/XConversationDemo.vue'),
|
|
95
95
|
// component: () => import('@vue2-client/base-client/components/common/XButtons/XButtonDemo.vue'),
|