feat: 优化图片尺寸用于移动端

This commit is contained in:
sudoooooo 2024-07-18 21:07:25 +08:00
parent 492e0055f0
commit 2ed5b64b18
9 changed files with 131 additions and 84 deletions

1
web/components.d.ts vendored
View File

@ -17,7 +17,6 @@ declare module 'vue' {
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElForm: typeof import('element-plus/es')['ElForm'] ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenu: typeof import('element-plus/es')['ElMenu']

View File

@ -1,20 +1,38 @@
<template> <template>
<div class="editor-wrapper border"> <div class="editor-wrapper border">
<Toolbar :class="['toolbar', props.staticToolBar ? 'static-toolbar' : 'dynamic-toolbar']" ref="toolbar" <Toolbar
v-show="showToolbar" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" /> :class="['toolbar', props.staticToolBar ? 'static-toolbar' : 'dynamic-toolbar']"
<Editor class="editor" ref="editor" :modelValue="curValue" :defaultConfig="editorConfig" @onCreated="onCreated" ref="toolbar"
@onChange="onChange" @onBlur="onBlur" @onFocus="onFocus" :mode="mode" /> v-show="showToolbar"
:editor="editorRef"
:defaultConfig="toolbarConfig"
:mode="mode"
/>
<Editor
class="editor"
ref="editor"
:modelValue="curValue"
:defaultConfig="editorConfig"
@onCreated="onCreated"
@onChange="onChange"
@onBlur="onBlur"
@onFocus="onFocus"
:mode="mode"
/>
</div> </div>
</template> </template>
<script setup> <script setup>
import '@wangeditor/editor/dist/css/style.css'
import './styles/reset-wangeditor.scss'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { ref, shallowRef, onBeforeMount, watch, computed } from 'vue' import { ref, shallowRef, onBeforeMount, watch, computed } from 'vue'
import { useStore } from 'vuex' import { useStore } from 'vuex'
import { get as _get } from 'lodash-es' import { get as _get } from 'lodash-es'
import '@wangeditor/editor/dist/css/style.css'
import './styles/reset-wangeditor.scss'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { replacePxWithRem } from './utils'
const emit = defineEmits(['input', 'onFocus', 'change', 'blur', 'created']) const emit = defineEmits(['input', 'onFocus', 'change', 'blur', 'created'])
const model = defineModel() const model = defineModel()
const props = defineProps({ const props = defineProps({
@ -34,7 +52,7 @@ const toolbarConfig = computed(() => {
'color', // 'color', //
'bgColor', // 'bgColor', //
'bold', 'bold',
'insertLink', // 'insertLink' //
] ]
} }
if (props.needUploadImage) { if (props.needUploadImage) {
@ -51,13 +69,14 @@ const editorConfig = {
const store = useStore() const store = useStore()
const token = _get(store, 'state.user.userInfo.token') const token = _get(store, 'state.user.userInfo.token')
//
editorConfig.MENU_CONF['uploadImage'] = { editorConfig.MENU_CONF['uploadImage'] = {
allowedFileTypes: ['image/jpeg', 'image/png'], allowedFileTypes: ['image/jpeg', 'image/png'],
server: '/api/file/upload', server: '/api/file/upload',
fieldName: 'file', fieldName: 'file',
meta: { meta: {
//! channelchannel //! channelchannel
channel: 'upload' channel: 'upload'
}, },
headers: { headers: {
Authorization: `Bearer ${token}` Authorization: `Bearer ${token}`
@ -65,7 +84,7 @@ editorConfig.MENU_CONF['uploadImage'] = {
customInsert(res, insertFn) { customInsert(res, insertFn) {
const url = res.data.url const url = res.data.url
insertFn(url, '', '') insertFn(url, '', '')
}, }
} }
const setHtml = (newHtml) => { const setHtml = (newHtml) => {
@ -82,7 +101,8 @@ const onCreated = (editor) => {
emit('created', editor) emit('created', editor)
} }
const onChange = (editor) => { const onChange = (editor) => {
const editorHtml = editor.getHtml() const editorHtml = replacePxWithRem(editor.getHtml())
curValue.value = editorHtml // html curValue.value = editorHtml // html
emit('input', editorHtml) // v-model emit('input', editorHtml) // v-model
} }

View File

@ -0,0 +1,31 @@
// px 转换为 rem
const pxToRem = (px) => {
return `${(parseFloat(px) / 50).toFixed(2)}rem`
}
// 图片style的宽高改成rem
export const replacePxWithRem = (html) => {
const imgRegex = /<img[^>]*style=["'][^"']*\b(?:width|height):\s*\d+(\.\d+)?px[^"']*["'][^>]*>/gi
const styleRegex = /style="([^"]*)"/g
if (!imgRegex.test(html)) {
console.log('无需修改')
return html
}
const res = html.replaceAll(imgRegex, (imgHtml) => {
return imgHtml.replace(styleRegex, (match, content) => {
let styleContent = content
const pxRegex = /(width|height):\s*(\d+(\.\d+)?)px/g
styleContent = styleContent.replace(pxRegex, (pxMatch, prop, value) => {
return `${prop}: ${pxToRem(value)}`
})
return `style="${styleContent}"`
})
})
console.log('生成的结果', res)
return res
}

View File

@ -31,7 +31,9 @@ export const cleanRichTextWithMediaTag = (text) => {
if (!text) { if (!text) {
return text === 0 ? 0 : '' return text === 0 ? 0 : ''
} }
const html = transformHtmlTag(text).replace(/<img([\w\W]+?)\/>/g,'[图片]').replace(/<video.*\/video>/g,'[视频]') const html = transformHtmlTag(text)
.replace(/<img([\w\W]+?)\/>/g, '[图片]')
.replace(/<video.*\/video>/g, '[视频]')
const content = html.replace(/<[^<>]+>/g, '').replace(/&nbsp;/g, '') const content = html.replace(/<[^<>]+>/g, '').replace(/&nbsp;/g, '')
return content return content

View File

@ -6,9 +6,12 @@
<div <div
:class="[ :class="[
'tab-btn', 'tab-btn',
(['QuestionEditIndex', 'QuestionEditSetting', 'QuestionSkinSetting'].includes( ([
route.name 'QuestionEditIndex',
) && 'QuestionEditSetting',
'QuestionSkinSetting',
'QuestionEditResultConfig'
].includes(route.name) &&
tab.to.name === 'QuestionEditIndex') || tab.to.name === 'QuestionEditIndex') ||
isActive isActive
? 'router-link-active' ? 'router-link-active'

View File

@ -16,11 +16,11 @@
:label="item.title" :label="item.title"
minWidth="200" minWidth="200"
> >
<template #header="scope"> <template #header="scope">
<div class="table-row-cell"> <div class="table-row-cell">
<span <span
class="table-row-head" class="table-row-head"
@click="onPreviewImage"
@mouseover="onPopoverRefOver(scope, 'head')" @mouseover="onPopoverRefOver(scope, 'head')"
:ref="(el) => (popoverRefMap[scope.column.id] = el)" :ref="(el) => (popoverRefMap[scope.column.id] = el)"
v-html="item.title" v-html="item.title"
@ -46,21 +46,19 @@
ref="popover" ref="popover"
popper-style="text-align: center;font-size: 13px;" popper-style="text-align: center;font-size: 13px;"
:virtual-ref="popoverVirtualRef" :virtual-ref="popoverVirtualRef"
placement="top" placement="bottom"
width="400"
trigger="hover" trigger="hover"
virtual-triggering virtual-triggering
> >
<div v-html="popoverContent"></div> <div v-html="popoverContent"></div>
</el-popover> </el-popover>
<ImagePreview :url="previewImageUrl" v-model:visible="showPreviewImage"/> <ImagePreview :url="previewImageUrl" v-model:visible="showPreviewImage" />
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import { cleanRichText } from '@/common/xss'
import ImagePreview from './ImagePreview.vue' import ImagePreview from './ImagePreview.vue'
const props = defineProps({ const props = defineProps({
@ -106,7 +104,6 @@ const onPreviewImage = (e) => {
previewImageUrl.value = e.target.src previewImageUrl.value = e.target.src
showPreviewImage.value = true showPreviewImage.value = true
} }
console.log(e.target.src)
} }
</script> </script>
@ -140,12 +137,14 @@ const onPreviewImage = (e) => {
overflow: hidden; /* 超出部分隐藏 */ overflow: hidden; /* 超出部分隐藏 */
text-overflow: ellipsis; /* 显示省略号 */ text-overflow: ellipsis; /* 显示省略号 */
:deep(img) { :deep(img) {
height: 23px; height: 23px !important;
width: auto; width: auto !important;
object-fit: cover;
margin-left: 5px;
} }
:deep(p) { :deep(p) {
display: flex; display: flex;
align-items: center; align-items: center;
} }
} }
} }
@ -153,8 +152,3 @@ const onPreviewImage = (e) => {
font-size: 13px; font-size: 13px;
} }
</style> </style>
<style>
.el-popover p image {
max-width: 100%;
}
</style>

View File

@ -1,72 +1,70 @@
<template> <template>
<Teleport to="body"> <Teleport to="body">
<Transition> <Transition>
<div class="image-preview" v-show="visible"> <div class="image-preview" v-show="visible">
<div class="close-btn" @click="visible = false"> <i-ep-close /> </div> <div class="close-btn" @click="visible = false"><i-ep-close /></div>
<div class="image-con"> <div class="image-con">
<img :src="props.url" class="image-item" /> <img :src="props.url" class="image-item" />
</div> </div>
</div> </div>
</Transition> </Transition>
</Teleport> </Teleport>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const props = defineProps<{ const props = defineProps<{
url: string url: string
}>() }>()
const visible = defineModel('visible') const visible = defineModel('visible')
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.v-enter-active, .v-enter-active,
.v-leave-active { .v-leave-active {
transition: opacity 0.2s ease-out; transition: opacity 0.2s ease-out;
} }
.v-enter-from, .v-enter-from,
.v-leave-to { .v-leave-to {
opacity: 0; opacity: 0;
} }
.image-preview { .image-preview {
position: fixed; position: fixed;
left: 0; left: 0;
top: 0; top: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background-color: rgba($color: #000000, $alpha: .4); background-color: rgba($color: #000000, $alpha: 0.4);
z-index: 2024; z-index: 2024;
display: flex;
align-items: center;
justify-content: center;
.image-con {
width: 100%;
height: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
.image-con { .image-item {
width: 100%; max-width: 80%;
height: 100%; max-height: 70%;
display: flex;
align-items: center;
justify-content: center;
.image-item {
max-width: 80%;
max-height: 70%;
}
} }
}
.close-btn {
.close-btn { position: absolute;
position: absolute; right: 20px;
right: 20px; top: 20px;
top: 20px; width: 30px;
width: 30px; height: 30px;
height: 30px; display: flex;
display: flex; align-items: center;
align-items: center; justify-content: center;
justify-content: center; cursor: pointer;
cursor: pointer; border-radius: 50%;
border-radius: 50%; background-color: #d0d0d0;
background-color: #d0d0d0; }
}
} }
</style> </style>

View File

@ -58,7 +58,7 @@ watch(
position: absolute; position: absolute;
top: 10px; top: 10px;
cursor: pointer; cursor: pointer;
z-index: 9999; z-index: 999;
:deep(.el-radio-button__original-radio + .el-radio-button__inner) { :deep(.el-radio-button__original-radio + .el-radio-button__inner) {
font-size: 12px; font-size: 12px;
height: 28px; height: 28px;

View File

@ -1,8 +1,8 @@
// 富文本标题选项中的预览弹窗的图片宽度 // 富文本标题选项中的预览弹窗的图片宽度
.el-popover { .el-popover {
p { p {
img { img {
max-width: 100%; max-width: 100%;
}
} }
} }
}