@tmsfe/tms-core 0.0.59 → 0.0.62

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": "@tmsfe/tms-core",
3
- "version": "0.0.59",
3
+ "version": "0.0.62",
4
4
  "description": "tms运行时框架",
5
5
  "repository": {
6
6
  "type": "git",
package/src/index.js CHANGED
@@ -25,11 +25,14 @@ import {
25
25
  round,
26
26
  } from './numUtils';
27
27
  import {
28
+ groupTimeDuration,
29
+ formatTimeDuration,
28
30
  formatTime,
29
31
  formatTimeStr,
30
32
  formatTimeWithDetails,
31
33
  formatDateTime,
32
34
  dateToString,
35
+ parseDateTime,
33
36
  } from './timeUtils';
34
37
  import {
35
38
  ipxInit,
@@ -157,11 +160,14 @@ const api = {
157
160
  round,
158
161
 
159
162
  /* 时间方法 */
163
+ groupTimeDuration,
164
+ formatTimeDuration,
160
165
  formatTime,
161
166
  formatTimeStr,
162
167
  formatTimeWithDetails,
163
168
  formatDateTime,
164
169
  dateToString,
170
+ parseDateTime,
165
171
 
166
172
  /* IPX方法 */
167
173
  ipxInit,
@@ -9,6 +9,31 @@ import clone from './clone';
9
9
 
10
10
  let originalComponent: any = null;
11
11
 
12
+ // 劫持Component的生命周期
13
+ function proxyLifeMethod(componentName: string, componentOptions: any, methodName: string): void {
14
+ /* eslint-disable */
15
+ componentOptions.lifetimes = componentOptions.lifetimes || {};
16
+ let original = helper.emptyFunc;
17
+ const proxyMethod = function (...args: any[]): any {
18
+ // 生命周期函数先发埋点,避免次过程用户退出而丢失埋点
19
+ const data = clone.deepClone(this.data);
20
+ const eventName = `Component_${componentName}_${methodName}`;
21
+ helper.reportData(eventName, data);
22
+ // 执行原函数
23
+ return helper.executeFunc(this, original, args, helper.emptyFunc);
24
+ }
25
+ if (componentOptions.lifetimes[methodName]) {
26
+ original = componentOptions.lifetimes[methodName];
27
+ componentOptions.lifetimes[methodName] = proxyMethod;
28
+ } else if (componentOptions[methodName]) {
29
+ original = componentOptions[methodName];
30
+ componentOptions[methodName] = proxyMethod;
31
+ } else {
32
+ componentOptions.lifetimes[methodName] = proxyMethod;
33
+ }
34
+ /* eslint-enable */
35
+ }
36
+
12
37
  // 劫持绑定事件
13
38
  function proxyBindEvent(componentName: string, methods: any, methodName: string): void {
14
39
  const original = methods[methodName];
@@ -30,7 +55,6 @@ function proxyBindEvent(componentName: string, methods: any, methodName: string)
30
55
 
31
56
  // 劫持Page
32
57
  function init(): void {
33
- // @ts-ignore
34
58
  originalComponent = Component;
35
59
  // @ts-ignore
36
60
  Component = function (options: any) {
@@ -41,6 +65,7 @@ function init(): void {
41
65
  for (const name of options.tmsReportEvents) {
42
66
  proxyBindEvent(options.tmsComponentName, methods, name);
43
67
  }
68
+ proxyLifeMethod(options.tmsComponentName, options, 'ready');
44
69
  }
45
70
  originalComponent(options);
46
71
  };
@@ -101,9 +101,7 @@ function proxyBindEvent(pageOptions: any, methodName: string): void {
101
101
 
102
102
  // 劫持Page
103
103
  function proxyPage(): void {
104
- // @ts-ignore
105
104
  originalPage = Page;
106
- // @ts-ignore
107
105
  Page = function (options: any) {
108
106
  // 只有tms的页面才需要上报
109
107
  if (options.tmsAutoReport) {
@@ -122,9 +120,8 @@ function proxyPage(): void {
122
120
  // 劫持导航api
123
121
  function proxyNavigateApi(api: string): void {
124
122
  // @ts-ignore
125
- const owx = wx;
126
- const originalApi = owx[api];
127
- Object.defineProperty(owx, api, {
123
+ const originalApi = wx[api];
124
+ Object.defineProperty(wx, api, {
128
125
  writable: true,
129
126
  enumerable: true,
130
127
  configurable: true,
@@ -49,10 +49,11 @@ function batchSendData(): void {
49
49
  const request = helper.getTms().createRequest();
50
50
  request.post('basic/event/upload', { batch })
51
51
  .then((res: any) => {
52
- if (res.errCode !== 0) {
53
- requestFail(batch);
54
- } else {
52
+ if (res.errCode === 0) {
55
53
  startTimer();
54
+ } else if (res.errMsg !== 'Invalid or expired session!') {
55
+ // 如果是多端登录了,就不要再发了
56
+ requestFail(batch);
56
57
  }
57
58
  })
58
59
  .catch((e: any) => {
package/src/timeUtils.js CHANGED
@@ -7,6 +7,86 @@
7
7
  * 2017-07-26 @davislu modify.
8
8
  */
9
9
 
10
+ import { round } from './numUtils';
11
+ import { roundStr } from './stringUtils';
12
+
13
+ /**
14
+ * 将时间段进行聚合计算,得出不同时间单位的计数值,例如:3601秒 -> { hour: 1, minute: 0, second: 1 },即:1小时0分1秒
15
+ * @param {Number} seconds 时间段的总秒数
16
+ * @param {String} maxUnit 最大计数单位:second-秒,minute-分,hour-小时,day-天,month-月,year-年;默认 year
17
+ * 单位大小:year > Month > day > hour > month > second
18
+ * @param {String} minUnit 最小计数单位:second-秒,minute-分,hour-小时,day-天,month-月,year-年;默认 second
19
+ * @param {Number} decimal 有不足最小单位数值的情况下,保留几位小数;默认 2
20
+ * @returns {Object} result 各计数单位下的数值:
21
+ * {Number} result.year - 年; 当maxUnit < 'year' 时,此字段永远为0;此字段为-1时,表示 seconds 参数非法 或 minUnit > maxUnit等异常情况
22
+ * {Number} result.month - 月; 当maxUnit < 'month' 时,此字段永远为0;
23
+ * {Number} result.day - 天; 当maxUnit < 'day' 时,此字段永远为0;
24
+ * {Number} result.hour - 小时;当maxUnit < 'hour' 时,此字段永远为0;
25
+ * {Number} result.minute - 分钟;当maxUnit < 'minute' 时,此字段永远为0;
26
+ * {Number} result.second - 秒;
27
+ * {Number} result.decimal - 不足最小单位的部分,具体含义与 minUnit 参数相关;例如 minUnit = day 时,此字段表示不足1天的部分
28
+ */
29
+ const groupTimeDuration = (seconds, maxUnit = 'year', minUnit = 'second', decimal = 2) => {
30
+ // 时间参数(seconds)检查
31
+ const sec = typeof seconds === 'number' ? seconds : parseFloat(seconds);
32
+ if (isNaN(sec)) return { year: -1, month: 0, day: 0, hour: 0, minute: 0, second: 0, decimal: 0 };
33
+
34
+ // 各时间单位对应的秒数
35
+ const unitSeconds = { year: 31536000, month: 2592000, day: 86400, hour: 3600, minute: 60, second: 1 };
36
+
37
+ // 时间单位参数(minUnit,maxUnit)检查
38
+ const minSeconds = unitSeconds[minUnit] || unitSeconds.second;
39
+ const maxSeconds = unitSeconds[maxUnit] || unitSeconds.year;
40
+ if (maxSeconds < minSeconds) return { year: -1, month: 0, day: 0, hour: 0, minute: 0, second: 0, decimal: 0 };
41
+
42
+ // 截取在 minUnit maxUnit 之间的计数单位,并按从大到小排序
43
+ const allUnitKeys = Object.keys(unitSeconds);
44
+ const groupUnits = allUnitKeys
45
+ .filter(unit => unitSeconds[unit] >= minSeconds && unitSeconds[unit] <= maxSeconds) // 过滤超出范围的单位
46
+ .sort((a, b) => unitSeconds[b] - unitSeconds[a]) // 排序
47
+ .map(unit => ({ name: unit, seconds: unitSeconds[unit] })); // 转换为 { name, seconds } 的形式,方便后面用
48
+
49
+ // 各返回字段初始赋值
50
+ const result = { decimal: 0 };
51
+ allUnitKeys.forEach(unit => result[unit] = 0);
52
+ // 从大到小逐个计算赋值
53
+ // const periodArr = [];
54
+ let restSeconds = sec; // 剩余待统计描述
55
+ groupUnits.forEach((unit) => {
56
+ const curUnitNum = Math.floor(restSeconds / unit.seconds); // 高于当前计数单位的数值
57
+ result[unit.name] = curUnitNum; // 例如 { year: 1 }
58
+ restSeconds %= unit.seconds;
59
+ if (unit.seconds === minSeconds) result.decimal = round(restSeconds / minSeconds, decimal);
60
+ });
61
+ return result;
62
+ };
63
+
64
+ /**
65
+ * 将时间段进行聚合计算并格式化,得出方便人阅读的文案,例如:3601秒 -> 1小时0分1秒
66
+ * @param {Number} seconds 时间段的总秒数
67
+ * @param {String} maxUnit 最大计数单位:second-秒,minute-分,hour-小时,day-天,month-月,year-年;默认 year
68
+ * 单位大小:year > Month > day > hour > month > second
69
+ * @param {String} minUnit 最小计数单位:second-秒,minute-分,hour-小时,day-天,month-月,year-年;默认 second
70
+ * @param {Number} decimal 有不足最小单位数值的情况下,保留几位小数;默认 2
71
+ * @returns {String} 示例见测试用例
72
+ */
73
+ const formatTimeDuration = (seconds, maxUnit = 'year', minUnit = 'second', decimal = 2) => {
74
+ const result = groupTimeDuration(seconds, maxUnit, minUnit, decimal);
75
+ if (result.year === -1) return ''; // seconds 不合法 或 maxUnit < minUnit
76
+ const unitWording = { year: '年', month: '月', day: '天', hour: '小时', minute: '分钟', second: '秒' };
77
+ const periodArr = [];
78
+ ['year', 'month', 'day', 'hour', 'minute', 'second'].forEach((unit) => {
79
+ if (unit !== minUnit) {
80
+ if (result[unit] > 0) periodArr.push(`${result[unit]}${unitWording[unit]}`); // 如2小时
81
+ } else { // 当前unit是最小单位,处理不足最小单位的部分
82
+ const min = round(result[unit] + result.decimal, decimal);
83
+ if (min > 0) periodArr.push(`${roundStr(min, 2, true)}${unitWording[unit]}`); // 如1.2,0.2
84
+ else if (periodArr.length === 0) periodArr.push(`1${unitWording[unit]}内`); // 1 unit内
85
+ }
86
+ });
87
+ return periodArr.join('');
88
+ };
89
+
10
90
  /**
11
91
  * @function
12
92
  * @description 格式化时间
@@ -15,72 +95,44 @@
15
95
  */
16
96
  const formatTime = (seconds) => {
17
97
  if (typeof seconds !== 'number') return seconds;
18
-
19
- const PER_MINUTE = 60 * 1;
20
- const PER_HOUR = 60 * PER_MINUTE;
21
- const PRE_DAY = 24 * PER_HOUR;
22
- let cost = '';
98
+ const { day, hour, minute, decimal } = groupTimeDuration(seconds, 'day', 'minute', 2);
23
99
  // >24小时的显示 【x天】
24
- if (seconds >= PRE_DAY) {
25
- cost = `${Math.floor(seconds / PRE_DAY)}天`;
100
+ if (day > 0) return `${day}天`;
26
101
  // <1小时的显示 【x分钟】 ,x取整数上限,最低为1分钟。
27
- } else if (seconds < PER_HOUR) {
28
- cost = `${Math.ceil(seconds / PER_MINUTE)}分钟`;
102
+ if (hour === 0) return `${Math.ceil(minute + decimal)}分钟`;
29
103
  // <24小时&>1小时的显示 【x小时y分钟】 ,分钟取整数上限
30
- } else {
31
- cost = `${Math.floor(seconds / PER_HOUR)}小时`;
32
- const s = seconds % PER_HOUR;
33
- if (s > 0) {
34
- cost += `${Math.ceil(s / PER_MINUTE)}分钟`;
35
- }
36
- }
37
-
104
+ let cost = `${hour}小时`;
105
+ const min = Math.ceil(minute + decimal);
106
+ if (min > 0) cost += `${min}分钟`;
38
107
  return cost;
39
108
  };
40
109
 
41
110
  /**
42
111
  * @function
43
112
  * @description 将秒数格式化为x天y小时z分钟
44
- * @param {Number} oriSeconds 秒数
113
+ * @param {Number} seconds 秒数
45
114
  * @returns {String} 格式化后的文案
46
115
  */
47
- const formatTimeWithDetails = (oriSeconds) => {
48
- let seconds = oriSeconds;
116
+ const formatTimeWithDetails = (seconds) => {
49
117
  // 非Number类型,直接返回,不进行处理
50
118
  if (typeof seconds !== 'number') return seconds;
51
119
  // 参数为NaN类型,直接抛出异常
52
120
  if (isNaN(seconds)) throw new Error(`formatTimeWithDetails方法的参数seconds必须时一个非NaN数字,现在的值为${seconds}`);
53
121
 
54
- // 定义一些常量
55
- // 1分钟包含的秒数
56
- const PER_MINUTE = 60 * 1;
57
- // 1小时包含的秒数
58
- const PER_HOUR = 60 * PER_MINUTE;
59
- // 1天包含的秒数
60
- const PRE_DAY = 24 * PER_HOUR;
122
+ const { day, hour, minute, decimal } = groupTimeDuration(seconds, 'day', 'minute', 2);
61
123
  let cost = '';
62
-
63
- // 秒数多于1天
64
- if (seconds >= PRE_DAY) {
65
- cost = `${Math.floor(seconds / PRE_DAY)}天`;
66
- seconds %= PRE_DAY;
124
+ const min = Math.ceil(minute + decimal);
125
+ if (day > 0) { // 秒数多于1天
126
+ cost = `${day}天${hour}小时`;
127
+ // 这里大概率是有bug的,从测试用例里能看出:formatTime(86400) => 1天0小时0分钟
128
+ // 但为了不改变方法原有表现,先这样吧
129
+ if (min > 0 || hour === 0) cost += `${min}分钟`;
130
+ } else if (hour === 0) { // 秒数小于1小时
131
+ cost = `${min}分钟`;
132
+ } else { // 秒数介于1天和1分钟之间
133
+ cost = `${hour}小时`;
134
+ if (min > 0) cost += `${min}分钟`;
67
135
  }
68
-
69
- // 秒数小于1小时
70
- if (seconds < PER_HOUR) {
71
- if (cost) {
72
- cost += '0小时';
73
- }
74
- cost += `${Math.ceil(seconds / PER_MINUTE)}分钟`;
75
- } else {
76
- // 秒数介于1天和1分钟之间
77
- cost += `${Math.floor(seconds / PER_HOUR)}小时`;
78
- const s = seconds % PER_HOUR;
79
- if (s > 0) {
80
- cost += `${Math.ceil(s / PER_MINUTE)}分钟`;
81
- }
82
- }
83
-
84
136
  return cost;
85
137
  };
86
138
 
@@ -157,10 +209,46 @@ const dateToString = (date, withTime = false, withSeconds = false, join = '-') =
157
209
  return formatDateTime(date || new Date(), fmt);
158
210
  };
159
211
 
212
+ /**
213
+ * 将时间字符串转换为Date对象
214
+ * @param {String} str 时间字符串,如:2022-03-24 20:02:05
215
+ * 支持对日期完整但时间部分缺失的情况进行处理,如:
216
+ * 2022-03-24 20:02 -> Date(1648123320000)
217
+ * 2022-03-24 20: -> Date(1648123200000)
218
+ * 2022-03-24 20 -> Date(1648123200000)
219
+ * 2022-03-24 -> Date(1648051200000)
220
+ * 小于10的数字可以省略0:
221
+ * 2022-3-24 20:2 -> Date(1648123320000)
222
+ * 支持任意分割符,如:
223
+ * 2022年03月24日 20时02分 -> Date(1648123320000)
224
+ * 2022/03/24 20:02:05 -> Date(1648123325000)
225
+ * @returns {Date|Null} 时间对象;转换失败时返回null
226
+ */
227
+ const parseDateTime = (str) => {
228
+ const arr = new RegExp(/(\d{4})[^\d]+(\d{1,2})[^\d]+(\d{1,2})(.*)/, 'g').exec(str); // 分割日期和时间
229
+ if (!(arr && arr.index > -1)) return null; // 日期不合法,转换失败
230
+ const timeArr = new RegExp(/[^\d]?(\d{1,2})([^\d]+(\d{1,2})([^\d]+(\d{1,2}))?)?/, 'g').exec(arr[4]); // 分割时间
231
+ const [, year = 0, month = 0, day = 0] = arr; // 解析日期
232
+ const [, hour = 0, , minute = 0, , second = 0] = timeArr || []; // 解析时间
233
+
234
+ const date = new Date();
235
+ date.setFullYear(year);
236
+ date.setMonth(month - 1);
237
+ date.setDate(day);
238
+ date.setHours(hour);
239
+ date.setMinutes(minute);
240
+ date.setSeconds(second);
241
+ date.setMilliseconds(0);
242
+ return date;
243
+ };
244
+
160
245
  export {
246
+ groupTimeDuration,
247
+ formatTimeDuration,
161
248
  formatDateTime,
162
249
  formatTime,
163
250
  formatTimeStr,
164
251
  formatTimeWithDetails,
165
252
  dateToString,
253
+ parseDateTime,
166
254
  };
@@ -0,0 +1,129 @@
1
+ import { formatTimeDuration, formatTime, formatTimeWithDetails, parseDateTime } from './timeUtils';
2
+
3
+ describe('timeUtils', () => {
4
+ describe.only('formatTimeDuration test cases', () => {
5
+ test.each([
6
+ [0.001, undefined, undefined, undefined, '1秒内'],
7
+ [0.01, 'year', 'second', 2, '0.01秒'],
8
+ [0.01, 'year', 'minute', 2, '1分钟内'],
9
+ [0.1, 'year', 'second', 2, '0.1秒'],
10
+ [0.1, 'year', 'minute', 2, '1分钟内'],
11
+ [1, 'year', 'second', 2, '1秒'],
12
+ [1, 'year', 'minute', 2, '0.02分钟'],
13
+ [1, 'year', 'hour', 0, '1小时内'],
14
+ [1.1, 'year', 'second', 1, '1.1秒'],
15
+ [60, 'second', 'second', 2, '60秒'],
16
+ [60, 'year', 'second', 2, '1分钟'],
17
+ [60, 'year', 'hour', 2, '0.02小时'],
18
+ [60, 'year', 'hour', 1, '1小时内'],
19
+ [60, 'year', 'month', 0, '1月内'],
20
+ [61, 'year', 'second', 0, '1分钟1秒'],
21
+ [61, 'year', 'minute', 2, '1.02分钟'],
22
+ [3600, 'year', 'second', 0, '1小时'],
23
+ [3600, 'year', 'minute', 0, '1小时'],
24
+ [3600, 'year', 'hour', 0, '1小时'],
25
+ [3600, 'second', 'second', 2, '3600秒'],
26
+ [3600, 'minute', 'second', 2, '60分钟'],
27
+ [3600, 'year', 'day', 2, '0.04天'],
28
+ [3600, 'year', 'month', 2, '1月内'],
29
+ [3601, 'year', 'second', 2, '1小时1秒'],
30
+ [3601, 'year', 'minute', 2, '1小时0.02分钟'],
31
+ [3601, 'year', 'hour', 2, '1小时'],
32
+ [86400, 'year', 'second', 2, '1天'],
33
+ [86400, 'year', 'minute', 2, '1天'],
34
+ [86400, 'year', 'hour', 2, '1天'],
35
+ [86400, 'year', 'day', 2, '1天'],
36
+ [86400, 'hour', 'second', 2, '24小时'],
37
+ [86400, 'minute', 'second', 2, '1440分钟'],
38
+ [86400, 'year', 'month', 2, '0.03月'],
39
+ [86400, 'year', 'year', 2, '1年内'],
40
+ [86401, 'year', 'second', 2, '1天1秒'],
41
+ [86401, 'year', 'minute', 2, '1天0.02分钟'],
42
+ [86401, 'year', 'minute', 0, '1天'],
43
+ [86401, 'year', 'hour', 2, '1天'],
44
+ [31536000, 'year', 'second', 2, '1年'],
45
+ [31536000, 'month', 'second', 2, '12月5天'],
46
+ [31626061, 'year', 'second', 2, '1年1天1小时1分钟1秒'],
47
+ ])('formatTimeDuration(%s, %s, %s, %d) => %s', (seconds, maxUnit, minUnit, decimal, expected) => {
48
+ expect(formatTimeDuration(seconds, maxUnit, minUnit, decimal)).toBe(expected);
49
+ });
50
+ });
51
+
52
+ describe('formatTime', () => {
53
+ test.each([
54
+ ['invalid', 'invalid'],
55
+ [172800, '2天'],
56
+ [172799, '1天'],
57
+ [86401, '1天'],
58
+ [86400, '1天'],
59
+ [86399, '23小时60分钟'],
60
+ [86341, '23小时60分钟'],
61
+ [86340, '23小时59分钟'],
62
+ [7200, '2小时'],
63
+ [3601, '1小时1分钟'],
64
+ [3600, '1小时'],
65
+ [3599, '60分钟'],
66
+ [3541, '60分钟'],
67
+ [3540, '59分钟'],
68
+ [61, '2分钟'],
69
+ [60, '1分钟'],
70
+ [59, '1分钟'],
71
+ [1, '1分钟'],
72
+ ])('formatTime(%s) => %s', (seconds, result) => expect(formatTime(seconds)).toBe(result));
73
+ });
74
+
75
+ describe('formatTimeWithDetails', () => {
76
+ test('数据非法', () => expect(formatTimeWithDetails('invalid')).toBe('invalid'));
77
+ test('NaN', () => expect(() => formatTimeWithDetails(parseInt('invalid', 10))).toThrow());
78
+
79
+ test.each([
80
+ [172800, '2天0小时0分钟'],
81
+ [172799, '1天23小时60分钟'],
82
+ [172741, '1天23小时60分钟'],
83
+ [172740, '1天23小时59分钟'],
84
+ [90001, '1天1小时1分钟'],
85
+ [90000, '1天1小时'],
86
+ [86401, '1天0小时1分钟'],
87
+ [86400, '1天0小时0分钟'],
88
+ [86399, '23小时60分钟'],
89
+ [86341, '23小时60分钟'],
90
+ [86340, '23小时59分钟'],
91
+ [7200, '2小时'],
92
+ [7199, '1小时60分钟'],
93
+ [7141, '1小时60分钟'],
94
+ [7140, '1小时59分钟'],
95
+ [3601, '1小时1分钟'],
96
+ [3600, '1小时'],
97
+ [3599, '60分钟'],
98
+ [3541, '60分钟'],
99
+ [3540, '59分钟'],
100
+ [61, '2分钟'],
101
+ [60, '1分钟'],
102
+ [59, '1分钟'],
103
+ [1, '1分钟'],
104
+ ])('formatTime(%s) => %s', (seconds, result) => expect(formatTimeWithDetails(seconds)).toBe(result));
105
+ });
106
+
107
+ describe('parseDateTime', () => {
108
+ // 转换失败的情况
109
+ test('不合法转换', () => {
110
+ const date = parseDateTime('invalid');
111
+ expect(date).toBe(null);
112
+ });
113
+
114
+ // 转换成功的情况
115
+ test.each([
116
+ ['2022-03-24 20:02:05', 1648123325000],
117
+ ['2022/03/24 20:02', 1648123320000],
118
+ ['2022/03/24 20:', 1648123200000],
119
+ ['2022/03/24 20', 1648123200000],
120
+ ['2022/03/24', 1648051200000],
121
+ ['2022/03/24 20:2', 1648123320000],
122
+ ['2022年03月24日20时02分', 1648123320000],
123
+ ])('parseDateTime(%s) => Date(%d)', (str, timeStamp) => {
124
+ const date = parseDateTime(str);
125
+ expect(date).not.toBe(null);
126
+ expect(date.getTime()).toBe(timeStamp);
127
+ });
128
+ });
129
+ });