重构图片组件,使用naive-ui的NImage替换el-image,移除冗余依赖和样式
This commit is contained in:
1
components.d.ts
vendored
1
components.d.ts
vendored
@ -24,6 +24,7 @@ declare module 'vue' {
|
|||||||
NForm: typeof import('naive-ui')['NForm']
|
NForm: typeof import('naive-ui')['NForm']
|
||||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||||
NIcon: typeof import('naive-ui')['NIcon']
|
NIcon: typeof import('naive-ui')['NIcon']
|
||||||
|
NImage: typeof import('naive-ui')['NImage']
|
||||||
NInput: typeof import('naive-ui')['NInput']
|
NInput: typeof import('naive-ui')['NInput']
|
||||||
NInputGroup: typeof import('naive-ui')['NInputGroup']
|
NInputGroup: typeof import('naive-ui')['NInputGroup']
|
||||||
NLayout: typeof import('naive-ui')['NLayout']
|
NLayout: typeof import('naive-ui')['NLayout']
|
||||||
|
|||||||
2
extra.d.ts
vendored
2
extra.d.ts
vendored
@ -1,3 +1,5 @@
|
|||||||
declare module "aplayer";
|
declare module "aplayer";
|
||||||
declare module "vue3-video-play";
|
declare module "vue3-video-play";
|
||||||
declare module "vue3-masonry-plus";
|
declare module "vue3-masonry-plus";
|
||||||
|
declare module "vite";
|
||||||
|
declare module "vue-devui/tag";
|
||||||
@ -19,7 +19,6 @@
|
|||||||
"@unocss/reset": "^66.3.3",
|
"@unocss/reset": "^66.3.3",
|
||||||
"aplayer": "^1.10.1",
|
"aplayer": "^1.10.1",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"crypto-js": "^4.2.0",
|
|
||||||
"es-toolkit": "^1.39.8",
|
"es-toolkit": "^1.39.8",
|
||||||
"less": "^4.4.0",
|
"less": "^4.4.0",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
@ -29,14 +28,10 @@
|
|||||||
"unplugin-vue-components": "^28.8.0",
|
"unplugin-vue-components": "^28.8.0",
|
||||||
"unplugin-vue-router": "^0.15.0",
|
"unplugin-vue-router": "^0.15.0",
|
||||||
"v-infinite-scroll": "^1.0.4",
|
"v-infinite-scroll": "^1.0.4",
|
||||||
"vditor": "^3.11.2",
|
|
||||||
"vite-svg-loader": "^5.1.0",
|
"vite-svg-loader": "^5.1.0",
|
||||||
"vue": "^3.6.0-alpha.2",
|
"vue": "^3.6.0-alpha.2",
|
||||||
"vue-masonry": "^0.16.0",
|
|
||||||
"vue-router": "^4.5.1",
|
"vue-router": "^4.5.1",
|
||||||
"vue3-cookies": "^1.0.6",
|
"vue3-cookies": "^1.0.6",
|
||||||
"vue3-masonry-plus": "^1.2.5",
|
|
||||||
"vue3-perfect-scrollbar": "^2.0.0",
|
|
||||||
"vue3-video-play": "^1.3.2"
|
"vue3-video-play": "^1.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -1,3 +1,2 @@
|
|||||||
@import "./base.less";
|
@import "./base.less";
|
||||||
@import "qweather-icons/font/qweather-icons.css";
|
@import "qweather-icons/font/qweather-icons.css";
|
||||||
@import "vue3-perfect-scrollbar/style.css";
|
|
||||||
|
|||||||
@ -1,47 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="lazy__box">
|
|
||||||
<div class="lazy__resource">
|
|
||||||
<div class="image-container">
|
<div class="image-container">
|
||||||
<el-image
|
<n-image ref="lazyRef" class="lazy__img" :src="url" @load="handleLoad" @error="handleError">
|
||||||
ref="lazyRef"
|
|
||||||
class="lazy__img"
|
|
||||||
:src="url"
|
|
||||||
:preview-src-list="previewSrcList.length > 0 ? previewSrcList : []"
|
|
||||||
fit="contain"
|
|
||||||
:preview-teleported="true"
|
|
||||||
:initial-index="0"
|
|
||||||
:hide-on-click-modal="hideOnClickModal"
|
|
||||||
@load="handleLoad"
|
|
||||||
@error="handleError"
|
|
||||||
>
|
|
||||||
<template #placeholder>
|
<template #placeholder>
|
||||||
<img :src="loading" alt="loading" />
|
<img :src="loading" alt="loading" />
|
||||||
</template>
|
</template>
|
||||||
<template #error>
|
<template #error>
|
||||||
<img :src="errorImg" alt="error" />
|
<img :src="errorImg" alt="error" />
|
||||||
</template>
|
</template>
|
||||||
</el-image>
|
</n-image>
|
||||||
<div
|
|
||||||
class="overlay"
|
|
||||||
v-if="previewSrcList.length > 0 && previewIcon"
|
|
||||||
@click="hanldeShowPreview"
|
|
||||||
>
|
|
||||||
<div class="preview-icon">
|
|
||||||
<img :src="previewIcon" alt="preview" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ElImage } from "element-plus";
|
|
||||||
import type { PropType } from "vue";
|
|
||||||
import { inject, onMounted, ref, toRefs } from "vue";
|
import { inject, onMounted, ref, toRefs } from "vue";
|
||||||
import loadError from "../assets/loadError.png";
|
import loadError from "../assets/loadError.png";
|
||||||
import loadingImg from "../assets/loading.gif";
|
import loadingImg from "../assets/loading.gif";
|
||||||
import type { Nullable } from "../types/util";
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
previewIcon: {
|
previewIcon: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -58,21 +31,12 @@ const props = defineProps({
|
|||||||
errorImg: {
|
errorImg: {
|
||||||
type: String,
|
type: String,
|
||||||
default: loadError,
|
default: loadError,
|
||||||
},
|
}
|
||||||
previewSrcList: {
|
|
||||||
type: Array as PropType<string[]>,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
hideOnClickModal: {
|
|
||||||
// 是否可以通过点击遮罩层关闭预览
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
const { url, loading, errorImg, previewSrcList } = toRefs(props);
|
const { url, loading, errorImg } = toRefs(props);
|
||||||
|
|
||||||
const imgLoaded = inject("imgLoaded") as () => void;
|
const imgLoaded = inject("imgLoaded") as () => void;
|
||||||
const lazyRef = ref<Nullable<InstanceType<typeof ElImage>>>(null);
|
const lazyRef = ref<any>(null);
|
||||||
|
|
||||||
const handleLoad = () => {
|
const handleLoad = () => {
|
||||||
imgLoaded();
|
imgLoaded();
|
||||||
@ -81,12 +45,7 @@ const handleLoad = () => {
|
|||||||
const handleError = () => {
|
const handleError = () => {
|
||||||
// 可以在这里添加错误处理逻辑
|
// 可以在这里添加错误处理逻辑
|
||||||
};
|
};
|
||||||
// 显示预览
|
|
||||||
const hanldeShowPreview = () => {
|
|
||||||
if (lazyRef.value) {
|
|
||||||
lazyRef.value.showPreview();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (lazyRef.value) {
|
if (lazyRef.value) {
|
||||||
imgLoaded();
|
imgLoaded();
|
||||||
@ -95,89 +54,18 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.lazy__box {
|
:deep(.n-image img) {
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lazy__resource {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lazy__img {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
display: block;
|
/* object-fit: cover; */
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-image) {
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-image__inner) {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.lazy__img img[alt="loading"],
|
.lazy__img img[alt="loading"],
|
||||||
.lazy__img img[alt="error"] {
|
.lazy__img img[alt="error"] {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 80px;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-container {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overlay {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-container:hover .overlay {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-icon {
|
|
||||||
color: white;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-image__placeholder),
|
|
||||||
:deep(.el-image__error) {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-image__placeholder) img,
|
|
||||||
:deep(.el-image__error) img {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,34 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div ref="waterfallWrapper" class="waterfall-list" :style="{ height: `${wrapperHeight}px` }">
|
||||||
ref="waterfallWrapper"
|
<div v-for="(item, index) in list" :key="getKey(item, index)" class="waterfall-item">
|
||||||
class="waterfall-list"
|
|
||||||
:style="{ height: `${wrapperHeight}px` }"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="(item, index) in list"
|
|
||||||
:key="getKey(item, index)"
|
|
||||||
class="waterfall-item"
|
|
||||||
>
|
|
||||||
<div class="waterfall-card">
|
<div class="waterfall-card">
|
||||||
<slot
|
<slot name="item" :item="item" :index="index" :url="getRenderURL(item)" />
|
||||||
name="item"
|
|
||||||
:item="item"
|
|
||||||
:index="index"
|
|
||||||
:url="getRenderURL(item)"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useDebounceFn } from "@vueuse/core";
|
||||||
import type { PropType } from "vue";
|
import type { PropType } from "vue";
|
||||||
import { provide, ref, watch } from "vue";
|
import { provide, ref, watch } from "vue";
|
||||||
import { useDebounceFn } from "@vueuse/core";
|
import type { ViewCard } from "../types/waterfall";
|
||||||
import { useCalculateCols, useLayout } from "../use";
|
import { useCalculateCols, useLayout } from "../use";
|
||||||
import Lazy from "../utils/Lazy";
|
import Lazy from "../utils/Lazy";
|
||||||
import { getValue } from "../utils/util";
|
import { getValue } from "../utils/util";
|
||||||
import type { ViewCard } from "../types/waterfall";
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
list: {
|
list: {
|
||||||
@ -85,7 +72,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
loadProps: {
|
loadProps: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {},
|
default: () => { },
|
||||||
},
|
},
|
||||||
crossOrigin: {
|
crossOrigin: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@ -174,6 +161,7 @@ defineExpose({
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: v-bind(backgroundColor);
|
background-color: v-bind(backgroundColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.waterfall-item {
|
.waterfall-item {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -189,18 +177,22 @@ defineExpose({
|
|||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fadeIn {
|
.fadeIn {
|
||||||
-webkit-animation-name: fadeIn;
|
-webkit-animation-name: fadeIn;
|
||||||
animation-name: fadeIn;
|
animation-name: fadeIn;
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import router from "./router";
|
|||||||
// 自定义主题配置 - 设置主色和二级色\
|
// 自定义主题配置 - 设置主色和二级色\
|
||||||
import "vfonts/FiraCode.css";
|
import "vfonts/FiraCode.css";
|
||||||
import Tag from 'vue-devui/tag';
|
import Tag from 'vue-devui/tag';
|
||||||
import { PerfectScrollbarPlugin } from "vue3-perfect-scrollbar";
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
app.use(Tag)
|
app.use(Tag)
|
||||||
@ -21,5 +20,4 @@ for (const key in icon) {
|
|||||||
// console.log(key, icon[key]);
|
// console.log(key, icon[key]);
|
||||||
app.component("icon-" + key, icon[key] as any);
|
app.component("icon-" + key, icon[key] as any);
|
||||||
}
|
}
|
||||||
app.use(PerfectScrollbarPlugin);
|
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- <PerfectScrollbar ref="scrollbar" @ps-scroll-y="handleScroll"> -->
|
<!-- <PerfectScrollbar ref="scrollbar" @ps-scroll-y="handleScroll"> -->
|
||||||
<div ref="myCon" class="gallery-page py-5 px-[10%]">
|
<div ref="myCon" class="py-5 px-[10%]">
|
||||||
<!-- <d-search class="mt-0 mb-8 w-2/3 mx-auto rounded-full" v-model="kw" is-keyup-search :delay="1000"
|
<n-input class="my-4 !w-[90%] ml-[5%]" round size="large" v-model:value="kw" @keyup.enter="onSearch" placeholder="请输入关键字">
|
||||||
@search="onSearch"></d-search> -->
|
|
||||||
<n-input class="mb-8 !w-[90%] ml-[5%]" size="large" v-model:value="kw" @keyup.enter="onSearch" placeholder="请输入">
|
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<n-icon size="large">
|
<n-icon size="large">
|
||||||
<icon-search />
|
<icon-search />
|
||||||
@ -12,7 +10,7 @@
|
|||||||
</n-input>
|
</n-input>
|
||||||
|
|
||||||
<!-- <div v-infinite-scroll="loadMore"> -->
|
<!-- <div v-infinite-scroll="loadMore"> -->
|
||||||
<Waterfall ref="waterfall" :list="fileList" :width="cwidth" :gutter="gutter" :columns="column" img-selector="url"
|
<Waterfall ref="waterfall" :list="fileList" :gutter="gutter" :columns="column" img-selector="url"
|
||||||
animation-effect="fadeIn" :animation-duration="1000" :animation-delay="300" backgroundColor="transparent"> >
|
animation-effect="fadeIn" :animation-duration="1000" :animation-delay="300" backgroundColor="transparent"> >
|
||||||
<template #item="{ item }">
|
<template #item="{ item }">
|
||||||
<div
|
<div
|
||||||
@ -24,11 +22,11 @@
|
|||||||
class="hidden truncate group-hover:block absolute rounded-md z-10 truncate top-0 text-center w-full bg-[#00000070] text-white">
|
class="hidden truncate group-hover:block absolute rounded-md z-10 truncate top-0 text-center w-full bg-[#00000070] text-white">
|
||||||
{{ item.filename }}
|
{{ item.filename }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<!-- <div
|
||||||
class="absolute rounded-md flex z-10 truncate bottom-0 w-full bg-[#00000070] text-white justify-between items-center">
|
class="absolute hidden rounded-md flex z-10 truncate bottom-0 w-full bg-[#00000070] text-white justify-between items-center">
|
||||||
<div>由 <span class="text-[#f1d9db] font-600">{{ item.nickname }}</span> 分享</div>
|
<div>由 <span class="text-[#f1d9db] font-600">{{ item.nickname }}</span> 分享</div>
|
||||||
<icon-download class="cursor-pointer w-4 h-4" @click="downloadFile(item.filepath)" />
|
<icon-download class="cursor-pointer w-4 h-4" @click="downloadFile(item.filepath)" />
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Waterfall>
|
</Waterfall>
|
||||||
@ -60,14 +58,14 @@ const pn = ref(1);
|
|||||||
const ps = ref(40);
|
const ps = ref(40);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const kw = ref<string>('');
|
const kw = ref<string>('');
|
||||||
const cwidth = ref<number>(220);
|
const cwidth = ref<number>(240);
|
||||||
const column = ref<number>(5);
|
const column = ref<number>(5);
|
||||||
const gutter = ref<number>(20);
|
const gutter = ref<number>(20);
|
||||||
|
|
||||||
// 计算列数
|
// 计算列数
|
||||||
function calculateColumns() {
|
function calculateColumns() {
|
||||||
const totalWidth = window.innerWidth * 0.8; // 画廊宽度为视口宽度的80%
|
const totalWidth = window.innerWidth * 0.8; // 画廊宽度为视口宽度的80%
|
||||||
const col = Math.floor(totalWidth / (cwidth.value + gutter.value));
|
const col = Math.floor((totalWidth + gutter.value) / (cwidth.value + gutter.value));
|
||||||
column.value = col > 0 ? col : 1;
|
column.value = col > 0 ? col : 1;
|
||||||
waterfall.value?.renderer()
|
waterfall.value?.renderer()
|
||||||
}
|
}
|
||||||
@ -162,37 +160,5 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
/* 画廊页样式 */
|
|
||||||
:deep(.devui-tabs__nav) {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100% !important;
|
|
||||||
// padding: 0 10%;
|
|
||||||
|
|
||||||
li {
|
|
||||||
width: 50%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
li a span {
|
|
||||||
font-size: 18px !important;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ps {
|
|
||||||
height: calc(100vh - 65px);
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.devui-upload) {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&>div {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@ -8,7 +8,7 @@ import Components from "unplugin-vue-components/vite";
|
|||||||
import { VueRouterAutoImports } from "unplugin-vue-router";
|
import { VueRouterAutoImports } from "unplugin-vue-router";
|
||||||
import VueRouter from "unplugin-vue-router/vite";
|
import VueRouter from "unplugin-vue-router/vite";
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
// import vueDevTools from "vite-plugin-vue-devtools";
|
import vueDevTools from "vite-plugin-vue-devtools";
|
||||||
import svgLoader from "vite-svg-loader";
|
import svgLoader from "vite-svg-loader";
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
@ -36,7 +36,7 @@ export default defineConfig({
|
|||||||
}),
|
}),
|
||||||
UnoCSS(),
|
UnoCSS(),
|
||||||
svgLoader(),
|
svgLoader(),
|
||||||
// vueDevTools(),
|
vueDevTools(),
|
||||||
],
|
],
|
||||||
esbuild: {
|
esbuild: {
|
||||||
pure: ["console.log"], // 删除 console.log
|
pure: ["console.log"], // 删除 console.log
|
||||||
|
|||||||
Reference in New Issue
Block a user