@ercao/ts-utils 0.0.1

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,39 @@
1
+ # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2
+ # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
3
+
4
+ name: Node.js Package
5
+
6
+ on:
7
+ workflow_dispatch:
8
+ release:
9
+ types: [created]
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-node@v6
17
+ with:
18
+ node-version: 20
19
+
20
+ publish-npm:
21
+ needs: build
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - uses: actions/setup-node@v6
26
+ with:
27
+ node-version: 20
28
+ registry-url: https://registry.npmjs.org/
29
+ - uses: pnpm/action-setup@v4
30
+ name: Install pnpm
31
+ with:
32
+ version: 10
33
+ cache: true
34
+ - name: Install dependencies
35
+ run: pnpm install
36
+ #
37
+ - run: pnpm publish --access public
38
+ env:
39
+ NODE_AUTH_TOKEN: ${{secrets.npm_token}}
package/.oxfmtrc.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "./node_modules/oxfmt/configuration_schema.json",
3
+ "ignorePatterns": [],
4
+ "printWidth": 120,
5
+ "singleQuote": true
6
+ }
package/.oxlintrc.json ADDED
@@ -0,0 +1,144 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "plugins": ["unicorn", "typescript", "oxc"],
4
+ "categories": {},
5
+ "rules": {
6
+ "constructor-super": "warn",
7
+ "for-direction": "warn",
8
+ "no-async-promise-executor": "warn",
9
+ "no-caller": "warn",
10
+ "no-class-assign": "warn",
11
+ "no-compare-neg-zero": "warn",
12
+ "no-cond-assign": "warn",
13
+ "no-const-assign": "warn",
14
+ "no-constant-binary-expression": "warn",
15
+ "no-constant-condition": "warn",
16
+ "no-control-regex": "warn",
17
+ "no-debugger": "warn",
18
+ "no-delete-var": "warn",
19
+ "no-dupe-class-members": "warn",
20
+ "no-dupe-else-if": "warn",
21
+ "no-dupe-keys": "warn",
22
+ "no-duplicate-case": "warn",
23
+ "no-empty-character-class": "warn",
24
+ "no-empty-pattern": "warn",
25
+ "no-empty-static-block": "warn",
26
+ "no-eval": "warn",
27
+ "no-ex-assign": "warn",
28
+ "no-extra-boolean-cast": "warn",
29
+ "no-func-assign": "warn",
30
+ "no-global-assign": "warn",
31
+ "no-import-assign": "warn",
32
+ "no-invalid-regexp": "warn",
33
+ "no-irregular-whitespace": "warn",
34
+ "no-loss-of-precision": "warn",
35
+ "no-new-native-nonconstructor": "warn",
36
+ "no-nonoctal-decimal-escape": "warn",
37
+ "no-obj-calls": "warn",
38
+ "no-self-assign": "warn",
39
+ "no-setter-return": "warn",
40
+ "no-shadow-restricted-names": "warn",
41
+ "no-sparse-arrays": "warn",
42
+ "no-this-before-super": "warn",
43
+ "no-unassigned-vars": "warn",
44
+ "no-unsafe-finally": "warn",
45
+ "no-unsafe-negation": "warn",
46
+ "no-unsafe-optional-chaining": "warn",
47
+ "no-unused-expressions": "warn",
48
+ "no-unused-labels": "warn",
49
+ "no-unused-private-class-members": "warn",
50
+ "no-unused-vars": "warn",
51
+ "no-useless-backreference": "warn",
52
+ "no-useless-catch": "warn",
53
+ "no-useless-escape": "warn",
54
+ "no-useless-rename": "warn",
55
+ "no-with": "warn",
56
+ "require-yield": "warn",
57
+ "use-isnan": "warn",
58
+ "valid-typeof": "warn",
59
+ "oxc/bad-array-method-on-arguments": "warn",
60
+ "oxc/bad-char-at-comparison": "warn",
61
+ "oxc/bad-comparison-sequence": "warn",
62
+ "oxc/bad-min-max-func": "warn",
63
+ "oxc/bad-object-literal-comparison": "warn",
64
+ "oxc/bad-replace-all-arg": "warn",
65
+ "oxc/const-comparisons": "warn",
66
+ "oxc/double-comparisons": "warn",
67
+ "oxc/erasing-op": "warn",
68
+ "oxc/missing-throw": "warn",
69
+ "oxc/number-arg-out-of-range": "warn",
70
+ "oxc/only-used-in-recursion": "warn",
71
+ "oxc/uninvoked-array-callback": "warn",
72
+ "typescript/await-thenable": "warn",
73
+ "typescript/no-array-delete": "warn",
74
+ "typescript/no-base-to-string": "warn",
75
+ "typescript/no-duplicate-enum-values": "warn",
76
+ "typescript/no-duplicate-type-constituents": "warn",
77
+ "typescript/no-extra-non-null-assertion": "warn",
78
+ "typescript/no-floating-promises": "warn",
79
+ "typescript/no-for-in-array": "warn",
80
+ "typescript/no-implied-eval": "warn",
81
+ "typescript/no-meaningless-void-operator": "warn",
82
+ "typescript/no-misused-new": "warn",
83
+ "typescript/no-misused-spread": "warn",
84
+ "typescript/no-non-null-asserted-optional-chain": "warn",
85
+ "typescript/no-redundant-type-constituents": "warn",
86
+ "typescript/no-this-alias": "warn",
87
+ "typescript/no-unnecessary-parameter-property-assignment": "warn",
88
+ "typescript/no-unsafe-declaration-merging": "warn",
89
+ "typescript/no-unsafe-unary-minus": "warn",
90
+ "typescript/no-useless-empty-export": "warn",
91
+ "typescript/no-wrapper-object-types": "warn",
92
+ "typescript/prefer-as-const": "warn",
93
+ "typescript/require-array-sort-compare": "warn",
94
+ "typescript/restrict-template-expressions": "warn",
95
+ "typescript/triple-slash-reference": "warn",
96
+ "typescript/unbound-method": "warn",
97
+ "unicorn/no-await-in-promise-methods": "warn",
98
+ "unicorn/no-empty-file": "warn",
99
+ "unicorn/no-invalid-fetch-options": "warn",
100
+ "unicorn/no-invalid-remove-event-listener": "warn",
101
+ "unicorn/no-new-array": "warn",
102
+ "unicorn/no-single-promise-in-promise-methods": "warn",
103
+ "unicorn/no-thenable": "warn",
104
+ "unicorn/no-unnecessary-await": "warn",
105
+ "unicorn/no-useless-fallback-in-spread": "warn",
106
+ "unicorn/no-useless-length-check": "warn",
107
+ "unicorn/no-useless-spread": "warn",
108
+ "unicorn/prefer-set-size": "warn",
109
+ "unicorn/prefer-string-starts-ends-with": "warn"
110
+ },
111
+ "settings": {
112
+ "jsx-a11y": {
113
+ "polymorphicPropName": null,
114
+ "components": {},
115
+ "attributes": {}
116
+ },
117
+ "next": {
118
+ "rootDir": []
119
+ },
120
+ "react": {
121
+ "formComponents": [],
122
+ "linkComponents": [],
123
+ "version": null
124
+ },
125
+ "jsdoc": {
126
+ "ignorePrivate": false,
127
+ "ignoreInternal": false,
128
+ "ignoreReplacesDocs": true,
129
+ "overrideReplacesDocs": true,
130
+ "augmentsExtendsReplacesDocs": false,
131
+ "implementsReplacesDocs": false,
132
+ "exemptDestructuredRootsFromChecks": false,
133
+ "tagNamePreference": {}
134
+ },
135
+ "vitest": {
136
+ "typecheck": false
137
+ }
138
+ },
139
+ "env": {
140
+ "builtin": true
141
+ },
142
+ "globals": {},
143
+ "ignorePatterns": []
144
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "[typescript]": {
3
+ "editor.defaultFormatter": "oxc.oxc-vscode"
4
+ }
5
+ }
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@ercao/ts-utils",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "author": "ercao",
6
+ "publishConfig": {
7
+ "registry": "https://registry.npmjs.org/"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ },
14
+ "./tw": {
15
+ "import": "./dist/tw.js",
16
+ "types": "./dist/tw.d.ts"
17
+ },
18
+ "./defu": {
19
+ "import": "./dist/defu.js",
20
+ "types": "./dist/defu.d.ts"
21
+ },
22
+ "./remeda": {
23
+ "import": "./dist/time.js",
24
+ "types": "./dist/time.d.ts"
25
+ },
26
+ "./ts-pattern": {
27
+ "import": "./dist/ts-pattern.js",
28
+ "types": "./dist/ts-pattern.d.ts"
29
+ },
30
+ "./type-fest": {
31
+ "import": "./dist/type-fest.js",
32
+ "types": "./dist/type-fest.d.ts"
33
+ }
34
+ },
35
+ "dependencies": {
36
+ "date-fns": "4.1.0",
37
+ "defu": "6.1.4",
38
+ "file64": "~1.0.5",
39
+ "filesize": "~11.0.13",
40
+ "js-file-download": "0.4.12",
41
+ "normalize-url": "8.1.0",
42
+ "query-string": "9.3.1",
43
+ "remeda": "~2.32.0",
44
+ "tailwind-variants": "~3.2.2",
45
+ "ts-pattern": "5.9.0"
46
+ },
47
+ "devDependencies": {
48
+ "oxfmt": "^0.23.0",
49
+ "oxlint": "^1.38.0",
50
+ "tsdown": "^0.18.1",
51
+ "type-fest": "5.2.0",
52
+ "typescript": "^5.9.3",
53
+ "vue-component-type-helpers": "~3.1.8"
54
+ },
55
+ "scripts": {
56
+ "dev": "tsdown --watch",
57
+ "build": " tsdown",
58
+ "lint": "oxlint",
59
+ "lint:fix": "oxlint"
60
+ }
61
+ }
package/renovate.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": [
4
+ "config:recommended"
5
+ ]
6
+ }
package/src/array.ts ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * 为 true 时返回数组,否则返回空数组
3
+ */
4
+ export const optional = <T>(arr: T[], flag: boolean) => (flag ? arr : []);
5
+
6
+ // 定义树形数据的返回类型,包含原始数据和 children 属性
7
+ type TreeDataEntity<T> = T & { children: TreeDataEntity<T>[] };
8
+
9
+ /**
10
+ * 将一个带有fatherId的列表转换成由children属性主导的树形结构
11
+ *
12
+ * @param list 数据列表
13
+ * @param fatherId 父级id的字段名,默认fatherId
14
+ *
15
+ * @returns 将原数据类型加上children属性,并制作成属性列表返回
16
+ *
17
+ * @author 作者 - Mikaisa
18
+ */
19
+ export const buildTreeDataListByFatherId = <T>(list: T[], fatherId: string = 'fatherId'): TreeDataEntity<T>[] => {
20
+ const map = new Map();
21
+
22
+ const rootNodes: TreeDataEntity<T>[] = [];
23
+
24
+ // 初始化Map
25
+ list.forEach((item: any) => {
26
+ if (!map.has(item.id)) {
27
+ map.set(item.id, { ...item, children: [] });
28
+ }
29
+ map.set(item.id, { ...map.get(item.id), ...item, children: map.get(item.id).children || [] });
30
+ });
31
+
32
+ // 构建层次结构
33
+ list.forEach((item: any) => {
34
+ const parent = item[fatherId] && map.get(item[fatherId]);
35
+ if (parent) {
36
+ parent.children.push(map.get(item.id));
37
+ // parent.hasChildren = true;
38
+ } else {
39
+ rootNodes.push(map.get(item.id));
40
+ }
41
+ });
42
+
43
+ return rootNodes;
44
+ };
45
+
46
+ /// 确保 obj[prop] 是数组
47
+ export const arrayGuard = (obj: any, prop: string | number | symbol) => {
48
+ if (!Array.isArray(obj?.[prop])) {
49
+ obj[prop] = [];
50
+ }
51
+ };
package/src/defu.ts ADDED
@@ -0,0 +1 @@
1
+ export { defu, defuArrayFn, defuFn, type Defu, createDefu } from 'defu';
package/src/file.ts ADDED
@@ -0,0 +1,33 @@
1
+ import downloadFile from 'js-file-download';
2
+ import { base64ToBlob } from 'file64';
3
+ import defu from 'defu';
4
+
5
+ export * from 'file64';
6
+
7
+ export { filesize } from 'filesize';
8
+
9
+ export const DEFAULT_CONTENT_TYPE = 'application/octet-stream';
10
+
11
+ /**
12
+ * 下载 base64 文件
13
+ */
14
+ export const downloadFileFromBase64 = async (base64: string, fileName: string, _options?: { contentType?: string }) => {
15
+ const options = defu(_options, {
16
+ contentType: DEFAULT_CONTENT_TYPE,
17
+ });
18
+
19
+ const blobData = await base64ToBlob(`data:${options.contentType};base64,${base64}`);
20
+ downloadFile(blobData, fileName);
21
+ };
22
+
23
+ /**
24
+ * 下载 blob 文件
25
+ */
26
+ export const downloadFileFromBlob = async (_blob, fileName: string, _options?: { contentType?: string }) => {
27
+ const options = defu(_options, {
28
+ contentType: DEFAULT_CONTENT_TYPE,
29
+ });
30
+
31
+ const blob = new Blob([_blob], { type: options.contentType });
32
+ downloadFile(blob, fileName);
33
+ };
package/src/index.ts ADDED
@@ -0,0 +1,27 @@
1
+ export * from './array';
2
+ export * from './tw';
3
+ export * from './file';
4
+ export * from './number';
5
+ export * from './object';
6
+ export * from './string';
7
+ // export * from './time';
8
+ export * from './remeda';
9
+ export * from './defu';
10
+
11
+ /**
12
+ * 依照枚举类型出现的键值对生成选项数组供组件使用
13
+ * @param map 键值对对象,例如: { 0:"Male",1:"Female" }
14
+ * @param keyIsNumber 返回的选项value是否为数字,默认为false
15
+ *
16
+ * @return array 生成的选项数组,例如 [ { intValue: 0,label:"Male", value:"0" }...]
17
+ *
18
+ * @author 作者 - Mikaisa
19
+ * */
20
+ export const optionsMaker = (map: Record<number | string, string>, keyIsNumber?: boolean): any[] => {
21
+ return Object.keys(map).map((key: string) => ({
22
+ intValue: Number.parseInt(key),
23
+ label: map[key],
24
+ value: keyIsNumber ? Number(key) : key,
25
+ text: map[key],
26
+ }));
27
+ };
package/src/number.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 将数字或字符串转换为固定小数位数的数字
3
+ */
4
+ export const numberToFixed = (value: string | number, fixed: number) => {
5
+ if (value == null) {
6
+ return null;
7
+ }
8
+
9
+ if (typeof value === "number") {
10
+ return Number.isInteger(value) ? value : value.toFixed(fixed);
11
+ }
12
+
13
+ return value;
14
+ };
package/src/object.ts ADDED
@@ -0,0 +1,17 @@
1
+ import * as R from 'remeda';
2
+ import { EMPTY_STRING } from './string';
3
+
4
+ /**
5
+ * 反转对象的键和值
6
+ */
7
+ export const reverseObj = <T extends Record<string, string | number>>(obj: T) => {
8
+ return R.mapToObj(Object.entries(obj), R.reverse());
9
+ };
10
+
11
+ /** 根据 path 返回对象的值 */
12
+ export const getPropByPath = <T extends {}>(obj: T, path: string, defaultValue: any = EMPTY_STRING) => {
13
+ if (!path) return defaultValue;
14
+
15
+ const value = R.pathOr(obj, R.stringToPath(path) as any, defaultValue as any);
16
+ return value != null ? value : defaultValue;
17
+ };
package/src/remeda.ts ADDED
@@ -0,0 +1,10 @@
1
+ import * as R from 'remeda';
2
+
3
+ export { R };
4
+
5
+ export * from 'remeda';
6
+
7
+ /**
8
+ * 大驼峰
9
+ */
10
+ export const toPascalCase = (value: string) => R.capitalize(R.toCamelCase(value));
package/src/string.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 空字符串
3
+ */
4
+ export const EMPTY_STRING = '—';
5
+
6
+ export { fileToBase64, blobToBase64, base64ToBlob, base64ToFile } from 'file64';
package/src/time.ts ADDED
@@ -0,0 +1,91 @@
1
+ import { EMPTY_STRING } from "./string";
2
+ import * as dateFns from "date-fns";
3
+ import { zhCN } from "date-fns/locale";
4
+
5
+ export * from "date-fns";
6
+
7
+ /**
8
+ * 将时间格式化为YYYY-MM-DD HH:mm:ss的格式
9
+ * @param datetime 时间
10
+ * @param nullString 时间不合规比如为null时,返回的字符串
11
+ * */
12
+ export const dateTimeFormater = (
13
+ datetime: number | string | Date | null | undefined,
14
+ format: string = "yyyy-MM-dd HH:mm",
15
+ nullString: string = EMPTY_STRING,
16
+ allowLongTerm: boolean = true
17
+ ) => {
18
+ if (
19
+ datetime == null ||
20
+ datetime == EMPTY_STRING ||
21
+ dateFns.isSameDay(datetime, SQL_SERVER_DEFAULT_DATETIME)
22
+ ) {
23
+ return nullString;
24
+ }
25
+
26
+ if (allowLongTerm && isLongTermDate(datetime)) {
27
+ return "长期有效";
28
+ }
29
+
30
+ // console.log("datetime", datetime);
31
+ return dateFns.format(new Date(datetime), format);
32
+ };
33
+
34
+ /**
35
+ * 将时间格式化为YYYY-MM-DD的格式
36
+ * */
37
+ export const dateFormater = (date: any, nullString = EMPTY_STRING) => {
38
+ return dateTimeFormater(date, "yyyy-MM-dd", nullString);
39
+ };
40
+
41
+ export const numToWeekDay = (num: number) => {
42
+ switch (num) {
43
+ case 1:
44
+ return "星期一";
45
+ case 2:
46
+ return "星期二";
47
+ case 3:
48
+ return "星期三";
49
+ case 4:
50
+ return "星期四";
51
+ case 5:
52
+ return "星期五";
53
+ case 6:
54
+ return "星期六";
55
+ case 0:
56
+ return "星期日";
57
+ default:
58
+ return "未知";
59
+ }
60
+ };
61
+
62
+ /**
63
+ * 将秒数格式化为 human 可读格式
64
+ */
65
+ export const formatShortDuration = (seconds: number) => {
66
+ if (seconds === 0) {
67
+ return "0秒";
68
+ }
69
+
70
+ const duration = dateFns.intervalToDuration({
71
+ start: 0,
72
+ end: seconds * 1000,
73
+ });
74
+ // const nonZeroDuration = Object.fromEntries(Object.entries(duration).filter(([_, value]) => value !== 0));
75
+ return dateFns
76
+ .formatDuration(duration, { locale: zhCN, zero: false })
77
+ .replace(" ", "");
78
+ };
79
+
80
+ /// SQL Server 默认日期
81
+ export const SQL_SERVER_DEFAULT_DATETIME = new Date(1900, 0, 1);
82
+
83
+ /// 长期有效日期
84
+ export const LONG_TERM_DATE = new Date(9999, 0, 1);
85
+ // dateFns.format(new Date(9999, 0, 1), "yyyy-MM-dd'T'HH:mm:ssXXX", { locale: zhCN });
86
+
87
+ /// 是否是长期有效时间
88
+ export const isLongTermDate = (date: dateFns.DateArg<Date>) => {
89
+ if (!date) return false;
90
+ return dateFns.isSameDay(date, LONG_TERM_DATE);
91
+ };
@@ -0,0 +1 @@
1
+ export * from 'ts-pattern';
package/src/tw.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { R } from './remeda';
2
+
3
+ export * from 'tailwind-variants';
4
+
5
+ export const forTW = R.identity();
@@ -0,0 +1 @@
1
+ export type * from "type-fest";
@@ -0,0 +1 @@
1
+ export * from "vue-component-type-helpers";
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "esModuleInterop": true,
7
+
8
+ "verbatimModuleSyntax": true,
9
+ "skipLibCheck": true,
10
+ "strict": true,
11
+ "noImplicitAny": false,
12
+ "noImplicitThis": true,
13
+
14
+ "noEmit": true,
15
+ "allowImportingTsExtensions": true,
16
+
17
+ "types": []
18
+ },
19
+
20
+ "exclude": ["dist"]
21
+ }
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'tsdown';
2
+
3
+ export default defineConfig({
4
+ entry: 'src/**/*.ts',
5
+ platform: 'neutral',
6
+
7
+ target: 'esnext',
8
+ plugins: [],
9
+ dts: true,
10
+ unbundle: true,
11
+ clean: false,
12
+ });