uni-oaview 1.2.4 → 1.3.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.
@@ -0,0 +1,54 @@
1
+ <template>
2
+ <view class="dev-tools">
3
+ <view class="section">
4
+ <text class="section-title">快捷入口</text>
5
+ <view class="page-item" @click="handleNavigateToMock">
6
+ <text class="page-title">Mock 页面</text>
7
+ <text class="page-path">pages/mock/index</text>
8
+ </view>
9
+ </view>
10
+ </view>
11
+ </template>
12
+
13
+ <script lang="ts" setup>
14
+ const handleNavigateToMock = () => {
15
+ uni.navigateTo({ url: '/pages/mock/index' });
16
+ };
17
+ </script>
18
+
19
+ <style lang="scss" scoped>
20
+ .dev-tools {
21
+ font-size: 12px;
22
+ }
23
+
24
+ .section {
25
+ margin-bottom: 16px;
26
+ }
27
+
28
+ .section-title {
29
+ display: block;
30
+ margin-bottom: 8px;
31
+ font-size: 14px;
32
+ font-weight: 500;
33
+ color: #333;
34
+ }
35
+
36
+ .page-item {
37
+ display: flex;
38
+ flex-direction: column;
39
+ gap: 4px;
40
+ padding: 12px 16px;
41
+ background: #f5f5f5;
42
+ border-radius: 8px;
43
+ }
44
+
45
+ .page-title {
46
+ font-weight: 500;
47
+ color: #333;
48
+ }
49
+
50
+ .page-path {
51
+ font-size: 10px;
52
+ color: #999;
53
+ }
54
+ </style>
@@ -1,65 +1,86 @@
1
1
  <template>
2
- <uni-collapse>
2
+ <uni-collapse ref="collapseRef" v-model="activeNames">
3
3
  <uni-collapse-item
4
- v-for="(log, index) in networkLogs"
5
- :title="getTitle(log)"
6
- :key="log.request.url + log.response?.data?.errno"
4
+ v-for="meta in networkLogMetas"
5
+ :key="meta.id"
6
+ :name="meta.id"
7
+ :title="meta.title"
7
8
  :class="{
8
- 'uni-collapse-item-failed': log.response && (log.response.statusCode !== 200 || log.response.data?.errno !== 0),
9
+ 'uni-collapse-item-failed': meta.failed,
10
+ 'uni-collapse-item-pending': meta.pending,
9
11
  }"
10
12
  >
11
13
  <view style="font-size: 10px; padding: 10px 15px">
12
14
  <view style="word-break: break-all; padding: 8px 0; margin-bottom: 10px">
13
15
  <text :selectable="true" style="font-weight: bolder">状态码:</text>
14
- {{ log.request.method }} {{ log.response?.statusCode }}
15
- </view>
16
- <view style="word-break: break-all; padding: 8px 0; margin-bottom: 10px">
17
- <text :selectable="true" style="font-weight: bolder">请求头:</text>
18
- <awesome-display-info :log="log.request.header" />
19
- </view>
20
- <view style="word-break: break-all; padding: 8px 0; margin-bottom: 10px">
21
- <text :selectable="true" style="font-weight: bolder">请求参数:</text>
22
- <awesome-display-info :log="log.request.data" />
23
- </view>
24
- <view style="word-break: break-all; padding: 8px 0">
25
- <text :selectable="true" style="font-weight: bolder">响应数据:</text>
26
- <awesome-display-info :log="log.response" />
16
+ {{ meta.method }} {{ meta.statusCode ?? '-' }}
27
17
  </view>
18
+ <template v-if="isExpanded(meta.id)">
19
+ <view style="word-break: break-all; padding: 8px 0; margin-bottom: 10px">
20
+ <text :selectable="true" style="font-weight: bolder">请求头:</text>
21
+ <awesome-display-info :log="getNetworkLogDetail(meta.id)?.request?.header" />
22
+ </view>
23
+ <view style="word-break: break-all; padding: 8px 0; margin-bottom: 10px">
24
+ <text :selectable="true" style="font-weight: bolder">请求参数:</text>
25
+ <awesome-display-info :log="getNetworkLogDetail(meta.id)?.request?.data" />
26
+ </view>
27
+ <view style="word-break: break-all; padding: 8px 0">
28
+ <text :selectable="true" style="font-weight: bolder">响应数据:</text>
29
+ <awesome-display-info :log="getNetworkLogDetail(meta.id)?.response" />
30
+ </view>
31
+ </template>
28
32
  </view>
29
33
  </uni-collapse-item>
30
34
  </uni-collapse>
31
35
  </template>
32
36
 
33
37
  <script lang="ts" setup>
34
- import { ref, onBeforeUnmount } from 'vue';
38
+ import { nextTick, onBeforeUnmount, ref, shallowRef, watch } from 'vue';
35
39
  import awesomeDisplayInfo from './awesome-display-info.vue';
36
40
  import { networkSubject, getNetworkLogs } from 'uniapp-log-sdk';
37
41
 
42
+ interface NetworkLogRequest {
43
+ url: string;
44
+ method?: string;
45
+ header?: unknown;
46
+ data?: unknown;
47
+ }
48
+
49
+ interface NetworkLogResponseData {
50
+ errno?: number;
51
+ [key: string]: unknown;
52
+ }
53
+
54
+ interface NetworkLogResponse {
55
+ statusCode?: number;
56
+ data?: NetworkLogResponseData;
57
+ [key: string]: unknown;
58
+ }
59
+
38
60
  interface NetworkLog {
39
- request: any;
40
- response: any;
61
+ request: NetworkLogRequest;
62
+ response?: NetworkLogResponse;
41
63
  startTime: number;
42
- endTime: number;
64
+ endTime?: number;
43
65
  }
44
66
 
45
- const MAX_NETWORK_LOGS = 100;
67
+ interface NetworkLogMeta {
68
+ id: string;
69
+ title: string;
70
+ method: string;
71
+ statusCode: number | null;
72
+ pending: boolean;
73
+ failed: boolean;
74
+ }
46
75
 
47
- const networkLogs = ref<NetworkLog[]>(getNetworkLogs().slice(-MAX_NETWORK_LOGS));
76
+ const MAX_NETWORK_LOGS = 100;
48
77
 
49
- const stop = networkSubject.subscribe((data: NetworkLog[]) => {
50
- if (data.length > MAX_NETWORK_LOGS) {
51
- networkLogs.value = data.slice(-MAX_NETWORK_LOGS);
52
- } else {
53
- networkLogs.value = data;
54
- }
55
- });
78
+ const collapseRef = ref<{ resize?: () => void } | null>(null);
79
+ // uni-collapse v-model 在不同模式下可能是 string 或 string[]
80
+ const activeNames = ref<string[] | string>([]);
56
81
 
57
- const getTitle = (log: NetworkLog): string => {
58
- const { startTime, endTime, request } = log;
59
- const timeStr = formatTime(startTime);
60
- const duration = endTime && startTime ? `${((endTime - startTime) / 1000).toFixed(2)}s` : 'pending';
61
- return `[${timeStr}] ${request.url};${duration}`;
62
- };
82
+ const networkLogDetailMap = shallowRef<Map<string, NetworkLog>>(new Map());
83
+ const networkLogMetas = ref<NetworkLogMeta[]>([]);
63
84
 
64
85
  const formatTime = (timestamp: number): string => {
65
86
  const date = new Date(timestamp);
@@ -73,6 +94,99 @@
73
94
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`;
74
95
  };
75
96
 
97
+ const buildNetworkLogId = (log: NetworkLog): string => {
98
+ const url = log.request?.url ?? '';
99
+ const method = log.request?.method ?? '';
100
+ const startTime = log.startTime ?? 0;
101
+ return `${startTime}|${method}|${url}`;
102
+ };
103
+
104
+ const buildMetaFromLog = (log: NetworkLog): NetworkLogMeta => {
105
+ const { startTime, endTime, request, response } = log;
106
+ const timeStr = formatTime(startTime);
107
+ const duration = endTime && startTime ? `${((endTime - startTime) / 1000).toFixed(2)}s` : 'pending';
108
+ const statusCode = typeof response?.statusCode === 'number' ? response.statusCode : null;
109
+ const errno = response?.data?.errno;
110
+ const pending = !endTime;
111
+ const failed = !pending && Boolean(response && (statusCode !== 200 || (typeof errno === 'number' && errno !== 0)));
112
+ const url = request?.url ?? '';
113
+ const method = request?.method ?? '';
114
+ return {
115
+ id: buildNetworkLogId(log),
116
+ title: `[${timeStr}] ${url};${duration}`,
117
+ method,
118
+ statusCode,
119
+ pending,
120
+ failed,
121
+ };
122
+ };
123
+
124
+ const getActiveNameList = (): string[] => {
125
+ const value = activeNames.value;
126
+ return Array.isArray(value) ? value : value ? [value] : [];
127
+ };
128
+
129
+ const isExpanded = (id: string): boolean => getActiveNameList().includes(id);
130
+
131
+ const getNetworkLogDetail = (id: string): NetworkLog | null => {
132
+ return networkLogDetailMap.value.get(id) ?? null;
133
+ };
134
+
135
+ const upsertLogs = (logs: NetworkLog[]) => {
136
+ const nextLogs = logs.slice(-MAX_NETWORK_LOGS);
137
+ const detailMap = networkLogDetailMap.value;
138
+
139
+ const nextMetas: NetworkLogMeta[] = [];
140
+ for (const log of nextLogs) {
141
+ const id = buildNetworkLogId(log);
142
+ detailMap.set(id, log);
143
+ nextMetas.push(buildMetaFromLog(log));
144
+ }
145
+
146
+ networkLogMetas.value = nextMetas;
147
+
148
+ // 清理 detailMap,避免无限增长
149
+ const keepIds = new Set(nextMetas.map((m) => m.id));
150
+ for (const key of detailMap.keys()) {
151
+ if (!keepIds.has(key)) detailMap.delete(key);
152
+ }
153
+
154
+ // 清理 activeNames(如果展开项已被裁剪掉)
155
+ const currentActive = activeNames.value;
156
+ if (Array.isArray(currentActive)) {
157
+ if (currentActive.length) activeNames.value = currentActive.filter((id) => keepIds.has(id));
158
+ } else if (currentActive && !keepIds.has(currentActive)) {
159
+ activeNames.value = '';
160
+ }
161
+ };
162
+
163
+ upsertLogs(getNetworkLogs());
164
+
165
+ const stop = networkSubject.subscribe((data: NetworkLog[]) => {
166
+ upsertLogs(data);
167
+ });
168
+
169
+ // 展开项变化时刷新 collapse 高度缓存(小程序端必须手动调用,否则内容高度可能为 0)
170
+ watch(
171
+ () => getActiveNameList().join('|'),
172
+ () => {
173
+ // #ifdef MP
174
+ nextTick(() => collapseRef.value?.resize?.());
175
+ // #endif
176
+ },
177
+ );
178
+
179
+ // 列表更新时,如果有已展开项(内容高度可能变化),同步刷新高度缓存
180
+ watch(
181
+ () => networkLogMetas.value,
182
+ () => {
183
+ // #ifdef MP
184
+ if (getActiveNameList().length) nextTick(() => collapseRef.value?.resize?.());
185
+ // #endif
186
+ },
187
+ { deep: false },
188
+ );
189
+
76
190
  onBeforeUnmount(() => {
77
191
  stop?.unsubscribe();
78
192
  });
@@ -84,11 +198,11 @@
84
198
  }
85
199
  ::v-deep {
86
200
  .uni-collapse-item__title-text {
87
- white-space: normal !important; /* 允许文本换行 */
88
- overflow: visible !important; /* 显示溢出的文本 */
89
- text-overflow: clip !important; /* 不显示省略标记(...) */
90
- line-height: normal !important; /* 重置行高 */
91
- word-break: break-all !important; /* 允许单词内换行 */
201
+ white-space: normal !important;
202
+ overflow: visible !important;
203
+ text-overflow: clip !important;
204
+ line-height: normal !important;
205
+ word-break: break-all !important;
92
206
  font-size: 10px;
93
207
  font-weight: normal;
94
208
  }
@@ -97,5 +211,10 @@
97
211
  color: red !important;
98
212
  }
99
213
  }
214
+ .uni-collapse-item-pending {
215
+ .uni-collapse-item__title-text {
216
+ color: #f90 !important;
217
+ }
218
+ }
100
219
  }
101
220
  </style>
@@ -1,12 +1,19 @@
1
1
  <template>
2
2
  <uni-popup ref="popupRef" background-color="#fff">
3
3
  <view style="height: 500px; background-color: white; width: 100vw">
4
- <uni-segmented-control
5
- styleType="text"
6
- :values="list"
7
- :current="current"
8
- @clickItem="handleTabClick"
9
- ></uni-segmented-control>
4
+ <scroll-view scroll-x class="tab-scroll" :show-scrollbar="false">
5
+ <view class="tab-container">
6
+ <view
7
+ v-for="(item, index) in list"
8
+ :key="item"
9
+ class="tab-item"
10
+ :class="{ active: current === index }"
11
+ @click="handleTabClick(index)"
12
+ >
13
+ <text class="tab-text">{{ item }}</text>
14
+ </view>
15
+ </view>
16
+ </scroll-view>
10
17
  <view style="height: calc(100% - 48px); overflow-y: auto; padding: 8px">
11
18
  <template v-if="current === 0">
12
19
  <Console />
@@ -26,6 +33,9 @@
26
33
  <template v-if="current === 5">
27
34
  <System />
28
35
  </template>
36
+ <template v-if="current === 6">
37
+ <DevTools />
38
+ </template>
29
39
  </view>
30
40
  </view>
31
41
  </uni-popup>
@@ -51,17 +61,14 @@
51
61
  import Storage from './storage.vue';
52
62
  import Console from './console.vue';
53
63
  import System from './system.vue';
54
-
55
- interface SegmentedControlClickEvent {
56
- currentIndex: number;
57
- }
64
+ import DevTools from './dev-tools.vue';
58
65
 
59
66
  const popupRef = ref<any>();
60
- const list = ref(['console', 'network', 'event', 'error', 'storage', 'system']);
67
+ const list = ref(['console', 'network', 'event', 'error', 'storage', 'system', 'dev-tools']);
61
68
  const current = ref(0);
62
69
 
63
- const handleTabClick = (e: SegmentedControlClickEvent) => {
64
- current.value = e.currentIndex;
70
+ const handleTabClick = (index: number) => {
71
+ current.value = index;
65
72
  };
66
73
 
67
74
  const openDebug = () => {
@@ -103,4 +110,49 @@
103
110
  box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
104
111
  background-color: white;
105
112
  }
113
+
114
+ .tab-scroll {
115
+ height: 48px;
116
+ white-space: nowrap;
117
+ }
118
+
119
+ .tab-container {
120
+ display: flex;
121
+ flex-direction: row;
122
+ height: 100%;
123
+ padding: 0 8px;
124
+ }
125
+
126
+ .tab-item {
127
+ display: flex;
128
+ align-items: center;
129
+ justify-content: center;
130
+ padding: 0 6px;
131
+ height: 100%;
132
+ flex-shrink: 0;
133
+ position: relative;
134
+
135
+ &.active {
136
+ .tab-text {
137
+ color: #007aff;
138
+ font-weight: 500;
139
+ }
140
+
141
+ &::after {
142
+ content: '';
143
+ position: absolute;
144
+ bottom: 0;
145
+ left: 6px;
146
+ right: 6px;
147
+ height: 2px;
148
+ background-color: #007aff;
149
+ }
150
+ }
151
+ }
152
+
153
+ .tab-text {
154
+ font-size: 14px;
155
+ color: #666;
156
+ white-space: nowrap;
157
+ }
106
158
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uni-oaview",
3
- "version": "1.2.04",
3
+ "version": "1.3.00",
4
4
  "description": "uniapp小程序组件库",
5
5
  "main": "dist/index.esm.js",
6
6
  "typings": "dist/index.d.ts",