uview-pro 0.3.7 → 0.3.9
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/changelog.md +41 -0
- package/components/u-action-sheet/types.ts +1 -1
- package/components/u-action-sheet-item/types.ts +1 -1
- package/components/u-calendar/types.ts +2 -2
- package/components/u-checkbox-group/types.ts +1 -1
- package/components/u-circle-progress/u-circle-progress.vue +101 -8
- package/components/u-dropdown/types.ts +2 -2
- package/components/u-fab/types.ts +75 -0
- package/components/u-fab/u-fab.vue +328 -0
- package/components/u-index-list/types.ts +1 -1
- package/components/u-link/types.ts +1 -1
- package/components/u-message-input/types.ts +2 -2
- package/components/u-modal/types.ts +2 -2
- package/components/u-picker/types.ts +2 -2
- package/components/u-radio-group/types.ts +1 -1
- package/components/u-read-more/types.ts +1 -1
- package/components/u-select/types.ts +2 -2
- package/components/u-slider/types.ts +2 -2
- package/components/u-step/types.ts +2 -2
- package/components/u-steps/types.ts +2 -2
- package/components/u-switch/types.ts +1 -1
- package/components/u-tabs/types.ts +2 -2
- package/components/u-tabs-swiper/types.ts +2 -2
- package/components/u-text/u-text.vue +6 -4
- package/index.ts +2 -2
- package/libs/function/color.ts +9 -3
- package/libs/index.ts +27 -2
- package/libs/util/canvas-2d.ts +49 -0
- package/package.json +3 -3
- package/types/components.d.ts +2 -1
- package/types/global.d.ts +44 -28
package/changelog.md
CHANGED
|
@@ -1,3 +1,44 @@
|
|
|
1
|
+
## 0.3.9(2025-11-05)
|
|
2
|
+
|
|
3
|
+
### 🐛 Bug Fixes | Bug 修复
|
|
4
|
+
|
|
5
|
+
- **types:** 修正组件声明文件中uLoadmore的命名大小写问题 ([eb69b18](https://github.com/anyup/uView-Pro/commit/eb69b18d969dac13a75375501018edc4f3097e33))
|
|
6
|
+
- **loadmore:** 修复 u-loadmore 组件使用类型声明报错问题 ([92facfd](https://github.com/anyup/uView-Pro/commit/92facfdb5b2b57bcd634d93412c90661c5f6e59d))
|
|
7
|
+
|
|
8
|
+
### ✨ Features | 新功能
|
|
9
|
+
|
|
10
|
+
- **theme:** 新增本地主题文件支持 ([a989314](https://github.com/anyup/uView-Pro/commit/a989314a7b691e94ed81e524100e70d6e9a22a12))
|
|
11
|
+
- **theme:** 实现运行时主题变更功能,新增 setTheme 函数 ([12765d0](https://github.com/anyup/uView-Pro/commit/12765d07244a9d6ca49c4dd34d81f66a61c87c6d))
|
|
12
|
+
|
|
13
|
+
### 👥 Contributors
|
|
14
|
+
|
|
15
|
+
<a href="https://github.com/liujiayii"><img src="https://github.com/liujiayii.png?size=40" width="40" height="40" alt="liujiayii" title="liujiayii"/></a> <a href="https://github.com/anyup"><img src="https://github.com/anyup.png?size=40" width="40" height="40" alt="anyup" title="anyup"/></a>
|
|
16
|
+
|
|
17
|
+
## 0.3.8(2025-11-04)
|
|
18
|
+
|
|
19
|
+
### 📦 Build System | 打包构建
|
|
20
|
+
|
|
21
|
+
- 新增钉钉小程序运行和打包命令 ([a5b4ab3](https://github.com/anyup/uView-Pro/commit/a5b4ab3abab95b4c56bf02415f1785371f7f19ee))
|
|
22
|
+
|
|
23
|
+
### 🐛 Bug Fixes | Bug 修复
|
|
24
|
+
|
|
25
|
+
- **u-circle-progress:** 修复微信小程序 canvas 2d 环形进度条绘制问题,适配不同平台的 canvas 上下文 ([e7ab701](https://github.com/anyup/uView-Pro/commit/e7ab701bcbd83e7861aeb9a104269265c6b38a56))
|
|
26
|
+
|
|
27
|
+
### ✨ Features | 新功能
|
|
28
|
+
|
|
29
|
+
- **u-fab:** 新增悬浮按钮组件及演示示例 ([85848de](https://github.com/anyup/uView-Pro/commit/85848de6bae15d91942a633959459ae8e6ecb857))
|
|
30
|
+
- **u-fab:** 优化悬浮组件功能和交互,新增预设定位position、拖动吸边autoStick属性 ([65a4bde](https://github.com/anyup/uView-Pro/commit/65a4bde2331c8b2a49933bb4c5d7e1f97a11c56a))
|
|
31
|
+
- **u-text:** 新增 u-text 组件默认插槽支持 ([a7b6e59](https://github.com/anyup/uView-Pro/commit/a7b6e5944afbcd48920c25a58c07642544ff3d3e))
|
|
32
|
+
|
|
33
|
+
### ♻️ Code Refactoring | 代码重构
|
|
34
|
+
|
|
35
|
+
- **fab:** 优化 fab 组件示例代码 ([ca71fa2](https://github.com/anyup/uView-Pro/commit/ca71fa28fa86baa53384eb366384d97a0b8d84b2))
|
|
36
|
+
- **u-fab:** 重构 gap 属性以支持对象类型 ([bee34bf](https://github.com/anyup/uView-Pro/commit/bee34bffd6a7587e9a82fe3e35cc64c096d8d2b3))
|
|
37
|
+
|
|
38
|
+
### 👥 Contributors
|
|
39
|
+
|
|
40
|
+
<a href="https://github.com/anyup"><img src="https://github.com/anyup.png?size=40" width="40" height="40" alt="anyup" title="anyup"/></a> <a href="https://github.com/wjp980108"><img src="https://github.com/wjp980108.png?size=40" width="40" height="40" alt="wjp980108" title="wjp980108"/></a>
|
|
41
|
+
|
|
1
42
|
## 0.3.7(2025-10-28)
|
|
2
43
|
|
|
3
44
|
### 🐛 Bug Fixes | Bug 修复
|
|
@@ -34,7 +34,7 @@ export const ActionSheetProps = {
|
|
|
34
34
|
/** 取消按钮的文字提示 */
|
|
35
35
|
cancelText: { type: String, default: '取消' },
|
|
36
36
|
/** 字体颜色 */
|
|
37
|
-
color: { type: String, default: $u.color.mainColor },
|
|
37
|
+
color: { type: String, default: () => $u.color.mainColor },
|
|
38
38
|
/** 字体大小 */
|
|
39
39
|
fontSize: { type: [String, Number], default: '32rpx' },
|
|
40
40
|
/** 是否异步关闭 */
|
|
@@ -15,7 +15,7 @@ export const ActionSheetItemProps = {
|
|
|
15
15
|
/** 边距 */
|
|
16
16
|
padding: { type: [Number, String] as PropType<number | string>, default: '34rpx 0' },
|
|
17
17
|
/** 字体颜色 */
|
|
18
|
-
color: { type: String, default: $u.color.mainColor },
|
|
18
|
+
color: { type: String, default: () => $u.color.mainColor },
|
|
19
19
|
/** 字体大小 */
|
|
20
20
|
fontSize: { type: [String, Number], default: '32rpx' },
|
|
21
21
|
/** 是否禁用 */
|
|
@@ -40,13 +40,13 @@ export const CalendarProps = {
|
|
|
40
40
|
/** 默认日期字体颜色 */
|
|
41
41
|
color: { type: String, default: '#303133' },
|
|
42
42
|
/** 选中|起始结束日期背景色 */
|
|
43
|
-
activeBgColor: { type: String, default: $u.color.primary },
|
|
43
|
+
activeBgColor: { type: String, default: () => $u.color.primary },
|
|
44
44
|
/** 选中|起始结束日期字体颜色 */
|
|
45
45
|
activeColor: { type: String, default: '#ffffff' },
|
|
46
46
|
/** 范围内日期背景色 */
|
|
47
47
|
rangeBgColor: { type: String, default: 'rgba(41,121,255,0.13)' },
|
|
48
48
|
/** 范围内日期字体颜色 */
|
|
49
|
-
rangeColor: { type: String, default: $u.color.primary },
|
|
49
|
+
rangeColor: { type: String, default: () => $u.color.primary },
|
|
50
50
|
/** mode=range时生效,起始日期自定义文案 */
|
|
51
51
|
startText: { type: String, default: '开始' },
|
|
52
52
|
/** mode=range时生效,结束日期自定义文案 */
|
|
@@ -21,7 +21,7 @@ export const CheckboxGroupProps = {
|
|
|
21
21
|
/** 形状,square为方形,circle为原型 */
|
|
22
22
|
shape: { type: String as PropType<Shape>, default: 'square' },
|
|
23
23
|
/** 选中状态下的颜色 */
|
|
24
|
-
activeColor: { type: String, default: $u.color.primary },
|
|
24
|
+
activeColor: { type: String, default: () => $u.color.primary },
|
|
25
25
|
/** 组件的整体大小 */
|
|
26
26
|
size: { type: [String, Number], default: 34 },
|
|
27
27
|
/** 每个checkbox占u-checkbox-group的宽度 */
|
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
"
|
|
15
15
|
>
|
|
16
16
|
<!-- 支付宝小程序不支持canvas-id属性,必须用id属性 -->
|
|
17
|
+
<!-- #ifdef MP-WEIXIN || MP-TOUTIAO -->
|
|
17
18
|
<canvas
|
|
18
19
|
class="u-canvas-bg"
|
|
20
|
+
type="2d"
|
|
19
21
|
:canvas-id="elBgId"
|
|
20
22
|
:id="elBgId"
|
|
21
23
|
:style="{
|
|
@@ -25,6 +27,7 @@
|
|
|
25
27
|
></canvas>
|
|
26
28
|
<canvas
|
|
27
29
|
class="u-canvas"
|
|
30
|
+
type="2d"
|
|
28
31
|
:canvas-id="elId"
|
|
29
32
|
:id="elId"
|
|
30
33
|
:style="{
|
|
@@ -32,6 +35,27 @@
|
|
|
32
35
|
height: widthPx + 'px'
|
|
33
36
|
}"
|
|
34
37
|
></canvas>
|
|
38
|
+
<!-- #endif -->
|
|
39
|
+
<!-- #ifndef MP-WEIXIN || MP-TOUTIAO -->
|
|
40
|
+
<canvas
|
|
41
|
+
class="u-canvas-bg"
|
|
42
|
+
:canvas-id="elBgId"
|
|
43
|
+
:id="elBgId"
|
|
44
|
+
:style="{
|
|
45
|
+
width: widthPx + 'px',
|
|
46
|
+
height: widthPx + 'px'
|
|
47
|
+
}"
|
|
48
|
+
></canvas>
|
|
49
|
+
<canvas
|
|
50
|
+
class="u-canvas"
|
|
51
|
+
:canvas-id="elId"
|
|
52
|
+
:id="elId"
|
|
53
|
+
:style="{
|
|
54
|
+
width: widthPx + 'px',
|
|
55
|
+
height: widthPx + 'px'
|
|
56
|
+
}"
|
|
57
|
+
></canvas>
|
|
58
|
+
<!-- #endif -->
|
|
35
59
|
<slot></slot>
|
|
36
60
|
</view>
|
|
37
61
|
</template>
|
|
@@ -50,9 +74,12 @@ export default {
|
|
|
50
74
|
</script>
|
|
51
75
|
|
|
52
76
|
<script setup lang="ts">
|
|
53
|
-
import { ref, computed, watch, onMounted, getCurrentInstance } from 'vue';
|
|
77
|
+
import { ref, computed, watch, onMounted, getCurrentInstance, onBeforeMount } from 'vue';
|
|
54
78
|
import { $u } from '../..';
|
|
55
79
|
import { CircleProgressProps } from './types';
|
|
80
|
+
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
|
81
|
+
import { canvas2d } from '../../libs/util/canvas-2d';
|
|
82
|
+
// #endif
|
|
56
83
|
|
|
57
84
|
/**
|
|
58
85
|
* circleProgress 环形进度条
|
|
@@ -72,12 +99,17 @@ const props = defineProps(CircleProgressProps);
|
|
|
72
99
|
|
|
73
100
|
let elBgId = $u.guid(); // 非微信端的时候,需用动态的id,否则一个页面多个圆形进度条组件数据会混乱
|
|
74
101
|
let elId = $u.guid();
|
|
75
|
-
// #ifdef MP-WEIXIN
|
|
76
|
-
elBgId = 'uCircleProgressBgId'; // 微信小程序中不能使用$u.guid()形式动态生成id值,否则会报错
|
|
77
|
-
elId = 'uCircleProgressElId';
|
|
102
|
+
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
|
103
|
+
// elBgId = 'uCircleProgressBgId'; // 微信小程序中不能使用$u.guid()形式动态生成id值,否则会报错
|
|
104
|
+
// elId = 'uCircleProgressElId';
|
|
78
105
|
// #endif
|
|
79
106
|
const instance = getCurrentInstance();
|
|
80
107
|
|
|
108
|
+
const pixelRatio = ref<number>(1); // 像素比
|
|
109
|
+
|
|
110
|
+
// 存储 MP-WEIXIN 下通过 selectorQuery 获取到的 canvas node,方便在绘制前清空
|
|
111
|
+
const canvasNodeMap = new Map<string, any>();
|
|
112
|
+
|
|
81
113
|
const widthPx = computed(() =>
|
|
82
114
|
typeof uni !== 'undefined' && uni.upx2px ? uni.upx2px(Number(props.width)) : Number(props.width)
|
|
83
115
|
);
|
|
@@ -99,6 +131,10 @@ const circleColor = computed(() => {
|
|
|
99
131
|
return props.activeColor;
|
|
100
132
|
});
|
|
101
133
|
|
|
134
|
+
onBeforeMount(() => {
|
|
135
|
+
pixelRatio.value = uni.getSystemInfoSync().pixelRatio;
|
|
136
|
+
});
|
|
137
|
+
|
|
102
138
|
// 监听percent变化,动态绘制进度
|
|
103
139
|
watch(
|
|
104
140
|
() => props.percent,
|
|
@@ -126,11 +162,55 @@ onMounted(() => {
|
|
|
126
162
|
}, 50);
|
|
127
163
|
});
|
|
128
164
|
|
|
165
|
+
/**
|
|
166
|
+
* 获取canvas上下文
|
|
167
|
+
*/
|
|
168
|
+
function getContext(canvasId) {
|
|
169
|
+
return new Promise<UniApp.CanvasContext>(resolve => {
|
|
170
|
+
let ctx = null;
|
|
171
|
+
// #ifndef MP-WEIXIN || MP-TOUTIAO
|
|
172
|
+
ctx = uni.createCanvasContext(canvasId, instance);
|
|
173
|
+
resolve(ctx);
|
|
174
|
+
// #endif
|
|
175
|
+
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
|
176
|
+
uni.createSelectorQuery()
|
|
177
|
+
.in(instance?.proxy)
|
|
178
|
+
.select(`#${canvasId}`)
|
|
179
|
+
.node(res => {
|
|
180
|
+
if (res && res.node) {
|
|
181
|
+
const canvas = res.node;
|
|
182
|
+
ctx = canvas2d(canvas.getContext('2d') as CanvasRenderingContext2D);
|
|
183
|
+
canvas.width = widthPx.value * pixelRatio.value;
|
|
184
|
+
canvas.height = widthPx.value * pixelRatio.value;
|
|
185
|
+
ctx.scale(pixelRatio.value, pixelRatio.value);
|
|
186
|
+
// 存储 canvas node,后续绘制时用于清空画布
|
|
187
|
+
canvasNodeMap.set(canvasId, canvas);
|
|
188
|
+
resolve(ctx);
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
.exec();
|
|
192
|
+
// #endif
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
129
196
|
/**
|
|
130
197
|
* 绘制底部灰色圆环
|
|
131
198
|
*/
|
|
132
|
-
function drawProgressBg() {
|
|
133
|
-
const ctx =
|
|
199
|
+
async function drawProgressBg() {
|
|
200
|
+
const ctx = await getContext(elBgId);
|
|
201
|
+
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
|
202
|
+
// 清空背景画布(如果可用)以确保绘制一致性
|
|
203
|
+
try {
|
|
204
|
+
if (typeof ctx.clearRect === 'function') {
|
|
205
|
+
const node = canvasNodeMap.get(elBgId);
|
|
206
|
+
const w = node ? node.width : widthPx.value * pixelRatio.value;
|
|
207
|
+
const h = node ? node.height : widthPx.value * pixelRatio.value;
|
|
208
|
+
ctx.clearRect(0, 0, w, h);
|
|
209
|
+
}
|
|
210
|
+
} catch (e) {
|
|
211
|
+
// ignore
|
|
212
|
+
}
|
|
213
|
+
// #endif
|
|
134
214
|
ctx.setLineWidth(borderWidthPx.value); // 设置圆环宽度
|
|
135
215
|
ctx.setStrokeStyle(props.inactiveColor); // 线条颜色
|
|
136
216
|
ctx.beginPath(); // 开始描绘路径
|
|
@@ -144,13 +224,26 @@ function drawProgressBg() {
|
|
|
144
224
|
* 绘制进度圆环
|
|
145
225
|
* @param progress 当前进度
|
|
146
226
|
*/
|
|
147
|
-
function drawCircleByProgress(progress: number) {
|
|
227
|
+
async function drawCircleByProgress(progress: number) {
|
|
148
228
|
// 第一次操作进度环时将上下文保存到了this.data中,直接使用即可
|
|
149
229
|
let ctx = progressContext.value;
|
|
150
230
|
if (!ctx) {
|
|
151
|
-
ctx =
|
|
231
|
+
ctx = await getContext(elId);
|
|
152
232
|
progressContext.value = ctx;
|
|
153
233
|
}
|
|
234
|
+
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
|
235
|
+
// 清空进度画布,避免旧的更大进度残留,导致无法降低的视觉错误
|
|
236
|
+
try {
|
|
237
|
+
if (typeof ctx.clearRect === 'function') {
|
|
238
|
+
const node = canvasNodeMap.get(elId);
|
|
239
|
+
const w = node ? node.width : widthPx.value * pixelRatio.value;
|
|
240
|
+
const h = node ? node.height : widthPx.value * pixelRatio.value;
|
|
241
|
+
ctx.clearRect(0, 0, w, h);
|
|
242
|
+
}
|
|
243
|
+
} catch (e) {
|
|
244
|
+
// ignore
|
|
245
|
+
}
|
|
246
|
+
// #endif
|
|
154
247
|
// 表示进度的两端为圆形
|
|
155
248
|
ctx.setLineCap('round');
|
|
156
249
|
// 设置线条的宽度和颜色
|
|
@@ -9,9 +9,9 @@ import { $u } from '../../';
|
|
|
9
9
|
export const DropdownProps = {
|
|
10
10
|
...baseProps,
|
|
11
11
|
/** 菜单标题和选项的激活态颜色 */
|
|
12
|
-
activeColor: { type: String, default: $u.color.primary },
|
|
12
|
+
activeColor: { type: String, default: () => $u.color.primary },
|
|
13
13
|
/** 菜单标题和选项的未激活态颜色 */
|
|
14
|
-
inactiveColor: { type: String, default: $u.color.contentColor },
|
|
14
|
+
inactiveColor: { type: String, default: () => $u.color.contentColor },
|
|
15
15
|
/** 点击遮罩是否关闭菜单 */
|
|
16
16
|
closeOnClickMask: { type: Boolean, default: true },
|
|
17
17
|
/** 点击当前激活项标题是否关闭菜单 */
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
|
|
2
|
+
import type { FabDirection, FabGap, FabPosition, ThemeType } from '../../types/global';
|
|
3
|
+
import { baseProps } from '../common/props';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* fab 悬浮按钮类型定义
|
|
7
|
+
* @description 供 u-fab 组件 props 使用
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const FabProps = {
|
|
11
|
+
...baseProps,
|
|
12
|
+
/** 按钮的预置样式,primary,info,error,warning,success */
|
|
13
|
+
type: { type: String as PropType<ThemeType>, default: 'primary' },
|
|
14
|
+
/** 是否禁止状态 */
|
|
15
|
+
disabled: { type: Boolean, default: false },
|
|
16
|
+
/** 按钮能否可以拖动 */
|
|
17
|
+
draggable: { type: Boolean, default: false },
|
|
18
|
+
/** 按钮与边缘的间距,单位 px。支持 number 或对象 {top,left,right,bottom},优先级按键存在顺序 */
|
|
19
|
+
gap: {
|
|
20
|
+
type: Object as PropType<FabGap>,
|
|
21
|
+
default: () => ({
|
|
22
|
+
top: 16,
|
|
23
|
+
left: 16,
|
|
24
|
+
right: 16,
|
|
25
|
+
bottom: 16
|
|
26
|
+
})
|
|
27
|
+
},
|
|
28
|
+
/** 拖动结束时是否自动吸边(仅当 draggable 为 true 时生效) */
|
|
29
|
+
autoStick: { type: Boolean, default: true },
|
|
30
|
+
/** 预设定位:控制组件默认停靠位置 */
|
|
31
|
+
position: {
|
|
32
|
+
type: String as PropType<FabPosition>,
|
|
33
|
+
default: 'right-bottom'
|
|
34
|
+
},
|
|
35
|
+
/** 菜单出现的方向 */
|
|
36
|
+
direction: { type: String as PropType<FabDirection>, default: 'top' },
|
|
37
|
+
/** 按钮自定义层级 */
|
|
38
|
+
zIndex: { type: Number, default: 99 }
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type FabProps = ExtractPropTypes<typeof FabProps>;
|
|
42
|
+
|
|
43
|
+
interface DirectionConfig {
|
|
44
|
+
opposite: keyof CSSProperties;
|
|
45
|
+
sizeKey: 'width' | 'height';
|
|
46
|
+
positionKey: 'left' | 'top';
|
|
47
|
+
flexBase: 'row' | 'row-reverse' | 'column' | 'column-reverse';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const directionConfig: Record<FabDirection, DirectionConfig> = {
|
|
51
|
+
top: {
|
|
52
|
+
opposite: 'bottom',
|
|
53
|
+
sizeKey: 'width',
|
|
54
|
+
positionKey: 'left',
|
|
55
|
+
flexBase: 'column'
|
|
56
|
+
},
|
|
57
|
+
bottom: {
|
|
58
|
+
opposite: 'top',
|
|
59
|
+
sizeKey: 'width',
|
|
60
|
+
positionKey: 'left',
|
|
61
|
+
flexBase: 'column-reverse'
|
|
62
|
+
},
|
|
63
|
+
left: {
|
|
64
|
+
opposite: 'right',
|
|
65
|
+
sizeKey: 'height',
|
|
66
|
+
positionKey: 'top',
|
|
67
|
+
flexBase: 'row'
|
|
68
|
+
},
|
|
69
|
+
right: {
|
|
70
|
+
opposite: 'left',
|
|
71
|
+
sizeKey: 'height',
|
|
72
|
+
positionKey: 'top',
|
|
73
|
+
flexBase: 'row-reverse'
|
|
74
|
+
}
|
|
75
|
+
};
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<view
|
|
3
|
+
class="u-fab"
|
|
4
|
+
:style="$u.toStyle(fabStyle, customStyle)"
|
|
5
|
+
:class="[customClass]"
|
|
6
|
+
@touchstart="handleTouchstart"
|
|
7
|
+
@touchmove.stop.prevent="handleTouchmove"
|
|
8
|
+
@touchend="handleTouchend"
|
|
9
|
+
>
|
|
10
|
+
<view class="u-fab-trigger" id="trigger">
|
|
11
|
+
<slot name="trigger">
|
|
12
|
+
<u-button
|
|
13
|
+
custom-class="u-fab-trigger-btn"
|
|
14
|
+
custom-style="width:112rpx;height:112rpx;border-radius:112rpx;"
|
|
15
|
+
:type="type"
|
|
16
|
+
:disabled="disabled"
|
|
17
|
+
:throttle-time="0"
|
|
18
|
+
@click="handleBtnClick"
|
|
19
|
+
>
|
|
20
|
+
<u-icon :name="expansion ? 'close' : 'plus'" size="36rpx"></u-icon>
|
|
21
|
+
</u-button>
|
|
22
|
+
</slot>
|
|
23
|
+
</view>
|
|
24
|
+
<view class="u-fab-actions" id="actions" :class="{ 'u-fab-actions__show': expansion }" :style="actionsStyle">
|
|
25
|
+
<slot></slot>
|
|
26
|
+
</view>
|
|
27
|
+
</view>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script lang="ts">
|
|
31
|
+
export default {
|
|
32
|
+
name: 'u-fab',
|
|
33
|
+
options: {
|
|
34
|
+
addGlobalClass: true,
|
|
35
|
+
// #ifndef MP-TOUTIAO
|
|
36
|
+
virtualHost: true,
|
|
37
|
+
// #endif
|
|
38
|
+
styleIsolation: 'shared'
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<script setup lang="ts">
|
|
44
|
+
import { directionConfig, FabProps } from './types.ts';
|
|
45
|
+
import { computed, getCurrentInstance, onMounted, reactive, ref, useSlots, watch } from 'vue';
|
|
46
|
+
import { $u } from '../../';
|
|
47
|
+
|
|
48
|
+
const props = defineProps(FabProps);
|
|
49
|
+
const emit = defineEmits(['trigger']);
|
|
50
|
+
|
|
51
|
+
const slots = useSlots();
|
|
52
|
+
const instance = getCurrentInstance();
|
|
53
|
+
const sysInfo = $u.sys();
|
|
54
|
+
const dragging = ref(true);
|
|
55
|
+
const minLeft = ref(0);
|
|
56
|
+
const maxLeft = ref(0);
|
|
57
|
+
const minTop = ref(0);
|
|
58
|
+
const maxTop = ref(0);
|
|
59
|
+
const expansion = ref(false);
|
|
60
|
+
const direction = ref(props.direction);
|
|
61
|
+
const effectiveWindowHeight = ref(sysInfo.windowHeight);
|
|
62
|
+
// #ifdef H5
|
|
63
|
+
effectiveWindowHeight.value = sysInfo.windowTop + sysInfo.windowHeight;
|
|
64
|
+
// #endif
|
|
65
|
+
const position = reactive({
|
|
66
|
+
top: 0,
|
|
67
|
+
left: 0
|
|
68
|
+
});
|
|
69
|
+
const actions = ref();
|
|
70
|
+
const btnInfo = ref({
|
|
71
|
+
width: 56,
|
|
72
|
+
height: 56
|
|
73
|
+
});
|
|
74
|
+
const start = reactive({
|
|
75
|
+
x: 0,
|
|
76
|
+
y: 0
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// 计算悬浮按钮样式
|
|
80
|
+
const fabStyle = computed(() => {
|
|
81
|
+
const style: Record<string, any> = {
|
|
82
|
+
transition: dragging.value ? 'none' : 'all 0.3s ease',
|
|
83
|
+
zIndex: props.zIndex,
|
|
84
|
+
left: `${position.left}px`,
|
|
85
|
+
top: `${position.top}px`
|
|
86
|
+
};
|
|
87
|
+
return style;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// 动画样式
|
|
91
|
+
const actionsStyle = computed(() => {
|
|
92
|
+
const config = directionConfig[direction.value];
|
|
93
|
+
const base: Record<string, any> = {
|
|
94
|
+
[config.opposite]: '100%',
|
|
95
|
+
[config.sizeKey]: '100%',
|
|
96
|
+
[config.positionKey]: 0,
|
|
97
|
+
flexDirection: config.flexBase,
|
|
98
|
+
transition: 'all 0.28s cubic-bezier(0.3, 0, 0.2, 1)'
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// 展开/收缩动画:根据方向设置初始偏移
|
|
102
|
+
const offset = 12; // rpx/px 相对轻微偏移,视觉更顺滑
|
|
103
|
+
if (expansion.value) {
|
|
104
|
+
base.transform = 'translate3d(0,0,0) scale(1)';
|
|
105
|
+
base.opacity = 1;
|
|
106
|
+
} else {
|
|
107
|
+
// 隐藏时做一个方向上的微位移并缩放以优化动画感
|
|
108
|
+
if (direction.value === 'top') base.transform = `translate3d(0, ${offset}px, 0) scale(0.96)`;
|
|
109
|
+
else if (direction.value === 'bottom') base.transform = `translate3d(0, -${offset}px, 0) scale(0.96)`;
|
|
110
|
+
else if (direction.value === 'left') base.transform = `translate3d(${offset}px, 0, 0) scale(0.96)`;
|
|
111
|
+
else base.transform = `translate3d(-${offset}px, 0, 0) scale(0.96)`;
|
|
112
|
+
base.opacity = 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return base;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
watch(
|
|
119
|
+
() => [props.position, props.gap],
|
|
120
|
+
() => {
|
|
121
|
+
initPosition();
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
watch(
|
|
126
|
+
() => props.direction,
|
|
127
|
+
() => {
|
|
128
|
+
if (expansion.value) direction.value = calcDirection();
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// helper:支持 gap 为 number 或对象形式
|
|
133
|
+
function getGap(side: 'top' | 'left' | 'right' | 'bottom') {
|
|
134
|
+
const g = props.gap as any;
|
|
135
|
+
if (typeof g === 'number') return g;
|
|
136
|
+
if (g && typeof g === 'object' && g[side] !== undefined) return Number(g[side]);
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 拖动开始事件
|
|
141
|
+
function handleTouchstart(e: TouchEvent) {
|
|
142
|
+
if (props.disabled || !props.draggable) return;
|
|
143
|
+
|
|
144
|
+
const touches = e.touches[0];
|
|
145
|
+
start.x = touches.clientX;
|
|
146
|
+
start.y = touches.clientY;
|
|
147
|
+
dragging.value = true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 拖动移动事件
|
|
151
|
+
function handleTouchmove(e: TouchEvent) {
|
|
152
|
+
if (props.disabled || !props.draggable) return;
|
|
153
|
+
|
|
154
|
+
const touches = e.touches[0];
|
|
155
|
+
const deltaX = touches.clientX - start.x;
|
|
156
|
+
const deltaY = touches.clientY - start.y;
|
|
157
|
+
|
|
158
|
+
position.left += deltaX;
|
|
159
|
+
position.top += deltaY;
|
|
160
|
+
|
|
161
|
+
start.x = touches.clientX;
|
|
162
|
+
start.y = touches.clientY;
|
|
163
|
+
|
|
164
|
+
// 设置边界,防止拖出边界
|
|
165
|
+
position.left = Math.max(minLeft.value, Math.min(maxLeft.value, position.left));
|
|
166
|
+
position.top = Math.max(minTop.value, Math.min(maxTop.value, position.top));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 拖动结束事件
|
|
170
|
+
function handleTouchend() {
|
|
171
|
+
if (props.disabled || !props.draggable) return;
|
|
172
|
+
|
|
173
|
+
dragging.value = false;
|
|
174
|
+
|
|
175
|
+
// 如果 autoStick 为 false,则释放后不进行自动吸边,仅做边界限制
|
|
176
|
+
if (props.autoStick === false) {
|
|
177
|
+
position.left = Math.max(minLeft.value, Math.min(maxLeft.value, position.left));
|
|
178
|
+
position.top = Math.max(minTop.value, Math.min(maxTop.value, position.top));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const middle = sysInfo.windowWidth / 2;
|
|
183
|
+
const buttonCenter = position.left + btnInfo.value.width / 2;
|
|
184
|
+
|
|
185
|
+
// 自动吸边,按钮中心位置大于视口的一半时,自动依附在右边,不然就是左边
|
|
186
|
+
position.left =
|
|
187
|
+
buttonCenter > middle ? sysInfo.windowWidth - btnInfo.value.width - getGap('right') : getGap('left');
|
|
188
|
+
|
|
189
|
+
if (expansion.value) direction.value = calcDirection();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 按钮点击事件
|
|
193
|
+
function handleBtnClick() {
|
|
194
|
+
if (slots?.default) {
|
|
195
|
+
expansion.value = !expansion.value;
|
|
196
|
+
if (expansion.value) direction.value = calcDirection();
|
|
197
|
+
} else {
|
|
198
|
+
emit('trigger');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 计算方向
|
|
203
|
+
function calcDirection() {
|
|
204
|
+
if (!actions.value) return props.direction;
|
|
205
|
+
|
|
206
|
+
let dir = props.direction;
|
|
207
|
+
|
|
208
|
+
const actionsHeight = actions.value?.height || 0;
|
|
209
|
+
const actionsWidth = actions.value?.width || 0;
|
|
210
|
+
|
|
211
|
+
// 菜单展开时,如果位置不够,则反转方向
|
|
212
|
+
if (dir === 'top') {
|
|
213
|
+
// 按钮上方剩余空间: 按钮顶部 - 顶部边距
|
|
214
|
+
if (position.top - minTop.value < actionsHeight) dir = 'bottom';
|
|
215
|
+
} else if (dir === 'bottom') {
|
|
216
|
+
// 按钮下方剩余空间: 有效窗口高度 - (按钮顶部 + 按钮高 + 边距)
|
|
217
|
+
const bottom = effectiveWindowHeight.value - (position.top + btnInfo.value.height + getGap('bottom'));
|
|
218
|
+
if (bottom < actionsHeight) dir = 'top';
|
|
219
|
+
} else if (dir === 'left') {
|
|
220
|
+
// 按钮左侧剩余空间: 有效窗口宽度 - 边距
|
|
221
|
+
if (position.left - getGap('left') < actionsWidth) dir = 'right';
|
|
222
|
+
} else if (dir === 'right') {
|
|
223
|
+
// 按钮右侧剩余空间: 有效窗口宽度 - (按钮左侧 + 按钮宽 + 边距)
|
|
224
|
+
const right = sysInfo.windowWidth - (position.left + btnInfo.value.width + getGap('right'));
|
|
225
|
+
if (right < actionsWidth) dir = 'left';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return dir;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 初始化位置
|
|
232
|
+
function initPosition() {
|
|
233
|
+
// 根据 props.position 计算初始 left/top
|
|
234
|
+
const pos = props.position || 'right-bottom';
|
|
235
|
+
const winW = sysInfo.windowWidth;
|
|
236
|
+
const winH = effectiveWindowHeight.value;
|
|
237
|
+
|
|
238
|
+
switch (pos) {
|
|
239
|
+
case 'left-top':
|
|
240
|
+
position.left = getGap('left');
|
|
241
|
+
position.top = getGap('top') + sysInfo.windowTop;
|
|
242
|
+
break;
|
|
243
|
+
case 'right-top':
|
|
244
|
+
position.left = winW - btnInfo.value.width - getGap('right');
|
|
245
|
+
position.top = getGap('top') + sysInfo.windowTop;
|
|
246
|
+
break;
|
|
247
|
+
case 'left-bottom':
|
|
248
|
+
position.left = getGap('left');
|
|
249
|
+
position.top = winH - btnInfo.value.height - getGap('bottom');
|
|
250
|
+
break;
|
|
251
|
+
case 'right-bottom':
|
|
252
|
+
position.left = winW - btnInfo.value.width - getGap('right');
|
|
253
|
+
position.top = winH - btnInfo.value.height - getGap('bottom');
|
|
254
|
+
break;
|
|
255
|
+
case 'left-center':
|
|
256
|
+
position.left = getGap('left');
|
|
257
|
+
position.top = Math.round((winH - btnInfo.value.height) / 2);
|
|
258
|
+
break;
|
|
259
|
+
case 'right-center':
|
|
260
|
+
position.left = winW - btnInfo.value.width - getGap('right');
|
|
261
|
+
position.top = Math.round((winH - btnInfo.value.height) / 2);
|
|
262
|
+
break;
|
|
263
|
+
case 'top-center':
|
|
264
|
+
position.left = Math.round((winW - btnInfo.value.width) / 2);
|
|
265
|
+
position.top = getGap('top') + sysInfo.windowTop;
|
|
266
|
+
break;
|
|
267
|
+
case 'bottom-center':
|
|
268
|
+
position.left = Math.round((winW - btnInfo.value.width) / 2);
|
|
269
|
+
position.top = winH - btnInfo.value.height - getGap('bottom');
|
|
270
|
+
break;
|
|
271
|
+
default:
|
|
272
|
+
position.left = winW - btnInfo.value.width - getGap('right');
|
|
273
|
+
position.top = winH - btnInfo.value.height - getGap('bottom');
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
onMounted(async () => {
|
|
279
|
+
btnInfo.value = await $u.getRect('#trigger', instance);
|
|
280
|
+
actions.value = await $u.getRect('#actions', instance);
|
|
281
|
+
|
|
282
|
+
initPosition();
|
|
283
|
+
|
|
284
|
+
minLeft.value = getGap('left');
|
|
285
|
+
minTop.value = getGap('top') + sysInfo.windowTop;
|
|
286
|
+
maxLeft.value = sysInfo.windowWidth - btnInfo.value.width - getGap('right');
|
|
287
|
+
maxTop.value = effectiveWindowHeight.value - btnInfo.value.height - getGap('bottom');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
defineExpose({
|
|
291
|
+
toggle: handleBtnClick
|
|
292
|
+
});
|
|
293
|
+
</script>
|
|
294
|
+
|
|
295
|
+
<style scoped lang="scss">
|
|
296
|
+
.u-fab {
|
|
297
|
+
position: fixed;
|
|
298
|
+
|
|
299
|
+
.u-fab-trigger {
|
|
300
|
+
:deep(.u-fab-trigger-btn) {
|
|
301
|
+
width: 112rpx;
|
|
302
|
+
height: 112rpx;
|
|
303
|
+
border-radius: 112rpx;
|
|
304
|
+
|
|
305
|
+
&::after {
|
|
306
|
+
border-radius: 112rpx;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.u-fab-actions {
|
|
312
|
+
position: absolute;
|
|
313
|
+
display: flex;
|
|
314
|
+
justify-content: center;
|
|
315
|
+
align-items: center;
|
|
316
|
+
visibility: hidden;
|
|
317
|
+
opacity: 0;
|
|
318
|
+
transform-origin: center;
|
|
319
|
+
will-change: transform, opacity;
|
|
320
|
+
transition: all 0.28s cubic-bezier(0.3, 0, 0.2, 1);
|
|
321
|
+
|
|
322
|
+
&.u-fab-actions__show {
|
|
323
|
+
visibility: visible;
|
|
324
|
+
opacity: 1;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
</style>
|
|
@@ -50,12 +50,12 @@ export const MessageInputProps = {
|
|
|
50
50
|
/** 激活样式 */
|
|
51
51
|
activeColor: {
|
|
52
52
|
type: String,
|
|
53
|
-
default: $u.color.primary
|
|
53
|
+
default: () => $u.color.primary
|
|
54
54
|
},
|
|
55
55
|
/** 未激活的样式 */
|
|
56
56
|
inactiveColor: {
|
|
57
57
|
type: String,
|
|
58
|
-
default: $u.color.contentColor
|
|
58
|
+
default: () => $u.color.contentColor
|
|
59
59
|
},
|
|
60
60
|
/** 输入框的大小,单位rpx,宽等于高 */
|
|
61
61
|
width: {
|
|
@@ -59,12 +59,12 @@ export const ModalProps = {
|
|
|
59
59
|
/** 确认按钮颜色 */
|
|
60
60
|
confirmColor: {
|
|
61
61
|
type: String,
|
|
62
|
-
default: $u.color.primary
|
|
62
|
+
default: () => $u.color.primary
|
|
63
63
|
},
|
|
64
64
|
/** 取消文字颜色 */
|
|
65
65
|
cancelColor: {
|
|
66
66
|
type: String,
|
|
67
|
-
default: $u.color.contentColor
|
|
67
|
+
default: () => $u.color.contentColor
|
|
68
68
|
},
|
|
69
69
|
/** 圆角值 */
|
|
70
70
|
borderRadius: {
|
|
@@ -62,12 +62,12 @@ export const PickerProps = {
|
|
|
62
62
|
/** "取消"按钮的颜色 */
|
|
63
63
|
cancelColor: {
|
|
64
64
|
type: String,
|
|
65
|
-
default: $u.color.contentColor
|
|
65
|
+
default: () => $u.color.contentColor
|
|
66
66
|
},
|
|
67
67
|
/** "确定"按钮的颜色 */
|
|
68
68
|
confirmColor: {
|
|
69
69
|
type: String,
|
|
70
|
-
default: $u.color.primary
|
|
70
|
+
default: () => $u.color.primary
|
|
71
71
|
},
|
|
72
72
|
/** 默认显示的时间,2025-07-02 || 2025-07-02 13:01:00 || 2025/07/02 */
|
|
73
73
|
defaultTime: {
|
|
@@ -14,7 +14,7 @@ export const RadioGroupProps = {
|
|
|
14
14
|
/** 匹配某一个radio组件,如果某个radio的name值等于此值,那么这个radio就被会选中 */
|
|
15
15
|
modelValue: { type: [String, Number] as PropType<string | number>, default: '' },
|
|
16
16
|
/** 选中状态下的颜色 */
|
|
17
|
-
activeColor: { type: String, default: $u.color.primary },
|
|
17
|
+
activeColor: { type: String, default: () => $u.color.primary },
|
|
18
18
|
/** 组件的整体大小 */
|
|
19
19
|
size: { type: [String, Number] as PropType<number | string>, default: 34 },
|
|
20
20
|
/** 是否禁止点击提示语选中复选框 */
|
|
@@ -17,7 +17,7 @@ export const ReadMoreProps = {
|
|
|
17
17
|
/** 展开时的提示文字 */
|
|
18
18
|
openText: { type: String, default: '收起' },
|
|
19
19
|
/** 提示的文字颜色 */
|
|
20
|
-
color: { type: String, default: $u.color.primary },
|
|
20
|
+
color: { type: String, default: () => $u.color.primary },
|
|
21
21
|
/** 提示文字的大小 */
|
|
22
22
|
fontSize: { type: [String, Number] as PropType<number | string>, default: 28 },
|
|
23
23
|
/** 是否显示阴影 */
|
|
@@ -16,9 +16,9 @@ export const SelectProps = {
|
|
|
16
16
|
/** 通过双向绑定控制组件的弹出与收起 */
|
|
17
17
|
modelValue: { type: Boolean, default: false },
|
|
18
18
|
/** "取消"按钮的颜色 */
|
|
19
|
-
cancelColor: { type: String, default: $u.color.contentColor },
|
|
19
|
+
cancelColor: { type: String, default: () => $u.color.contentColor },
|
|
20
20
|
/** "确定"按钮的颜色 */
|
|
21
|
-
confirmColor: { type: String, default: $u.color.primary },
|
|
21
|
+
confirmColor: { type: String, default: () => $u.color.primary },
|
|
22
22
|
/** 弹出的z-index值 */
|
|
23
23
|
zIndex: { type: [String, Number] as PropType<string | number>, default: 0 },
|
|
24
24
|
/** 是否开启底部安全区适配 */
|
|
@@ -23,9 +23,9 @@ export const SliderProps = {
|
|
|
23
23
|
/** 滑块条高度,单位rpx */
|
|
24
24
|
height: { type: [Number, String] as PropType<number | string>, default: 6 },
|
|
25
25
|
/** 进度条的激活部分颜色 */
|
|
26
|
-
activeColor: { type: String, default: $u.color.primary },
|
|
26
|
+
activeColor: { type: String, default: () => $u.color.primary },
|
|
27
27
|
/** 进度条的背景颜色 */
|
|
28
|
-
inactiveColor: { type: String, default: $u.color.lightColor },
|
|
28
|
+
inactiveColor: { type: String, default: () => $u.color.lightColor },
|
|
29
29
|
/** 滑块的背景颜色 */
|
|
30
30
|
blockColor: { type: String, default: '#ffffff' },
|
|
31
31
|
/** 用户对滑块的自定义颜色 */
|
|
@@ -15,9 +15,9 @@ export const StepProps = {
|
|
|
15
15
|
/** 主题类型, primary|success|info|warning|error */
|
|
16
16
|
type: { type: String as PropType<ThemeType>, default: 'primary' },
|
|
17
17
|
/** 激活步骤的颜色 */
|
|
18
|
-
activeColor: { type: String, default: $u.color.primary },
|
|
18
|
+
activeColor: { type: String, default: () => $u.color.primary },
|
|
19
19
|
/** 未激活的颜色 */
|
|
20
|
-
unActiveColor: { type: String, default: $u.color.info },
|
|
20
|
+
unActiveColor: { type: String, default: () => $u.color.info },
|
|
21
21
|
/** 自定义图标 */
|
|
22
22
|
icon: { type: String, default: 'checkmark' },
|
|
23
23
|
/** 标题 */
|
|
@@ -19,9 +19,9 @@ export const StepsProps = {
|
|
|
19
19
|
/** 当前哪一步是激活的 */
|
|
20
20
|
current: { type: [Number, String] as PropType<number | string>, default: 0 },
|
|
21
21
|
/** 激活步骤的颜色 */
|
|
22
|
-
activeColor: { type: String, default: $u.color.primary },
|
|
22
|
+
activeColor: { type: String, default: () => $u.color.primary },
|
|
23
23
|
/** 未激活的颜色 */
|
|
24
|
-
unActiveColor: { type: String, default: $u.color.info },
|
|
24
|
+
unActiveColor: { type: String, default: () => $u.color.info },
|
|
25
25
|
/** 自定义图标 */
|
|
26
26
|
icon: { type: String, default: 'checkmark' },
|
|
27
27
|
/** step的排列方向,row-横向,column-竖向 */
|
|
@@ -15,7 +15,7 @@ export const SwitchProps = {
|
|
|
15
15
|
/** 开关尺寸,单位rpx */
|
|
16
16
|
size: { type: [Number, String] as PropType<number | string>, default: 50 },
|
|
17
17
|
/** 打开时的颜色 */
|
|
18
|
-
activeColor: { type: String, default: $u.color.primary },
|
|
18
|
+
activeColor: { type: String, default: () => $u.color.primary },
|
|
19
19
|
/** 关闭时的颜色 */
|
|
20
20
|
inactiveColor: { type: String, default: '#ffffff' },
|
|
21
21
|
/** v-model 绑定值,是否选中 */
|
|
@@ -22,9 +22,9 @@ export const TabsProps = {
|
|
|
22
22
|
/** 过渡动画时长, 单位s */
|
|
23
23
|
duration: { type: [String, Number] as PropType<number | string>, default: 0.5 },
|
|
24
24
|
/** 选中项的主题颜色 */
|
|
25
|
-
activeColor: { type: String, default: $u.color.primary },
|
|
25
|
+
activeColor: { type: String, default: () => $u.color.primary },
|
|
26
26
|
/** 未选中项的颜色 */
|
|
27
|
-
inactiveColor: { type: String, default: $u.color.mainColor },
|
|
27
|
+
inactiveColor: { type: String, default: () => $u.color.mainColor },
|
|
28
28
|
/** 菜单底部移动的bar的宽度,单位rpx */
|
|
29
29
|
barWidth: { type: [String, Number] as PropType<number | string>, default: 40 },
|
|
30
30
|
/** 移动bar的高度 */
|
|
@@ -22,9 +22,9 @@ export const TabsSwiperProps = {
|
|
|
22
22
|
/** tabs组件外部swiper的宽度,单位rpx */
|
|
23
23
|
swiperWidth: { type: [String, Number] as PropType<number | string>, default: 750 },
|
|
24
24
|
/** 滑块和激活tab文字的颜色 */
|
|
25
|
-
activeColor: { type: String, default: $u.color.primary },
|
|
25
|
+
activeColor: { type: String, default: () => $u.color.primary },
|
|
26
26
|
/** tabs文字颜色 */
|
|
27
|
-
inactiveColor: { type: String, default: $u.color.mainColor },
|
|
27
|
+
inactiveColor: { type: String, default: () => $u.color.mainColor },
|
|
28
28
|
/** 滑块宽度,单位rpx */
|
|
29
29
|
barWidth: { type: [Number, String] as PropType<number | string>, default: 40 },
|
|
30
30
|
/** 滑块高度,单位rpx */
|
|
@@ -22,10 +22,12 @@
|
|
|
22
22
|
:class="['u-text__price', props.type && `u-text__value--${props.type}`]"
|
|
23
23
|
:style="textValueStyle"
|
|
24
24
|
>
|
|
25
|
-
|
|
25
|
+
<slot>¥{{ displayValue }}</slot>
|
|
26
26
|
</text>
|
|
27
27
|
<!-- link 模式 -->
|
|
28
|
-
<u-link v-else-if="props.mode === 'link'" :href="props.href" underLine>
|
|
28
|
+
<u-link v-else-if="props.mode === 'link'" :href="props.href" underLine>
|
|
29
|
+
<slot>{{ displayValue }}</slot>
|
|
30
|
+
</u-link>
|
|
29
31
|
<template v-else-if="props.openType && isMp">
|
|
30
32
|
<button
|
|
31
33
|
class="u-reset-button u-text__value"
|
|
@@ -46,7 +48,7 @@
|
|
|
46
48
|
:show-message-card="props.showMessageCard"
|
|
47
49
|
:app-parameter="props.appParameter"
|
|
48
50
|
>
|
|
49
|
-
{{ displayValue }}
|
|
51
|
+
<slot>{{ displayValue }}</slot>
|
|
50
52
|
</button>
|
|
51
53
|
</template>
|
|
52
54
|
<!-- 默认模式 -->
|
|
@@ -56,7 +58,7 @@
|
|
|
56
58
|
:style="textValueStyle"
|
|
57
59
|
:class="[props.type && `u-text__value--${props.type}`, props.lines ? `u-line-${props.lines}` : '']"
|
|
58
60
|
>
|
|
59
|
-
{{ displayValue }}
|
|
61
|
+
<slot>{{ displayValue }}</slot>
|
|
60
62
|
</text>
|
|
61
63
|
<!-- 后缀图标 -->
|
|
62
64
|
<view class="u-text__icon u-text__suffix-icon" v-if="props.suffixIcon">
|
package/index.ts
CHANGED
|
@@ -15,9 +15,9 @@ declare const uni: {
|
|
|
15
15
|
const install = (app: any, options?: UViewProOptions): void => {
|
|
16
16
|
uni.$u = $u;
|
|
17
17
|
if (options) {
|
|
18
|
-
//
|
|
18
|
+
// 配置主题:使用 $u.setTheme 以就地更新 reactive $u.color
|
|
19
19
|
if (options.theme) {
|
|
20
|
-
$u.
|
|
20
|
+
$u.setTheme?.(options.theme);
|
|
21
21
|
}
|
|
22
22
|
// 设置调试模式
|
|
23
23
|
logger
|
package/libs/function/color.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { ThemeColor } from '../../types/global';
|
|
1
|
+
import type { ColorType, ThemeColor } from '../../types/global';
|
|
2
|
+
import { reactive } from 'vue';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
// 使用 reactive 包装颜色对象,使其在运行时可被响应式读取与更新
|
|
5
|
+
const color: ThemeColor = reactive({
|
|
4
6
|
primary: '#2979ff',
|
|
5
7
|
primaryDark: '#2b85e4',
|
|
6
8
|
primaryDisabled: '#a0cfff',
|
|
@@ -32,6 +34,10 @@ const color: ThemeColor = {
|
|
|
32
34
|
tipsColor: '#909399',
|
|
33
35
|
lightColor: '#c0c4cc',
|
|
34
36
|
borderColor: '#e4e7ed'
|
|
35
|
-
};
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export function getColor(name: ColorType) {
|
|
40
|
+
return (color as any)[name] || '';
|
|
41
|
+
}
|
|
36
42
|
|
|
37
43
|
export default color;
|
package/libs/index.ts
CHANGED
|
@@ -12,6 +12,8 @@ import colorGradients from './function/colorGradient';
|
|
|
12
12
|
import guid from './function/guid';
|
|
13
13
|
// 主题相关颜色,info|success|warning|primary|default|error,此颜色已在uview.scss中定义,但是为js中也能使用,故也定义一份
|
|
14
14
|
import color from './function/color';
|
|
15
|
+
import { getColor } from './function/color';
|
|
16
|
+
import type { ThemeColor } from '../types/global';
|
|
15
17
|
// 根据type获取图标名称
|
|
16
18
|
import type2icon from './function/type2icon';
|
|
17
19
|
// 打乱数组的顺序
|
|
@@ -242,6 +244,25 @@ export function kebabCase(word: string): string {
|
|
|
242
244
|
return newWord;
|
|
243
245
|
}
|
|
244
246
|
|
|
247
|
+
/**
|
|
248
|
+
* 运行时设置主题颜色(就地合并到 reactive 的 $u.color)
|
|
249
|
+
* @param theme Partial<ThemeColor>
|
|
250
|
+
*/
|
|
251
|
+
function setTheme(theme: Partial<ThemeColor> | undefined) {
|
|
252
|
+
if (!theme) return;
|
|
253
|
+
try {
|
|
254
|
+
const merged = deepMerge($u.color, theme);
|
|
255
|
+
Object.keys(merged).forEach(k => {
|
|
256
|
+
$u.color[k] = (merged as any)[k];
|
|
257
|
+
});
|
|
258
|
+
} catch (e) {
|
|
259
|
+
// 兜底:直接 assign
|
|
260
|
+
Object.keys(theme).forEach(k => {
|
|
261
|
+
$u.color[k] = (theme as any)[k];
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
245
266
|
export {
|
|
246
267
|
queryParams,
|
|
247
268
|
route,
|
|
@@ -249,6 +270,7 @@ export {
|
|
|
249
270
|
timeFrom,
|
|
250
271
|
guid,
|
|
251
272
|
color,
|
|
273
|
+
getColor,
|
|
252
274
|
sys,
|
|
253
275
|
os,
|
|
254
276
|
type2icon,
|
|
@@ -268,7 +290,8 @@ export {
|
|
|
268
290
|
clipboard,
|
|
269
291
|
config,
|
|
270
292
|
zIndex,
|
|
271
|
-
mitt
|
|
293
|
+
mitt,
|
|
294
|
+
setTheme
|
|
272
295
|
};
|
|
273
296
|
|
|
274
297
|
export const $u = {
|
|
@@ -281,6 +304,7 @@ export const $u = {
|
|
|
281
304
|
colorToRgba: colorGradients.colorToRgba,
|
|
282
305
|
guid,
|
|
283
306
|
color,
|
|
307
|
+
getColor,
|
|
284
308
|
sys,
|
|
285
309
|
os,
|
|
286
310
|
type2icon,
|
|
@@ -309,7 +333,8 @@ export const $u = {
|
|
|
309
333
|
formatName,
|
|
310
334
|
addStyle,
|
|
311
335
|
toStyle,
|
|
312
|
-
kebabCase
|
|
336
|
+
kebabCase,
|
|
337
|
+
setTheme
|
|
313
338
|
};
|
|
314
339
|
|
|
315
340
|
// 颜色相关方法单独导出
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 适配 canvas 2d 上下文
|
|
3
|
+
* @param ctx canvas 2d 上下文
|
|
4
|
+
* @returns
|
|
5
|
+
*/
|
|
6
|
+
export function canvas2d(ctx: CanvasRenderingContext2D): UniApp.CanvasContext {
|
|
7
|
+
return Object.assign(ctx, {
|
|
8
|
+
setFillStyle(color: string | CanvasGradient) {
|
|
9
|
+
ctx.fillStyle = color;
|
|
10
|
+
},
|
|
11
|
+
setStrokeStyle(color: string | CanvasGradient | CanvasPattern) {
|
|
12
|
+
ctx.strokeStyle = color;
|
|
13
|
+
},
|
|
14
|
+
setLineWidth(lineWidth: number) {
|
|
15
|
+
ctx.lineWidth = lineWidth;
|
|
16
|
+
},
|
|
17
|
+
setLineCap(lineCap: 'butt' | 'round' | 'square') {
|
|
18
|
+
ctx.lineCap = lineCap;
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
setFontSize(font: string) {
|
|
22
|
+
ctx.font = font;
|
|
23
|
+
},
|
|
24
|
+
setGlobalAlpha(alpha: number) {
|
|
25
|
+
ctx.globalAlpha = alpha;
|
|
26
|
+
},
|
|
27
|
+
setLineJoin(lineJoin: 'bevel' | 'round' | 'miter') {
|
|
28
|
+
ctx.lineJoin = lineJoin;
|
|
29
|
+
},
|
|
30
|
+
setTextAlign(align: 'left' | 'center' | 'right') {
|
|
31
|
+
ctx.textAlign = align;
|
|
32
|
+
},
|
|
33
|
+
setMiterLimit(miterLimit: number) {
|
|
34
|
+
ctx.miterLimit = miterLimit;
|
|
35
|
+
},
|
|
36
|
+
setShadow(offsetX: number, offsetY: number, blur: number, color: string) {
|
|
37
|
+
ctx.shadowOffsetX = offsetX;
|
|
38
|
+
ctx.shadowOffsetY = offsetY;
|
|
39
|
+
ctx.shadowBlur = blur;
|
|
40
|
+
ctx.shadowColor = color;
|
|
41
|
+
},
|
|
42
|
+
setTextBaseline(textBaseline: 'top' | 'bottom' | 'middle') {
|
|
43
|
+
ctx.textBaseline = textBaseline;
|
|
44
|
+
},
|
|
45
|
+
createCircularGradient() {},
|
|
46
|
+
draw() {},
|
|
47
|
+
addColorStop() {}
|
|
48
|
+
}) as unknown as UniApp.CanvasContext;
|
|
49
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "uview-pro",
|
|
3
3
|
"name": "uview-pro",
|
|
4
|
-
"displayName": "【Vue3重构版】uView Pro|基于Vue3+TS全面重构的
|
|
5
|
-
"version": "0.3.
|
|
6
|
-
"description": "uView Pro,是全面支持Vue3的uni-app生态框架,
|
|
4
|
+
"displayName": "【Vue3重构版】uView Pro|基于Vue3+TS全面重构的80+精选UI组件库",
|
|
5
|
+
"version": "0.3.9",
|
|
6
|
+
"description": "uView Pro,是全面支持Vue3的uni-app生态框架,80+精选组件已使用TypeScript重构,已全面支持uni-app Vue3.0",
|
|
7
7
|
"main": "index.ts",
|
|
8
8
|
"module": "index.ts",
|
|
9
9
|
"browser": "index.ts",
|
package/types/components.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ declare module 'vue' {
|
|
|
44
44
|
uLine: (typeof import('../components/u-line/u-line.vue'))['default'];
|
|
45
45
|
uLineProgress: (typeof import('../components/u-line-progress/u-line-progress.vue'))['default'];
|
|
46
46
|
uLink: (typeof import('../components/u-link/u-link.vue'))['default'];
|
|
47
|
-
|
|
47
|
+
uLoadmore: (typeof import('../components/u-loadmore/u-loadmore.vue'))['default'];
|
|
48
48
|
uLoading: (typeof import('../components/u-loading/u-loading.vue'))['default'];
|
|
49
49
|
uLoadingPopup: (typeof import('../components/u-loading-popup/u-loading-popup.vue'))['default'];
|
|
50
50
|
uMask: (typeof import('../components/u-mask/u-mask.vue'))['default'];
|
|
@@ -95,6 +95,7 @@ declare module 'vue' {
|
|
|
95
95
|
uStatusBar: (typeof import('../components/u-status-bar/u-status-bar.vue'))['default'];
|
|
96
96
|
uSafeBottom: (typeof import('../components/u-safe-bottom/u-safe-bottom.vue'))['default'];
|
|
97
97
|
uTextarea: (typeof import('../components/u-textarea/u-textarea.vue'))['default'];
|
|
98
|
+
uFab: (typeof import('../components/u-fab/u-fab.vue'))['default'];
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
101
|
|
package/types/global.d.ts
CHANGED
|
@@ -290,35 +290,51 @@ export type TagSize = 'default' | 'mini' | 'medium';
|
|
|
290
290
|
export type ToastPosition = 'top' | 'center' | 'bottom';
|
|
291
291
|
export type UploadSizeType = 'original' | 'compressed';
|
|
292
292
|
export type UploadSourceType = 'album' | 'camera';
|
|
293
|
+
// fab 组件 position
|
|
294
|
+
export type FabPosition =
|
|
295
|
+
| 'left-top'
|
|
296
|
+
| 'right-top'
|
|
297
|
+
| 'left-bottom'
|
|
298
|
+
| 'right-bottom'
|
|
299
|
+
| 'left-center'
|
|
300
|
+
| 'right-center'
|
|
301
|
+
| 'top-center'
|
|
302
|
+
| 'bottom-center';
|
|
303
|
+
// fab 组件 direction
|
|
304
|
+
export type FabDirection = 'top' | 'bottom' | 'left' | 'right';
|
|
305
|
+
// fab 组件 gap
|
|
306
|
+
export type FabGap = Partial<Record<'top' | 'left' | 'right' | 'bottom', number>>;
|
|
307
|
+
|
|
308
|
+
export type ColorType =
|
|
309
|
+
| 'primary'
|
|
310
|
+
| 'primaryDark'
|
|
311
|
+
| 'primaryDisabled'
|
|
312
|
+
| 'primaryLight'
|
|
313
|
+
| 'bgColor'
|
|
314
|
+
| 'info'
|
|
315
|
+
| 'infoDark'
|
|
316
|
+
| 'infoDisabled'
|
|
317
|
+
| 'infoLight'
|
|
318
|
+
| 'warning'
|
|
319
|
+
| 'warningDark'
|
|
320
|
+
| 'warningDisabled'
|
|
321
|
+
| 'warningLight'
|
|
322
|
+
| 'error'
|
|
323
|
+
| 'errorDark'
|
|
324
|
+
| 'errorDisabled'
|
|
325
|
+
| 'errorLight'
|
|
326
|
+
| 'success'
|
|
327
|
+
| 'successDark'
|
|
328
|
+
| 'successDisabled'
|
|
329
|
+
| 'successLight'
|
|
330
|
+
| 'mainColor'
|
|
331
|
+
| 'contentColor'
|
|
332
|
+
| 'tipsColor'
|
|
333
|
+
| 'lightColor'
|
|
334
|
+
| 'borderColor';
|
|
335
|
+
|
|
293
336
|
// 自定义主题色
|
|
294
|
-
export type ThemeColor = Partial<
|
|
295
|
-
primary: string;
|
|
296
|
-
primaryDark: string;
|
|
297
|
-
primaryDisabled: string;
|
|
298
|
-
primaryLight: string;
|
|
299
|
-
bgColor: string;
|
|
300
|
-
info: string;
|
|
301
|
-
infoDark: string;
|
|
302
|
-
infoDisabled: string;
|
|
303
|
-
infoLight: string;
|
|
304
|
-
warning: string;
|
|
305
|
-
warningDark: string;
|
|
306
|
-
warningDisabled: string;
|
|
307
|
-
warningLight: string;
|
|
308
|
-
error: string;
|
|
309
|
-
errorDark: string;
|
|
310
|
-
errorDisabled: string;
|
|
311
|
-
errorLight: string;
|
|
312
|
-
success: string;
|
|
313
|
-
successDark: string;
|
|
314
|
-
successDisabled: string;
|
|
315
|
-
successLight: string;
|
|
316
|
-
mainColor: string;
|
|
317
|
-
contentColor: string;
|
|
318
|
-
tipsColor: string;
|
|
319
|
-
lightColor: string;
|
|
320
|
-
borderColor: string;
|
|
321
|
-
}>;
|
|
337
|
+
export type ThemeColor = Partial<Record<ColorType, string>>;
|
|
322
338
|
|
|
323
339
|
export type LogConfig = Partial<{
|
|
324
340
|
debug?: boolean;
|