@done-coding/admin-core 0.22.0 → 0.22.1-alpha.0
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/es/components/modal/VideoPreviewTrigger.vue.mjs +1 -1
- package/es/components/modal/VideoPreviewTrigger.vue2.mjs +32 -2
- package/es/components/view-layout/ViewLayout.vue.mjs +1 -1
- package/es/components/view-layout/ViewLayout.vue2.mjs +68 -19
- package/es/style.css +35 -11
- package/package.json +2 -2
- package/src/components/modal/docs/README-VideoPreviewTrigger.md +14 -10
- package/src/components/view-layout/README.md +1 -1
- package/src/components/view-layout/docs/README-ViewLayout.md +8 -3
- package/types/components/modal/VideoPreviewTrigger.vue.d.ts +9 -6
- package/types/components/view-layout/types.d.ts +16 -0
- package/types/injectInfo.json.d.ts +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import _sfc_main from "./VideoPreviewTrigger.vue2.mjs";
|
|
2
2
|
/* empty css */
|
|
3
3
|
import _export_sfc from "../../_virtual/_plugin-vue_export-helper.mjs";
|
|
4
|
-
const VideoPreviewTrigger = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-
|
|
4
|
+
const VideoPreviewTrigger = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-3b6fbdd2"]]);
|
|
5
5
|
export {
|
|
6
6
|
VideoPreviewTrigger as default
|
|
7
7
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineComponent, inject, ref, openBlock, createElementBlock, Fragment, renderSlot, createVNode, unref, mergeProps, withCtx, createElementVNode, withKeys, createBlock, createCommentVNode } from "vue";
|
|
1
|
+
import { defineComponent, inject, ref, computed, openBlock, createElementBlock, Fragment, renderSlot, createVNode, unref, mergeProps, withCtx, createElementVNode, withKeys, createBlock, createCommentVNode } from "vue";
|
|
2
2
|
import { ElImage, ElIcon } from "element-plus";
|
|
3
3
|
import { VideoPlay } from "@element-plus/icons-vue";
|
|
4
4
|
import { GLOBAL_MODAL_REMOTE_KEY, APP_GLOBAL_VIDEO_PREVIEW_KEY } from "./modal-global-keys.mjs";
|
|
@@ -7,12 +7,18 @@ const _hoisted_1 = {
|
|
|
7
7
|
class: "video-preview-trigger__play-badge",
|
|
8
8
|
"aria-hidden": "true"
|
|
9
9
|
};
|
|
10
|
+
const _hoisted_2 = ["src"];
|
|
11
|
+
const _hoisted_3 = {
|
|
12
|
+
class: "video-preview-trigger__play-badge",
|
|
13
|
+
"aria-hidden": "true"
|
|
14
|
+
};
|
|
10
15
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
11
16
|
...{ inheritAttrs: false },
|
|
12
17
|
__name: "VideoPreviewTrigger",
|
|
13
18
|
props: {
|
|
14
19
|
url: {},
|
|
15
20
|
coverUrl: {},
|
|
21
|
+
frameCover: { type: Boolean, default: true },
|
|
16
22
|
lazy: { type: Boolean, default: true },
|
|
17
23
|
preview: { type: Boolean, default: true }
|
|
18
24
|
},
|
|
@@ -20,6 +26,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
20
26
|
const props = __props;
|
|
21
27
|
const remote = inject(GLOBAL_MODAL_REMOTE_KEY);
|
|
22
28
|
const localShow = ref(false);
|
|
29
|
+
const frameCoverSrc = computed(() => `${props.url}#t=0.01`);
|
|
23
30
|
const openPreview = () => {
|
|
24
31
|
var _a, _b;
|
|
25
32
|
if (!props.preview) return;
|
|
@@ -61,8 +68,31 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
61
68
|
_: 1
|
|
62
69
|
})
|
|
63
70
|
])
|
|
64
|
-
])) : (openBlock(), createElementBlock("div", {
|
|
71
|
+
])) : __props.frameCover ? (openBlock(), createElementBlock("div", {
|
|
65
72
|
key: 1,
|
|
73
|
+
class: "dc-video-preview-trigger video-preview-trigger video-preview-trigger_frame",
|
|
74
|
+
role: "button",
|
|
75
|
+
tabindex: "0",
|
|
76
|
+
onClick: openPreview,
|
|
77
|
+
onKeydown: withKeys(openPreview, ["enter"])
|
|
78
|
+
}, [
|
|
79
|
+
createElementVNode("video", {
|
|
80
|
+
class: "video-preview-trigger__frame",
|
|
81
|
+
src: frameCoverSrc.value,
|
|
82
|
+
preload: "metadata",
|
|
83
|
+
muted: "",
|
|
84
|
+
playsinline: ""
|
|
85
|
+
}, null, 8, _hoisted_2),
|
|
86
|
+
createElementVNode("span", _hoisted_3, [
|
|
87
|
+
createVNode(unref(ElIcon), { size: 20 }, {
|
|
88
|
+
default: withCtx(() => [
|
|
89
|
+
createVNode(unref(VideoPlay))
|
|
90
|
+
]),
|
|
91
|
+
_: 1
|
|
92
|
+
})
|
|
93
|
+
])
|
|
94
|
+
], 32)) : (openBlock(), createElementBlock("div", {
|
|
95
|
+
key: 2,
|
|
66
96
|
class: "dc-video-preview-trigger video-preview-trigger video-preview-trigger_placeholder",
|
|
67
97
|
role: "button",
|
|
68
98
|
tabindex: "0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import _sfc_main from "./ViewLayout.vue2.mjs";
|
|
2
2
|
/* empty css */
|
|
3
3
|
import _export_sfc from "../../_virtual/_plugin-vue_export-helper.mjs";
|
|
4
|
-
const ViewLayout = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-
|
|
4
|
+
const ViewLayout = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-3c537d41"]]);
|
|
5
5
|
export {
|
|
6
6
|
ViewLayout as default
|
|
7
7
|
};
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import { defineComponent, shallowRef, ref, computed, provide, watch, openBlock, createElementBlock, createBlock, unref, withCtx, createElementVNode, normalizeStyle, renderSlot, createVNode, Fragment } from "vue";
|
|
1
|
+
import { defineComponent, shallowRef, ref, computed, provide, watch, openBlock, createElementBlock, createBlock, unref, withCtx, createElementVNode, normalizeStyle, renderSlot, withDirectives, createVNode, vShow, Fragment } from "vue";
|
|
2
2
|
import AutoRefreshGroup from "../misc/AutoRefreshGroup.vue.mjs";
|
|
3
3
|
import _sfc_main$1 from "../display/HeightProvider.vue.mjs";
|
|
4
|
+
import _sfc_main$2 from "../display/ActionBtnGroup.vue.mjs";
|
|
4
5
|
import { useChannelViewportHeight } from "../../hooks/use-channel-viewport-height.mjs";
|
|
5
6
|
import { useActivated } from "../../hooks/activated.mjs";
|
|
6
7
|
import { SCOPE_VIEWPORT_MAX_HEIGHT } from "../../inject/key.mjs";
|
|
7
8
|
const _hoisted_1 = { class: "dc-view-layout view-layout" };
|
|
8
9
|
const _hoisted_2 = { class: "view-layout-toolbar" };
|
|
9
|
-
const _hoisted_3 = { class: "view-layout-
|
|
10
|
-
const _hoisted_4 = { class: "view-layout-
|
|
10
|
+
const _hoisted_3 = { class: "view-layout-toolbar__left" };
|
|
11
|
+
const _hoisted_4 = { class: "view-layout-toolbar__right" };
|
|
12
|
+
const _hoisted_5 = { class: "view-layout-toolbar" };
|
|
13
|
+
const _hoisted_6 = { class: "view-layout-toolbar__left" };
|
|
14
|
+
const _hoisted_7 = { class: "view-layout-toolbar__right" };
|
|
15
|
+
const _hoisted_8 = { class: "view-layout-content" };
|
|
11
16
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
12
17
|
__name: "ViewLayout",
|
|
13
18
|
props: {
|
|
@@ -19,7 +24,9 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
19
24
|
parentChannel: {},
|
|
20
25
|
minHeight: { default: 100 },
|
|
21
26
|
showAutoRefresh: { type: Boolean, default: true },
|
|
22
|
-
showRefresh: { type: Boolean, default: true }
|
|
27
|
+
showRefresh: { type: Boolean, default: true },
|
|
28
|
+
leftExtraButtons: { default: () => [] },
|
|
29
|
+
rightExtraButtons: { default: () => [] }
|
|
23
30
|
},
|
|
24
31
|
setup(__props) {
|
|
25
32
|
const props = __props;
|
|
@@ -35,6 +42,16 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
35
42
|
}
|
|
36
43
|
};
|
|
37
44
|
const refresh = fetch;
|
|
45
|
+
const toolbarCtx = computed(() => ({
|
|
46
|
+
loading: loading.value,
|
|
47
|
+
refresh
|
|
48
|
+
}));
|
|
49
|
+
const hasToolbarContent = computed(
|
|
50
|
+
() => {
|
|
51
|
+
var _a, _b;
|
|
52
|
+
return ((_a = props.leftExtraButtons) == null ? void 0 : _a.length) || ((_b = props.rightExtraButtons) == null ? void 0 : _b.length) || props.showAutoRefresh || props.showRefresh;
|
|
53
|
+
}
|
|
54
|
+
);
|
|
38
55
|
useActivated((info) => {
|
|
39
56
|
if (info.isActivated) fetch();
|
|
40
57
|
});
|
|
@@ -67,13 +84,29 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
67
84
|
onChildViewportHeightChange
|
|
68
85
|
}, {
|
|
69
86
|
header: withCtx(() => [
|
|
70
|
-
createElementVNode("div", _hoisted_2, [
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
withDirectives(createElementVNode("div", _hoisted_2, [
|
|
88
|
+
createElementVNode("div", _hoisted_3, [
|
|
89
|
+
createVNode(unref(_sfc_main$2), {
|
|
90
|
+
configs: __props.leftExtraButtons,
|
|
91
|
+
ctx: toolbarCtx.value,
|
|
92
|
+
"default-size": "small"
|
|
93
|
+
}, null, 8, ["configs", "ctx"])
|
|
94
|
+
]),
|
|
95
|
+
createElementVNode("div", _hoisted_4, [
|
|
96
|
+
createVNode(unref(_sfc_main$2), {
|
|
97
|
+
configs: __props.rightExtraButtons,
|
|
98
|
+
ctx: toolbarCtx.value,
|
|
99
|
+
"default-size": "small"
|
|
100
|
+
}, null, 8, ["configs", "ctx"]),
|
|
101
|
+
createVNode(unref(AutoRefreshGroup), {
|
|
102
|
+
refreshFn: fetch,
|
|
103
|
+
loading: loading.value,
|
|
104
|
+
showAutoRefresh: __props.showAutoRefresh,
|
|
105
|
+
showRefresh: __props.showRefresh
|
|
106
|
+
}, null, 8, ["loading", "showAutoRefresh", "showRefresh"])
|
|
107
|
+
])
|
|
108
|
+
], 512), [
|
|
109
|
+
[vShow, hasToolbarContent.value]
|
|
77
110
|
])
|
|
78
111
|
]),
|
|
79
112
|
default: withCtx(({ viewportHeight }) => [
|
|
@@ -91,15 +124,31 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
91
124
|
]),
|
|
92
125
|
_: 3
|
|
93
126
|
}, 8, ["viewportHeight", "minHeight"])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
|
|
94
|
-
createElementVNode("div",
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
127
|
+
withDirectives(createElementVNode("div", _hoisted_5, [
|
|
128
|
+
createElementVNode("div", _hoisted_6, [
|
|
129
|
+
createVNode(unref(_sfc_main$2), {
|
|
130
|
+
configs: __props.leftExtraButtons,
|
|
131
|
+
ctx: toolbarCtx.value,
|
|
132
|
+
"default-size": "small"
|
|
133
|
+
}, null, 8, ["configs", "ctx"])
|
|
134
|
+
]),
|
|
135
|
+
createElementVNode("div", _hoisted_7, [
|
|
136
|
+
createVNode(unref(_sfc_main$2), {
|
|
137
|
+
configs: __props.rightExtraButtons,
|
|
138
|
+
ctx: toolbarCtx.value,
|
|
139
|
+
"default-size": "small"
|
|
140
|
+
}, null, 8, ["configs", "ctx"]),
|
|
141
|
+
createVNode(unref(AutoRefreshGroup), {
|
|
142
|
+
refreshFn: fetch,
|
|
143
|
+
loading: loading.value,
|
|
144
|
+
showAutoRefresh: __props.showAutoRefresh,
|
|
145
|
+
showRefresh: __props.showRefresh
|
|
146
|
+
}, null, 8, ["loading", "showAutoRefresh", "showRefresh"])
|
|
147
|
+
])
|
|
148
|
+
], 512), [
|
|
149
|
+
[vShow, hasToolbarContent.value]
|
|
101
150
|
]),
|
|
102
|
-
createElementVNode("div",
|
|
151
|
+
createElementVNode("div", _hoisted_8, [
|
|
103
152
|
renderSlot(_ctx.$slots, "default", {
|
|
104
153
|
data: data.value,
|
|
105
154
|
loading: loading.value,
|
package/es/style.css
CHANGED
|
@@ -916,7 +916,7 @@ to {
|
|
|
916
916
|
.dc-form-upload-image__thumb_empty[data-v-9d62114a] {
|
|
917
917
|
background: var(--el-fill-color-light);
|
|
918
918
|
}
|
|
919
|
-
.video-preview-trigger_placeholder[data-v-
|
|
919
|
+
.video-preview-trigger_placeholder[data-v-3b6fbdd2] {
|
|
920
920
|
display: flex;
|
|
921
921
|
align-items: center;
|
|
922
922
|
justify-content: center;
|
|
@@ -928,10 +928,11 @@ to {
|
|
|
928
928
|
cursor: pointer;
|
|
929
929
|
transition: background-color 0.2s;
|
|
930
930
|
}
|
|
931
|
-
.video-preview-trigger_placeholder[data-v-
|
|
931
|
+
.video-preview-trigger_placeholder[data-v-3b6fbdd2]:hover {
|
|
932
932
|
background: var(--el-fill-color-darker);
|
|
933
933
|
}
|
|
934
|
-
.video-preview-trigger_cover[data-v-
|
|
934
|
+
.video-preview-trigger_cover[data-v-3b6fbdd2],
|
|
935
|
+
.video-preview-trigger_frame[data-v-3b6fbdd2] {
|
|
935
936
|
position: relative;
|
|
936
937
|
display: inline-block;
|
|
937
938
|
line-height: 0;
|
|
@@ -939,10 +940,23 @@ to {
|
|
|
939
940
|
overflow: hidden;
|
|
940
941
|
cursor: pointer;
|
|
941
942
|
}
|
|
942
|
-
.video-preview-trigger_cover .video-preview-trigger__cover[data-v-
|
|
943
|
+
.video-preview-trigger_cover .video-preview-trigger__cover[data-v-3b6fbdd2],
|
|
944
|
+
.video-preview-trigger_frame .video-preview-trigger__cover[data-v-3b6fbdd2] {
|
|
943
945
|
display: block;
|
|
944
946
|
}
|
|
945
|
-
.video-preview-
|
|
947
|
+
.video-preview-trigger_frame[data-v-3b6fbdd2] {
|
|
948
|
+
width: 100%;
|
|
949
|
+
max-width: 360px;
|
|
950
|
+
aspect-ratio: 16 / 9;
|
|
951
|
+
background: var(--el-fill-color-dark);
|
|
952
|
+
}
|
|
953
|
+
.video-preview-trigger_frame .video-preview-trigger__frame[data-v-3b6fbdd2] {
|
|
954
|
+
display: block;
|
|
955
|
+
width: 100%;
|
|
956
|
+
height: 100%;
|
|
957
|
+
object-fit: cover;
|
|
958
|
+
}
|
|
959
|
+
.video-preview-trigger__play-badge[data-v-3b6fbdd2] {
|
|
946
960
|
position: absolute;
|
|
947
961
|
top: 50%;
|
|
948
962
|
left: 50%;
|
|
@@ -958,10 +972,11 @@ to {
|
|
|
958
972
|
pointer-events: none;
|
|
959
973
|
transition: transform 0.2s;
|
|
960
974
|
}
|
|
961
|
-
.video-preview-trigger_cover:hover .video-preview-trigger__play-badge[data-v-
|
|
975
|
+
.video-preview-trigger_cover:hover .video-preview-trigger__play-badge[data-v-3b6fbdd2],
|
|
976
|
+
.video-preview-trigger_frame:hover .video-preview-trigger__play-badge[data-v-3b6fbdd2] {
|
|
962
977
|
transform: translate(-50%, -50%) scale(1.08);
|
|
963
978
|
}
|
|
964
|
-
.video-preview-trigger__play-icon[data-v-
|
|
979
|
+
.video-preview-trigger__play-icon[data-v-3b6fbdd2] {
|
|
965
980
|
color: var(--dc-core-primary-color, var(--el-color-primary));
|
|
966
981
|
}
|
|
967
982
|
.dc-form-upload[data-v-5eac98bc] .video-preview-trigger_placeholder {
|
|
@@ -1152,15 +1167,24 @@ to {
|
|
|
1152
1167
|
gap: 8px;
|
|
1153
1168
|
margin-top: 8px;
|
|
1154
1169
|
}
|
|
1155
|
-
.view-layout[data-v-
|
|
1170
|
+
.view-layout[data-v-3c537d41] {
|
|
1156
1171
|
display: flex;
|
|
1157
1172
|
flex-direction: column;
|
|
1158
1173
|
gap: 8px;
|
|
1159
1174
|
}
|
|
1160
|
-
.view-layout-toolbar[data-v-
|
|
1175
|
+
.view-layout-toolbar[data-v-3c537d41] {
|
|
1161
1176
|
display: flex;
|
|
1162
|
-
justify-content:
|
|
1177
|
+
justify-content: space-between;
|
|
1178
|
+
align-items: center;
|
|
1179
|
+
padding-bottom: 8px;
|
|
1180
|
+
gap: 20px;
|
|
1181
|
+
}
|
|
1182
|
+
.view-layout-toolbar__left[data-v-3c537d41],
|
|
1183
|
+
.view-layout-toolbar__right[data-v-3c537d41] {
|
|
1184
|
+
display: flex;
|
|
1185
|
+
align-items: center;
|
|
1186
|
+
gap: 10px;
|
|
1163
1187
|
}
|
|
1164
|
-
.view-layout-content[data-v-
|
|
1188
|
+
.view-layout-content[data-v-3c537d41] {
|
|
1165
1189
|
overflow: auto;
|
|
1166
1190
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@done-coding/admin-core",
|
|
3
|
-
"version": "0.22.0",
|
|
3
|
+
"version": "0.22.1-alpha.0",
|
|
4
4
|
"description": "内部前端库",
|
|
5
5
|
"private": false,
|
|
6
6
|
"main": "es/index.mjs",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"node": ">=18.0.0",
|
|
81
81
|
"pnpm": ">=9.0.0"
|
|
82
82
|
},
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "a83c5cc998bee80e722035afba37af45e71a0611"
|
|
84
84
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# VideoPreviewTrigger(视频预览触发组件)
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> 视频触发原语:封面**三形态**——封面图(`ElImage` 吃原生懒加载)/ 首帧封面(`frameCover` 默认开,无封面用 `video#t=0.01` 抠首帧)/ 深色占位(`frameCover` 关,零请求)——**三形态统一叠加播放图标**(标识可播放);**默认插槽可整体自定义**(吐 `open` fn 复用唤起逻辑);点击经全局遥控器唤起 `ModalVideoPreview`,无全局则自带弹窗兜底。
|
|
4
4
|
> 与 `ImagePreviewTrigger` 对等成对(图片触发端点击直达 ElImage 原生预览;视频无 el 原生预览,仍需弹窗决策链)。
|
|
5
5
|
> 范式页:`apps/reference/src/pages/modal/preview/showcase/`
|
|
6
6
|
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
- **何时用**:任意「视频卡片/缩略图可点开播放」场景——上传文件列表、视频素材库;与 `ModalVideoPreview` 成对(触发端 + 播放端)
|
|
10
10
|
- **何时不用**:页面内直接播放 → 用 `<video>` 原生/第三方播放器;仅展示封面不点击 → `ElImage`
|
|
11
|
-
- 关键机制:**核心 = 唤起预览弹窗的逻辑(决策链 + 自带弹窗兜底)内置**;**默认插槽 =
|
|
12
|
-
- **有 `coverUrl`** → 落地 **`ElImage`**(`src=coverUrl`)→ **原生 `loading="lazy"` 直接可用** + `#placeholder`/`#error`
|
|
13
|
-
- **无 `coverUrl
|
|
11
|
+
- 关键机制:**核心 = 唤起预览弹窗的逻辑(决策链 + 自带弹窗兜底)内置**;**默认插槽 = 封面按来源分流 + 播放图标统一**,并吐 `open` 供自定义内容复用——
|
|
12
|
+
- **有 `coverUrl`** → 落地 **`ElImage`**(`src=coverUrl`)→ **原生 `loading="lazy"` 直接可用** + `#placeholder`/`#error` 占位兜底,**封面图上叠加播放图标**(有播放按钮就知道是啥——三形态统一标识可播放);
|
|
13
|
+
- **无 `coverUrl` · `frameCover` 开(默认)** → 落地 **`<video :src="url#t=0.01" preload="metadata">`**——Media Fragments 定位 0.01s 渲染**首帧当封面** + 播放图标叠加;`preload="metadata"` 只拉元数据 + 首帧所在分片(**不拉全片**);
|
|
14
|
+
- **无 `coverUrl` · `frameCover` 关** → **深色占位底 + 播放图标**——零请求零副作用(无需封面的素材库 / 纯占位场景)
|
|
14
15
|
- **默认插槽可整体自定义**:`#default="{ open }"` 接自定义内容(如按钮「播放」)——`open` = 唤起预览的 fn(内部决策链 + 自带弹窗兜底不变),组件不关心自定义内容形态;缺省用内置封面/占位 + 播放图标
|
|
15
16
|
- **点击决策链(全局优先,自带兜底)**:
|
|
16
17
|
```
|
|
@@ -31,16 +32,18 @@
|
|
|
31
32
|
|
|
32
33
|
**要点**:
|
|
33
34
|
|
|
34
|
-
- `url`
|
|
35
|
+
- `url` 必传(视频地址);无封面默认走 **`frameCover` 首帧封面**(`#t=0.01` + `preload="metadata"`——只拉元数据 + 首帧,不拉全片)
|
|
35
36
|
- 有封面:`coverUrl` 传封面图 → 缩略图吃原生懒加载
|
|
37
|
+
- 不想要首帧请求:`:frame-cover="false"` → 回退深色占位(**零请求**,连 metadata 都不拉)
|
|
36
38
|
- 点击后:AppLayout 开了 `enableGlobalVideoPreview`(全局 key 已注册)→ 全局遥控唤起;否则组件自带 `ModalVideoPreview` 本地弹窗
|
|
37
39
|
|
|
38
40
|
## 能力边界 / 按需使用
|
|
39
41
|
|
|
40
42
|
- **默认即够用:封面/占位 + 播放图标 + 点击弹播放**——绝大多数场景只传 `url`。
|
|
41
|
-
- `coverUrl`:仅当业务有封面图(如上传时单独传了封面)时传——有封面走 img 路径白拿懒加载 +
|
|
43
|
+
- `coverUrl`:仅当业务有封面图(如上传时单独传了封面)时传——有封面走 img 路径白拿懒加载 + 图标叠加。
|
|
44
|
+
- `frameCover`(默认 `true`):无 `coverUrl` 时是否用 `video#t=0.01` 抠首帧作封面——开 = 首帧封面(视觉接近封面图,代价是**每实例一个 metadata 小请求**,`video` 无原生 lazy);关 = 深色占位(**零请求**)。视频素材库大列表介意请求量 → 关掉或按需补懒加载(YAGNI,暂未内置)。
|
|
42
45
|
- **`#default="{ open }"`**:整体自定义触发 UI(按钮 / 卡片 / 列表行)——`open` 即「打开预览弹窗」,核心决策链与兜底不变;`placeholder` / `error` 插槽仅内置封面内容使用。
|
|
43
|
-
- 弹窗端行为(`coverUrl` 为空时 `#t=0.01` 首帧兜底等)在 `ModalVideoPreview` 上,见 README-ModalVideoPreview
|
|
46
|
+
- 弹窗端行为(`coverUrl` 为空时 `#t=0.01` 首帧兜底等)在 `ModalVideoPreview` 上,见 README-ModalVideoPreview——**触发端首帧由 `frameCover` 控制**。
|
|
44
47
|
- **完整能力演示**:`apps/reference/src/pages/modal/preview/showcase/`——能力展示,非推荐默认。
|
|
45
48
|
|
|
46
49
|
## API
|
|
@@ -51,7 +54,8 @@
|
|
|
51
54
|
| name | type | 默认 | 语义 |
|
|
52
55
|
| --- | --- | --- | --- |
|
|
53
56
|
| `url` | `string`(必传) | — | 视频地址(点击唤起预览弹窗的视频) |
|
|
54
|
-
| `coverUrl` | `string` | — | 封面图地址;**空 →
|
|
57
|
+
| `coverUrl` | `string` | — | 封面图地址;**空 → 首帧封面(`frameCover` 默认开)或深色占位(关)**;有 → ElImage 封面(原生懒加载) |
|
|
58
|
+
| `frameCover` | `boolean` | `true` | 无 `coverUrl` 时用 `video#t=0.01` 首帧作封面(`preload="metadata"` 只拉元数据 + 首帧,不拉全片);关 = 深色占位 + 播放图标(零请求) |
|
|
55
59
|
| `lazy` | `boolean` | `true` | 封面图懒加载(仅 `coverUrl` 有值时生效) |
|
|
56
60
|
| `preview` | `boolean` | `true` | 是否可点击唤起预览(false = 纯展示封面/占位) |
|
|
57
61
|
|
|
@@ -76,9 +80,9 @@
|
|
|
76
80
|
|
|
77
81
|
## 反模式 / 注意
|
|
78
82
|
|
|
79
|
-
-
|
|
83
|
+
- **`frameCover` 默认开会发请求**:无封面首帧封面 = 每实例一个 metadata 小请求(**非零请求**);素材库大列表(几十个)首帧渲染会同时发 N 个小请求(单个体量极小,但 `video` 无原生 lazy)。介意请求量 → `:frame-cover="false"` 回退零请求占位,或按需补 IntersectionObserver 懒加载(YAGNI,暂未内置)
|
|
84
|
+
- **Safari/iOS `#t` 兼容薄弱**:Media Fragments 时间片做首帧在移动端 Safari 历史上不可靠(现代版本基本可用);若目标用户强 iOS,可 `:frame-cover="false"` 走占位或业务侧自行传 `coverUrl`
|
|
80
85
|
- **`$attrs` 只落封面层**:想配「弹窗端视频」(`autoplay` / `muted` 等)[MUST NOT] 经触发组件透传——去 `ModalVideoPreview` 上配
|
|
81
|
-
- **无封面零请求是硬行为**:不预加载、不拉 metadata——触发组件在列表里挂几十个也不产生视频流量
|
|
82
86
|
- **决策链静默回退是硬行为**:任何环境(无遥控器/未注册/未开启全局)都不许报错——自带弹窗兜底保证触发组件永远可用
|
|
83
87
|
- **`preview=false` 只关点击**:仍渲染封面/占位——「展示」能力不随「预览」关掉
|
|
84
88
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# view-layout 展示布局族
|
|
2
2
|
|
|
3
|
-
展示空间布局壳:`ViewLayout` 消费 `props.api`(内部 `useActivated` 激活拉取)+
|
|
3
|
+
展示空间布局壳:`ViewLayout` 消费 `props.api`(内部 `useActivated` 激活拉取)+ toolbar 对齐 `TableToolbar`(左/右 `ActionBtnGroup` extra 按钮 + 右侧 `AutoRefreshGroup` 自动刷新 / 手动刷新)+ 默认插槽自由内容。
|
|
4
4
|
|
|
5
5
|
## 组件文档(docs/)
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ViewLayout(展示布局)
|
|
2
2
|
|
|
3
|
-
> 展示空间布局壳:`props.api` 数据拉取(内部 `useActivated` 激活拉取)+
|
|
3
|
+
> 展示空间布局壳:`props.api` 数据拉取(内部 `useActivated` 激活拉取)+ toolbar 对齐 `TableToolbar`(左/右 `ActionBtnGroup` extra 按钮 + 右侧 `AutoRefreshGroup` 自动刷新 / 手动刷新)+ 默认插槽自由内容 + 精细流高度链(refine 默认开)。
|
|
4
4
|
> 范式页:`apps/reference/src/pages/view-layout/guide/`
|
|
5
5
|
|
|
6
6
|
## 定位
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
- **空间一等公民**:展示空间布局首选(页级数据展示的布局壳)——承接 PanelMain 废弃的页级展示场景
|
|
9
9
|
- **何时用**:页级数据展示(详情 / 预览 / 摘要展示)——api 拉数 + 自动刷新 + 内容自由编排
|
|
10
10
|
- **何时不用**:结构化详情字段面板用 `PanelMain`(仅表单预览 / 就地编辑保留场景);列表用 `ListLayout`
|
|
11
|
-
- 内部组合:`AutoRefreshGroup
|
|
11
|
+
- 内部组合:`ActionBtnGroup`(左/右 extra 按钮,`default-size="small"` 对齐 table-toolbar 视觉惯例)+ `AutoRefreshGroup`(右侧,refreshFn=api 拉取,经 `HeightProvider` #header 槽 WatchSize 量高)+ 默认插槽(`{ data, loading, refresh, viewportHeight }` 注入)
|
|
12
12
|
|
|
13
13
|
## 快速上手(最小可用)
|
|
14
14
|
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
- `api` 必传:`(params?: P) => Promise<R>`,params 来自 `props.params`(不传则 api 收 undefined)
|
|
29
29
|
- 激活即拉取:首访 mounted / KeepAlive 重新激活自动拉取,失活不拉(数据保留)
|
|
30
30
|
- 右上角 AutoRefreshGroup 自动接管刷新按钮 / 自动刷新开关(`showAutoRefresh` / `showRefresh` 控制显隐)
|
|
31
|
+
- toolbar 对齐 `TableToolbar` 结构:`leftExtraButtons` / `rightExtraButtons`(`ActionBtnGroup` 配置,`default-size="small"`)左右分栏;不含表格特有能力(导出 / 批量下载 / 视图切换)
|
|
31
32
|
- **refine 默认开**:内容区可用高 = 视口高 − toolbar 高(`scope.viewportHeight` + 容器 max-height 双出口)——超高内滚
|
|
32
33
|
|
|
33
34
|
## 能力边界 / 按需使用
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
- `viewportHeight` / `channel` / `parentChannel`:默认不用。仅当 ViewLayout 需要参与可用高 scope 链(显式视口高 / 作为父提供 scope / 作为子定向消费祖先 scope)才配;⚠️ 每级 [MUST] 显式 `refine`、channel 名 [MUST NOT] 响应式变更。
|
|
38
39
|
- `params`:仅当 api 需要拉取参数时传;⚠️ **响应式变化不自动重拉**(需要时调 `scope.refresh()`)。
|
|
39
40
|
- `showAutoRefresh` / `showRefresh`:仅当不需要自动刷新或手动刷新时关。
|
|
41
|
+
- `leftExtraButtons` / `rightExtraButtons`:仅当 toolbar 需要业务操作按钮时配——配置为 `ActionBtnGroup` 单项(`key` / `label` / `actionBtnProps` / `render` / `show`),ctx 注入 `{ loading, refresh }`(刷新与右上角同源);按钮默认 `size="small"` 对齐 table-toolbar。
|
|
40
42
|
- **插槽内容自由编排**:纯展示 [MUST NOT] 套 PanelMain 作页级根(已废弃场景);需要结构化字段面板时在插槽内用 PanelMain,自由内容直接写。
|
|
41
43
|
- **完整能力演示**:`apps/reference/src/pages/view-layout/guide/`——能力展示,非推荐默认。
|
|
42
44
|
|
|
@@ -56,6 +58,8 @@
|
|
|
56
58
|
| `minHeight` | `number?` | `100` | 内容区可用高兜底下限(对齐 ListLayout/TabsMain 惯例) |
|
|
57
59
|
| `showAutoRefresh` | `boolean?` | `true` | 是否显示自动刷新开关(透传 AutoRefreshGroup) |
|
|
58
60
|
| `showRefresh` | `boolean?` | `true` | 是否显示手动刷新按钮(透传 AutoRefreshGroup) |
|
|
61
|
+
| `leftExtraButtons` | `ViewLayoutButtonConfig[]` | `[]` | 左侧额外按钮(`ActionBtnGroup` 配置,ctx = `{ loading, refresh }`,`default-size="small"` 对齐 table-toolbar) |
|
|
62
|
+
| `rightExtraButtons` | `ViewLayoutButtonConfig[]` | `[]` | 右侧额外按钮(置于 AutoRefreshGroup 之前) |
|
|
59
63
|
|
|
60
64
|
### Slots
|
|
61
65
|
|
|
@@ -72,10 +76,11 @@
|
|
|
72
76
|
- **纯展示套 PanelMain 作页级根**:PanelMain 已废弃页级展示场景(见 panel 族文档)——纯展示在插槽内自由编排
|
|
73
77
|
- **channel / parentChannel 响应式变更**:[MUST NOT]——setup 期快照,后续变更无效;缺 refine 的 channel 链静默失效
|
|
74
78
|
- **api 抛错不吞**:api 内部自行处理错误(ViewLayout 只保证 loading 复位,不拦截异常)
|
|
79
|
+
- **toolbar 不含表格能力全集**:对齐的是 table-toolbar 的**结构与视觉惯例**(左右分栏 + extra 按钮 + AutoRefreshGroup),[MUST NOT] 期待导出 / 批量下载 / 视图切换等表格特有能力出现在 ViewLayout——需要表格能力用 `ListLayout` / `TableMain`
|
|
75
80
|
|
|
76
81
|
## 关联
|
|
77
82
|
|
|
78
|
-
- 内部组合:`
|
|
83
|
+
- 内部组合:`ActionBtnGroup` + `HeightProvider` + `WatchSize`(display 族)/ `AutoRefreshGroup`(misc 族)/ `useActivated` / `useChannelViewportHeight`(hooks 桶)
|
|
79
84
|
- 高度链:`SCOPE_VIEWPORT_MAX_HEIGHT` scope 链(viewportHeight / parentChannel 消费侧)
|
|
80
85
|
- 定位衔接:PanelMain 一等公民撤销(将废弃)——ViewLayout 承接页级展示
|
|
81
86
|
- 范式页:`apps/reference/src/pages/view-layout/guide/`
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* VideoPreviewTrigger —— 视频预览触发原语(默认插槽 =
|
|
2
|
+
* VideoPreviewTrigger —— 视频预览触发原语(默认插槽 = 封面三形态 + 播放图标,
|
|
3
3
|
* 吐 `open` 供自定义内容复用;核心唤起逻辑内置)。与 ImagePreviewTrigger 对等成对
|
|
4
4
|
* (视频无 el 原生预览,弹窗是唯一路径——决策链保留;图片触发端点击直达原生预览,
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* 无决策链)。无封面默认 frameCover 抠首帧(#t=0.01 + preload=metadata 只拉元数据 +
|
|
6
|
+
* 首帧,不拉全片);关 frameCover 才深色占位零请求。$attrs 只落封面层(coverUrl 路径)
|
|
7
|
+
* ——弹窗端视频属性(autoplay/muted 等)在 ModalVideoPreview 上配,
|
|
8
|
+
* [MUST NOT] 经本组件透传。详见 docs/README-VideoPreviewTrigger.md。
|
|
9
9
|
*/
|
|
10
10
|
type __VLS_Props = {
|
|
11
11
|
/** 视频地址(点击唤起预览弹窗的视频) */
|
|
12
12
|
url: string;
|
|
13
|
-
/** 封面图地址;空 →
|
|
13
|
+
/** 封面图地址;空 → 首帧封面(frameCover 默认开)或深色占位(关);有 → ElImage 封面(原生懒加载)+ 图标叠加 */
|
|
14
14
|
coverUrl?: string;
|
|
15
|
+
/** 无 coverUrl 时用 video#t=0.01 首帧作封面(preload=metadata 只拉元数据 + 首帧);关 = 深色占位 + 播放图标(零请求) */
|
|
16
|
+
frameCover?: boolean;
|
|
15
17
|
/** 封面图懒加载(仅 coverUrl 有值时生效) */
|
|
16
18
|
lazy?: boolean;
|
|
17
19
|
/** 是否可点击唤起预览(false = 纯展示封面/占位) */
|
|
@@ -33,6 +35,7 @@ type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
|
|
|
33
35
|
declare const __VLS_component: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
34
36
|
lazy: boolean;
|
|
35
37
|
preview: boolean;
|
|
38
|
+
frameCover: boolean;
|
|
36
39
|
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
37
40
|
declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
|
|
38
41
|
export default _default;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ActionBtnConfigItem } from '../display';
|
|
1
2
|
/**
|
|
2
3
|
* ViewLayout 默认插槽 scope(注入给插槽内容的数据与操作句柄)
|
|
3
4
|
*/
|
|
@@ -11,6 +12,17 @@ export interface ViewLayoutScope<R = any> {
|
|
|
11
12
|
/** 内容区可用高度(refine 时 = 视口高 − toolbar reserve,超高内滚的 max-height 预算);自然流 undefined */
|
|
12
13
|
viewportHeight: number | undefined;
|
|
13
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* ViewLayout toolbar 按钮渲染上下文(对齐 TableToolbar 惯例,但只含展示布局句柄——不含表格能力)
|
|
17
|
+
*/
|
|
18
|
+
export interface ViewLayoutButtonContext {
|
|
19
|
+
/** 是否拉取进行中 */
|
|
20
|
+
loading: boolean;
|
|
21
|
+
/** 手动刷新(与右上角刷新按钮同源,等价插槽 scope.refresh) */
|
|
22
|
+
refresh: () => Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/** ViewLayout toolbar 额外按钮配置(ActionBtnGroup 单项,default-size=small 对齐 table-toolbar 视觉惯例) */
|
|
25
|
+
export type ViewLayoutButtonConfig = ActionBtnConfigItem<ViewLayoutButtonContext>;
|
|
14
26
|
/**
|
|
15
27
|
* ViewLayout 展示布局 props
|
|
16
28
|
*/
|
|
@@ -33,4 +45,8 @@ export interface ViewLayoutProps<P = Record<string, any>, R = any> {
|
|
|
33
45
|
showAutoRefresh?: boolean;
|
|
34
46
|
/** 是否显示手动刷新按钮(默认 true) */
|
|
35
47
|
showRefresh?: boolean;
|
|
48
|
+
/** 左侧额外按钮(ActionBtnGroup 配置,ctx = { loading, refresh },default-size=small 对齐 table-toolbar) */
|
|
49
|
+
leftExtraButtons?: ViewLayoutButtonConfig[];
|
|
50
|
+
/** 右侧额外按钮(置于 AutoRefreshGroup 之前) */
|
|
51
|
+
rightExtraButtons?: ViewLayoutButtonConfig[];
|
|
36
52
|
}
|