@ecolovo/uniapp-components 2.0.0 → 2.0.2
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/README.md +137 -33
- package/components/button/button.vue +33 -7
- package/composable/component-name.d.ts +2 -0
- package/composable/component-name.js +8 -0
- package/composable/component-name.ts +1 -1
- package/configs/index.d.ts +37 -0
- package/configs/index.js +59 -0
- package/easyCom.d.ts +9 -0
- package/easyCom.js +30 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/init.d.ts +16 -0
- package/init.js +21 -0
- package/package.json +4 -2
- package/utils/date.d.ts +47 -0
- package/utils/date.js +131 -0
- package/utils/freezeDeep.d.ts +1 -0
- package/utils/freezeDeep.js +9 -0
- package/utils/price.d.ts +24 -0
- package/utils/price.js +38 -0
- package/utils/round.d.ts +2 -0
- package/utils/round.js +10 -0
- package/utils/windowInfo.d.ts +24 -0
- package/utils/windowInfo.js +45 -0
package/README.md
CHANGED
|
@@ -12,12 +12,6 @@ UniApp **Vue3** 组件库。
|
|
|
12
12
|
npm install @ecolovo/uniapp-components
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
同时需要使用 `uni-pages-hot-modules` 用来动态配置
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm install uni-pages-hot-modules --save-dev
|
|
19
|
-
```
|
|
20
|
-
|
|
21
15
|
## 组件库的配置
|
|
22
16
|
|
|
23
17
|
使用前,请按照以下步骤进行一些必要的配置。
|
|
@@ -27,7 +21,7 @@ npm install uni-pages-hot-modules --save-dev
|
|
|
27
21
|
```javascript
|
|
28
22
|
// src/main.js
|
|
29
23
|
|
|
30
|
-
import init from '@ecolovo/uniapp-components
|
|
24
|
+
import { init } from '@ecolovo/uniapp-components';
|
|
31
25
|
|
|
32
26
|
init({
|
|
33
27
|
// 配置组件前缀
|
|
@@ -36,32 +30,146 @@ init({
|
|
|
36
30
|
});
|
|
37
31
|
```
|
|
38
32
|
|
|
39
|
-
### 2. 配置 `src/pages.js`
|
|
33
|
+
### 2. 配置 `src/pages.js` 文件
|
|
40
34
|
|
|
41
35
|
```javascript
|
|
42
36
|
// src/pages.js
|
|
43
37
|
|
|
44
38
|
const mergeWith = require('lodash/mergeWith');
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
module.exports =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
pagesConfig,
|
|
55
|
-
componentEasyCom('ui'), // 填入和步骤 2 中同样的前缀
|
|
56
|
-
function (objValue, srcValue) {
|
|
57
|
-
if (Array.isArray(objValue)) {
|
|
58
|
-
return objValue.concat(srcValue);
|
|
59
|
-
}
|
|
39
|
+
const componentEasyCom = require('@ecolovo/uniapp-components/easyCom').default;
|
|
40
|
+
const pageJson = require('./pages-config/index');
|
|
41
|
+
|
|
42
|
+
module.exports = mergeWith(
|
|
43
|
+
pageJson,
|
|
44
|
+
componentEasyCom('ui'), // 填入和步骤 2 中同样的前缀
|
|
45
|
+
function (objValue, srcValue) {
|
|
46
|
+
if (Array.isArray(objValue)) {
|
|
47
|
+
return objValue.concat(srcValue);
|
|
60
48
|
}
|
|
61
|
-
|
|
62
|
-
|
|
49
|
+
}
|
|
50
|
+
);
|
|
63
51
|
```
|
|
64
52
|
|
|
53
|
+
> `uni-pages-hot-modules` 库在 Vue3.x 版本中无法正常使用,可使用以下脚本进行 `pages.js` 的动态配置
|
|
54
|
+
>
|
|
55
|
+
> ```javascript
|
|
56
|
+
> // scripts/build.js
|
|
57
|
+
>
|
|
58
|
+
> /* eslint-disable no-console, @typescript-eslint/no-require-imports */
|
|
59
|
+
>
|
|
60
|
+
> const fs = require('fs');
|
|
61
|
+
> const path = require('path');
|
|
62
|
+
> const srcDir = path.resolve(__dirname, '../src');
|
|
63
|
+
> const pagesJsPath = path.join(srcDir, 'pages.js');
|
|
64
|
+
> const pagesConfigDir = path.join(srcDir, 'pages-config');
|
|
65
|
+
> const mainTsPath = path.join(srcDir, 'main.ts');
|
|
66
|
+
>
|
|
67
|
+
> // 1. Inject environment variables before pages.js is evaluated
|
|
68
|
+
> const platformArgIndex = process.argv.indexOf('-p');
|
|
69
|
+
> const platform = platformArgIndex !== -1 ? process.argv[platformArgIndex + 1] : 'h5';
|
|
70
|
+
> process.env.UNI_PLATFORM = platform;
|
|
71
|
+
>
|
|
72
|
+
> const envPath = path.resolve(__dirname, '../.env');
|
|
73
|
+
> if (fs.existsSync(envPath)) {
|
|
74
|
+
> const envContent = fs.readFileSync(envPath, 'utf-8');
|
|
75
|
+
> for (const line of envContent.split(/\r?\n/)) {
|
|
76
|
+
> const trimmed = line.trim();
|
|
77
|
+
> if (!trimmed || trimmed.startsWith('#')) continue;
|
|
78
|
+
> const eqIndex = trimmed.indexOf('=');
|
|
79
|
+
> if (eqIndex !== -1) {
|
|
80
|
+
> const key = trimmed.slice(0, eqIndex).trim();
|
|
81
|
+
> let value = trimmed.slice(eqIndex + 1).trim();
|
|
82
|
+
> if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
83
|
+
> value = value.slice(1, -1);
|
|
84
|
+
> }
|
|
85
|
+
> process.env[key] = value;
|
|
86
|
+
> }
|
|
87
|
+
> }
|
|
88
|
+
> }
|
|
89
|
+
>
|
|
90
|
+
> // 2. Evaluate pages.js to generate config in memory
|
|
91
|
+
> Object.keys(require.cache).forEach((key) => {
|
|
92
|
+
> if (key.includes('pages-config') || key.endsWith(path.join('src', 'pages.js'))) {
|
|
93
|
+
> delete require.cache[key];
|
|
94
|
+
> }
|
|
95
|
+
> });
|
|
96
|
+
> const pagesConfig = require(pagesJsPath);
|
|
97
|
+
> let pagesJsonStr = JSON.stringify(pagesConfig);
|
|
98
|
+
>
|
|
99
|
+
> // 3. Patch fs.readFileSync to intercept pages.json reads
|
|
100
|
+
> const originalReadFileSync = fs.readFileSync;
|
|
101
|
+
> fs.readFileSync = function (...args) {
|
|
102
|
+
> const filePath = typeof args[0] === 'string' ? path.resolve(args[0]) : '';
|
|
103
|
+
> if (filePath.endsWith('pages.json')) {
|
|
104
|
+
> const options = args[1];
|
|
105
|
+
> const encoding = typeof options === 'string' ? options : options && options.encoding;
|
|
106
|
+
> if (encoding === 'utf8' || encoding === 'utf-8') {
|
|
107
|
+
> return pagesJsonStr;
|
|
108
|
+
> }
|
|
109
|
+
> return Buffer.from(pagesJsonStr, 'utf8');
|
|
110
|
+
> }
|
|
111
|
+
> return originalReadFileSync.apply(this, args);
|
|
112
|
+
> };
|
|
113
|
+
>
|
|
114
|
+
> // 4. Replace parsePagesJsonOnce with non-cached version, so rebuilds pick up new config
|
|
115
|
+
> const pagesModulePath = require.resolve('@dcloudio/uni-cli-shared/dist/json/pages');
|
|
116
|
+
> delete require.cache[pagesModulePath];
|
|
117
|
+
> const pagesModule = require(pagesModulePath);
|
|
118
|
+
> const originalParsePagesJson = pagesModule.parsePagesJson;
|
|
119
|
+
> pagesModule.parsePagesJsonOnce = function (inputDir, platform) {
|
|
120
|
+
> return originalParsePagesJson(inputDir, platform);
|
|
121
|
+
> };
|
|
122
|
+
>
|
|
123
|
+
> // 5. Set up file watcher for HMR (watch mode only)
|
|
124
|
+
> const hasBuildCommand = process.argv.includes('build');
|
|
125
|
+
> const hasWatch = process.argv.includes('-w') || process.argv.includes('--watch');
|
|
126
|
+
> const isDevMode = !hasBuildCommand || hasWatch;
|
|
127
|
+
>
|
|
128
|
+
> if (isDevMode && fs.existsSync(pagesConfigDir)) {
|
|
129
|
+
> try {
|
|
130
|
+
> const chokidar = require('chokidar');
|
|
131
|
+
> const watcher = chokidar.watch(pagesConfigDir, {
|
|
132
|
+
> ignoreInitial: true,
|
|
133
|
+
> awaitWriteFinish: { stabilityThreshold: 300 },
|
|
134
|
+
> });
|
|
135
|
+
> watcher.on('change', (file) => {
|
|
136
|
+
> console.log(`[HMR] Pages config changed: ${path.relative(srcDir, file)}`);
|
|
137
|
+
>
|
|
138
|
+
> // Clear require cache for config modules
|
|
139
|
+
> Object.keys(require.cache).forEach((key) => {
|
|
140
|
+
> if (key.includes('pages-config') || key.endsWith(path.join('src', 'pages.js'))) {
|
|
141
|
+
> delete require.cache[key];
|
|
142
|
+
> }
|
|
143
|
+
> });
|
|
144
|
+
>
|
|
145
|
+
> // Re-evaluate pages.js and update in-memory config
|
|
146
|
+
> const newConfig = require(pagesJsPath);
|
|
147
|
+
> pagesJsonStr = JSON.stringify(newConfig);
|
|
148
|
+
>
|
|
149
|
+
> // Touch main.ts to trigger Rollup rebuild
|
|
150
|
+
> try {
|
|
151
|
+
> const now = new Date();
|
|
152
|
+
> fs.utimesSync(mainTsPath, now, now);
|
|
153
|
+
> } catch (_) {
|
|
154
|
+
> // Fallback: touch any .vue file
|
|
155
|
+
> const fallback = path.join(srcDir, 'App.vue');
|
|
156
|
+
> if (fs.existsSync(fallback)) {
|
|
157
|
+
> const now = new Date();
|
|
158
|
+
> fs.utimesSync(fallback, now, now);
|
|
159
|
+
> }
|
|
160
|
+
> }
|
|
161
|
+
> });
|
|
162
|
+
> } catch {
|
|
163
|
+
> console.warn('[HMR] chokidar not available, pages hot reload disabled');
|
|
164
|
+
> }
|
|
165
|
+
> }
|
|
166
|
+
>
|
|
167
|
+
> // 6. Run the uni CLI
|
|
168
|
+
> require('@dcloudio/vite-plugin-uni/bin/uni');
|
|
169
|
+
> ```
|
|
170
|
+
>
|
|
171
|
+
> package.json 中的脚本需要替换,如 `dev:mp-weixin` 由原来的 `uni -p mp-weixin` 替换为 `node scripts/build.js -p mp-weixin` 。
|
|
172
|
+
|
|
65
173
|
### 3. 配置样式
|
|
66
174
|
|
|
67
175
|
#### 全局样式
|
|
@@ -101,20 +209,16 @@ module.exports = hot((pagesJson) => {
|
|
|
101
209
|
```
|
|
102
210
|
|
|
103
211
|
```javascript
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
const sass = require('sass');
|
|
107
|
-
|
|
212
|
+
// vite.config.ts
|
|
108
213
|
module.exports = {
|
|
109
|
-
// configs...
|
|
214
|
+
// other configs...
|
|
110
215
|
css: {
|
|
111
|
-
|
|
216
|
+
preprocessorOptions: {
|
|
112
217
|
scss: {
|
|
113
|
-
|
|
218
|
+
additionalData: '@use "@/styles/prepend.scss" as *;',
|
|
114
219
|
},
|
|
115
220
|
},
|
|
116
221
|
},
|
|
117
|
-
// other configs...
|
|
118
222
|
};
|
|
119
223
|
```
|
|
120
224
|
|
|
@@ -20,9 +20,18 @@
|
|
|
20
20
|
},
|
|
21
21
|
buttonSize,
|
|
22
22
|
]"
|
|
23
|
+
:disabled="isNotClickable"
|
|
23
24
|
:hover-class="disabled ? 'none' : $style['Button--active']"
|
|
24
25
|
:style="inlineCssVar"
|
|
25
26
|
@click="onClick"
|
|
27
|
+
@getphonenumber="$emit('getphonenumber', $event)"
|
|
28
|
+
@getuserinfo="$emit('getuserinfo', $event)"
|
|
29
|
+
@error="$emit('error', $event)"
|
|
30
|
+
@opensetting="$emit('opensetting', $event)"
|
|
31
|
+
@launchapp="$emit('launchapp', $event)"
|
|
32
|
+
@contact="$emit('contact', $event)"
|
|
33
|
+
@chooseavatar="$emit('chooseavatar', $event)"
|
|
34
|
+
@agreeprivacyauthorization="$emit('agreeprivacyauthorization', $event)"
|
|
26
35
|
>
|
|
27
36
|
<!-- 线性按钮边框:仅在线性按钮且不是禁用态时显示 -->
|
|
28
37
|
<view v-if="!disabled && !text && plain" :class="$style['Button--plain-border']"></view>
|
|
@@ -116,6 +125,14 @@ const props = withDefaults(defineProps<_Props>(), {
|
|
|
116
125
|
|
|
117
126
|
const emit = defineEmits<{
|
|
118
127
|
(event: 'click', payload?: Omit<PromiseWithResolvers<unknown>, 'promise'>): void;
|
|
128
|
+
(event: 'getphonenumber', payload?: unknown): void;
|
|
129
|
+
(event: 'getuserinfo', payload?: unknown): void;
|
|
130
|
+
(event: 'error', payload?: unknown): void;
|
|
131
|
+
(event: 'opensetting', payload?: unknown): void;
|
|
132
|
+
(event: 'launchapp', payload?: unknown): void;
|
|
133
|
+
(event: 'contact', payload?: unknown): void;
|
|
134
|
+
(event: 'chooseavatar', payload?: unknown): void;
|
|
135
|
+
(event: 'agreeprivacyauthorization', payload?: unknown): void;
|
|
119
136
|
}>();
|
|
120
137
|
|
|
121
138
|
/** 按钮是否冷却中 */
|
|
@@ -228,6 +245,22 @@ $button-sizes: (
|
|
|
228
245
|
.Button {
|
|
229
246
|
@extend %external-vars;
|
|
230
247
|
@extend %button-themes;
|
|
248
|
+
--button-color: #{UTILS__LOCAL_VAR(text-color)};
|
|
249
|
+
--button-background: #{UTILS__LOCAL_VAR(theme)};
|
|
250
|
+
color: var(--button-color);
|
|
251
|
+
background: var(--button-background);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.Button[disabled]:not([type]),
|
|
255
|
+
.Button[disabled][type='default'] {
|
|
256
|
+
color: var(--button-color);
|
|
257
|
+
background: var(--button-background);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.Button--plain,
|
|
261
|
+
.Button-text {
|
|
262
|
+
--button-color: var(var(#{__DO_NOT_USE__local-var-name(text-color)}), #{UTILS__LOCAL_VAR(theme)});
|
|
263
|
+
--button-background: transparent;
|
|
231
264
|
}
|
|
232
265
|
|
|
233
266
|
.Button--block {
|
|
@@ -241,8 +274,6 @@ $button-sizes: (
|
|
|
241
274
|
text-decoration: none;
|
|
242
275
|
text-align: center;
|
|
243
276
|
overflow: hidden;
|
|
244
|
-
color: UTILS__LOCAL_VAR(text-color);
|
|
245
|
-
background: UTILS__LOCAL_VAR(theme);
|
|
246
277
|
border: none;
|
|
247
278
|
|
|
248
279
|
@include button-size('block', 'normal');
|
|
@@ -309,9 +340,6 @@ $button-sizes: (
|
|
|
309
340
|
}
|
|
310
341
|
|
|
311
342
|
.Button--plain {
|
|
312
|
-
color: UTILS__LOCAL_VAR(theme);
|
|
313
|
-
background: transparent;
|
|
314
|
-
|
|
315
343
|
.Button--plain-border {
|
|
316
344
|
position: absolute;
|
|
317
345
|
top: 0;
|
|
@@ -329,9 +357,7 @@ $button-sizes: (
|
|
|
329
357
|
@extend %button-reset;
|
|
330
358
|
display: inline-block;
|
|
331
359
|
padding: 0;
|
|
332
|
-
color: UTILS__LOCAL_VAR(theme);
|
|
333
360
|
line-height: inherit;
|
|
334
|
-
background: transparent;
|
|
335
361
|
font-weight: inherit;
|
|
336
362
|
will-change: color;
|
|
337
363
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { kebabCase, pascalCase } from 'case-anything';
|
|
2
|
+
import { componentPrefix } from '../configs';
|
|
3
|
+
const useClassNames = (componentName) => {
|
|
4
|
+
const pascalCaseClass = pascalCase(componentPrefix) + pascalCase(componentName);
|
|
5
|
+
const kebabCaseClass = kebabCase(pascalCaseClass);
|
|
6
|
+
return [pascalCaseClass, kebabCaseClass];
|
|
7
|
+
};
|
|
8
|
+
export default useClassNames;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { kebabCase, pascalCase } from 'case-anything';
|
|
2
2
|
import { componentPrefix } from '../configs';
|
|
3
3
|
|
|
4
|
-
const useClassNames = (componentName: string) => {
|
|
4
|
+
const useClassNames = (componentName: string): [string, string] => {
|
|
5
5
|
const pascalCaseClass = pascalCase(componentPrefix) + pascalCase(componentName);
|
|
6
6
|
const kebabCaseClass = kebabCase(pascalCaseClass);
|
|
7
7
|
return [pascalCaseClass, kebabCaseClass];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** 组件前缀 */
|
|
2
|
+
declare let componentPrefix: string;
|
|
3
|
+
/** 首页路径 */
|
|
4
|
+
declare let homePath: string;
|
|
5
|
+
/**
|
|
6
|
+
* 设置组件前缀
|
|
7
|
+
* @param prefix 组件前缀
|
|
8
|
+
*/
|
|
9
|
+
declare function setComponentPrefix(prefix?: string): void;
|
|
10
|
+
/**
|
|
11
|
+
* 设置首页路径
|
|
12
|
+
* @param path 首页路径
|
|
13
|
+
*/
|
|
14
|
+
declare function setHomePath(path?: string): void;
|
|
15
|
+
interface IconInfo {
|
|
16
|
+
/** 字体文件名 */
|
|
17
|
+
fontFamily: string;
|
|
18
|
+
/** 图标前缀 */
|
|
19
|
+
prefix: string;
|
|
20
|
+
/** 图标名称 */
|
|
21
|
+
name: string;
|
|
22
|
+
}
|
|
23
|
+
/** iconfont 图标 map */
|
|
24
|
+
declare const iconMap: Map<string, IconInfo>;
|
|
25
|
+
export interface IconFontJson {
|
|
26
|
+
font_family: string;
|
|
27
|
+
css_prefix_text: string;
|
|
28
|
+
glyphs: {
|
|
29
|
+
font_class: string;
|
|
30
|
+
}[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 添加一组图标,传入 iconfont.json 文件内容
|
|
34
|
+
* @param iconJson iconfont.json 文件内容
|
|
35
|
+
*/
|
|
36
|
+
declare function addIcons(iconJson: IconFontJson): void;
|
|
37
|
+
export { componentPrefix, homePath, setComponentPrefix, setHomePath, iconMap, addIcons };
|
package/configs/index.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import defaultIconfonts from '../resources/iconfont/uniapp_ui_icon/iconfont.json';
|
|
2
|
+
/** 组件前缀 */
|
|
3
|
+
let componentPrefix = 'app';
|
|
4
|
+
/** 首页路径 */
|
|
5
|
+
let homePath = '/pages/index/index';
|
|
6
|
+
/**
|
|
7
|
+
* 设置组件前缀
|
|
8
|
+
* @param prefix 组件前缀
|
|
9
|
+
*/
|
|
10
|
+
function setComponentPrefix(prefix = 'app') {
|
|
11
|
+
if (!prefix) throw new Error('组件前缀不能为空');
|
|
12
|
+
componentPrefix = prefix;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 设置首页路径
|
|
16
|
+
* @param path 首页路径
|
|
17
|
+
*/
|
|
18
|
+
function setHomePath(path = '/pages/index/index') {
|
|
19
|
+
if (!path) throw new Error('app 首页不能为空');
|
|
20
|
+
homePath = path;
|
|
21
|
+
}
|
|
22
|
+
/** iconfont 图标 map */
|
|
23
|
+
const iconMap = new Map();
|
|
24
|
+
/**
|
|
25
|
+
* 添加单个图标
|
|
26
|
+
* @param fontFamily 字体文件名
|
|
27
|
+
* @param prefix 图标前缀
|
|
28
|
+
* @param name 图标名称
|
|
29
|
+
*/
|
|
30
|
+
function addIconToMap(fontFamily, prefix, name) {
|
|
31
|
+
if (iconMap.has(name)) {
|
|
32
|
+
// eslint-disable-next-line no-console
|
|
33
|
+
console.warn(
|
|
34
|
+
`图标库 "${fontFamily}" 中的图标 "${name}" 在图标库 "${
|
|
35
|
+
iconMap.get(name).fontFamily
|
|
36
|
+
}" 中已存在,新的图标将会覆盖已存在的同名图标`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
iconMap.set(name, {
|
|
40
|
+
fontFamily,
|
|
41
|
+
prefix,
|
|
42
|
+
name,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 添加一组图标,传入 iconfont.json 文件内容
|
|
47
|
+
* @param iconJson iconfont.json 文件内容
|
|
48
|
+
*/
|
|
49
|
+
function addIcons(iconJson) {
|
|
50
|
+
const fontFamily = iconJson.font_family;
|
|
51
|
+
const prefix = iconJson.css_prefix_text;
|
|
52
|
+
const glyphs = iconJson.glyphs;
|
|
53
|
+
glyphs.forEach((glyph) => {
|
|
54
|
+
addIconToMap(fontFamily, prefix, glyph.font_class);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// 添加内部图标库
|
|
58
|
+
addIcons(defaultIconfonts);
|
|
59
|
+
export { componentPrefix, homePath, setComponentPrefix, setHomePath, iconMap, addIcons };
|
package/easyCom.d.ts
ADDED
package/easyCom.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
function componentEasyCom(componentPrefix = 'app') {
|
|
2
|
+
return {
|
|
3
|
+
easycom: {
|
|
4
|
+
autoscan: true,
|
|
5
|
+
custom: {
|
|
6
|
+
[`^${componentPrefix}-date-picker-view`]:
|
|
7
|
+
'@ecolovo/uniapp-components/components/date-picker/date-picker-view.vue',
|
|
8
|
+
[`^${componentPrefix}-dialog-content`]:
|
|
9
|
+
'@ecolovo/uniapp-components/components/dialog/dialog-content.vue',
|
|
10
|
+
[`^${componentPrefix}-loading-animation(.*)`]:
|
|
11
|
+
'@ecolovo/uniapp-components/components/loading/animations/loading-animation$1.vue',
|
|
12
|
+
[`^${componentPrefix}-page-(.*)`]: '@ecolovo/uniapp-components/components/page/page-$1.vue',
|
|
13
|
+
[`^${componentPrefix}-password-input`]:
|
|
14
|
+
'@ecolovo/uniapp-components/components/input/password-input.vue',
|
|
15
|
+
[`^${componentPrefix}-picker-popup`]: '@ecolovo/uniapp-components/components/picker/picker-popup.vue',
|
|
16
|
+
[`^${componentPrefix}-popover-content`]:
|
|
17
|
+
'@ecolovo/uniapp-components/components/popover/popover-content.vue',
|
|
18
|
+
[`^${componentPrefix}-popover-mask`]: '@ecolovo/uniapp-components/components/popover/popover-mask.vue',
|
|
19
|
+
[`^${componentPrefix}-popup-content`]: '@ecolovo/uniapp-components/components/popup/popup-content.vue',
|
|
20
|
+
[`^${componentPrefix}-media-uploader`]:
|
|
21
|
+
'@ecolovo/uniapp-components/components/uploader/media-uploader.vue',
|
|
22
|
+
[`^${componentPrefix}-file-uploader`]:
|
|
23
|
+
'@ecolovo/uniapp-components/components/uploader/file-uploader.vue',
|
|
24
|
+
[`^${componentPrefix}-(.*)`]: '@ecolovo/uniapp-components/components/$1/$1.vue',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// 导出 easycom 配置
|
|
30
|
+
export default componentEasyCom;
|
package/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as init } from './init';
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as init } from './init';
|
package/init.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IconFontJson } from './configs';
|
|
2
|
+
/** 组件库初始化配置 */
|
|
3
|
+
interface InitOptions {
|
|
4
|
+
/** 组件前缀 */
|
|
5
|
+
componentPrefix?: string;
|
|
6
|
+
/** 首页路径 */
|
|
7
|
+
homePath?: string;
|
|
8
|
+
/** iconfont 图标 json 文件 */
|
|
9
|
+
iconfont?: IconFontJson | IconFontJson[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 组件库初始化方法,在使用组件库前调用
|
|
13
|
+
* @param options 组件库初始化配置
|
|
14
|
+
*/
|
|
15
|
+
declare function init(options?: InitOptions): void;
|
|
16
|
+
export default init;
|
package/init.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { setComponentPrefix, setHomePath, addIcons } from './configs';
|
|
2
|
+
/**
|
|
3
|
+
* 组件库初始化方法,在使用组件库前调用
|
|
4
|
+
* @param options 组件库初始化配置
|
|
5
|
+
*/
|
|
6
|
+
function init(options = {}) {
|
|
7
|
+
if (options.componentPrefix) {
|
|
8
|
+
setComponentPrefix(options.componentPrefix);
|
|
9
|
+
}
|
|
10
|
+
if (options.homePath) {
|
|
11
|
+
setHomePath(options.homePath);
|
|
12
|
+
}
|
|
13
|
+
if (options.iconfont) {
|
|
14
|
+
if (Array.isArray(options.iconfont)) {
|
|
15
|
+
options.iconfont.forEach(addIcons);
|
|
16
|
+
} else {
|
|
17
|
+
addIcons(options.iconfont);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export default init;
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecolovo/uniapp-components",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Ecolovo UniApp Vue3 组件库",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"lint": "eslint
|
|
6
|
+
"lint": "eslint",
|
|
7
|
+
"lint:fix": "eslint --fix",
|
|
8
|
+
"build": "tsc"
|
|
7
9
|
},
|
|
8
10
|
"keywords": [
|
|
9
11
|
"uniapp",
|
package/utils/date.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
type ValidDateValue = Date | number | string;
|
|
2
|
+
/**
|
|
3
|
+
* 将Date 对象、时间戳或时间格式字符串转为 Date 对象
|
|
4
|
+
* @param dateValue Date 对象、时间戳或时间格式字符串
|
|
5
|
+
* @returns Date 对象
|
|
6
|
+
*/
|
|
7
|
+
export declare function toDate(dateValue: ValidDateValue | null | undefined): Date | null;
|
|
8
|
+
type FormatFn = {
|
|
9
|
+
(date: Date | null | undefined, format?: string): string;
|
|
10
|
+
DATE: string;
|
|
11
|
+
DATE_WITH_YEAR: string;
|
|
12
|
+
TIME: string;
|
|
13
|
+
TIME_MS: string;
|
|
14
|
+
TIME_MINUTE: string;
|
|
15
|
+
DATE_TIME: string;
|
|
16
|
+
DATE_TIME_MS: string;
|
|
17
|
+
DATE_TIME_WITH_YEAR: string;
|
|
18
|
+
DATE_TIME_MS_WITH_YEAR: string;
|
|
19
|
+
DATE_MINUTE: string;
|
|
20
|
+
DATE_MINUTE_WITH_YEAR: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* 格式化日期
|
|
24
|
+
*
|
|
25
|
+
* format 参数说明:
|
|
26
|
+
* - Y: 年
|
|
27
|
+
* - M: 月
|
|
28
|
+
* - D: 日
|
|
29
|
+
* - H: 时
|
|
30
|
+
* - m: 分
|
|
31
|
+
* - s: 秒
|
|
32
|
+
* - S: 毫秒
|
|
33
|
+
* - w: 星期
|
|
34
|
+
* - W: 星期(中文)
|
|
35
|
+
* @param date 要格式化的日期对象
|
|
36
|
+
* @param format 格式化参数 默认值 YYYY-MM-DD
|
|
37
|
+
* @returns 格式化后的日期
|
|
38
|
+
*/
|
|
39
|
+
export declare const formatDate: FormatFn;
|
|
40
|
+
/**
|
|
41
|
+
* 获取两个时间的天数差
|
|
42
|
+
* @param day1 时间1
|
|
43
|
+
* @param day2 时间2
|
|
44
|
+
* @returns 天数差,负数: day1 < day2
|
|
45
|
+
*/
|
|
46
|
+
export declare function daysDiff(day1: ValidDateValue, day2: ValidDateValue): number;
|
|
47
|
+
export {};
|
package/utils/date.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 将Date 对象、时间戳或时间格式字符串转为 Date 对象
|
|
3
|
+
* @param dateValue Date 对象、时间戳或时间格式字符串
|
|
4
|
+
* @returns Date 对象
|
|
5
|
+
*/
|
|
6
|
+
export function toDate(dateValue) {
|
|
7
|
+
if (dateValue instanceof Date) return new Date(dateValue);
|
|
8
|
+
if (typeof dateValue === 'number') return new Date(dateValue);
|
|
9
|
+
if (!dateValue) return null;
|
|
10
|
+
if (typeof dateValue !== 'string') throw new Error("dateValue's type is invalid");
|
|
11
|
+
// 兼容 iOS 日期格式
|
|
12
|
+
const result = new Date(dateValue.replace(/-/g, '/'));
|
|
13
|
+
if (isNaN(result.getTime())) throw new Error('dateValue cannot be parsed to Date object');
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 格式化日期
|
|
18
|
+
*
|
|
19
|
+
* format 参数说明:
|
|
20
|
+
* - Y: 年
|
|
21
|
+
* - M: 月
|
|
22
|
+
* - D: 日
|
|
23
|
+
* - H: 时
|
|
24
|
+
* - m: 分
|
|
25
|
+
* - s: 秒
|
|
26
|
+
* - S: 毫秒
|
|
27
|
+
* - w: 星期
|
|
28
|
+
* - W: 星期(中文)
|
|
29
|
+
* @param date 要格式化的日期对象
|
|
30
|
+
* @param format 格式化参数 默认值 YYYY-MM-DD
|
|
31
|
+
* @returns 格式化后的日期
|
|
32
|
+
*/
|
|
33
|
+
export const formatDate = (date, format = formatDate.DATE_WITH_YEAR) => {
|
|
34
|
+
if (!date) return '';
|
|
35
|
+
const fullYear = date.getFullYear().toString();
|
|
36
|
+
const year = fullYear.substring(fullYear.length - 2);
|
|
37
|
+
const month = (date.getMonth() + 1).toString();
|
|
38
|
+
const day = date.getDate().toString();
|
|
39
|
+
const hour = date.getHours().toString();
|
|
40
|
+
const min = date.getMinutes().toString();
|
|
41
|
+
const sec = date.getSeconds().toString();
|
|
42
|
+
const ms = date.getMilliseconds().toString();
|
|
43
|
+
const week = date.getDay().toString();
|
|
44
|
+
let weekCN = '';
|
|
45
|
+
switch (week) {
|
|
46
|
+
case '0':
|
|
47
|
+
weekCN = '日';
|
|
48
|
+
break;
|
|
49
|
+
case '1':
|
|
50
|
+
weekCN = '一';
|
|
51
|
+
break;
|
|
52
|
+
case '2':
|
|
53
|
+
weekCN = '二';
|
|
54
|
+
break;
|
|
55
|
+
case '3':
|
|
56
|
+
weekCN = '三';
|
|
57
|
+
break;
|
|
58
|
+
case '4':
|
|
59
|
+
weekCN = '四';
|
|
60
|
+
break;
|
|
61
|
+
case '5':
|
|
62
|
+
weekCN = '五';
|
|
63
|
+
break;
|
|
64
|
+
case '6':
|
|
65
|
+
weekCN = '六';
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
const replacement = [
|
|
69
|
+
{ reg: 'Y{3,4}', val: fullYear, pad: 0 },
|
|
70
|
+
{ reg: 'Y{1,2}', val: year, pad: 0 },
|
|
71
|
+
{ reg: 'M{1,2}', val: month, pad: 2 },
|
|
72
|
+
{ reg: 'D{1,2}', val: day, pad: 2 },
|
|
73
|
+
{ reg: 'H{1,2}', val: hour, pad: 2 },
|
|
74
|
+
{ reg: 'm{1,2}', val: min, pad: 2 },
|
|
75
|
+
{ reg: 's{1,2}', val: sec, pad: 2 },
|
|
76
|
+
{ reg: 'S{1,3}', val: ms, pad: 3 },
|
|
77
|
+
{ reg: 'w', val: week, pad: 0 },
|
|
78
|
+
{ reg: 'W', val: weekCN, pad: 0 },
|
|
79
|
+
];
|
|
80
|
+
for (let i = 0; i < replacement.length; i++) {
|
|
81
|
+
const r = replacement[i];
|
|
82
|
+
format = format.replace(new RegExp(r.reg, 'g'), (m) =>
|
|
83
|
+
r.pad > 0 ? r.val.padStart(Math.min(r.pad, m.length), '0') : r.val
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return format;
|
|
87
|
+
};
|
|
88
|
+
/** 月日 */
|
|
89
|
+
formatDate.DATE = 'MM-DD';
|
|
90
|
+
/** 年月日 */
|
|
91
|
+
formatDate.DATE_WITH_YEAR = 'YYYY-MM-DD';
|
|
92
|
+
/** 时分秒 */
|
|
93
|
+
formatDate.TIME = 'HH:mm:ss';
|
|
94
|
+
/** 时分秒.毫秒 */
|
|
95
|
+
formatDate.TIME_MS = 'HH:mm:ss.SSS';
|
|
96
|
+
/** 时分 */
|
|
97
|
+
formatDate.TIME_MINUTE = 'HH:mm';
|
|
98
|
+
/** 月日 时分秒 */
|
|
99
|
+
formatDate.DATE_TIME = 'MM-DD HH:mm:ss';
|
|
100
|
+
/** 月日 时分秒.毫秒 */
|
|
101
|
+
formatDate.DATE_TIME_MS = 'MM-DD HH:mm:ss.SSS';
|
|
102
|
+
/** 年月日 时分秒 */
|
|
103
|
+
formatDate.DATE_TIME_WITH_YEAR = 'YYYY-MM-DD HH:mm:ss';
|
|
104
|
+
/** 年月日 时分秒.毫秒 */
|
|
105
|
+
formatDate.DATE_TIME_MS_WITH_YEAR = 'YYYY-MM-DD HH:mm:ss.SSS';
|
|
106
|
+
/** 月日 时分 */
|
|
107
|
+
formatDate.DATE_MINUTE = 'MM-DD HH:mm';
|
|
108
|
+
/** 年月日 时分 */
|
|
109
|
+
formatDate.DATE_MINUTE_WITH_YEAR = 'YYYY-MM-DD HH:mm';
|
|
110
|
+
/** 时区差 */
|
|
111
|
+
const TIMEZONE_OFFSET = new Date().getTimezoneOffset() * 60 * 1000;
|
|
112
|
+
/**
|
|
113
|
+
* 获取时间的UTC相对时间戳
|
|
114
|
+
* @param date 时间
|
|
115
|
+
* @returns 毫秒数
|
|
116
|
+
*/
|
|
117
|
+
function toDayTimestamp(date) {
|
|
118
|
+
if (typeof date === 'number') return date - TIMEZONE_OFFSET;
|
|
119
|
+
if (typeof date === 'string') return toDate(date).getTime() - TIMEZONE_OFFSET;
|
|
120
|
+
if (date instanceof Date) return date.getTime() - TIMEZONE_OFFSET;
|
|
121
|
+
return Date.now() - TIMEZONE_OFFSET;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 获取两个时间的天数差
|
|
125
|
+
* @param day1 时间1
|
|
126
|
+
* @param day2 时间2
|
|
127
|
+
* @returns 天数差,负数: day1 < day2
|
|
128
|
+
*/
|
|
129
|
+
export function daysDiff(day1, day2) {
|
|
130
|
+
return Math.floor(toDayTimestamp(day1) / 8.64e7) - Math.floor(toDayTimestamp(day2) / 8.64e7);
|
|
131
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function freezeDeep<T>(obj: T): Readonly<T>;
|
package/utils/price.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
interface FormatPriceOption {
|
|
2
|
+
/** 精度,默认为 2 */
|
|
3
|
+
precision: number;
|
|
4
|
+
/** 是否补齐小数部分 0 */
|
|
5
|
+
padDecimal: boolean;
|
|
6
|
+
}
|
|
7
|
+
interface FormatPriceResult {
|
|
8
|
+
/** 格式化的价格字符串 */
|
|
9
|
+
price: string;
|
|
10
|
+
/** 是否负值 */
|
|
11
|
+
negative: boolean;
|
|
12
|
+
/** 整数部分价格 */
|
|
13
|
+
integer: string;
|
|
14
|
+
/** 小数部分价格 */
|
|
15
|
+
decimal: string;
|
|
16
|
+
/** 价格对应数值 */
|
|
17
|
+
number: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 格式化价格
|
|
21
|
+
* @param price 价格
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatPrice(price: number | string, option?: FormatPriceOption): FormatPriceResult;
|
|
24
|
+
export {};
|
package/utils/price.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 格式化价格
|
|
3
|
+
* @param price 价格
|
|
4
|
+
*/
|
|
5
|
+
export function formatPrice(price, option) {
|
|
6
|
+
if (isNaN(Number(price))) {
|
|
7
|
+
return {
|
|
8
|
+
price: '',
|
|
9
|
+
negative: false,
|
|
10
|
+
integer: '',
|
|
11
|
+
decimal: '',
|
|
12
|
+
number: NaN,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
price = Number(price);
|
|
16
|
+
let negative = false;
|
|
17
|
+
if (price < 0) {
|
|
18
|
+
negative = true;
|
|
19
|
+
price = -price;
|
|
20
|
+
}
|
|
21
|
+
const integer = Math.floor(price).toString();
|
|
22
|
+
const decimalNum = Math.round(Number(price + 'e' + (option?.precision ?? 2))) % 100;
|
|
23
|
+
let decimal;
|
|
24
|
+
if (option?.padDecimal ?? true) {
|
|
25
|
+
decimal = '.' + decimalNum.toString().padStart(option?.precision ?? 2, '0');
|
|
26
|
+
} else {
|
|
27
|
+
if (decimalNum === 0) decimal = '';
|
|
28
|
+
else decimal = decimalNum.toString().slice(1);
|
|
29
|
+
}
|
|
30
|
+
const formattedPrice = `${negative ? '-' : ''}${integer}.${decimal}`;
|
|
31
|
+
return {
|
|
32
|
+
price: formattedPrice,
|
|
33
|
+
negative,
|
|
34
|
+
integer,
|
|
35
|
+
decimal,
|
|
36
|
+
number: Number(formattedPrice),
|
|
37
|
+
};
|
|
38
|
+
}
|
package/utils/round.d.ts
ADDED
package/utils/round.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function round(val, precision = 0) {
|
|
2
|
+
if (val.toString().indexOf('e') >= 0) {
|
|
3
|
+
const exponential = val.toString().split('e');
|
|
4
|
+
return Math.round(+`${exponential[0]}e${+exponential[1] + precision}`) / 10 ** precision;
|
|
5
|
+
}
|
|
6
|
+
return Math.round(+`${val}e${precision}`) / 10 ** precision;
|
|
7
|
+
}
|
|
8
|
+
export function round2(val) {
|
|
9
|
+
return round(val, 2);
|
|
10
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/// <reference types="@dcloudio/types" />
|
|
2
|
+
/** 胶囊按钮位置信息 */
|
|
3
|
+
declare let menuButton: UniApp.GetMenuButtonBoundingClientRectRes | null;
|
|
4
|
+
/** 胶囊按钮宽度,仅在 css 中使用,当有胶囊按钮时,表示胶囊按钮左边缘到屏幕右侧的宽度,否则为 `0px` */
|
|
5
|
+
declare let menuButtonWidthInCss: string;
|
|
6
|
+
/** 系统信息 */
|
|
7
|
+
declare let windowInfo: UniApp.GetWindowInfoResult | null;
|
|
8
|
+
/** 导航栏高度 */
|
|
9
|
+
declare let navBarHeight: number;
|
|
10
|
+
/** 导航栏顶部距离 */
|
|
11
|
+
declare let navBarTopOffset: number;
|
|
12
|
+
/** 导航栏底部距离 */
|
|
13
|
+
declare let navBarBottomOffset: number;
|
|
14
|
+
/** 底部安全区域高度 */
|
|
15
|
+
declare let safeAreaInsetBottom: number;
|
|
16
|
+
export {
|
|
17
|
+
menuButton,
|
|
18
|
+
menuButtonWidthInCss,
|
|
19
|
+
windowInfo,
|
|
20
|
+
navBarHeight,
|
|
21
|
+
navBarTopOffset,
|
|
22
|
+
navBarBottomOffset,
|
|
23
|
+
safeAreaInsetBottom,
|
|
24
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** 默认导航栏高度 */
|
|
2
|
+
const NAV_TITLE_HEIGHT = 44;
|
|
3
|
+
/** 胶囊按钮位置信息 */
|
|
4
|
+
let menuButton = null;
|
|
5
|
+
/** 胶囊按钮宽度,仅在 css 中使用,当有胶囊按钮时,表示胶囊按钮左边缘到屏幕右侧的宽度,否则为 `0px` */
|
|
6
|
+
let menuButtonWidthInCss = '0px';
|
|
7
|
+
/** 系统信息 */
|
|
8
|
+
let windowInfo = null;
|
|
9
|
+
/** 导航栏高度 */
|
|
10
|
+
let navBarHeight = NAV_TITLE_HEIGHT;
|
|
11
|
+
/** 导航栏顶部距离 */
|
|
12
|
+
let navBarTopOffset = 0;
|
|
13
|
+
/** 导航栏底部距离 */
|
|
14
|
+
let navBarBottomOffset = NAV_TITLE_HEIGHT;
|
|
15
|
+
/** 底部安全区域高度 */
|
|
16
|
+
let safeAreaInsetBottom = uni.upx2px(50);
|
|
17
|
+
/* #ifdef MP-WEIXIN || MP-ALIPAY */
|
|
18
|
+
menuButton = uni.getMenuButtonBoundingClientRect();
|
|
19
|
+
/* #endif */
|
|
20
|
+
windowInfo = uni.getWindowInfo();
|
|
21
|
+
// eslint-disable-next-line no-console
|
|
22
|
+
console.log('windowInfo:', windowInfo);
|
|
23
|
+
if (menuButton) {
|
|
24
|
+
if (navBarHeight < menuButton.height) {
|
|
25
|
+
navBarHeight = menuButton.height + 12;
|
|
26
|
+
}
|
|
27
|
+
navBarTopOffset = menuButton.top - (navBarHeight - menuButton.height) / 2;
|
|
28
|
+
menuButtonWidthInCss = 'calc(100vw - ' + menuButton.left + 'px)';
|
|
29
|
+
} else {
|
|
30
|
+
navBarTopOffset = windowInfo.safeArea.top || windowInfo.statusBarHeight;
|
|
31
|
+
}
|
|
32
|
+
navBarBottomOffset = navBarTopOffset + navBarHeight;
|
|
33
|
+
const _safeAreaBottom = windowInfo.screenHeight - windowInfo.safeArea.bottom;
|
|
34
|
+
if (_safeAreaBottom) {
|
|
35
|
+
safeAreaInsetBottom = _safeAreaBottom;
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
menuButton,
|
|
39
|
+
menuButtonWidthInCss,
|
|
40
|
+
windowInfo,
|
|
41
|
+
navBarHeight,
|
|
42
|
+
navBarTopOffset,
|
|
43
|
+
navBarBottomOffset,
|
|
44
|
+
safeAreaInsetBottom,
|
|
45
|
+
};
|