vite-uni-dev-tool 1.0.0 → 1.2.0

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 (170) hide show
  1. package/README.md +54 -0
  2. package/dist/const.cjs +1 -1
  3. package/dist/const.d.ts +13 -0
  4. package/dist/const.d.ts.map +1 -1
  5. package/dist/const.js +1 -1
  6. package/dist/core-shared.d.ts +1 -1
  7. package/dist/core-shared.d.ts.map +1 -1
  8. package/dist/core-shared.js +1 -1
  9. package/dist/core.d.ts +10 -3
  10. package/dist/core.d.ts.map +1 -1
  11. package/dist/core.js +2 -2
  12. package/dist/i18n/locales/en.cjs +1 -1
  13. package/dist/i18n/locales/en.d.ts +86 -0
  14. package/dist/i18n/locales/en.d.ts.map +1 -1
  15. package/dist/i18n/locales/en.js +1 -1
  16. package/dist/i18n/locales/zh-Hans.cjs +1 -1
  17. package/dist/i18n/locales/zh-Hans.d.ts +87 -1
  18. package/dist/i18n/locales/zh-Hans.d.ts.map +1 -1
  19. package/dist/i18n/locales/zh-Hans.js +1 -1
  20. package/dist/modules/devConsole/index.cjs +1 -1
  21. package/dist/modules/devConsole/index.js +3 -3
  22. package/dist/modules/devEvent/index.cjs +3 -3
  23. package/dist/modules/devEvent/index.d.ts +1 -0
  24. package/dist/modules/devEvent/index.d.ts.map +1 -1
  25. package/dist/modules/devEvent/index.js +3 -3
  26. package/dist/modules/devIntercept/index.cjs +14 -13
  27. package/dist/modules/devIntercept/index.d.ts +20 -1
  28. package/dist/modules/devIntercept/index.d.ts.map +1 -1
  29. package/dist/modules/devIntercept/index.js +14 -13
  30. package/dist/modules/devStore/index.cjs +1 -1
  31. package/dist/modules/devStore/index.d.ts +21 -0
  32. package/dist/modules/devStore/index.d.ts.map +1 -1
  33. package/dist/modules/devStore/index.js +1 -1
  34. package/dist/plugins/uniDevTool/transform/transformMain.cjs +3 -3
  35. package/dist/plugins/uniDevTool/transform/transformMain.d.ts +2 -1
  36. package/dist/plugins/uniDevTool/transform/transformMain.d.ts.map +1 -1
  37. package/dist/plugins/uniDevTool/transform/transformMain.js +3 -3
  38. package/dist/plugins/uniDevTool/transform/transformVue.cjs +31 -25
  39. package/dist/plugins/uniDevTool/transform/transformVue.d.ts +2 -1
  40. package/dist/plugins/uniDevTool/transform/transformVue.d.ts.map +1 -1
  41. package/dist/plugins/uniDevTool/transform/transformVue.js +30 -24
  42. package/dist/plugins/uniDevTool/uniDevTool.cjs +3 -3
  43. package/dist/plugins/uniDevTool/uniDevTool.d.ts +3 -1
  44. package/dist/plugins/uniDevTool/uniDevTool.d.ts.map +1 -1
  45. package/dist/plugins/uniDevTool/uniDevTool.js +3 -3
  46. package/dist/type.d.ts +50 -2
  47. package/dist/type.d.ts.map +1 -1
  48. package/dist/utils/language.cjs +1 -1
  49. package/dist/utils/language.d.ts.map +1 -1
  50. package/dist/utils/language.js +1 -1
  51. package/dist/utils/object.cjs +1 -1
  52. package/dist/utils/object.d.ts.map +1 -1
  53. package/dist/utils/object.js +1 -1
  54. package/dist/v3/DevTool/components/BluetoothList/BluetoothItem.vue +199 -0
  55. package/dist/v3/DevTool/components/BluetoothList/BluetoothTool.vue +730 -0
  56. package/dist/v3/DevTool/components/BluetoothList/index.vue +167 -0
  57. package/dist/v3/{CaptureScreen → DevTool/components/CaptureScreen}/index.vue +4 -4
  58. package/dist/v3/{ConsoleList → DevTool/components/ConsoleList}/ConsoleItem.vue +22 -16
  59. package/dist/v3/{ConsoleList → DevTool/components/ConsoleList}/RunJSInput.vue +4 -6
  60. package/dist/v3/{ConsoleList → DevTool/components/ConsoleList}/index.vue +21 -10
  61. package/dist/v3/{DevToolButton → DevTool/components/DevToolButton}/index.vue +7 -4
  62. package/dist/v3/{DevToolWindow → DevTool/components/DevToolWindow}/DevToolOverlay.vue +17 -2
  63. package/dist/v3/{DevToolWindow → DevTool/components/DevToolWindow}/const.ts +28 -5
  64. package/dist/v3/{DevToolWindow → DevTool/components/DevToolWindow}/hooks/dataUtils.ts +1 -1
  65. package/dist/v3/{DevToolWindow → DevTool/components/DevToolWindow}/hooks/useDevToolData.ts +55 -6
  66. package/dist/v3/{DevToolWindow → DevTool/components/DevToolWindow}/hooks/useDevToolHandlers.ts +85 -5
  67. package/dist/v3/{DevToolWindow → DevTool/components/DevToolWindow}/hooks/useDevToolOverlay.ts +25 -8
  68. package/dist/v3/{DevToolWindow → DevTool/components/DevToolWindow}/index.vue +67 -16
  69. package/dist/v3/{ElEvent → DevTool/components/ElEvent}/ElEventItem.vue +3 -3
  70. package/dist/v3/{ElEvent → DevTool/components/ElEvent}/index.vue +10 -13
  71. package/dist/v3/{Instance → DevTool/components/Instance}/index.vue +1 -1
  72. package/dist/v3/{Instance → DevTool/components/Instance}/transformTree.ts +1 -1
  73. package/dist/v3/{Instance → DevTool/components/Instance}/transformTreeCtx.ts +1 -1
  74. package/dist/v3/{InstanceDetail → DevTool/components/InstanceDetail}/index.vue +4 -4
  75. package/dist/v3/{JsonDetail → DevTool/components/JsonDetail}/index.vue +4 -4
  76. package/dist/v3/{NFCList → DevTool/components/NFCList}/NFCItem.vue +4 -5
  77. package/dist/v3/{NFCList → DevTool/components/NFCList}/NFCTool.vue +33 -57
  78. package/dist/v3/{NFCList → DevTool/components/NFCList}/index.vue +12 -16
  79. package/dist/v3/{NetworkList → DevTool/components/NetworkList}/InterceptConfig.vue +20 -4
  80. package/dist/v3/{NetworkList → DevTool/components/NetworkList}/InterceptItem.vue +3 -3
  81. package/dist/v3/{NetworkList → DevTool/components/NetworkList}/NetworkDetail.vue +18 -27
  82. package/dist/v3/{NetworkList → DevTool/components/NetworkList}/NetworkIntercept.vue +11 -16
  83. package/dist/v3/{NetworkList → DevTool/components/NetworkList}/NetworkItem.vue +10 -14
  84. package/dist/v3/{NetworkList → DevTool/components/NetworkList}/NetworkSend.vue +67 -34
  85. package/dist/v3/{NetworkList → DevTool/components/NetworkList}/index.vue +8 -8
  86. package/dist/v3/{Performance → DevTool/components/Performance}/index.vue +7 -4
  87. package/dist/v3/{Performance → DevTool/components/Performance}/modules/PerformanceWidget.vue +12 -9
  88. package/dist/v3/{Performance → DevTool/components/Performance}/modules/usePerformanceChart.ts +1 -1
  89. package/dist/v3/{Performance → DevTool/components/Performance}/modules/usePerformanceData.ts +2 -2
  90. package/dist/v3/{PiniaList → DevTool/components/PiniaList}/index.vue +5 -6
  91. package/dist/v3/{RouteList → DevTool/components/RouteList}/index.vue +21 -24
  92. package/dist/v3/{RunJS → DevTool/components/RunJS}/index.vue +3 -3
  93. package/dist/v3/{ScanCodeList → DevTool/components/ScanCodeList}/ScanCodeItem.vue +3 -4
  94. package/dist/v3/{ScanCodeList → DevTool/components/ScanCodeList}/index.vue +12 -16
  95. package/dist/v3/{SettingList → DevTool/components/SettingList}/index.vue +68 -0
  96. package/dist/v3/DevTool/components/SettingList/modules/SettingBarrage.vue +304 -0
  97. package/dist/v3/{SettingList → DevTool/components/SettingList}/modules/SettingDevTool.vue +8 -4
  98. package/dist/v3/{SettingList → DevTool/components/SettingList}/modules/SettingInfo.vue +47 -9
  99. package/dist/v3/{SettingList → DevTool/components/SettingList}/modules/SettingLanguage.vue +2 -2
  100. package/dist/v3/{SettingList → DevTool/components/SettingList}/modules/SettingLog.vue +2 -2
  101. package/dist/v3/{SettingList → DevTool/components/SettingList}/modules/SettingNetwork.vue +3 -3
  102. package/dist/v3/{SettingList → DevTool/components/SettingList}/modules/SettingTheme.vue +37 -7
  103. package/dist/v3/{SourceCode → DevTool/components/SourceCode}/Line.vue +22 -11
  104. package/dist/v3/{SourceCode → DevTool/components/SourceCode}/index.vue +8 -8
  105. package/dist/v3/{SourceCode → DevTool/components/SourceCode}/parseCode.ts +136 -228
  106. package/dist/v3/{StorageList → DevTool/components/StorageList}/index.vue +7 -7
  107. package/dist/v3/{TransferList → DevTool/components/TransferList}/TransferDetail.vue +6 -6
  108. package/dist/v3/{TransferList → DevTool/components/TransferList}/TransferItem.vue +4 -4
  109. package/dist/v3/{TransferList → DevTool/components/TransferList}/index.vue +8 -8
  110. package/dist/v3/{UniEvent → DevTool/components/UniEvent}/UniEventItem.vue +6 -7
  111. package/dist/v3/{UniEvent → DevTool/components/UniEvent}/index.vue +6 -6
  112. package/dist/v3/{VuexList → DevTool/components/VuexList}/index.vue +3 -3
  113. package/dist/v3/{WebSocket → DevTool/components/WebSocket}/WebSocketDetail.vue +8 -8
  114. package/dist/v3/{WebSocket → DevTool/components/WebSocket}/WebSocketItem.vue +4 -4
  115. package/dist/v3/{WebSocket → DevTool/components/WebSocket}/index.vue +8 -8
  116. package/dist/v3/DevTool/index.vue +179 -4
  117. package/dist/v3/{AppTransition → components/AppTransition}/index.vue +6 -0
  118. package/dist/v3/components/Barrage/BarrageItem.vue +137 -0
  119. package/dist/v3/components/Barrage/index.vue +202 -0
  120. package/dist/v3/components/DevErrorBoundary/index.vue +380 -0
  121. package/dist/v3/{DraggableContainer → components/DraggableContainer}/index.vue +1 -1
  122. package/dist/v3/{FilterInput → components/FilterInput}/index.vue +1 -1
  123. package/dist/v3/{JsonPretty → components/JsonPretty}/components/CheckController/index.vue +1 -1
  124. package/dist/v3/{JsonPretty → components/JsonPretty}/components/TreeNode/index.vue +11 -5
  125. package/dist/v3/{JsonPretty → components/JsonPretty}/index.vue +16 -13
  126. package/dist/v3/{JsonPretty → components/JsonPretty}/type.ts +1 -0
  127. package/dist/v3/{JsonPretty → components/JsonPretty}/utils/index.ts +1 -1
  128. package/dist/v3/{MovableContainer → components/MovableContainer}/index.vue +9 -5
  129. package/dist/v3/{Pick → components/Pick}/index.vue +1 -1
  130. package/dist/v3/{Tabs → components/Tabs}/index.vue +30 -4
  131. package/dist/v3/{VirtualList → components/VirtualList}/AutoSize.vue +1 -1
  132. package/dist/v3/{VirtualList → components/VirtualList}/index.vue +4 -0
  133. package/dist/v3/hooks/useBluetooth/index.ts +561 -0
  134. package/dist/v3/hooks/useRequest/index.ts +33 -20
  135. package/dist/v3/hooks/useWebsocket/README.md +79 -0
  136. package/dist/v3/hooks/useWebsocket/index.ts +253 -0
  137. package/dist/v3/styles/theme.css +17 -10
  138. package/package.json +67 -64
  139. package/dist/plugins/uniParseStock/index.d.ts +0 -10
  140. package/dist/plugins/uniParseStock/index.d.ts.map +0 -1
  141. /package/dist/v3/{ConsoleList → DevTool/components/ConsoleList}/staticTips.ts +0 -0
  142. /package/dist/v3/{DevToolTitle → DevTool/components/DevToolTitle}/index.vue +0 -0
  143. /package/dist/v3/{DevToolWindow → DevTool/components/DevToolWindow}/index.css +0 -0
  144. /package/dist/v3/{Instance → DevTool/components/Instance}/components/InstanceTreeNode.vue +0 -0
  145. /package/dist/v3/{Instance → DevTool/components/Instance}/flatten.ts +0 -0
  146. /package/dist/v3/{Instance → DevTool/components/Instance}/registry.ts +0 -0
  147. /package/dist/v3/{Instance → DevTool/components/Instance}/typing.d.ts +0 -0
  148. /package/dist/v3/{NFCList → DevTool/components/NFCList}/const.ts +0 -0
  149. /package/dist/v3/{NetworkList → DevTool/components/NetworkList}/const.ts +0 -0
  150. /package/dist/v3/{NetworkList → DevTool/components/NetworkList}/hooks/useNetworkForm.ts +0 -0
  151. /package/dist/v3/{NetworkList → DevTool/components/NetworkList}/utils.ts +0 -0
  152. /package/dist/v3/{Performance → DevTool/components/Performance}/modules/PerformanceMetrics.vue +0 -0
  153. /package/dist/v3/{SettingButton → DevTool/components/SettingButton}/index.vue +0 -0
  154. /package/dist/v3/{SettingList → DevTool/components/SettingList}/index.css +0 -0
  155. /package/dist/v3/{SettingList → DevTool/components/SettingList}/typing.d.ts +0 -0
  156. /package/dist/v3/{AutoSizer → components/AutoSizer}/index.vue +0 -0
  157. /package/dist/v3/{AutoSizer → components/AutoSizer}/index1.vue +0 -0
  158. /package/dist/v3/{AutoSizer → components/AutoSizer}/utils.ts +0 -0
  159. /package/dist/v3/{CircularButton → components/CircularButton}/index.vue +0 -0
  160. /package/dist/v3/{CustomSwiper → components/CustomSwiper}/CustomSwiperItem.vue +0 -0
  161. /package/dist/v3/{CustomSwiper → components/CustomSwiper}/index.vue +0 -0
  162. /package/dist/v3/{Empty → components/Empty}/empty.png +0 -0
  163. /package/dist/v3/{Empty → components/Empty}/index.vue +0 -0
  164. /package/dist/v3/{FilterSelect → components/FilterSelect}/index.vue +0 -0
  165. /package/dist/v3/{JsonPretty → components/JsonPretty}/components/Brackets/index.vue +0 -0
  166. /package/dist/v3/{JsonPretty → components/JsonPretty}/components/Carets/index.vue +0 -0
  167. /package/dist/v3/{JsonPretty → components/JsonPretty}/hooks/useClipboard.ts +0 -0
  168. /package/dist/v3/{JsonPretty → components/JsonPretty}/hooks/useError.ts +0 -0
  169. /package/dist/v3/{Tag → components/Tag}/index.vue +0 -0
  170. /package/dist/v3/{VirtualList → components/VirtualList}/readme.md +0 -0
@@ -0,0 +1,730 @@
1
+ <template>
2
+ <view class="bluetooth-tool" :style="customStyle">
3
+ <view class="bluetooth-tool-control">
4
+ <DevToolTitle>
5
+ {{ device.name || device.localName || device.deviceId }}
6
+ </DevToolTitle>
7
+
8
+ <CircularButton
9
+ style="margin-left: auto"
10
+ text="×"
11
+ @click="emit('back')" />
12
+ </view>
13
+
14
+ <!-- 配置区域 -->
15
+ <view class="tool-config">
16
+ <view class="config-item">
17
+ <text class="label">{{ t('bluetooth.service') }}:</text>
18
+ <Pick
19
+ v-model="selectedServiceUuid"
20
+ :options="
21
+ services.map((item) => ({ label: item.uuid, value: item.uuid }))
22
+ "
23
+ :placeholder="t('bluetooth.selectService')"
24
+ @change="onServiceChangePick"
25
+ customStyle="width: 200px" />
26
+ <view class="refresh-btn" @click="fetchServices">↻</view>
27
+ </view>
28
+
29
+ <view class="config-item" v-if="selectedService">
30
+ <text class="label">{{ t('bluetooth.characteristic') }}:</text>
31
+ <Pick
32
+ v-model="selectedCharUuid"
33
+ :options="
34
+ characteristics.map((item) => ({
35
+ label: item.uuid,
36
+ value: item.uuid,
37
+ }))
38
+ "
39
+ :placeholder="t('bluetooth.selectChar')"
40
+ @change="onCharacteristicChangePick"
41
+ customStyle="width: 200px" />
42
+ </view>
43
+
44
+ <view class="char-props" v-if="selectedCharacteristic">
45
+ {{ t('bluetooth.properties') }}:
46
+ <text class="prop-tag" v-if="selectedCharacteristic.properties?.read">
47
+ {{ t('bluetooth.propertyRead') }}
48
+ </text>
49
+ <text class="prop-tag" v-if="selectedCharacteristic.properties?.write">
50
+ {{ t('bluetooth.propertyWrite') }}
51
+ </text>
52
+ <text class="prop-tag" v-if="selectedCharacteristic.properties?.notify">
53
+ {{ t('bluetooth.propertyNotify') }}
54
+ </text>
55
+ <text
56
+ class="prop-tag"
57
+ v-if="selectedCharacteristic.properties?.indicate">
58
+ {{ t('bluetooth.propertyIndicate') }}
59
+ </text>
60
+ </view>
61
+
62
+ <view
63
+ class="notify-switch"
64
+ v-if="
65
+ selectedCharacteristic &&
66
+ (selectedCharacteristic.properties?.notify ||
67
+ selectedCharacteristic.properties?.indicate)
68
+ ">
69
+ <text>{{ t('bluetooth.receiveNotify') }}</text>
70
+ <switch
71
+ :checked="isNotifying"
72
+ @change="(e: any) => toggleNotify(e)"
73
+ style="transform: scale(0.7)" />
74
+ </view>
75
+ </view>
76
+
77
+ <!-- 日志区域 -->
78
+ <view class="log-area">
79
+ <scroll-view scroll-y class="log-scroll" :scroll-top="scrollTop">
80
+ <view
81
+ v-for="(log, index) in logs"
82
+ :key="index"
83
+ :class="['log-item', log.type]">
84
+ <view class="log-time">{{ log.time }}</view>
85
+ <view class="log-dir">
86
+ {{
87
+ log.type === 'send'
88
+ ? t('bluetooth.tx')
89
+ : log.type === 'receive'
90
+ ? t('bluetooth.rx')
91
+ : t('bluetooth.sys')
92
+ }}
93
+ </view>
94
+ <view class="log-content">{{ log.content }}</view>
95
+ </view>
96
+ <view v-if="logs.length === 0" class="empty-log">
97
+ {{ t('bluetooth.noData') }}
98
+ </view>
99
+ </scroll-view>
100
+ <view class="log-actions">
101
+ <view class="format-switch" @click="isHexFormat = !isHexFormat">
102
+ {{ t('bluetooth.format') }}: {{ isHexFormat ? 'HEX' : 'TEXT' }} ({{
103
+ t('common.refresh')
104
+ }})
105
+ </view>
106
+ <view class="clear-btn" @click="clearLogs">
107
+ {{ t('common.clear') }}
108
+ </view>
109
+ </view>
110
+ </view>
111
+
112
+ <!-- 发送区域 -->
113
+ <view class="send-container">
114
+ <view class="send-options">
115
+ <view class="opt-item">
116
+ {{ t('bluetooth.suffix') }}:
117
+ <switch
118
+ :checked="appendCRLF"
119
+ @change="(e: any) => (appendCRLF = e.detail.value)"
120
+ style="transform: scale(0.6)" />
121
+ &#92;r&#92;n
122
+ </view>
123
+ <view class="opt-item">
124
+ {{ t('bluetooth.mode') }}:
125
+ <Pick
126
+ :options="writeTypeOptionsPick"
127
+ v-model="selectedWriteTypePick"
128
+ :placeholder="t('bluetooth.auto')"
129
+ customStyle="width: 100px; margin-left: 4px;"
130
+ :allowClear="false"
131
+ :readonly="true" />
132
+ </view>
133
+ </view>
134
+ <view class="send-area">
135
+ <textarea
136
+ v-model="sendText"
137
+ class="send-input"
138
+ :placeholder="t('bluetooth.placeholder')"
139
+ :maxlength="-1" />
140
+ <view class="send-btn" @click="sendData">
141
+ {{ t('bluetooth.send') }}
142
+ </view>
143
+ </view>
144
+ </view>
145
+ </view>
146
+ </template>
147
+
148
+ <script setup lang="ts">
149
+ import { ref, onMounted, onUnmounted, nextTick, computed } from 'vue';
150
+ import type { DevTool } from '../../../../type';
151
+ import DevToolTitle from '../DevToolTitle/index.vue';
152
+ import CircularButton from '../../../components/CircularButton/index.vue';
153
+ import Pick from '../../../components/Pick/index.vue';
154
+ import useBluetooth from '../../../hooks/useBluetooth';
155
+ import { useI18n } from '../../../../i18n';
156
+
157
+ const { t } = useI18n();
158
+
159
+ const props = defineProps<{
160
+ device: DevTool.BluetoothItem;
161
+ contentHeight: number;
162
+ customStyle?: Record<string, any>;
163
+ }>();
164
+
165
+ const emit = defineEmits<{
166
+ (e: 'back'): void;
167
+ }>();
168
+
169
+ const {
170
+ getBLEServices,
171
+ getBLECharacteristics,
172
+ writeBLEMessage,
173
+ subscribeBLEMessage,
174
+ onBLEMessageReceived,
175
+ } = useBluetooth();
176
+
177
+ interface BLEService {
178
+ uuid: string;
179
+ isPrimary: boolean;
180
+ }
181
+
182
+ interface BLECharacteristic {
183
+ uuid: string;
184
+ properties: {
185
+ read: boolean;
186
+ write: boolean;
187
+ notify: boolean;
188
+ indicate: boolean;
189
+ writeNoResponse?: boolean;
190
+ };
191
+ }
192
+
193
+ // --- 状态定义 ---
194
+ const services = ref<BLEService[]>([]);
195
+ const selectedService = ref<BLEService | null>(null);
196
+ const selectedServiceUuid = ref('');
197
+
198
+ const characteristics = ref<BLECharacteristic[]>([]);
199
+ const selectedCharacteristic = ref<BLECharacteristic | null>(null);
200
+ const selectedCharUuid = ref('');
201
+ const isNotifying = ref(false);
202
+
203
+ const sendText = ref('');
204
+ const isHexFormat = ref(true); // 默认以 HEX 显示和发送
205
+ const appendCRLF = ref(false); // 是否自动追加换行
206
+
207
+ // WriteType Pick 选项
208
+ const writeTypeOptionsPick = computed(() => [
209
+ { label: t('bluetooth.auto'), value: '0' },
210
+ { label: 'Write', value: '1' },
211
+ { label: 'WriteNoResp', value: '2' },
212
+ ]);
213
+ const selectedWriteTypePick = ref('0');
214
+
215
+ const logs = ref<
216
+ Array<{ type: 'send' | 'receive' | 'system'; time: string; content: string }>
217
+ >([]);
218
+ const scrollTop = ref(0);
219
+
220
+ // --- 辅助函数 ---
221
+ const getTime = () => {
222
+ const now = new Date();
223
+ return `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}.${now.getMilliseconds().toString().padStart(3, '0')}`;
224
+ };
225
+
226
+ const addLog = (type: 'send' | 'receive' | 'system', content: string) => {
227
+ logs.value.push({ type, time: getTime(), content });
228
+ if (logs.value.length > 500) {
229
+ logs.value.shift(); // 限制最多500条日志
230
+ }
231
+ nextTick(() => {
232
+ scrollTop.value = logs.value.length * 100; // 简单粗暴滚动到底部
233
+ });
234
+ };
235
+
236
+ const clearLogs = () => {
237
+ logs.value = [];
238
+ };
239
+
240
+ // 字符串转 ArrayBuffer
241
+ const stringToArrayBuffer = (str: string) => {
242
+ const buffer = new ArrayBuffer(str.length);
243
+ const dataView = new DataView(buffer);
244
+ for (let i = 0; i < str.length; i++) {
245
+ dataView.setUint8(i, str.charCodeAt(i));
246
+ }
247
+ return buffer;
248
+ };
249
+
250
+ // Hex 字符串转 ArrayBuffer (支持空格分隔或去空格无缝连接)
251
+ const hexStringToArrayBuffer = (hexStr: string) => {
252
+ const hex = hexStr.replace(/\s+/g, '');
253
+ if (hex.length % 2 !== 0) {
254
+ throw new Error('Hex string must have an even number of characters');
255
+ }
256
+ const buffer = new ArrayBuffer(hex.length / 2);
257
+ const dataView = new DataView(buffer);
258
+ for (let i = 0; i < hex.length; i += 2) {
259
+ dataView.setUint8(i / 2, parseInt(hex.substring(i, i + 2), 16));
260
+ }
261
+ return buffer;
262
+ };
263
+
264
+ // ArrayBuffer 转 Hex 字符串
265
+ const arrayBufferToHexString = (buffer: ArrayBuffer) => {
266
+ const uint8Array = new Uint8Array(buffer);
267
+ return Array.from(uint8Array, (byte) =>
268
+ byte.toString(16).padStart(2, '0').toUpperCase(),
269
+ ).join(' ');
270
+ };
271
+
272
+ // ArrayBuffer 转 字符串
273
+ const arrayBufferToString = (buffer: ArrayBuffer) => {
274
+ return String.fromCharCode.apply(
275
+ null,
276
+ new Uint8Array(buffer) as unknown as number[],
277
+ );
278
+ };
279
+
280
+ // --- 业务操作 ---
281
+
282
+ const fetchServices = async () => {
283
+ try {
284
+ uni.showLoading({ title: t('bluetooth.gettingService') });
285
+ const res = await getBLEServices(props.device.deviceId);
286
+ services.value = (res as BLEService[]).filter((item) => item.isPrimary);
287
+ if (services.value.length > 0) {
288
+ addLog(
289
+ 'system',
290
+ `${t('bluetooth.gettingService')} ${services.value.length} ${t('bluetooth.service')}`,
291
+ );
292
+ } else {
293
+ addLog('system', t('bluetooth.noDevice'));
294
+ }
295
+ } catch (error: unknown) {
296
+ const err = error as { message?: string };
297
+ addLog(
298
+ 'system',
299
+ `${t('bluetooth.gettingService')}${t('common.error')}: ${err.message || 'Error'}`,
300
+ );
301
+ } finally {
302
+ uni.hideLoading();
303
+ }
304
+ };
305
+
306
+ const onServiceChangePick = async (item: { value: any }) => {
307
+ const index = services.value.findIndex((s) => s.uuid === item.value);
308
+ if (index === -1) return;
309
+
310
+ selectedService.value = services.value[index];
311
+ selectedCharacteristic.value = null; // 重置特征值
312
+ selectedCharUuid.value = '';
313
+ characteristics.value = [];
314
+ isNotifying.value = false;
315
+
316
+ if (!selectedService.value) return;
317
+
318
+ try {
319
+ uni.showLoading({ title: t('bluetooth.gettingChar') });
320
+ const res = await getBLECharacteristics(
321
+ props.device.deviceId,
322
+ selectedService.value!.uuid as string,
323
+ );
324
+ characteristics.value = res as BLECharacteristic[];
325
+ addLog(
326
+ 'system',
327
+ `${t('bluetooth.gettingChar')} ${res.length} ${t('bluetooth.characteristic')} (${selectedService.value!.uuid})`,
328
+ );
329
+ } catch (error: unknown) {
330
+ const err = error as { message?: string };
331
+ addLog(
332
+ 'system',
333
+ `${t('bluetooth.gettingChar')}${t('common.error')}: ${err.message || 'Error'}`,
334
+ );
335
+ } finally {
336
+ uni.hideLoading();
337
+ }
338
+ };
339
+
340
+ const onCharacteristicChangePick = (item: { value: any }) => {
341
+ const index = characteristics.value.findIndex((c) => c.uuid === item.value);
342
+ if (index === -1) return;
343
+
344
+ selectedCharacteristic.value = characteristics.value[index];
345
+ isNotifying.value = false;
346
+ addLog(
347
+ 'system',
348
+ `${t('bluetooth.characteristic')}: ${selectedCharacteristic.value?.uuid}`,
349
+ );
350
+ };
351
+
352
+ const toggleNotify = async (e: { detail: { value: boolean } }) => {
353
+ if (!selectedCharacteristic.value) return;
354
+ const targetState = (e.detail as { value: boolean }).value;
355
+ try {
356
+ uni.showLoading({
357
+ title: targetState ? t('bluetooth.notifying') : t('bluetooth.stopNotify'),
358
+ });
359
+ await subscribeBLEMessage(
360
+ props.device.deviceId,
361
+ selectedService.value!.uuid as string,
362
+ selectedCharacteristic.value.uuid as string,
363
+ targetState,
364
+ );
365
+ isNotifying.value = targetState;
366
+ addLog(
367
+ 'system',
368
+ `${targetState ? t('bluetooth.notifyEnabled') : t('bluetooth.notifyDisabled')} (${selectedCharacteristic.value.uuid})`,
369
+ );
370
+ } catch (error: unknown) {
371
+ const err = error as { message?: string };
372
+ // 恢复开关状态
373
+ isNotifying.value = !targetState;
374
+ addLog(
375
+ 'system',
376
+ `${t('bluetooth.notifyToggleFail')}: ${err.message || 'Error'}`,
377
+ );
378
+ } finally {
379
+ uni.hideLoading();
380
+ }
381
+ };
382
+
383
+ const sendData = async () => {
384
+ if (!selectedService.value || !selectedCharacteristic.value) {
385
+ uni.showToast({ title: t('bluetooth.selectCharFirst'), icon: 'none' });
386
+ return;
387
+ }
388
+ if (
389
+ !selectedCharacteristic.value.properties?.write &&
390
+ !selectedCharacteristic.value.properties?.writeNoResponse
391
+ ) {
392
+ uni.showToast({ title: t('bluetooth.charNotWritable'), icon: 'none' });
393
+ return;
394
+ }
395
+ if (!sendText.value) return;
396
+
397
+ let finalStr = sendText.value;
398
+ if (appendCRLF.value) {
399
+ if (isHexFormat.value) {
400
+ finalStr += ' 0D 0A';
401
+ } else {
402
+ finalStr += '\r\n';
403
+ }
404
+ }
405
+
406
+ let buffer: ArrayBuffer;
407
+ try {
408
+ if (isHexFormat.value) {
409
+ buffer = hexStringToArrayBuffer(finalStr);
410
+ } else {
411
+ buffer = stringToArrayBuffer(finalStr);
412
+ }
413
+ } catch (_error: unknown) {
414
+ uni.showToast({ title: t('bluetooth.invalidData'), icon: 'none' });
415
+ return;
416
+ }
417
+
418
+ try {
419
+ const rawVal = finalStr;
420
+ let writeTypeStr: string | undefined;
421
+ if (selectedWriteTypePick.value === '1') writeTypeStr = 'write';
422
+ else if (selectedWriteTypePick.value === '2')
423
+ writeTypeStr = 'writeNoResponse';
424
+ else {
425
+ writeTypeStr = selectedCharacteristic.value.properties?.write
426
+ ? 'write'
427
+ : 'writeNoResponse';
428
+ }
429
+
430
+ // 规避底层的 Proxy 或内存池引起的环境桥接报错:
431
+ // 使用纯粹的新 ArrayBuffer 处理
432
+ const pureBuffer = buffer.slice(0);
433
+
434
+ const res = await writeBLEMessage(
435
+ props.device.deviceId,
436
+ selectedService.value!.uuid as string,
437
+ selectedCharacteristic.value.uuid as string,
438
+ pureBuffer,
439
+ writeTypeStr as 'write' | 'writeNoResponse',
440
+ );
441
+ console.log('[DevTool] BLE Write Success:', res);
442
+ addLog('send', rawVal);
443
+ } catch (error: unknown) {
444
+ const err = error as { message?: string };
445
+ addLog('system', `${t('bluetooth.sendFail')}: ${err.message || 'Error'}`);
446
+ }
447
+ };
448
+
449
+ // 监听接收到的消息
450
+ onBLEMessageReceived((res: unknown) => {
451
+ const result = res as {
452
+ deviceId: string;
453
+ characteristicId: string;
454
+ value: ArrayBuffer;
455
+ };
456
+ // 不再过滤当前 deviceId,以便接收所有设备的广播。这里我们将设备ID和特征字附加上,以作区分。
457
+ const arrBuf = result.value;
458
+ const content = isHexFormat.value
459
+ ? arrayBufferToHexString(arrBuf)
460
+ : arrayBufferToString(arrBuf);
461
+
462
+ let prefix = '';
463
+ if (result.deviceId !== props.device.deviceId) {
464
+ prefix = `[${t('bluetooth.fromDevice')}: ${result.deviceId.substring(result.deviceId.length - 5)}] `;
465
+ }
466
+
467
+ // 可以根据需要暴露特征字
468
+ prefix += `[${t('bluetooth.charField')}:${result.characteristicId.substring(4, 8)}] `;
469
+
470
+ addLog('receive', prefix + content);
471
+ });
472
+
473
+ onMounted(() => {
474
+ fetchServices();
475
+ });
476
+
477
+ onUnmounted(() => {
478
+ // 离开时如果正在通知,尝试关闭它以免耗电
479
+ if (
480
+ isNotifying.value &&
481
+ selectedCharacteristic.value &&
482
+ selectedService.value
483
+ ) {
484
+ subscribeBLEMessage(
485
+ props.device.deviceId,
486
+ selectedService.value.uuid as string,
487
+ selectedCharacteristic.value.uuid as string,
488
+ false,
489
+ ).catch(() => {});
490
+ }
491
+ });
492
+ </script>
493
+
494
+ <style scoped>
495
+ .bluetooth-tool {
496
+ position: fixed;
497
+ width: 100vw;
498
+ height: 100%;
499
+ top: 0;
500
+ left: 0;
501
+ display: flex;
502
+ flex-direction: column;
503
+ background-color: var(--dev-tool-bg3-color);
504
+ box-sizing: border-box;
505
+ }
506
+
507
+ .bluetooth-tool-control {
508
+ padding: 0 16px;
509
+ display: flex;
510
+ align-items: center;
511
+ height: 32px;
512
+ border-bottom: 1px solid var(--dev-tool-border-color);
513
+ box-sizing: border-box;
514
+ }
515
+
516
+ .tool-config {
517
+ padding: 12px 16px;
518
+ background: var(--dev-tool-bg-color);
519
+ border-bottom: 2px solid var(--dev-tool-bg3-color);
520
+ }
521
+
522
+ .config-item {
523
+ display: flex;
524
+ align-items: center;
525
+ margin-bottom: 8px;
526
+
527
+ /* 覆盖原生样式的部分,适配 Pick */
528
+ overflow: visible;
529
+ }
530
+
531
+ .label {
532
+ font-size: 12px;
533
+ color: var(--dev-tool-text-color-tips);
534
+ width: 90px;
535
+ flex-shrink: 0;
536
+ }
537
+
538
+ .picker-inner {
539
+ overflow: hidden;
540
+ text-overflow: ellipsis;
541
+ white-space: nowrap;
542
+ }
543
+
544
+ .refresh-btn {
545
+ margin-left: 8px;
546
+ color: var(--dev-tool-primary-color, #007aff);
547
+ cursor: pointer;
548
+ padding: 4px;
549
+ }
550
+
551
+ .char-props {
552
+ font-size: 12px;
553
+ color: var(--dev-tool-text-color-tips);
554
+ margin-top: 4px;
555
+ margin-bottom: 8px;
556
+ display: flex;
557
+ align-items: center;
558
+ flex-wrap: wrap;
559
+ gap: 4px;
560
+ }
561
+
562
+ .prop-tag {
563
+ background: var(--dev-tool-primary-color, #007aff);
564
+ color: #fff;
565
+ padding: 2px 6px;
566
+ border-radius: 4px;
567
+ font-size: 10px;
568
+ }
569
+
570
+ .notify-switch {
571
+ display: flex;
572
+ justify-content: space-between;
573
+ align-items: center;
574
+ font-size: 12px;
575
+ color: var(--dev-tool-text-color);
576
+ border-top: 1px dashed var(--dev-tool-border-color);
577
+ padding-top: 8px;
578
+ }
579
+
580
+ .log-area {
581
+ flex: 1;
582
+ display: flex;
583
+ flex-direction: column;
584
+ overflow: hidden;
585
+ padding: 12px 16px;
586
+ }
587
+
588
+ .log-scroll {
589
+ flex: 1;
590
+ border: 1px solid var(--dev-tool-border-color);
591
+ border-radius: 4px;
592
+ background: var(--dev-tool-component-bg, #fafafa);
593
+ padding: 8px;
594
+ overflow-y: auto;
595
+ }
596
+
597
+ .log-item {
598
+ display: flex;
599
+ align-items: flex-start;
600
+ margin-bottom: 6px;
601
+ word-break: break-all;
602
+ }
603
+
604
+ .log-time {
605
+ color: var(--dev-tool-text-color-tips);
606
+ margin-right: 8px;
607
+ flex-shrink: 0;
608
+ }
609
+
610
+ .log-dir {
611
+ font-weight: bold;
612
+ margin-right: 8px;
613
+ width: 40px;
614
+ flex-shrink: 0;
615
+ }
616
+
617
+ .log-content {
618
+ flex: 1;
619
+ }
620
+
621
+ .log-item.system .log-dir {
622
+ color: #8f8f8f;
623
+ }
624
+
625
+ .log-item.system .log-content {
626
+ color: #8f8f8f;
627
+ }
628
+
629
+ .log-item.send .log-dir {
630
+ color: #ff9500;
631
+ }
632
+
633
+ .log-item.send .log-content {
634
+ color: var(--dev-tool-text-color);
635
+ }
636
+
637
+ .log-item.receive .log-dir {
638
+ color: #4cd964;
639
+ }
640
+
641
+ .log-item.receive .log-content {
642
+ color: var(--dev-tool-primary-color, #007aff);
643
+ }
644
+
645
+ .empty-log {
646
+ text-align: center;
647
+ color: var(--dev-tool-text-color-tips);
648
+ margin-top: 20px;
649
+ font-size: 12px;
650
+ }
651
+
652
+ .log-actions {
653
+ display: flex;
654
+ justify-content: space-between;
655
+ align-items: center;
656
+ margin-top: 8px;
657
+ font-size: 12px;
658
+ }
659
+
660
+ .format-switch {
661
+ color: var(--dev-tool-primary-color, #007aff);
662
+ cursor: pointer;
663
+ }
664
+
665
+ .clear-btn {
666
+ color: #ff3b30;
667
+ cursor: pointer;
668
+ }
669
+
670
+ .send-container {
671
+ display: flex;
672
+ flex-direction: column;
673
+ border-top: 1px solid var(--dev-tool-border-color);
674
+ background: var(--dev-tool-component-bg, #f8f8f8);
675
+ }
676
+
677
+ .send-options {
678
+ display: flex;
679
+ align-items: center;
680
+ padding: 4px 16px;
681
+ font-size: 12px;
682
+ color: var(--dev-tool-text-color-tips);
683
+ gap: 16px;
684
+ border-bottom: 1px dashed var(--dev-tool-border-color);
685
+ }
686
+
687
+ .opt-item {
688
+ display: flex;
689
+ align-items: center;
690
+ }
691
+
692
+ .mode-picker {
693
+ margin-left: 4px;
694
+ color: var(--dev-tool-primary-color, #007aff);
695
+ }
696
+
697
+ .send-area {
698
+ display: flex;
699
+ padding: 8px 16px;
700
+ height: 80px;
701
+ }
702
+
703
+ .send-input {
704
+ flex: 1;
705
+ border: 1px solid var(--dev-tool-border-color);
706
+ border-radius: 4px;
707
+ padding: 8px;
708
+ background: var(--dev-tool-bg-color);
709
+ font-size: 14px;
710
+ height: 100%;
711
+ box-sizing: border-box;
712
+ }
713
+
714
+ .send-btn {
715
+ width: 60px;
716
+ margin-left: 12px;
717
+ display: flex;
718
+ align-items: center;
719
+ justify-content: center;
720
+ background: var(--dev-tool-primary-color, #007aff);
721
+ color: #fff;
722
+ border-radius: 4px;
723
+ font-size: 14px;
724
+ cursor: pointer;
725
+ }
726
+
727
+ .send-btn:active {
728
+ opacity: 0.8;
729
+ }
730
+ </style>