|
@@ -0,0 +1,455 @@
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import { onMounted, ref } from 'vue'
|
|
|
|
|
+import { setPageSetting } from '@/api/sys/pageSetting'
|
|
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
+import { usePageSettingStore } from '@/pinia/modules/pageSetting'
|
|
|
|
|
+import 'vue-cropper/dist/index.css'
|
|
|
|
|
+import { VueCropper } from 'vue-cropper'
|
|
|
|
|
+import defaultLogo from '@/assets/logo.png'
|
|
|
|
|
+
|
|
|
|
|
+defineOptions({
|
|
|
|
|
+ name: 'MaskPageSetting'
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const pageSettingStore = usePageSettingStore()
|
|
|
|
|
+const formRef = ref(null)
|
|
|
|
|
+const form = ref({
|
|
|
|
|
+ name: '',
|
|
|
|
|
+ logo: '',
|
|
|
|
|
+ isLoaded: false
|
|
|
|
|
+})
|
|
|
|
|
+const rules = ref({
|
|
|
|
|
+ name: [{ min: 2, max: 10, message: '自定义名称长度为2-10个字符', trigger: 'blur' }]
|
|
|
|
|
+})
|
|
|
|
|
+const handleGetPageSetting = async() => {
|
|
|
|
|
+ await pageSettingStore.initPageSetting()
|
|
|
|
|
+ form.value = {
|
|
|
|
|
+ name: pageSettingStore.pageSetting.name || '非结构化数据透明加密平台',
|
|
|
|
|
+ logo: pageSettingStore.pageSetting.logo || '',
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+const changeSetting = async() => {
|
|
|
|
|
+ await formRef.value.validate()
|
|
|
|
|
+ const params = {
|
|
|
|
|
+ name: form.value.name,
|
|
|
|
|
+ logo: form.value.logo
|
|
|
|
|
+ }
|
|
|
|
|
+ const res = await setPageSetting(params)
|
|
|
|
|
+ if (res.code === 100000) {
|
|
|
|
|
+ ElMessage.success('修改成功')
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error(res.msg)
|
|
|
|
|
+ }
|
|
|
|
|
+ handleGetPageSetting()
|
|
|
|
|
+}
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ handleGetPageSetting()
|
|
|
|
|
+})
|
|
|
|
|
+function resolveLogo(path) {
|
|
|
|
|
+ // 如果为空,返回默认logo
|
|
|
|
|
+ if (!path) {
|
|
|
|
|
+ return defaultLogo
|
|
|
|
|
+ }
|
|
|
|
|
+ // 如果是 base64,直接返回
|
|
|
|
|
+ if (path.startsWith('data:image')) {
|
|
|
|
|
+ return path
|
|
|
|
|
+ }
|
|
|
|
|
+ // 如果是相对路径/别名路径,解析成真实地址
|
|
|
|
|
+ return new URL(path, import.meta.url).href
|
|
|
|
|
+}
|
|
|
|
|
+const dialogImageVisible = ref(false)
|
|
|
|
|
+const option = ref({
|
|
|
|
|
+ img: resolveLogo(pageSettingStore.pageSetting.logo), // 裁剪图片的地址
|
|
|
|
|
+ outputSize: 1, // 裁剪生成图片的质量(可选0.1 - 1)
|
|
|
|
|
+ outputType: 'jpeg', // 裁剪生成图片的格式(jpeg || png || webp)
|
|
|
|
|
+ info: true, // 图片大小信息
|
|
|
|
|
+ canScale: true, // 图片是否允许滚轮缩放
|
|
|
|
|
+ autoCrop: true, // 是否默认生成截图框
|
|
|
|
|
+ autoCropWidth: 150, // 默认生成截图框宽度
|
|
|
|
|
+ autoCropHeight: 150, // 默认生成截图框高度
|
|
|
|
|
+ fixed: true, // 是否开启截图框宽高固定比例
|
|
|
|
|
+ fixedNumber: [1, 1], // 截图框的宽高比例
|
|
|
|
|
+ full: false, // false按原比例裁切图片,不失真
|
|
|
|
|
+ fixedBox: false, // 固定截图框大小,不允许改变
|
|
|
|
|
+ canMove: true, // 上传图片是否可以移动
|
|
|
|
|
+ canMoveBox: true, // 截图框能否拖动
|
|
|
|
|
+ original: false, // 上传图片按照原始比例渲染
|
|
|
|
|
+ centerBox: false, // 截图框是否被限制在图片里面
|
|
|
|
|
+ height: true, // 是否按照设备的dpr 输出等比例图片
|
|
|
|
|
+ infoTrue: false, // true为展示真实输出图片宽高,false展示看到的截图框宽高
|
|
|
|
|
+ maxImgSize: 3000, // 限制图片最大宽度和高度
|
|
|
|
|
+ enlarge: 1, // 图片根据截图框输出比例倍数
|
|
|
|
|
+ mode: '300px 300px' // 图片默认渲染方式
|
|
|
|
|
+})
|
|
|
|
|
+const imgLoad = msg => {
|
|
|
|
|
+ if (msg === 'success') {
|
|
|
|
|
+ isReady.value = true
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+// 实时的刷新
|
|
|
|
|
+const realTime = () => {
|
|
|
|
|
+ if (loading.value) return
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ // 节流
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ cropper.value.getCropBlob(data => {
|
|
|
|
|
+ baseUrl.value = URL.createObjectURL(data) // 创建获取Blob对象的URL
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ })
|
|
|
|
|
+ }, 500)
|
|
|
|
|
+}
|
|
|
|
|
+const cropper = ref(null)
|
|
|
|
|
+const fileName = ref('')
|
|
|
|
|
+
|
|
|
|
|
+const isReady = ref(false)
|
|
|
|
|
+const baseUrl = ref()
|
|
|
|
|
+// 选择图片
|
|
|
|
|
+const selectClick = () => {
|
|
|
|
|
+ inputRef.value?.click()
|
|
|
|
|
+}
|
|
|
|
|
+// 选择图片
|
|
|
|
|
+const selectImg = e => {
|
|
|
|
|
+ const eTarget = e.target
|
|
|
|
|
+ if (!eTarget.files) return
|
|
|
|
|
+ const file = eTarget.files[0]
|
|
|
|
|
+ // 确保 fileName 被正确赋值
|
|
|
|
|
+ fileName.value = file.name
|
|
|
|
|
+ if (!/\.(jpg|jpeg|png|JPG|PNG)$/.test(eTarget.value)) {
|
|
|
|
|
+ ElMessage.warning('图片格式错误')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ const reader = new FileReader()
|
|
|
|
|
+ reader.readAsDataURL(file) // 将文件转化为base64
|
|
|
|
|
+ reader.onload = e => {
|
|
|
|
|
+ let data
|
|
|
|
|
+ if (!e.target) return
|
|
|
|
|
+ if (typeof e.target.result === 'object') {
|
|
|
|
|
+ data = URL.createObjectURL(new Blob([e.target.result ?? '']))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ data = e.target.result
|
|
|
|
|
+ }
|
|
|
|
|
+ option.value.img = data
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+const inputRef = ref()
|
|
|
|
|
+// 向左旋转
|
|
|
|
|
+const rotateLeft = () => {
|
|
|
|
|
+ cropper.value.rotateLeft()
|
|
|
|
|
+}
|
|
|
|
|
+// 向右旋转
|
|
|
|
|
+const rotateRight = () => {
|
|
|
|
|
+ cropper.value.rotateRight()
|
|
|
|
|
+}
|
|
|
|
|
+// 上传图片
|
|
|
|
|
+const imgUpload = () => {
|
|
|
|
|
+ // 获取截图的 blob 数据
|
|
|
|
|
+ cropper.value.getCropBlob(async data => {
|
|
|
|
|
+ // 上传到服务器
|
|
|
|
|
+ // 获取原始文件类型
|
|
|
|
|
+ const fileType = fileName.value.split('.').pop().toLowerCase()
|
|
|
|
|
+ const mimeType = `image/${fileType === 'jpg' ? 'jpeg' : fileType}`
|
|
|
|
|
+ const file = new File([data], fileName.value, { type: mimeType })
|
|
|
|
|
+
|
|
|
|
|
+ // 校验图片尺寸
|
|
|
|
|
+ const isSizeValid = await validateImageSize(file)
|
|
|
|
|
+ if (!isSizeValid) {
|
|
|
|
|
+ ElMessage.warning('图片尺寸不符合要求,请重新裁剪!')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 把图片转成base64
|
|
|
|
|
+ form.value.logo = await new Promise(resolve => {
|
|
|
|
|
+ const reader = new FileReader()
|
|
|
|
|
+ reader.readAsDataURL(file)
|
|
|
|
|
+ reader.onloadend = () => resolve(reader.result)
|
|
|
|
|
+ })
|
|
|
|
|
+ // 重置裁剪组件状态
|
|
|
|
|
+ resetCropper()
|
|
|
|
|
+ dialogImageVisible.value = false
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+// 校验图片尺寸的函数
|
|
|
|
|
+const validateImageSize = file => {
|
|
|
|
|
+ return new Promise(resolve => {
|
|
|
|
|
+ const reader = new FileReader()
|
|
|
|
|
+ reader.onload = e => {
|
|
|
|
|
+ const img = new Image()
|
|
|
|
|
+ img.src = e.target.result
|
|
|
|
|
+ img.onload = () => {
|
|
|
|
|
+ const width = img.width
|
|
|
|
|
+ const height = img.height
|
|
|
|
|
+ // 这里可以根据实际需求设置图片尺寸的限制
|
|
|
|
|
+ const minWidth = 150
|
|
|
|
|
+ const minHeight = 150
|
|
|
|
|
+ const isValid =
|
|
|
|
|
+ width <= minWidth &&
|
|
|
|
|
+ height <= minHeight &&
|
|
|
|
|
+ width / height === minWidth / minHeight
|
|
|
|
|
+ resolve(isValid)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ reader.readAsDataURL(file)
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+// 重置裁剪组件状态的函数
|
|
|
|
|
+const resetCropper = () => {
|
|
|
|
|
+ option.value.img = `${baseUrl.value}`
|
|
|
|
|
+ fileName.value = ''
|
|
|
|
|
+ isReady.value = false
|
|
|
|
|
+ baseUrl.value = ''
|
|
|
|
|
+ // 你还可以根据需要重置其他相关状态
|
|
|
|
|
+}
|
|
|
|
|
+const openFileDialog = () => {
|
|
|
|
|
+ dialogImageVisible.value = true
|
|
|
|
|
+}
|
|
|
|
|
+const resetSetting = async() => {
|
|
|
|
|
+ const confirm = await ElMessageBox.confirm(
|
|
|
|
|
+ '确认恢复默认设置吗?',
|
|
|
|
|
+ '提示',
|
|
|
|
|
+ {
|
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
|
+ type: 'warning'
|
|
|
|
|
+ }
|
|
|
|
|
+ ).catch(() => {
|
|
|
|
|
+ ElMessage.info('已取消操作')
|
|
|
|
|
+ // 取消操作
|
|
|
|
|
+ })
|
|
|
|
|
+ if (!confirm) return
|
|
|
|
|
+ await pageSettingStore.resetPageSetting()
|
|
|
|
|
+
|
|
|
|
|
+ form.value = {
|
|
|
|
|
+ name: pageSettingStore.pageSetting.name || '非结构化数据透明加密平台',
|
|
|
|
|
+ logo: pageSettingStore.pageSetting.logo || '',
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div >
|
|
|
|
|
+ <div class="p-6 bg-[#fff] space-y-4 h-[calc(100vh-220px)]">
|
|
|
|
|
+ <div class="text-3xl">页面配置</div>
|
|
|
|
|
+ <el-divider class="my-4" />
|
|
|
|
|
+ <el-form
|
|
|
|
|
+ ref="formRef"
|
|
|
|
|
+ :model="form"
|
|
|
|
|
+ :rules="rules"
|
|
|
|
|
+ label-position="left"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-form-item
|
|
|
|
|
+ label="自定义名称"
|
|
|
|
|
+ prop="name"
|
|
|
|
|
+ class="my-10"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="form.name"
|
|
|
|
|
+ placeholder="请输入自定义名称"
|
|
|
|
|
+ class="w-1/6 "
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item
|
|
|
|
|
+ label="自定义Logo"
|
|
|
|
|
+ prop="logo"
|
|
|
|
|
+ class="form-item-center "
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="image-container">
|
|
|
|
|
+ <el-image
|
|
|
|
|
+ :src="resolveLogo(form.logo)"
|
|
|
|
|
+ class="image-position"
|
|
|
|
|
+ fit="cover"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-preReClick
|
|
|
|
|
+ class="button-position"
|
|
|
|
|
+ @click="openFileDialog()"
|
|
|
|
|
+ >修改头像</el-button
|
|
|
|
|
+ >
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <div class="mt-10">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-preReClick
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+
|
|
|
|
|
+ @click="changeSetting()"
|
|
|
|
|
+ >修改配置</el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-preReClick
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="resetSetting()"
|
|
|
|
|
+ >
|
|
|
|
|
+ 恢复默认
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-dialog
|
|
|
|
|
+ v-model="dialogImageVisible"
|
|
|
|
|
+ class="p-[50px]"
|
|
|
|
|
+ width="900"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="flex items-center justify-center">
|
|
|
|
|
+ <div class="w-[710px]">
|
|
|
|
|
+ <div class="w-[710px] h-[300px] flex items-center">
|
|
|
|
|
+ <!-- 给组件固定宽高 -->
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="h-[300px] w-[510px]"
|
|
|
|
|
+ style="width: 500px"
|
|
|
|
|
+ >
|
|
|
|
|
+ <VueCropper
|
|
|
|
|
+ ref="cropper"
|
|
|
|
|
+ :auto-crop="option.autoCrop"
|
|
|
|
|
+ :auto-crop-height="option.autoCropHeight"
|
|
|
|
|
+ :auto-crop-width="option.autoCropWidth"
|
|
|
|
|
+ :can-move="option.canMove"
|
|
|
|
|
+ :can-move-box="option.canMoveBox"
|
|
|
|
|
+ :can-scale="option.canScale"
|
|
|
|
|
+ :center-box="option.centerBox"
|
|
|
|
|
+ :enlarge="option.enlarge"
|
|
|
|
|
+ :fixed="option.fixed"
|
|
|
|
|
+ :fixed-box="option.fixedBox"
|
|
|
|
|
+ :fixed-number="option.fixedNumber"
|
|
|
|
|
+ :full="option.full"
|
|
|
|
|
+ :height="option.height"
|
|
|
|
|
+ :img="option.img"
|
|
|
|
|
+ :info="option.info"
|
|
|
|
|
+ :info-true="option.infoTrue"
|
|
|
|
|
+ :max-img-size="option.maxImgSize"
|
|
|
|
|
+ :mode="option.mode"
|
|
|
|
|
+ :original="option.original"
|
|
|
|
|
+ :output-size="option.outputSize"
|
|
|
|
|
+ :output-type="option.outputType"
|
|
|
|
|
+ @imgLoad="imgLoad"
|
|
|
|
|
+ @realTime="realTime"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="ml-[60px]">
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="text-center text-[#409EFF] mb-[20px] text-[20px] font-medium"
|
|
|
|
|
+ >
|
|
|
|
|
+ 预览
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
|
+ <div v-if="isReady">
|
|
|
|
|
+ <el-image
|
|
|
|
|
+ :src="baseUrl"
|
|
|
|
|
+ class="rounded-[50%] w-[150px] h-[150px]"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="flex items-center justify-center mt-[30px]">
|
|
|
|
|
+ <div class="relative">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ class="mr-[15px]"
|
|
|
|
|
+ plain
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="selectClick"
|
|
|
|
|
+ >
|
|
|
|
|
+ 选择图片
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <input
|
|
|
|
|
+ ref="inputRef"
|
|
|
|
|
+ accept="image/png, image/jpeg, image/gif, image/jpg"
|
|
|
|
|
+ style="position: absolute; clip: rect(0 0 0 0)"
|
|
|
|
|
+ type="file"
|
|
|
|
|
+ @change="selectImg"
|
|
|
|
|
+ >
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ style="width: 96px"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="rotateLeft"
|
|
|
|
|
+ >↺ 左旋转</el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ style="width: 96px"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="rotateRight"
|
|
|
|
|
+ >↻ 右旋转</el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ plain
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="imgUpload"
|
|
|
|
|
+ >上传图片</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+.image-position {
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ width: 75px;
|
|
|
|
|
+ height: 75px;
|
|
|
|
|
+}
|
|
|
|
|
+/* 让该行整体垂直居中 */
|
|
|
|
|
+.form-item-center {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center; /* 垂直居中 label 和内容 */
|
|
|
|
|
+ /* 注意:Element Plus 的 label 宽度可能会影响,这里不改 label-width,仅做垂直对齐 */
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 保证 label 本身也是纵向居中(增强兼容性) */
|
|
|
|
|
+.form-item-center :deep(.el-form-item__label) {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ /* 可选:居中对齐文本 */
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ padding-top: 0; /* 覆盖默认上内边距 */
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 内容区也用 flex 对齐,方便图片和按钮横向对齐 */
|
|
|
|
|
+.form-item-center :deep(.el-form-item__content) {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 限制图片尺寸并确保内部 img cover 填充 */
|
|
|
|
|
+.image-position {
|
|
|
|
|
+ width: 75px;
|
|
|
|
|
+ height: 75px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 因为 el-image 是个包装元素,强制其内部 img 填充容器 */
|
|
|
|
|
+.form-item-center :deep(.image-position) img {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ object-fit: cover;
|
|
|
|
|
+ display: block;
|
|
|
|
|
+}
|
|
|
|
|
+.button-position {
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ width: 75px;
|
|
|
|
|
+ height: 75px;
|
|
|
|
|
+ background-color: rgba(0, 0, 0, 0);
|
|
|
|
|
+ color: rgba(0, 0, 0, 0);
|
|
|
|
|
+}
|
|
|
|
|
+.button-position:hover {
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ width: 75px;
|
|
|
|
|
+ height: 75px;
|
|
|
|
|
+ background-color: rgba(0, 0, 0, 0.5);
|
|
|
|
|
+ color: #ffffff;
|
|
|
|
|
+ font-weight: bolder;
|
|
|
|
|
+}
|
|
|
|
|
+.image-container {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.image-container .button-position {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|