(component): layer弹层扩展photos方法,用于图片预览

This commit is contained in:
0o张不歪o0 2022-06-25 15:36:18 +08:00
parent 1438147ec3
commit 892722860d
7 changed files with 212 additions and 15 deletions

View File

@ -350,6 +350,42 @@ const openRight = function() {
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>图片层</legend>
</fieldset>
::: demo 通过 layer.photos(options) 创建图片预览弹层, 参数`options`主要传入预览的图片链接。
<template>
<button @click="signleImg">快速预览一张图片</button>
<button @click="signleImg2">单张图片带文字描述</button>
<button @click="groupImg">图片组</button>
</template>
<script>
import { layer } from "../../../layer/src/index"
const signleImg = function() {
layer.photos("/src/assets/logo.jpg")
}
const signleImg2 = function() {
layer.photos({
imgList:[ {src:'/src/assets/logo.jpg',alt:'layer for vue'}]
})
}
const groupImg = function() {
layer.photos({
imgList:[
{ src:'http://www.pearadmin.com/assets/images/un8.svg', alt:'图片1'},
{ src:'http://www.pearadmin.com/assets/images/un32.svg', alt:'图片2'}
]
})
}
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>通讯</legend>
</fieldset>
@ -496,7 +532,7 @@ const changeVisible = () => {
| shadeClose | 遮盖层关闭 | boolean | `true` | `true` `false` |
| shadeOpacity | 遮盖层透明度 | string | `0.1` | `0.1` - `1` |
| isHtmlFragment | 解析 html 字符 | boolean | `false` | `true` `false` |
| imgList | 图片数据数组 | array[{src:图片链接,alt:图片名字可选'}] | - | - |
<fieldset class="layui-elem-field layui-field-title">
<legend>动画</legend>
</fieldset>

View File

@ -0,0 +1,51 @@
<template>
<div class="layui-layer-phimg">
<img :src="imgList[index].src" />
<div class="layui-layer-imgsee" v-if="imgList.length > 0">
<span class="layui-layer-imguide" v-if="imgList.length > 1">
<a href="javascript:;" class="layui-layer-iconext layui-layer-imgprev" @click="changeIndex(-1)"></a>
<a href="javascript:;" class="layui-layer-iconext layui-layer-imgnext" @click="changeIndex(1)"></a>
</span>
<div class="layui-layer-imgbar" style="display: block" v-if="imgList.length > 1 || imgList[index].alt">
<span class="layui-layer-imgtit">
<span v-if="imgList[index].alt">{{ imgList[index].alt }}</span>
<em v-if="imgList.length > 1">{{ index + 1 }} / {{ imgList.length }}</em>
</span>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: "Photos",
};
</script>
<script lang="ts" setup>
import { watch, ref } from "vue";
export interface LayPhotoProps {
imgList: { src: string, alt: string }[];
startIndex: number;
}
const emit = defineEmits(["resetCalculationPohtosArea"]);
const props = withDefaults(defineProps<LayPhotoProps>(), {
startIndex: 0,
});
const index = ref(props.startIndex);
watch(index, () => {
//
emit('resetCalculationPohtosArea', index.value)
})
const changeIndex = (step: number) => {
let nowIndex = index.value
let next = nowIndex + step
if (next < 0) {
next = props.imgList.length - 1
}
if (next >= props.imgList.length) {
next = 0
}
index.value = next
}
</script>

View File

@ -10,6 +10,7 @@ import Iframe from "./Iframe.vue";
import Title from "./Title.vue";
import CloseBtn from "./CloseBtn.vue";
import Resize from "./Resize.vue";
import Photos from "./Photos.vue";
import {
Ref,
ref,
@ -34,6 +35,7 @@ import {
updateMinArrays,
getDrawerAnimationClass,
calculateDrawerArea,
calculatePhotosArea,
} from "../utils";
import useMove from "../composable/useMove";
import useResize from "../composable/useResize";
@ -53,7 +55,7 @@ export interface LayModalProps {
btn?: Record<string, Function>[] | false;
move?: boolean | string;
resize?: boolean | string;
type?: 0 | 1 | 2 | 3 | "dialog" | "page" | "iframe" | "loading" | "drawer";
type?: 0 | 1 | 2 | 3 | "dialog" | "page" | "iframe" | "loading" | "drawer" | "photos";
content?: string | Function | object | VNodeTypes;
isHtmlFragment?: boolean;
shade?: boolean | string;
@ -73,6 +75,8 @@ export interface LayModalProps {
isFunction?: boolean;
isMessage?: boolean;
appContext?: any;
startIndex?: number;
imgList?: { src: string; alt: string }[];
}
const props = withDefaults(defineProps<LayModalProps>(), {
@ -101,6 +105,8 @@ const props = withDefaults(defineProps<LayModalProps>(), {
yesText: "确定",
isFunction: false,
isMessage: false,
startIndex: 0,
imgList: () => [],
});
const emit = defineEmits(["close", "update:modelValue"]);
@ -141,11 +147,18 @@ const _l: Ref<string> = ref(offset.value[1]);
* <p>
*/
const firstOpenDelayCalculation = function () {
nextTick(() => {
nextTick(async () => {
area.value = getArea(layero.value);
if (props.type === "drawer") {
area.value = calculateDrawerArea(props.offset, props.area);
}
if (props.type === "photos") {
// @ts-ignore
area.value = await calculatePhotosArea(
props.imgList[props.startIndex].src,
props
);
}
offset.value = calculateOffset(props.offset, area.value, props.type);
w.value = area.value[0];
h.value = area.value[1];
@ -334,6 +347,7 @@ const boxClasses = computed(() => {
type === 1 ? "layui-layer-page" : "",
type === 2 ? "layui-layer-iframe" : "",
type === 3 ? "layui-layer-loading" : "",
type === 4 ? "layui-layer-photos" : "",
props.isMessage ? "layui-layer-msg" : "",
props.isMessage && !props.icon ? "layui-layer-hui" : "",
props.skin,
@ -511,9 +525,28 @@ const showResize = computed(() => {
* @param type 类型
*/
const showTitle = computed(() => {
return props.title && props.type != 3;
return props.title && props.type != 3 && props.type != "photos";
});
/*
* 图片弹层重新计算
*/
const resetCalculationPohtosArea = function (index: number) {
nextTick(async () => {
// @ts-ignore
area.value = await calculatePhotosArea(props.imgList[index].src, props);
offset.value = calculateOffset(props.offset, area.value, props.type);
w.value = area.value[0];
h.value = area.value[1];
t.value = offset.value[0];
l.value = offset.value[1];
_w.value = area.value[0];
_l.value = area.value[1];
_t.value = offset.value[0];
_l.value = offset.value[1];
});
};
defineExpose({ reset, open, close });
</script>
@ -558,9 +591,15 @@ defineExpose({ reset, open, close });
</template>
</template>
<Iframe v-if="type === 2" :src="props.content"></Iframe>
<Photos
v-if="type === 4"
:imgList="props.imgList"
:startIndex="props.startIndex"
@resetCalculationPohtosArea="resetCalculationPohtosArea"
></Photos>
</div>
<!-- 工具栏 -->
<span class="layui-layer-setwin" v-if="type != 3">
<span class="layui-layer-setwin" v-if="type != 3 && type != 4">
<a
v-if="maxmin && !max"
class="layui-layer-min"

View File

@ -121,6 +121,23 @@ const layer = {
};
return layer.create(option, defaultOption, callback);
},
//图片预览
photos: (option: any, callback?: Function) => {
if (typeof option === 'string') {
option = {
imgList: [{ src: option }]
}
}
let defaultOption = {
type: 'photos',
anim: 2,
startIndex: 0,
isOutAnim: true,
shadeClose: true,
shadeOpacity: '0.7'
};
return layer.create(option, defaultOption, callback);
},
// 创建弹出层
create: (option: any, defaultOption: any, callback?: Function) => {
// 销毁定时

View File

@ -1092,7 +1092,8 @@ html #layuicss-layer {
.layui-layer-photos {
background: 0 0;
box-shadow: none
box-shadow: none;
border:none
}
.layui-layer-photos .layui-layer-content {

View File

@ -1,3 +1,5 @@
import { layer } from "../index"
// 随机数
export function nextId() {
var s: any = [];
@ -121,6 +123,8 @@ export function calculateType(modalType: number | string) {
return 2;
} else if (modalType === "loading" || modalType === 3 || modalType === "3") {
return 3;
} else if (modalType === "photos") {
return 4;
}
return 0;
}
@ -233,3 +237,61 @@ export function getDrawerAnimationClass(offset: any, isClose: boolean = false) {
}
return isClose ? `${prefix}-${suffix}-close` : `${prefix}-${suffix}`;
}
//图片预加载
export function loadImage(url: string, callback: Function, error: any) {
let img = new Image();
img.src = url;
if (img.complete) {
return callback(img);
}
img.onload = function () {
img.onload = null;
callback(img);
};
img.onerror = function (e) {
img.onerror = null;
error(e);
};
}
export async function calculatePhotosArea(url: string,options:object) {
let img = new Image();
img.src = url;
return new Promise((resolve, reject) => {
if (img.complete) {
resolve(area(img))
return
}
const layerId=layer.load(2)
img.onload = () => {
layer.close(layerId)
resolve(area(img))
};
img.onerror = () => {
layer.close(layerId)
layer.msg('图片加载失败')
reject(false)
}
})
function area(img:{width:number,height:number}){
var imgarea = [img.width, img.height];
var winarea = [window.innerWidth - 100, window.innerHeight - 100];
//如果 实际图片的宽或者高比 屏幕大(那么进行缩放)
if ( imgarea[0] > winarea[0] || imgarea[1] > winarea[1]) {
let wh = [imgarea[0] / winarea[0], imgarea[1] / winarea[1]]; //取宽度缩放比例、高度缩放比例
if (wh[0] > wh[1]) {
//取缩放比例最大的进行缩放
imgarea[0] = imgarea[0] / wh[0];
imgarea[1] = imgarea[1] / wh[0];
} else if (wh[0] < wh[1]) {
imgarea[0] = imgarea[0] / wh[1];
imgarea[1] = imgarea[1] / wh[1];
}
}
return [imgarea[0] + "px", imgarea[1] + "px"];
}
}

View File

@ -1,6 +1,5 @@
import { defineConfig } from "vite";
import { name } from "./package.json";
import babel from "@rollup/plugin-babel";
import vue from "@vitejs/plugin-vue";
import path from "path";
@ -30,14 +29,6 @@ export default defineConfig({
},
assetFileNames: "index.css",
},
plugins: [
// @ts-ignore
babel({
exclude: "node_modules/**",
extensions: [".js", ".jsx", ".ts", ".tsx", ".vue"],
presets: ["@babel/preset-env", "@babel/preset-typescript"],
}),
],
external: ["vue"],
},
},