| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 |
- <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 { resolvePageLogo } from '@/utils/resolvePageLogo'
- 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 || '',
- }
- option.value.img = resolvePageLogo(form.value.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('修改成功')
- // 保存成功后直接同步 store,布局/登录页立即生效
- pageSettingStore.setPageSetting(params)
- form.value = {
- ...form.value,
- ...params
- }
- option.value.img = resolvePageLogo(params.logo)
- } else {
- ElMessage.error(res.message || res.msg || '修改失败')
- }
- }
- onMounted(() => {
- handleGetPageSetting()
- })
- const dialogImageVisible = ref(false)
- const option = ref({
- img: resolvePageLogo(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 || '',
- }
- option.value.img = resolvePageLogo(form.value.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="resolvePageLogo(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>
|