Skip to content

Commit 8f9fd4c

Browse files
committed
图片预览组件
1 parent 6e86eab commit 8f9fd4c

17 files changed

+455
-42
lines changed

package-lock.json

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"preview": "vite preview"
1010
},
1111
"dependencies": {
12+
"@types/node": "^18.14.0",
1213
"vue": "^3.2.45",
1314
"vue-router": "^4.1.6"
1415
},

src/App.vue

+43-26
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,47 @@
1-
<script setup lang="ts">
2-
import { ref } from "vue";
3-
import Message from './components/message/main';
4-
const num = ref(1)
5-
function handleMsg (type: string) {
6-
Message({type: type, message: createRandomChinese(Math.floor(Math.random() * 100))})
7-
}
8-
9-
function createRandomChinese (count: Number) {
10-
const start = parseInt('4e00', 16)
11-
const end = parseInt('9fa5', 16)
12-
let name = ''
13-
for(let i = 0; i < count; i++) {
14-
const cha = Math.floor(Math.random() * (end - start))
15-
name += '\\u' + (start + cha).toString(16)
16-
}
17-
return eval(`'${name}'`)
18-
}
19-
</script>
20-
211
<template>
22-
<button @click="handleMsg('info')">msg-test{{num}}info</button>
23-
<button @click="handleMsg('success')">msg-test{{num}}success</button>
24-
<button @click="handleMsg('warning')">msg-test{{num}}warning</button>
25-
<button @click="handleMsg('error')">msg-test{{num}}error</button>
2+
<div v-if="!isHome">
3+
<RouterLink to="/">
4+
<div class="fcc">
5+
<button class="back-home-btn">回到主页</button>
6+
</div>
7+
</RouterLink>
8+
<div class="line"></div>
9+
<div class="view-content">
10+
<RouterView />
11+
</div>
12+
</div>
13+
<div v-else class="view-content">
14+
<RouterView />
15+
</div>
2616
</template>
2717

28-
<style scoped>
18+
<script setup lang='ts'>
19+
import { useRoute } from 'vue-router'
20+
import { ref, watch } from "vue"
21+
const route = useRoute()
22+
const isHome = ref(true)
23+
watch(route, (val) => {
24+
isHome.value = val.fullPath == '/'
25+
})
26+
</script>
2927

30-
</style>
28+
<style>
29+
.back-home-btn{
30+
background: burlywood;
31+
color: #fff;
32+
margin: 20px 0 0 0;
33+
}
34+
.line{
35+
width: 100%;
36+
height: 1px;
37+
background: #ccc;
38+
margin: 20px 0;
39+
}
40+
.view-content{
41+
border: 1rpx solid red;
42+
border-radius: 10px;
43+
margin: 20px;
44+
padding: 20px;
45+
display: block;
46+
}
47+
</style>

src/components/imgUpload/README.md

Whitespace-only changes.

src/components/imgUpload/imgView.vue

+245
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
<template>
2+
<Transition name="fade" @after-leave="onDestroy">
3+
<div v-show="visible" class="img-view" :style="[{zIndex: zIndex}]" @mousewheel="handleMouseWheel($event)">
4+
<div style="position: absolute;z-index: 2;top: 20px;color: red;width: 100%;text-align: center;">组件未引入框架,请自己在组件内修改相关icon(改完这条删)</div>
5+
<div class="view-btn set-close fcc" @click="onClose" title="关闭">×</div>
6+
<div v-show="imgIndex > 0" class="view-btn set-pre fcc" @click="onPre" title="上一张">←</div>
7+
<div v-show="imgIndex < imgList.length - 1" class="view-btn set-next fcc" @click="onNext" title="下一张">→</div>
8+
<div class="view-set fcc">
9+
<span class="set-item" @click="handleImgSet('enlarge')" title="缩小">-</span>
10+
<span class="set-item" @click="handleImgSet('narrow')" title="放大">+</span>
11+
<span class="set-item" @click="handleImgSet('recovery')" title="恢复">口</span>
12+
<span class="set-item" @click="handleImgSet('turnLeft')" title="左转">↪</span>
13+
<span class="set-item" @click="handleImgSet('turnRight')" title="右转">↩</span>
14+
</div>
15+
<div class="img-box">
16+
<img
17+
class="img-content"
18+
:style="[{transform: `scale(${imgStyle.scale}) rotate(${imgStyle.rotate}deg)`, marginTop: imgStyle.marginTop + 'px', marginLeft: imgStyle.marginLeft + 'px', transition: `all ${imgStyle.tranistionTime}s linear`}]"
19+
:src="imgSrc.url"
20+
@dragstart.prevent
21+
@mousedown="handleMouseDown($event)"
22+
@mousemove="handleMouseMove($event)"
23+
@mouseup="handleMouseUp($event)"
24+
@mouseleave="handleMouseUp($event)"
25+
>
26+
</div>
27+
</div>
28+
</Transition>
29+
30+
</template>
31+
<script setup lang='ts'>
32+
import { ImgContent } from "./main"
33+
import { ref, reactive, onMounted, onBeforeUnmount, defineProps, defineEmits, computed } from 'vue'
34+
const props = defineProps({
35+
imgList: {//图片列表
36+
type: Array<ImgContent>,
37+
default: []
38+
},
39+
index: {//图片显示下标
40+
type: Number,
41+
default: 0,
42+
},
43+
zIndex: {//层级
44+
type: Number,
45+
default: 1000,
46+
}
47+
})
48+
const emit = defineEmits(['destroy'])
49+
const visible = ref(false)
50+
const imgIndex = ref(props.index)
51+
const imgStyle = reactive({
52+
tranistionTime: 0.2,
53+
scale: 1,
54+
rotate: 0,
55+
marginTop: 0,
56+
marginLeft: 0,
57+
startX: 0,
58+
startY: 0,
59+
})
60+
const imgSrc = computed(() => props.imgList[imgIndex.value])
61+
onMounted(() => {
62+
visible.value = true
63+
document.addEventListener('keydown', handleKeyDown)
64+
})
65+
onBeforeUnmount (() => {
66+
document.removeEventListener('keydown', handleKeyDown)
67+
})
68+
function handleKeyDown (e: KeyboardEvent) {
69+
switch (e.code) {
70+
case 'Escape':
71+
onClose()
72+
break;
73+
case 'ArrowLeft':
74+
onPre()
75+
break;
76+
case 'ArrowRight':
77+
onNext()
78+
break;
79+
case 'ArrowUp':
80+
handleImgSet('narrow')
81+
break;
82+
case 'ArrowDown':
83+
handleImgSet('enlarge')
84+
break;
85+
case 'Enter':
86+
handleImgSet('recovery')
87+
break;
88+
default:
89+
break;
90+
}
91+
}
92+
//按钮操作
93+
function onClose () {//关闭
94+
visible.value = false
95+
}
96+
function onPre () {//上一张
97+
if (imgIndex.value > 0) {
98+
imgStyle.scale = 1
99+
imgIndex.value--
100+
}
101+
}
102+
function onNext () {//下一张
103+
if (imgIndex.value < props.imgList.length - 1) {
104+
imgStyle.scale = 1
105+
imgIndex.value++
106+
}
107+
}
108+
function handleImgSet (set: String) {//图片操作
109+
if (set == 'enlarge') {
110+
imgStyle.scale = imgStyle.scale - 0.1
111+
} else if (set == 'narrow') {
112+
imgStyle.scale = imgStyle.scale + 0.1
113+
} else if (set == 'recovery') {
114+
imgStyle.scale = 1
115+
imgStyle.rotate = 0
116+
imgStyle.marginTop = 0
117+
imgStyle.marginLeft = 0
118+
imgStyle.startX = 0
119+
imgStyle.startY = 0
120+
} else if (set == 'turnLeft') {
121+
imgStyle.rotate = imgStyle.rotate - 90
122+
} else if (set == 'turnRight') {
123+
imgStyle.rotate = imgStyle.rotate + 90
124+
}
125+
}
126+
//拖拽处理
127+
const isPressed = ref(false)
128+
function handleMouseDown (e: MouseEvent) {
129+
isPressed.value = true
130+
imgStyle.tranistionTime = 0
131+
imgStyle.startX = e.pageX - imgStyle.marginLeft/2
132+
imgStyle.startY = e.pageY - imgStyle.marginTop/2
133+
}
134+
function handleMouseMove (e: MouseEvent) {
135+
if (isPressed.value) {
136+
imgStyle.marginLeft = (e.pageX - imgStyle.startX) * 2
137+
imgStyle.marginTop = (e.pageY - imgStyle.startY) * 2
138+
}
139+
}
140+
function handleMouseUp (e: MouseEvent) {
141+
isPressed.value = false
142+
imgStyle.tranistionTime = 0.2
143+
imgStyle.startX = 0
144+
imgStyle.startY = 0
145+
}
146+
// 鼠标滚轮
147+
function handleMouseWheel (e: WheelEvent) {
148+
const scaleVal = imgStyle.scale - e.deltaY / 5000
149+
if (scaleVal > 0.1 && scaleVal < 3) {
150+
imgStyle.scale = scaleVal
151+
}
152+
}
153+
//动画结束销毁
154+
function onDestroy () {
155+
emit('destroy')
156+
}
157+
</script>
158+
159+
<style>
160+
.img-view{
161+
position: fixed;
162+
top: 0;
163+
left: 0;
164+
bottom: 0;
165+
right: 0;
166+
background-color: rgba(0, 0, 0, 0.5);
167+
}
168+
.set-close{
169+
position: absolute;
170+
top: 40px;
171+
right: 40px;
172+
}
173+
.set-pre{
174+
position: absolute;
175+
left: 40px;
176+
top: 50%;
177+
transform: rotateY(-50%);
178+
}
179+
.set-next{
180+
position: absolute;
181+
right: 40px;
182+
top: 50%;
183+
transform: rotateY(-50%);
184+
}
185+
.view-btn{
186+
width: 40px;
187+
height: 40px;
188+
border-radius: 50%;
189+
background-color: #606266;
190+
color: #fff;
191+
cursor: pointer;
192+
user-select: none;
193+
font-size: 25px;
194+
z-index: 2;
195+
}
196+
.view-set{
197+
position: absolute;
198+
bottom: 40px;
199+
left: 50%;
200+
transform: translateX(-50%);
201+
background-color: #606266;
202+
color: #fff;
203+
cursor: pointer;
204+
user-select: none;
205+
z-index: 2;
206+
border-radius: 50px;
207+
}
208+
.set-item{
209+
padding: 10px 15px;
210+
font-size: 20px;
211+
}
212+
.set-item:first-child{
213+
border-radius: 50px 0px 0px 50px;
214+
padding: 10px 15px 10px 20px;
215+
}
216+
.set-item:last-child{
217+
border-radius: 0px 50px 50px 0px;
218+
padding: 10px 20px 10px 15px;
219+
}
220+
.img-box{
221+
width: 100%;
222+
height: 100%;
223+
display: flex;
224+
align-items: center;
225+
justify-content: center;
226+
}
227+
.img-content{
228+
user-select: none;
229+
display: block;
230+
content: " ";
231+
max-width: 100%;
232+
max-height: 100%;
233+
}
234+
.fcc{
235+
display: flex;
236+
align-items: center;
237+
justify-content: center;
238+
}
239+
.fade-enter-from, .fade-leave-to{
240+
opacity: 0;
241+
}
242+
.fade-enter-active,.fade-leave-active {
243+
transition: all .3s cubic-bezier(.55,0,.1,1);
244+
}
245+
</style>

src/components/imgUpload/main.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import ImgViewConstructor from './imgView.vue'
2+
import { render, createVNode } from "vue"
3+
4+
export type ImgContent = {
5+
fileName: string,
6+
url: string
7+
}
8+
9+
function imgView (list: ImgContent[], index: Number) {
10+
const content = document.createElement('div')
11+
const vnode = createVNode(
12+
ImgViewConstructor,
13+
{
14+
imgList: list,
15+
index: index,
16+
zIndex: 1000,
17+
onDestroy () {
18+
render(null, content)
19+
},
20+
}
21+
)
22+
render(vnode, content)
23+
document.body.appendChild(content.firstElementChild!)
24+
}
25+
export default imgView

src/components/imgUpload/main.vue

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<template>
2+
</template>
3+
4+
<script setup lang='ts'>
5+
</script>
6+
7+
<style scoped>
8+
</style>

0 commit comments

Comments
 (0)