重构图片组件,使用naive-ui的NImage替换el-image,移除冗余依赖和样式

This commit is contained in:
2025-12-28 21:03:29 +08:00
parent be088ab15b
commit 589696c0ad
10 changed files with 1035 additions and 1614 deletions

1
components.d.ts vendored
View File

@ -24,6 +24,7 @@ declare module 'vue' {
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NIcon: typeof import('naive-ui')['NIcon']
NImage: typeof import('naive-ui')['NImage']
NInput: typeof import('naive-ui')['NInput']
NInputGroup: typeof import('naive-ui')['NInputGroup']
NLayout: typeof import('naive-ui')['NLayout']

2
extra.d.ts vendored
View File

@ -1,3 +1,5 @@
declare module "aplayer";
declare module "vue3-video-play";
declare module "vue3-masonry-plus";
declare module "vite";
declare module "vue-devui/tag";

View File

@ -19,7 +19,6 @@
"@unocss/reset": "^66.3.3",
"aplayer": "^1.10.1",
"axios": "^1.11.0",
"crypto-js": "^4.2.0",
"es-toolkit": "^1.39.8",
"less": "^4.4.0",
"pinia": "^3.0.3",
@ -29,14 +28,10 @@
"unplugin-vue-components": "^28.8.0",
"unplugin-vue-router": "^0.15.0",
"v-infinite-scroll": "^1.0.4",
"vditor": "^3.11.2",
"vite-svg-loader": "^5.1.0",
"vue": "^3.6.0-alpha.2",
"vue-masonry": "^0.16.0",
"vue-router": "^4.5.1",
"vue3-cookies": "^1.0.6",
"vue3-masonry-plus": "^1.2.5",
"vue3-perfect-scrollbar": "^2.0.0",
"vue3-video-play": "^1.3.2"
},
"devDependencies": {

View File

@ -1,3 +1,2 @@
@import "./base.less";
@import "qweather-icons/font/qweather-icons.css";
@import "vue3-perfect-scrollbar/style.css";

View File

@ -1,47 +1,20 @@
<template>
<div class="lazy__box">
<div class="lazy__resource">
<div class="image-container">
<el-image
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"
>
<n-image ref="lazyRef" class="lazy__img" :src="url" @load="handleLoad" @error="handleError">
<template #placeholder>
<img :src="loading" alt="loading" />
</template>
<template #error>
<img :src="errorImg" alt="error" />
</template>
</el-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>
</n-image>
</div>
</template>
<script setup lang="ts">
import { ElImage } from "element-plus";
import type { PropType } from "vue";
import { inject, onMounted, ref, toRefs } from "vue";
import loadError from "../assets/loadError.png";
import loadingImg from "../assets/loading.gif";
import type { Nullable } from "../types/util";
const props = defineProps({
previewIcon: {
type: String,
@ -58,21 +31,12 @@ const props = defineProps({
errorImg: {
type: String,
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 lazyRef = ref<Nullable<InstanceType<typeof ElImage>>>(null);
const lazyRef = ref<any>(null);
const handleLoad = () => {
imgLoaded();
@ -81,12 +45,7 @@ const handleLoad = () => {
const handleError = () => {
// 可以在这里添加错误处理逻辑
};
// 显示预览
const hanldeShowPreview = () => {
if (lazyRef.value) {
lazyRef.value.showPreview();
}
};
onMounted(() => {
if (lazyRef.value) {
imgLoaded();
@ -95,89 +54,18 @@ onMounted(() => {
</script>
<style scoped>
.lazy__box {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.lazy__resource {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.lazy__img {
:deep(.n-image img) {
width: 100%;
height: auto;
display: block;
}
:deep(.el-image) {
width: 100%;
display: block;
}
:deep(.el-image__inner) {
width: 100%;
height: auto;
object-fit: cover;
/* object-fit: cover; */
}
.lazy__img img[alt="loading"],
.lazy__img img[alt="error"] {
width: 48px;
height: 48px;
height: 80px;
padding: 1em;
margin: 0 auto;
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>

View File

@ -1,34 +1,21 @@
<template>
<div
ref="waterfallWrapper"
class="waterfall-list"
:style="{ height: `${wrapperHeight}px` }"
>
<div
v-for="(item, index) in list"
:key="getKey(item, index)"
class="waterfall-item"
>
<div ref="waterfallWrapper" 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">
<slot
name="item"
:item="item"
:index="index"
:url="getRenderURL(item)"
/>
<slot name="item" :item="item" :index="index" :url="getRenderURL(item)" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useDebounceFn } from "@vueuse/core";
import type { PropType } 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 Lazy from "../utils/Lazy";
import { getValue } from "../utils/util";
import type { ViewCard } from "../types/waterfall";
const props = defineProps({
list: {
@ -85,7 +72,7 @@ const props = defineProps({
},
loadProps: {
type: Object,
default: () => {},
default: () => { },
},
crossOrigin: {
type: Boolean,
@ -174,6 +161,7 @@ defineExpose({
overflow: hidden;
background-color: v-bind(backgroundColor);
}
.waterfall-item {
position: absolute;
left: 0;
@ -189,18 +177,22 @@ defineExpose({
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.fadeIn {
-webkit-animation-name: fadeIn;
animation-name: fadeIn;

View File

@ -9,7 +9,6 @@ import router from "./router";
// 自定义主题配置 - 设置主色和二级色\
import "vfonts/FiraCode.css";
import Tag from 'vue-devui/tag';
import { PerfectScrollbarPlugin } from "vue3-perfect-scrollbar";
const app = createApp(App);
app.use(Tag)
@ -21,5 +20,4 @@ for (const key in icon) {
// console.log(key, icon[key]);
app.component("icon-" + key, icon[key] as any);
}
app.use(PerfectScrollbarPlugin);
app.mount("#app");

View File

@ -1,9 +1,7 @@
<template>
<!-- <PerfectScrollbar ref="scrollbar" @ps-scroll-y="handleScroll"> -->
<div ref="myCon" class="gallery-page 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"
@search="onSearch"></d-search> -->
<n-input class="mb-8 !w-[90%] ml-[5%]" size="large" v-model:value="kw" @keyup.enter="onSearch" placeholder="请输入">
<div ref="myCon" class="py-5 px-[10%]">
<n-input class="my-4 !w-[90%] ml-[5%]" round size="large" v-model:value="kw" @keyup.enter="onSearch" placeholder="请输入关键字">
<template #suffix>
<n-icon size="large">
<icon-search />
@ -12,7 +10,7 @@
</n-input>
<!-- <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"> >
<template #item="{ item }">
<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">
{{ item.filename }}
</div>
<div
class="absolute rounded-md flex z-10 truncate bottom-0 w-full bg-[#00000070] text-white justify-between items-center">
<!-- <div
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>
<icon-download class="cursor-pointer w-4 h-4" @click="downloadFile(item.filepath)" />
</div>
</div> -->
</div>
</template>
</Waterfall>
@ -60,14 +58,14 @@ const pn = ref(1);
const ps = ref(40);
const loading = ref(false);
const kw = ref<string>('');
const cwidth = ref<number>(220);
const cwidth = ref<number>(240);
const column = ref<number>(5);
const gutter = ref<number>(20);
// 计算列数
function calculateColumns() {
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;
waterfall.value?.renderer()
}
@ -162,37 +160,5 @@ onUnmounted(() => {
</script>
<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>

View File

@ -8,7 +8,7 @@ import Components from "unplugin-vue-components/vite";
import { VueRouterAutoImports } from "unplugin-vue-router";
import VueRouter from "unplugin-vue-router/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";
// https://vite.dev/config/
export default defineConfig({
@ -36,7 +36,7 @@ export default defineConfig({
}),
UnoCSS(),
svgLoader(),
// vueDevTools(),
vueDevTools(),
],
esbuild: {
pure: ["console.log"], // 删除 console.log

2408
yarn.lock

File diff suppressed because it is too large Load Diff