hy-app 0.4.10 → 0.4.12
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/components/hy-card/hy-card.vue +55 -27
- package/components/hy-card/index.scss +13 -5
- package/components/hy-flex/hy-flex.vue +3 -6
- package/components/hy-folding-panel/hy-folding-panel.vue +345 -111
- package/components/hy-folding-panel-item/hy-folding-panel-item.vue +228 -0
- package/components/hy-grid/hy-grid.vue +2 -2
- package/components/hy-qrcode/hy-qrcode.vue +1 -1
- package/components/hy-qrcode/qrcode.js +134 -0
- package/components/hy-search/hy-search.vue +13 -6
- package/components/hy-search/index.scss +4 -0
- package/components/hy-watermark/hy-watermark.vue +413 -17
- package/package.json +2 -2
- package/web-types.json +1 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<view class="hy-folding-panel-item">
|
|
3
|
+
<HyCell
|
|
4
|
+
:title="title"
|
|
5
|
+
:value="value"
|
|
6
|
+
:icon="icon"
|
|
7
|
+
:border="border"
|
|
8
|
+
:size="size"
|
|
9
|
+
:disabled="disabled"
|
|
10
|
+
:clickable="!disabled"
|
|
11
|
+
@click="handleClick"
|
|
12
|
+
>
|
|
13
|
+
<template #icon="{ icon }">
|
|
14
|
+
<slot name="icon" :icon="icon"></slot>
|
|
15
|
+
</template>
|
|
16
|
+
<template #title="{ title }">
|
|
17
|
+
<slot name="title" :title="title">{{ title }}</slot>
|
|
18
|
+
</template>
|
|
19
|
+
<template #value="{ value }">
|
|
20
|
+
<slot name="value" :value="value">
|
|
21
|
+
<text v-if="value">{{ value }}</text>
|
|
22
|
+
<text v-else class="hy-folding-panel-item__arrow" :class="{ 'hy-folding-panel-item__arrow--up': spread }">
|
|
23
|
+
{{ spread ? '↑' : '↓' }}
|
|
24
|
+
</text>
|
|
25
|
+
</slot>
|
|
26
|
+
</template>
|
|
27
|
+
<template #bottom>
|
|
28
|
+
<transition name="hy-folding-panel-item__transition">
|
|
29
|
+
<view
|
|
30
|
+
v-show="spread"
|
|
31
|
+
class="hy-folding-panel-item__content"
|
|
32
|
+
:style="[
|
|
33
|
+
customStyle,
|
|
34
|
+
{ height: contentHeight }
|
|
35
|
+
]"
|
|
36
|
+
>
|
|
37
|
+
<slot />
|
|
38
|
+
</view>
|
|
39
|
+
</transition>
|
|
40
|
+
<HyLine v-if="spread && showBottomLine" />
|
|
41
|
+
</template>
|
|
42
|
+
</HyCell>
|
|
43
|
+
</view>
|
|
44
|
+
</template>
|
|
45
|
+
|
|
46
|
+
<script lang="ts">
|
|
47
|
+
export default {
|
|
48
|
+
name: "hy-folding-panel-item",
|
|
49
|
+
options: {
|
|
50
|
+
addGlobalClass: true,
|
|
51
|
+
virtualHost: true,
|
|
52
|
+
styleIsolation: "shared",
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<script setup lang="ts">
|
|
58
|
+
import { ref, computed, watch, onMounted, inject } from "vue";
|
|
59
|
+
import type { CSSProperties } from "vue";
|
|
60
|
+
import { addUnit } from "../../libs";
|
|
61
|
+
import HyCell from "../hy-cell/hy-cell.vue";
|
|
62
|
+
import HyLine from "../hy-line/hy-line.vue";
|
|
63
|
+
|
|
64
|
+
interface IProps {
|
|
65
|
+
/** 标题 */
|
|
66
|
+
title?: string;
|
|
67
|
+
/** 标题左侧的图标 */
|
|
68
|
+
icon?: string;
|
|
69
|
+
/** 右侧的值 */
|
|
70
|
+
value?: string;
|
|
71
|
+
/** 是否显示下边框 */
|
|
72
|
+
border?: boolean;
|
|
73
|
+
/** 单元的大小 */
|
|
74
|
+
size?: "large" | "medium" | "small";
|
|
75
|
+
/** 是否禁用 */
|
|
76
|
+
disabled?: boolean;
|
|
77
|
+
/** 内容面板高度 */
|
|
78
|
+
contentHeight?: string | number;
|
|
79
|
+
/** 是否默认展开 */
|
|
80
|
+
defaultOpen?: boolean;
|
|
81
|
+
/** 是否显示底部线条 */
|
|
82
|
+
showBottomLine?: boolean;
|
|
83
|
+
/** 定义需要用到的外部样式 */
|
|
84
|
+
customStyle?: CSSProperties;
|
|
85
|
+
/** 面板索引(由父组件提供) */
|
|
86
|
+
index?: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const props = withDefaults(defineProps<IProps>(), {
|
|
90
|
+
title: "",
|
|
91
|
+
icon: "",
|
|
92
|
+
value: "",
|
|
93
|
+
border: true,
|
|
94
|
+
size: "medium",
|
|
95
|
+
disabled: false,
|
|
96
|
+
contentHeight: 120,
|
|
97
|
+
defaultOpen: false,
|
|
98
|
+
showBottomLine: true,
|
|
99
|
+
customStyle: () => ({}),
|
|
100
|
+
index: -1,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const emit = defineEmits<{
|
|
104
|
+
(e: "click", value: boolean): void;
|
|
105
|
+
(e: "open"): void;
|
|
106
|
+
(e: "close"): void;
|
|
107
|
+
// 用于手风琴模式的特殊事件
|
|
108
|
+
(e: "child-click", index: number, isOpen: boolean): void;
|
|
109
|
+
}>();
|
|
110
|
+
|
|
111
|
+
// 尝试从父组件注入手风琴模式配置
|
|
112
|
+
const accordion = inject<boolean>('hy-folding-panel-accordion', false);
|
|
113
|
+
|
|
114
|
+
const spread = ref(props.defaultOpen);
|
|
115
|
+
const contentHeight = computed(() => {
|
|
116
|
+
return typeof props.contentHeight === 'number'
|
|
117
|
+
? `${props.contentHeight}px`
|
|
118
|
+
: props.contentHeight;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// 监听默认展开状态
|
|
122
|
+
watch(
|
|
123
|
+
() => props.defaultOpen,
|
|
124
|
+
(newValue) => {
|
|
125
|
+
spread.value = newValue;
|
|
126
|
+
},
|
|
127
|
+
{ immediate: true }
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// 处理点击事件
|
|
131
|
+
const handleClick = () => {
|
|
132
|
+
if (props.disabled) return;
|
|
133
|
+
|
|
134
|
+
spread.value = !spread.value;
|
|
135
|
+
emit("click", spread.value);
|
|
136
|
+
|
|
137
|
+
// 向父组件通知子组件点击事件(用于手风琴模式)
|
|
138
|
+
if (props.index >= 0) {
|
|
139
|
+
emit('child-click', props.index, spread.value);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (spread.value) {
|
|
143
|
+
emit("open");
|
|
144
|
+
} else {
|
|
145
|
+
emit("close");
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// 提供给父组件控制展开/收起的方法
|
|
150
|
+
defineExpose({
|
|
151
|
+
open: () => {
|
|
152
|
+
if (!props.disabled && !spread.value) {
|
|
153
|
+
spread.value = true;
|
|
154
|
+
emit("click", true);
|
|
155
|
+
emit("open");
|
|
156
|
+
|
|
157
|
+
// 通知父组件
|
|
158
|
+
if (props.index >= 0) {
|
|
159
|
+
emit('child-click', props.index, true);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
close: () => {
|
|
164
|
+
if (!props.disabled && spread.value) {
|
|
165
|
+
spread.value = false;
|
|
166
|
+
emit("click", false);
|
|
167
|
+
emit("close");
|
|
168
|
+
|
|
169
|
+
// 通知父组件
|
|
170
|
+
if (props.index >= 0) {
|
|
171
|
+
emit('child-click', props.index, false);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
toggle: () => {
|
|
176
|
+
handleClick();
|
|
177
|
+
},
|
|
178
|
+
getSpread: () => spread.value,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// 组件挂载时,如果是手风琴模式且默认打开,通知父组件
|
|
182
|
+
onMounted(() => {
|
|
183
|
+
if (spread.value && accordion && props.index >= 0) {
|
|
184
|
+
emit('child-click', props.index, true);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
</script>
|
|
188
|
+
|
|
189
|
+
<style lang="scss" scoped>
|
|
190
|
+
@import "./index.scss";
|
|
191
|
+
|
|
192
|
+
.hy-folding-panel-item {
|
|
193
|
+
&__arrow {
|
|
194
|
+
font-size: 16px;
|
|
195
|
+
color: #999;
|
|
196
|
+
transition: transform 0.3s;
|
|
197
|
+
|
|
198
|
+
&--up {
|
|
199
|
+
transform: rotate(0deg);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
&__content {
|
|
204
|
+
padding: 12px 16px;
|
|
205
|
+
box-sizing: border-box;
|
|
206
|
+
overflow: hidden;
|
|
207
|
+
background-color: #f8f8f8;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
&__transition-enter-active,
|
|
211
|
+
&__transition-leave-active {
|
|
212
|
+
transition: all 0.3s ease;
|
|
213
|
+
overflow: hidden;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
&__transition-enter-from,
|
|
217
|
+
&__transition-leave-to {
|
|
218
|
+
opacity: 0;
|
|
219
|
+
max-height: 0;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
&__transition-enter-to,
|
|
223
|
+
&__transition-leave-from {
|
|
224
|
+
opacity: 1;
|
|
225
|
+
max-height: 1000px; /* 足够大的值,确保内容能完全展开 */
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
</style>
|
|
@@ -151,8 +151,8 @@ const itemStyle = computed<CSSProperties>(() => {
|
|
|
151
151
|
/**
|
|
152
152
|
* @description 点击事件
|
|
153
153
|
* */
|
|
154
|
-
const childClick = (
|
|
155
|
-
emit("click",
|
|
154
|
+
const childClick = (temp: string | Record<string, any>) => {
|
|
155
|
+
emit("click", temp);
|
|
156
156
|
};
|
|
157
157
|
</script>
|
|
158
158
|
|
|
@@ -1176,6 +1176,108 @@ let QRCode = {};
|
|
|
1176
1176
|
}
|
|
1177
1177
|
return options.foreground;
|
|
1178
1178
|
};
|
|
1179
|
+
// 为2D Canvas添加辅助函数
|
|
1180
|
+
function drawIconWithBackground(canvas, ctx, options, qrCodeAlg) {
|
|
1181
|
+
// 在小程序中使用canvas.createImage()而不是wx.createImage()
|
|
1182
|
+
const img = canvas.createImage();
|
|
1183
|
+
img.src = options.image;
|
|
1184
|
+
img.onload = function() {
|
|
1185
|
+
var ratioImgSize = options.imageSize || 40;
|
|
1186
|
+
var x = Number(((options.size - ratioImgSize) / 2).toFixed(2));
|
|
1187
|
+
var y = Number(((options.size - ratioImgSize) / 2).toFixed(2));
|
|
1188
|
+
|
|
1189
|
+
// 绘制圆角矩形背景
|
|
1190
|
+
drawRoundedRect2D(ctx, x, y, ratioImgSize, ratioImgSize, 2, options.background);
|
|
1191
|
+
|
|
1192
|
+
// 绘制图标
|
|
1193
|
+
ctx.drawImage(img, x, y, ratioImgSize, ratioImgSize);
|
|
1194
|
+
|
|
1195
|
+
// 导出图片
|
|
1196
|
+
exportCanvasImage(canvas, options);
|
|
1197
|
+
};
|
|
1198
|
+
|
|
1199
|
+
// 添加错误处理
|
|
1200
|
+
img.onerror = function(e) {
|
|
1201
|
+
console.error('Failed to load QR code icon:', e);
|
|
1202
|
+
// 即使图标加载失败也要导出二维码
|
|
1203
|
+
exportCanvasImage(canvas, options);
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
function drawRoundedRect2D(ctx, x, y, width, height, r, fillColor) {
|
|
1208
|
+
ctx.beginPath();
|
|
1209
|
+
ctx.moveTo(x + r, y);
|
|
1210
|
+
ctx.arcTo(x + width, y, x + width, y + r, r);
|
|
1211
|
+
ctx.arcTo(x + width, y + height, x + width - r, y + height, r);
|
|
1212
|
+
ctx.arcTo(x, y + height, x, y + height - r, r);
|
|
1213
|
+
ctx.arcTo(x, y, x + r, y, r);
|
|
1214
|
+
ctx.closePath();
|
|
1215
|
+
ctx.fillStyle = fillColor;
|
|
1216
|
+
ctx.fill();
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
function exportCanvasImage(canvas, options) {
|
|
1220
|
+
setTimeout(() => {
|
|
1221
|
+
wx.canvasToTempFilePath({
|
|
1222
|
+
canvas: canvas,
|
|
1223
|
+
quality: 1,
|
|
1224
|
+
success: function(res) {
|
|
1225
|
+
if (options.cbResult) {
|
|
1226
|
+
options.cbResult(res.tempFilePath);
|
|
1227
|
+
}
|
|
1228
|
+
},
|
|
1229
|
+
fail: function(res) {
|
|
1230
|
+
if (options.cbResult) {
|
|
1231
|
+
options.cbResult(res);
|
|
1232
|
+
}
|
|
1233
|
+
},
|
|
1234
|
+
complete: function() {
|
|
1235
|
+
uni.hideLoading();
|
|
1236
|
+
}
|
|
1237
|
+
}, options.context);
|
|
1238
|
+
}, options.text.length + 100);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// 2D Canvas二维码绘制函数
|
|
1242
|
+
function drawQRCode2D(canvas, ctx, options, qrCodeAlg) {
|
|
1243
|
+
var count = qrCodeAlg.getModuleCount();
|
|
1244
|
+
var ratioSize = options.size;
|
|
1245
|
+
|
|
1246
|
+
// 清除画布
|
|
1247
|
+
ctx.clearRect(0, 0, ratioSize, ratioSize);
|
|
1248
|
+
// 设置背景色
|
|
1249
|
+
ctx.fillStyle = options.background;
|
|
1250
|
+
ctx.fillRect(0, 0, ratioSize, ratioSize);
|
|
1251
|
+
|
|
1252
|
+
//计算每个点的长宽
|
|
1253
|
+
var tileW = (ratioSize / count).toPrecision(4);
|
|
1254
|
+
var tileH = (ratioSize / count).toPrecision(4);
|
|
1255
|
+
|
|
1256
|
+
//绘制二维码
|
|
1257
|
+
for (var row = 0; row < count; row++) {
|
|
1258
|
+
for (var col = 0; col < count; col++) {
|
|
1259
|
+
var w = Math.ceil((col + 1) * tileW) - Math.floor(col * tileW);
|
|
1260
|
+
var h = Math.ceil((row + 1) * tileW) - Math.floor(row * tileW);
|
|
1261
|
+
var foreground = getForeGround({
|
|
1262
|
+
row: row,
|
|
1263
|
+
col: col,
|
|
1264
|
+
count: count,
|
|
1265
|
+
options: options
|
|
1266
|
+
});
|
|
1267
|
+
ctx.fillStyle = qrCodeAlg.modules[row][col] ? foreground : options.background;
|
|
1268
|
+
ctx.fillRect(Math.round(col * tileW), Math.round(row * tileH), w, h);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
if (options.image) {
|
|
1273
|
+
// 绘制中间图标
|
|
1274
|
+
drawIconWithBackground(canvas, ctx, options, qrCodeAlg);
|
|
1275
|
+
} else {
|
|
1276
|
+
// 导出图片
|
|
1277
|
+
exportCanvasImage(canvas, options);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1179
1281
|
// 创建canvas
|
|
1180
1282
|
let createCanvas = function (options) {
|
|
1181
1283
|
if (options.showLoading) {
|
|
@@ -1184,6 +1286,38 @@ let QRCode = {};
|
|
|
1184
1286
|
mask: true
|
|
1185
1287
|
});
|
|
1186
1288
|
}
|
|
1289
|
+
|
|
1290
|
+
// 尝试使用2D Canvas (小程序端)
|
|
1291
|
+
try {
|
|
1292
|
+
if (!options.nvueContext && typeof wx !== 'undefined' && wx.createSelectorQuery) {
|
|
1293
|
+
const query = wx.createSelectorQuery().in(options.context);
|
|
1294
|
+
query.select('#' + options.canvasId)
|
|
1295
|
+
.fields({ node: true, size: true })
|
|
1296
|
+
.exec((res) => {
|
|
1297
|
+
if (res[0] && res[0].node) {
|
|
1298
|
+
const canvas = res[0].node;
|
|
1299
|
+
const ctx = canvas.getContext('2d');
|
|
1300
|
+
|
|
1301
|
+
// 设置canvas尺寸
|
|
1302
|
+
const dpr = wx.getSystemInfoSync().pixelRatio;
|
|
1303
|
+
canvas.width = options.size * dpr;
|
|
1304
|
+
canvas.height = options.size * dpr;
|
|
1305
|
+
ctx.scale(dpr, dpr);
|
|
1306
|
+
|
|
1307
|
+
// 创建二维码算法实例
|
|
1308
|
+
var qrCodeAlg = new QRCodeAlg(options.text, options.correctLevel);
|
|
1309
|
+
|
|
1310
|
+
// 使用2D Canvas绘制
|
|
1311
|
+
drawQRCode2D(canvas, ctx, options, qrCodeAlg);
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
} catch (e) {
|
|
1317
|
+
console.warn('2D Canvas initialization failed, falling back to legacy mode:', e);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// 回退到传统Canvas方式
|
|
1187
1321
|
var ctx = "";
|
|
1188
1322
|
if (options.nvueContext) {
|
|
1189
1323
|
ctx = options.nvueContext;
|
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
borderColor: borderColor,
|
|
18
18
|
}"
|
|
19
19
|
>
|
|
20
|
-
<template
|
|
21
|
-
<slot name="label">
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
<template>
|
|
21
|
+
<slot v-if="$slots.label" name="label"></slot>
|
|
22
|
+
<text v-else-if="label" class="hy-search__content__label">{{
|
|
23
|
+
label
|
|
24
|
+
}}</text>
|
|
24
25
|
</template>
|
|
25
26
|
<view class="hy-search__content__icon" v-if="searchIcon">
|
|
26
27
|
<HyIcon
|
|
@@ -50,7 +51,10 @@
|
|
|
50
51
|
placeholder-class="hy-search__content__input--placeholder"
|
|
51
52
|
:placeholder="placeholder"
|
|
52
53
|
:placeholder-style="`color: ${placeholderColor}`"
|
|
53
|
-
class="
|
|
54
|
+
:class="[
|
|
55
|
+
'hy-search__content__input',
|
|
56
|
+
disabled && 'hy-search__content__input--disabled',
|
|
57
|
+
]"
|
|
54
58
|
type="text"
|
|
55
59
|
:always-embed="true"
|
|
56
60
|
:style="[
|
|
@@ -213,7 +217,10 @@ const props = defineProps({
|
|
|
213
217
|
default: "",
|
|
214
218
|
},
|
|
215
219
|
/** 输入框左边的图标属性集合,可以为图标名称或图片路径 */
|
|
216
|
-
searchIcon:
|
|
220
|
+
searchIcon: {
|
|
221
|
+
type: [Boolean, Object] as PropType<HyIconProps | boolean>,
|
|
222
|
+
default: { name: IconConfig.SEARCH },
|
|
223
|
+
},
|
|
217
224
|
/** 组件与其他上下左右之间的距离,带单位的字符串形式,如"30px" */
|
|
218
225
|
margin: {
|
|
219
226
|
type: [String, Number],
|
|
@@ -59,6 +59,10 @@ $hy-search-close-size: 20px !default;
|
|
|
59
59
|
color: $hy-text-color;
|
|
60
60
|
background-color: $hy-background--empty;
|
|
61
61
|
|
|
62
|
+
@include m(disabled) {
|
|
63
|
+
color: $hy-text-color--disabled;
|
|
64
|
+
}
|
|
65
|
+
|
|
62
66
|
@include m(placeholder) {
|
|
63
67
|
font-size: 25rpx;
|
|
64
68
|
color: $hy-text-color--placeholder;
|