feat(setting): 系统设置支持上传 App 登录页背景

This commit is contained in:
Vicky authored and trae-solo-agent committed 2026-10-30 17:14:50 +08:00
1 parent a94018d5d3
commit dd4be08d49
2 files changed
+138 -3

No files matched your search

@@ -46,6 +46,13 @@ export const BANNER_IMAGE: ImagePreset = {
maxHeight: 1164
}
export const LOGIN_BACKGROUND: ImagePreset = {
label: '登录页背景',
hint: '建议 1170×2532(390×844dp × 3,iPhone 整屏竖图),min 720×1560',
maxWidth: 1170,
maxHeight: 2532
}
/** 按规格压缩:尺寸未超且体积不大时原样返回,不做二次编码。 */
export function compressForPreset(file: File, preset: ImagePreset): Promise<File> {
return compressImage(file, {
+131 -3
View File
@@ -1,9 +1,11 @@
<!-- Author: fruit.com.cn -->
<script setup>
import { onMounted, ref } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { listConfigsApi, updateConfigsApi } from '@/api/admin/config'
import { uploadFile } from '@/utils/uploader'
import { compressForPreset, LOGIN_BACKGROUND } from '@/views/contentManagement/upload/imagePresets'
import { useRbac } from '@/stores/rbac'
const { can } = useRbac()
@@ -14,6 +16,15 @@ const workspaceName = ref('')
const defaultStatus = ref('draft')
const registerMaxPerIp = ref(5)
const registerMaxPerDevice = ref(3)
const loginBackgroundUrl = ref('')
const MAX_LOGIN_BG_SIZE = 8 * 1024 * 1024 // 8 MB
const ALLOWED_LOGIN_BG_TYPES = ['image/jpeg', 'image/png', 'image/webp']
const uploadingBg = ref(false)
const bgError = ref('')
const bgFileName = ref('')
const canSave = computed(() => can('system:role'))
const statusOptions = [
{ code: 'draft', text: '草稿' },
@@ -34,6 +45,7 @@ async function load() {
defaultStatus.value = map['content.default-status'] || 'draft'
registerMaxPerIp.value = parseRegisterLimit(map['app.register.max-per-ip-per-day'], 5)
registerMaxPerDevice.value = parseRegisterLimit(map['app.register.max-per-device-per-day'], 3)
loginBackgroundUrl.value = map['app.login.background.url'] || ''
} finally {
loading.value = false
}
@@ -46,7 +58,8 @@ async function save() {
'workspace.name': workspaceName.value,
'content.default-status': defaultStatus.value,
'app.register.max-per-ip-per-day': String(registerMaxPerIp.value),
'app.register.max-per-device-per-day': String(registerMaxPerDevice.value)
'app.register.max-per-device-per-day': String(registerMaxPerDevice.value),
'app.login.background.url': loginBackgroundUrl.value
})
ElMessage.success('设置已保存')
} finally {
@@ -54,6 +67,52 @@ async function save() {
}
}
function beforeBgUpload(file) {
bgError.value = ''
if (!ALLOWED_LOGIN_BG_TYPES.includes(file.type)) {
bgError.value = '图片仅支持 JPG、PNG 或 WebP 格式'
return false
}
if (file.size > MAX_LOGIN_BG_SIZE) {
bgError.value = '图片大小不能超过 8MB'
return false
}
return true
}
async function uploadBg(options) {
uploadingBg.value = true
bgError.value = ''
try {
const file = await compressForPreset(options.file, LOGIN_BACKGROUND)
const asset = await uploadFile(file, {
bizType: 'LOGIN_BG'
})
loginBackgroundUrl.value = asset.url
bgFileName.value = file.name
ElMessage.success('登录页背景已上传')
} catch {
bgError.value = '图片上传失败,请检查网络后重试'
} finally {
uploadingBg.value = false
}
}
function removeBg() {
if (uploadingBg.value) return
loginBackgroundUrl.value = ''
bgFileName.value = ''
bgError.value = ''
}
const loginBgPreview = computed(() => {
const url = loginBackgroundUrl.value
if (!url) return ''
if (url.startsWith('http://') || url.startsWith('https://')) return url
if (url.startsWith('/')) return url
return `/${url}`
})
onMounted(load)
</script>
@@ -78,9 +137,78 @@ onMounted(load)
><el-input-number v-model="registerMaxPerIp" :step="1" :precision="0" /></el-form-item
><el-form-item label="单设备 24 小时注册上限(0 或负数表示不限制)"
><el-input-number v-model="registerMaxPerDevice" :step="1" :precision="0" /></el-form-item
><el-button type="primary" :loading="saving" :disabled="!can('system:role')" @click="save"
>
<el-divider content-position="left">App 登录页</el-divider>
<el-form-item label="登录页背景图">
<div class="login-bg-field">
<el-upload
:show-file-list="false"
:before-upload="beforeBgUpload"
:http-request="uploadBg"
accept=".jpg,.jpeg,.png,.webp"
>
<el-button :disabled="uploadingBg" :loading="uploadingBg">
{{ loginBackgroundUrl ? '重新上传' : '上传背景图' }}
</el-button>
</el-upload>
<span class="field-hint">建议 {{ LOGIN_BACKGROUND.hint }}</span>
<span v-if="bgFileName" class="field-filename">当前:{{ bgFileName }}</span>
<span v-if="bgError" class="field-error">{{ bgError }}</span>
<div v-if="loginBgPreview" class="login-bg-preview">
<img :src="loginBgPreview" alt="登录页背景预览" />
<el-button
class="remove-bg-btn"
text
type="danger"
size="small"
@click="removeBg"
>移除</el-button>
</div>
</div>
</el-form-item>
<el-button type="primary" :loading="saving" :disabled="!canSave" @click="save"
>保存设置</el-button
></el-form
></section
>
</template>
<style scoped>
.login-bg-field {
display: flex;
flex-direction: column;
gap: 6px;
}
.field-hint {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.field-filename {
font-size: 12px;
color: var(--el-text-color-primary);
}
.field-error {
font-size: 12px;
color: var(--el-color-danger);
}
.login-bg-preview {
position: relative;
margin-top: 4px;
width: 96px;
aspect-ratio: 1170 / 2532;
border-radius: 6px;
overflow: hidden;
background: var(--el-fill-color-light);
}
.login-bg-preview img {
width: 100%;
height: 100%;
object-fit: cover;
}
.remove-bg-btn {
position: absolute;
top: 2px;
right: 2px;
padding: 2px 4px;
}
</style>