feat(client): 添加品牌logo功能支持
- 在客户端账户实体中新增brandLogo字段用于存储品牌logo URL - 实现品牌logo的上传、获取和删除接口 - 在Vue前端中集成品牌logo的展示和管理功能- 添加品牌logo的缓存机制提升访问性能 - 在设置对话框中增加品牌logo配置界面 - 实现品牌logo的预览、上传和删除操作 - 添加VIP权限控制确保只有VIP用户可使用该功能 - 增加品牌logo变更事件监听以实时更新界面显示- 更新数据库映射文件以支持brand_logo字段的读写- 在登录成功后异步加载品牌logo配置信息- 调整UI布局以适配品牌logo展示区域- 添加品牌logo相关的样式定义和响应式处理 - 实现品牌logo上传的文件类型和大小校验- 增加品牌logo删除确认提示增强用户体验 - 在App.vue中添加品牌logo的全局状态管理和展示逻辑- 优化品牌logo加载失败时的容错处理 - 完善品牌logo功能的相关错误处理和日志记录
This commit is contained in:
BIN
electron-vue-template/img.png
Normal file
BIN
electron-vue-template/img.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
BIN
electron-vue-template/public/icon/vipExclusive.png
Normal file
BIN
electron-vue-template/public/icon/vipExclusive.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
@@ -5,6 +5,7 @@ import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
|||||||
import 'element-plus/dist/index.css'
|
import 'element-plus/dist/index.css'
|
||||||
import {authApi} from './api/auth'
|
import {authApi} from './api/auth'
|
||||||
import {deviceApi, type DeviceItem, type DeviceQuota} from './api/device'
|
import {deviceApi, type DeviceItem, type DeviceQuota} from './api/device'
|
||||||
|
import {splashApi} from './api/splash'
|
||||||
import {getOrCreateDeviceId} from './utils/deviceId'
|
import {getOrCreateDeviceId} from './utils/deviceId'
|
||||||
import {getToken, setToken, removeToken, getUsernameFromToken, getClientIdFromToken} from './utils/token'
|
import {getToken, setToken, removeToken, getUsernameFromToken, getClientIdFromToken} from './utils/token'
|
||||||
import {CONFIG} from './api/http'
|
import {CONFIG} from './api/http'
|
||||||
@@ -61,7 +62,7 @@ const vipExpireTime = ref<Date | null>(null)
|
|||||||
const deviceTrialExpired = ref(false)
|
const deviceTrialExpired = ref(false)
|
||||||
const accountType = ref<string>('trial')
|
const accountType = ref<string>('trial')
|
||||||
const vipStatus = computed(() => {
|
const vipStatus = computed(() => {
|
||||||
if (!vipExpireTime.value) return { isVip: false, daysLeft: 0, hoursLeft: 0, status: 'expired', expiredType: 'account' }
|
if (!vipExpireTime.value) return {isVip: false, daysLeft: 0, hoursLeft: 0, status: 'expired', expiredType: 'account'}
|
||||||
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const expire = new Date(vipExpireTime.value)
|
const expire = new Date(vipExpireTime.value)
|
||||||
@@ -76,7 +77,7 @@ const vipStatus = computed(() => {
|
|||||||
else if (accountExpired) expiredType = 'account'
|
else if (accountExpired) expiredType = 'account'
|
||||||
else if (deviceExpired) expiredType = 'device'
|
else if (deviceExpired) expiredType = 'device'
|
||||||
|
|
||||||
return { isVip: false, daysLeft: 0, hoursLeft: 0, status: 'expired', expiredType }
|
return {isVip: false, daysLeft: 0, hoursLeft: 0, status: 'expired', expiredType}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hoursLeft = Math.floor(msLeft / (1000 * 60 * 60))
|
const hoursLeft = Math.floor(msLeft / (1000 * 60 * 60))
|
||||||
@@ -87,10 +88,10 @@ const vipStatus = computed(() => {
|
|||||||
expiredType = 'device' // 试用账号且设备过期
|
expiredType = 'device' // 试用账号且设备过期
|
||||||
}
|
}
|
||||||
|
|
||||||
if (daysLeft === 0) return { isVip: true, daysLeft, hoursLeft, status: 'warning', expiredType }
|
if (daysLeft === 0) return {isVip: true, daysLeft, hoursLeft, status: 'warning', expiredType}
|
||||||
if (daysLeft <= 7) return { isVip: true, daysLeft, hoursLeft, status: 'warning', expiredType }
|
if (daysLeft <= 7) return {isVip: true, daysLeft, hoursLeft, status: 'warning', expiredType}
|
||||||
if (daysLeft <= 30) return { isVip: true, daysLeft, hoursLeft, status: 'normal', expiredType }
|
if (daysLeft <= 30) return {isVip: true, daysLeft, hoursLeft, status: 'normal', expiredType}
|
||||||
return { isVip: true, daysLeft, hoursLeft, status: 'active', expiredType }
|
return {isVip: true, daysLeft, hoursLeft, status: 'active', expiredType}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 功能可用性(账号VIP + 设备试用期)
|
// 功能可用性(账号VIP + 设备试用期)
|
||||||
@@ -118,6 +119,9 @@ const showAccountManager = ref(false)
|
|||||||
// 当前版本
|
// 当前版本
|
||||||
const currentVersion = ref('')
|
const currentVersion = ref('')
|
||||||
|
|
||||||
|
// 品牌logo
|
||||||
|
const brandLogoUrl = ref('')
|
||||||
|
|
||||||
// 菜单配置 - 复刻ERP客户端格式
|
// 菜单配置 - 复刻ERP客户端格式
|
||||||
const menuConfig = [
|
const menuConfig = [
|
||||||
{key: 'rakuten', name: 'Rakuten', index: 'rakuten', icon: 'R', iconImage: rakutenIcon},
|
{key: 'rakuten', name: 'Rakuten', index: 'rakuten', icon: 'R', iconImage: rakutenIcon},
|
||||||
@@ -198,7 +202,13 @@ function handleMenuSelect(key: string) {
|
|||||||
addToHistory(key)
|
addToHistory(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleLoginSuccess(data: { token: string; permissions?: string; expireTime?: string; accountType?: string; deviceTrialExpired?: boolean }) {
|
async function handleLoginSuccess(data: {
|
||||||
|
token: string;
|
||||||
|
permissions?: string;
|
||||||
|
expireTime?: string;
|
||||||
|
accountType?: string;
|
||||||
|
deviceTrialExpired?: boolean
|
||||||
|
}) {
|
||||||
try {
|
try {
|
||||||
setToken(data.token)
|
setToken(data.token)
|
||||||
isAuthenticated.value = true
|
isAuthenticated.value = true
|
||||||
@@ -251,7 +261,7 @@ function clearLocalAuth() {
|
|||||||
async function logout() {
|
async function logout() {
|
||||||
try {
|
try {
|
||||||
const deviceId = getClientIdFromToken()
|
const deviceId = getClientIdFromToken()
|
||||||
if (deviceId) await deviceApi.offline({ deviceId, username: currentUsername.value })
|
if (deviceId) await deviceApi.offline({deviceId, username: currentUsername.value})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('离线通知失败:', error)
|
console.warn('离线通知失败:', error)
|
||||||
}
|
}
|
||||||
@@ -270,7 +280,8 @@ async function handleUserClick() {
|
|||||||
cancelButtonText: '取消'
|
cancelButtonText: '取消'
|
||||||
})
|
})
|
||||||
await logout()
|
await logout()
|
||||||
} catch {}
|
} catch {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showRegisterDialog() {
|
function showRegisterDialog() {
|
||||||
@@ -360,6 +371,19 @@ async function syncSettingsToElectron() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载品牌logo
|
||||||
|
async function loadBrandLogo() {
|
||||||
|
try {
|
||||||
|
const username = getUsernameFromToken()
|
||||||
|
if (!username) return
|
||||||
|
|
||||||
|
const res = await splashApi.getBrandLogo(username)
|
||||||
|
brandLogoUrl.value = res.data.url
|
||||||
|
} catch (error) {
|
||||||
|
brandLogoUrl.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 提供给子组件使用
|
// 提供给子组件使用
|
||||||
provide('refreshVipStatus', refreshVipStatus)
|
provide('refreshVipStatus', refreshVipStatus)
|
||||||
provide('vipStatus', vipStatus)
|
provide('vipStatus', vipStatus)
|
||||||
@@ -529,6 +553,14 @@ onMounted(async () => {
|
|||||||
console.warn('获取当前版本失败:', error)
|
console.warn('获取当前版本失败:', error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载品牌logo
|
||||||
|
loadBrandLogo()
|
||||||
|
|
||||||
|
// 监听品牌logo变化
|
||||||
|
window.addEventListener('brandLogoChanged', (e: any) => {
|
||||||
|
brandLogoUrl.value = e.detail
|
||||||
|
})
|
||||||
|
|
||||||
// 全局阻止文件拖拽到窗口(避免意外打开文件)
|
// 全局阻止文件拖拽到窗口(避免意外打开文件)
|
||||||
// 只在指定的 dropzone 区域处理拖拽上传
|
// 只在指定的 dropzone 区域处理拖拽上传
|
||||||
document.addEventListener('dragover', (e) => {
|
document.addEventListener('dragover', (e) => {
|
||||||
@@ -584,6 +616,12 @@ onUnmounted(() => {
|
|||||||
<div class="user-avatar">
|
<div class="user-avatar">
|
||||||
<img src="/icon/icon.png" alt="logo"/>
|
<img src="/icon/icon.png" alt="logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 品牌logo区域(有logo时显示) -->
|
||||||
|
<div v-if="brandLogoUrl" class="brand-logo-section">
|
||||||
|
<img :src="brandLogoUrl" alt="品牌logo" class="brand-logo"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="menu-group-title">电商平台</div>
|
<div class="menu-group-title">电商平台</div>
|
||||||
<ul class="menu">
|
<ul class="menu">
|
||||||
<li
|
<li
|
||||||
@@ -595,7 +633,7 @@ onUnmounted(() => {
|
|||||||
>
|
>
|
||||||
<span class="menu-text">
|
<span class="menu-text">
|
||||||
<span class="menu-icon" :data-k="item.key">
|
<span class="menu-icon" :data-k="item.key">
|
||||||
<img v-if="item.iconImage" :src="item.iconImage" :alt="item.name" class="menu-icon-img" />
|
<img v-if="item.iconImage" :src="item.iconImage" :alt="item.name" class="menu-icon-img"/>
|
||||||
<template v-else>{{ item.icon }}</template>
|
<template v-else>{{ item.icon }}</template>
|
||||||
</span>
|
</span>
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
@@ -604,7 +642,8 @@ onUnmounted(() => {
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<!-- VIP状态卡片 -->
|
<!-- VIP状态卡片 -->
|
||||||
<div v-if="isAuthenticated" class="vip-status-card" :class="'vip-' + vipStatus.status" @click="openSubscriptionDialog">
|
<div v-if="isAuthenticated" class="vip-status-card" :class="'vip-' + vipStatus.status"
|
||||||
|
@click="openSubscriptionDialog">
|
||||||
<div class="vip-info">
|
<div class="vip-info">
|
||||||
<div class="vip-status-text">
|
<div class="vip-status-text">
|
||||||
<template v-if="vipStatus.isVip">
|
<template v-if="vipStatus.isVip">
|
||||||
@@ -616,7 +655,13 @@ onUnmounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="vip-expire-date" v-if="vipExpireTime">
|
<div class="vip-expire-date" v-if="vipExpireTime">
|
||||||
有效期至:{{ new Date(vipExpireTime).toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' }) }}
|
有效期至:{{
|
||||||
|
new Date(vipExpireTime).toLocaleDateString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
})
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -672,16 +717,20 @@ onUnmounted(() => {
|
|||||||
@back-to-login="backToLogin"/>
|
@back-to-login="backToLogin"/>
|
||||||
|
|
||||||
<!-- 更新对话框 -->
|
<!-- 更新对话框 -->
|
||||||
<UpdateDialog ref="updateDialogRef" v-model="showUpdateDialog" />
|
<UpdateDialog ref="updateDialogRef" v-model="showUpdateDialog"/>
|
||||||
|
|
||||||
<!-- 设置对话框 -->
|
<!-- 设置对话框 -->
|
||||||
<SettingsDialog v-model="showSettingsDialog" @auto-update-changed="handleAutoUpdateChanged" @open-update-dialog="handleOpenUpdateDialog" />
|
<SettingsDialog
|
||||||
|
v-model="showSettingsDialog"
|
||||||
|
:is-vip="canUseFunctions"
|
||||||
|
@auto-update-changed="handleAutoUpdateChanged"
|
||||||
|
@open-update-dialog="handleOpenUpdateDialog" />
|
||||||
|
|
||||||
<!-- 试用期过期弹框 -->
|
<!-- 试用期过期弹框 -->
|
||||||
<TrialExpiredDialog v-model="showTrialExpiredDialog" :expired-type="trialExpiredType" />
|
<TrialExpiredDialog v-model="showTrialExpiredDialog" :expired-type="trialExpiredType"/>
|
||||||
|
|
||||||
<!-- 账号管理弹框 -->
|
<!-- 账号管理弹框 -->
|
||||||
<AccountManager v-model="showAccountManager" platform="zebra" />
|
<AccountManager v-model="showAccountManager" platform="zebra"/>
|
||||||
|
|
||||||
<!-- 设备管理弹框 -->
|
<!-- 设备管理弹框 -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
@@ -693,7 +742,9 @@ onUnmounted(() => {
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="device-dialog-header">
|
<div class="device-dialog-header">
|
||||||
<img src="/icon/img.png" alt="devices" class="device-illustration"/>
|
<img src="/icon/img.png" alt="devices" class="device-illustration"/>
|
||||||
<div class="device-title">设备管理 <span class="device-count">({{ deviceQuota.used || 0 }}/{{ deviceQuota.limit || 0 }})</span></div>
|
<div class="device-title">设备管理 <span class="device-count">({{
|
||||||
|
deviceQuota.used || 0
|
||||||
|
}}/{{ deviceQuota.limit || 0 }})</span></div>
|
||||||
<div class="device-subtitle">当前账号可以授权绑定 {{ deviceQuota.limit }} 台设备</div>
|
<div class="device-subtitle">当前账号可以授权绑定 {{ deviceQuota.limit }} 台设备</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -829,6 +880,24 @@ onUnmounted(() => {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 品牌logo区域 */
|
||||||
|
.brand-logo-section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 0 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-logo {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -952,6 +1021,7 @@ onUnmounted(() => {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #606266;
|
color: #606266;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-dialog-header {
|
.device-dialog-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -959,31 +1029,45 @@ onUnmounted(() => {
|
|||||||
padding: 12px 0 4px 0;
|
padding: 12px 0 4px 0;
|
||||||
margin-left: 40px;
|
margin-left: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-dialog :deep(.el-dialog__header) {
|
.device-dialog :deep(.el-dialog__header) {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.device-dialog :deep(.el-dialog__body) { padding-top: 0; }
|
|
||||||
|
.device-dialog :deep(.el-dialog__body) {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.device-illustration {
|
.device-illustration {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
height: auto;
|
height: auto;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-title {
|
.device-title {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
.device-count { color: #909399; font-weight: 500; }
|
|
||||||
.device-subtitle { font-size: 12px; color: #909399; }
|
.device-count {
|
||||||
|
color: #909399;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-subtitle {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
/* 浮动版本信息 */
|
/* 浮动版本信息 */
|
||||||
.version-info {
|
.version-info {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
background: rgba(255,255,255,0.9);
|
background: rgba(255, 255, 255, 0.9);
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
@@ -27,6 +27,24 @@ export const splashApi = {
|
|||||||
// 删除自定义开屏图片(恢复默认)
|
// 删除自定义开屏图片(恢复默认)
|
||||||
async deleteSplashImage(username: string) {
|
async deleteSplashImage(username: string) {
|
||||||
return http.post<{ data: string }>(`/monitor/account/splash-image/delete?username=${username}`)
|
return http.post<{ data: string }>(`/monitor/account/splash-image/delete?username=${username}`)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 上传品牌logo
|
||||||
|
async uploadBrandLogo(file: File, username: string) {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
formData.append('username', username)
|
||||||
|
return http.upload<{ data: { url: string; fileName: string } }>('/monitor/account/brand-logo/upload', formData)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取当前用户的品牌logo
|
||||||
|
async getBrandLogo(username: string) {
|
||||||
|
return http.get<{ data: { url: string } }>('/monitor/account/brand-logo', { username })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除品牌logo
|
||||||
|
async deleteBrandLogo(username: string) {
|
||||||
|
return http.post<{ data: string }>(`/monitor/account/brand-logo/delete?username=${username}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,8 +42,9 @@ async function handleAuth() {
|
|||||||
clientId: deviceId
|
clientId: deviceId
|
||||||
})
|
})
|
||||||
|
|
||||||
// 保存开屏图片配置(不阻塞登录)
|
// 保存开屏图片配置和品牌logo(不阻塞登录)
|
||||||
saveSplashConfigInBackground(authForm.value.username)
|
saveSplashConfigInBackground(authForm.value.username)
|
||||||
|
saveBrandLogoInBackground(authForm.value.username)
|
||||||
|
|
||||||
emit('loginSuccess', {
|
emit('loginSuccess', {
|
||||||
token: loginRes.data.accessToken || loginRes.data.token,
|
token: loginRes.data.accessToken || loginRes.data.token,
|
||||||
@@ -90,6 +91,18 @@ async function saveSplashConfigInBackground(username: string) {
|
|||||||
console.error('[开屏图片] 保存配置失败:', error)
|
console.error('[开屏图片] 保存配置失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 保存品牌logo配置
|
||||||
|
async function saveBrandLogoInBackground(username: string) {
|
||||||
|
try {
|
||||||
|
const res = await splashApi.getBrandLogo(username)
|
||||||
|
const url = res?.data?.url || ''
|
||||||
|
// 触发App.vue加载品牌logo
|
||||||
|
window.dispatchEvent(new CustomEvent('brandLogoChanged', { detail: url }))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[品牌logo] 加载配置失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, watch, nextTick } from 'vue'
|
import { ref, computed, onMounted, watch, nextTick, inject, defineAsyncComponent } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import {
|
import {
|
||||||
getSettings,
|
getSettings,
|
||||||
@@ -14,8 +14,13 @@ import { getOrCreateDeviceId } from '../../utils/deviceId'
|
|||||||
import { updateApi } from '../../api/update'
|
import { updateApi } from '../../api/update'
|
||||||
import { splashApi } from '../../api/splash'
|
import { splashApi } from '../../api/splash'
|
||||||
|
|
||||||
|
const TrialExpiredDialog = defineAsyncComponent(() => import('./TrialExpiredDialog.vue'))
|
||||||
|
const refreshVipStatus = inject<() => Promise<boolean>>('refreshVipStatus')
|
||||||
|
const vipStatus = inject<any>('vipStatus')
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
modelValue: boolean
|
modelValue: boolean
|
||||||
|
isVip?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Emits {
|
interface Emits {
|
||||||
@@ -75,6 +80,16 @@ const uploadingSplashImage = ref(false)
|
|||||||
const deletingSplashImage = ref(false)
|
const deletingSplashImage = ref(false)
|
||||||
const fileInputRef = ref<HTMLInputElement | null>(null)
|
const fileInputRef = ref<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
|
// 品牌logo相关
|
||||||
|
const brandLogoUrl = ref('')
|
||||||
|
const uploadingBrandLogo = ref(false)
|
||||||
|
const deletingBrandLogo = ref(false)
|
||||||
|
const brandLogoInputRef = ref<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
|
// VIP过期弹窗
|
||||||
|
const showTrialExpiredDialog = ref(false)
|
||||||
|
const trialExpiredType = ref<'device' | 'account' | 'both' | 'subscribe'>('account')
|
||||||
|
|
||||||
const show = computed({
|
const show = computed({
|
||||||
get: () => props.modelValue,
|
get: () => props.modelValue,
|
||||||
set: (value) => emit('update:modelValue', value)
|
set: (value) => emit('update:modelValue', value)
|
||||||
@@ -86,6 +101,7 @@ watch(() => props.modelValue, (newVal) => {
|
|||||||
loadAllSettings()
|
loadAllSettings()
|
||||||
loadCurrentVersion()
|
loadCurrentVersion()
|
||||||
loadSplashImage()
|
loadSplashImage()
|
||||||
|
loadBrandLogo()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -381,7 +397,7 @@ async function loadSplashImage() {
|
|||||||
if (!username) return
|
if (!username) return
|
||||||
|
|
||||||
const res = await splashApi.getSplashImage(username)
|
const res = await splashApi.getSplashImage(username)
|
||||||
splashImageUrl.value = res?.data?.data?.url || res?.data?.url || ''
|
splashImageUrl.value = res.data.url
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
splashImageUrl.value = ''
|
splashImageUrl.value = ''
|
||||||
}
|
}
|
||||||
@@ -392,6 +408,15 @@ async function handleSplashImageUpload(event: Event) {
|
|||||||
const input = event.target as HTMLInputElement
|
const input = event.target as HTMLInputElement
|
||||||
if (!input.files || input.files.length === 0) return
|
if (!input.files || input.files.length === 0) return
|
||||||
|
|
||||||
|
// VIP验证
|
||||||
|
if (refreshVipStatus) await refreshVipStatus()
|
||||||
|
if (!props.isVip) {
|
||||||
|
if (vipStatus) trialExpiredType.value = vipStatus.value.expiredType
|
||||||
|
showTrialExpiredDialog.value = true
|
||||||
|
input.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const file = input.files[0]
|
const file = input.files[0]
|
||||||
const username = getUsernameFromToken()
|
const username = getUsernameFromToken()
|
||||||
if (!username) return ElMessage.warning('请先登录')
|
if (!username) return ElMessage.warning('请先登录')
|
||||||
@@ -401,12 +426,9 @@ async function handleSplashImageUpload(event: Event) {
|
|||||||
try {
|
try {
|
||||||
uploadingSplashImage.value = true
|
uploadingSplashImage.value = true
|
||||||
const res = await splashApi.uploadSplashImage(file, username)
|
const res = await splashApi.uploadSplashImage(file, username)
|
||||||
const url = res?.data?.data?.url || res?.data?.url
|
splashImageUrl.value = res.url
|
||||||
if (url) {
|
await (window as any).electronAPI.saveSplashConfig(username, res.url)
|
||||||
splashImageUrl.value = url
|
|
||||||
await (window as any).electronAPI.saveSplashConfig(username, url)
|
|
||||||
ElMessage.success('开屏图片设置成功,重启应用后生效')
|
ElMessage.success('开屏图片设置成功,重启应用后生效')
|
||||||
}
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
ElMessage.error(error?.message || '上传失败')
|
ElMessage.error(error?.message || '上传失败')
|
||||||
} finally {
|
} finally {
|
||||||
@@ -439,11 +461,87 @@ async function handleDeleteSplashImage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载品牌logo
|
||||||
|
async function loadBrandLogo() {
|
||||||
|
try {
|
||||||
|
const username = getUsernameFromToken()
|
||||||
|
if (!username) return
|
||||||
|
|
||||||
|
const res = await splashApi.getBrandLogo(username)
|
||||||
|
brandLogoUrl.value = res.data.url
|
||||||
|
} catch (error) {
|
||||||
|
brandLogoUrl.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传品牌logo
|
||||||
|
async function handleBrandLogoUpload(event: Event) {
|
||||||
|
const input = event.target as HTMLInputElement
|
||||||
|
if (!input.files || input.files.length === 0) return
|
||||||
|
|
||||||
|
// VIP验证
|
||||||
|
if (refreshVipStatus) await refreshVipStatus()
|
||||||
|
if (!props.isVip) {
|
||||||
|
if (vipStatus) trialExpiredType.value = vipStatus.value.expiredType
|
||||||
|
showTrialExpiredDialog.value = true
|
||||||
|
input.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = input.files[0]
|
||||||
|
const username = getUsernameFromToken()
|
||||||
|
if (!username) return ElMessage.warning('请先登录')
|
||||||
|
if (!file.type.startsWith('image/')) return ElMessage.error('只支持图片文件')
|
||||||
|
if (file.size > 5 * 1024 * 1024) return ElMessage.error('图片大小不能超过5MB')
|
||||||
|
|
||||||
|
try {
|
||||||
|
uploadingBrandLogo.value = true
|
||||||
|
const res = await splashApi.uploadBrandLogo(file, username)
|
||||||
|
brandLogoUrl.value = res.url
|
||||||
|
ElMessage.success('品牌logo设置成功')
|
||||||
|
window.dispatchEvent(new CustomEvent('brandLogoChanged', { detail: res.url }))
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error(error?.message || '上传失败')
|
||||||
|
} finally {
|
||||||
|
uploadingBrandLogo.value = false
|
||||||
|
input.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除品牌logo
|
||||||
|
async function handleDeleteBrandLogo() {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
'确定要删除品牌logo吗?删除后将隐藏logo区域。',
|
||||||
|
'确认删除',
|
||||||
|
{ confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
|
||||||
|
)
|
||||||
|
|
||||||
|
deletingBrandLogo.value = true
|
||||||
|
const username = getUsernameFromToken()
|
||||||
|
if (!username) return ElMessage.warning('请先登录')
|
||||||
|
|
||||||
|
await splashApi.deleteBrandLogo(username)
|
||||||
|
|
||||||
|
// 立即清空本地状态
|
||||||
|
brandLogoUrl.value = ''
|
||||||
|
ElMessage.success('品牌logo已删除')
|
||||||
|
|
||||||
|
// 立即触发App.vue清空logo
|
||||||
|
window.dispatchEvent(new CustomEvent('brandLogoChanged', { detail: '' }))
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error !== 'cancel') ElMessage.error(error?.message || '删除失败')
|
||||||
|
} finally {
|
||||||
|
deletingBrandLogo.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadAllSettings()
|
loadAllSettings()
|
||||||
loadLogDates()
|
loadLogDates()
|
||||||
loadCurrentVersion()
|
loadCurrentVersion()
|
||||||
loadSplashImage()
|
loadSplashImage()
|
||||||
|
loadBrandLogo()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -486,6 +584,12 @@ onMounted(() => {
|
|||||||
<span class="sidebar-icon">🖼️</span>
|
<span class="sidebar-icon">🖼️</span>
|
||||||
<span class="sidebar-text">开屏图片</span>
|
<span class="sidebar-text">开屏图片</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
:class="['sidebar-item', { active: activeTab === 'brand' }]"
|
||||||
|
@click="scrollToSection('brand')">
|
||||||
|
<span class="sidebar-icon">🏷️</span>
|
||||||
|
<span class="sidebar-text">品牌logo</span>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
:class="['sidebar-item', { active: activeTab === 'feedback' }]"
|
:class="['sidebar-item', { active: activeTab === 'feedback' }]"
|
||||||
@click="scrollToSection('feedback')">
|
@click="scrollToSection('feedback')">
|
||||||
@@ -624,48 +728,67 @@ onMounted(() => {
|
|||||||
|
|
||||||
<!-- 开屏图片设置 -->
|
<!-- 开屏图片设置 -->
|
||||||
<div id="section-splash" class="setting-section" @mouseenter="activeTab = 'splash'">
|
<div id="section-splash" class="setting-section" @mouseenter="activeTab = 'splash'">
|
||||||
|
<div class="section-title-row">
|
||||||
<div class="section-title">开屏图片</div>
|
<div class="section-title">开屏图片</div>
|
||||||
<div class="section-subtitle-text">自定义应用启动时的开屏图片</div>
|
<img src="/icon/vipExclusive.png" alt="VIP专享" class="vip-exclusive-logo" />
|
||||||
|
</div>
|
||||||
|
<div class="section-subtitle-text">支持.jpg .png格式,最佳显示尺寸/比例 1200*736</div>
|
||||||
|
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="splash-preview-container" v-if="splashImageUrl">
|
<div class="image-preview-wrapper" v-if="splashImageUrl">
|
||||||
<img :src="splashImageUrl" alt="开屏图片预览" class="splash-preview-image" />
|
<img :src="splashImageUrl" alt="开屏图片预览" class="preview-image" />
|
||||||
|
<div class="image-buttons">
|
||||||
|
<input ref="fileInputRef" type="file" accept="image/*" @change="handleSplashImageUpload" style="display: none;" />
|
||||||
|
<button class="img-btn" :disabled="uploadingSplashImage" @click="triggerFileSelect">
|
||||||
|
{{ uploadingSplashImage ? '上传中' : '更换' }}
|
||||||
|
</button>
|
||||||
|
<button class="img-btn" :disabled="deletingSplashImage" @click="handleDeleteSplashImage">
|
||||||
|
{{ deletingSplashImage ? '删除中' : '删除' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="splash-placeholder" v-else>
|
<div class="splash-placeholder" v-else>
|
||||||
<span class="placeholder-icon">🖼️</span>
|
<span class="placeholder-icon">🖼️</span>
|
||||||
<span class="placeholder-text">未设置自定义开屏图片</span>
|
<span class="placeholder-text">未设置自定义开屏图片</span>
|
||||||
|
<input ref="fileInputRef" type="file" accept="image/*" @change="handleSplashImageUpload" style="display: none;" />
|
||||||
|
<el-button type="primary" size="small" style="margin-top: 8px;" :loading="uploadingSplashImage" @click="triggerFileSelect">
|
||||||
|
{{ uploadingSplashImage ? '上传中' : '选择图片' }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="setting-item" style="margin-top: 10px;">
|
<!-- 品牌logo页面 -->
|
||||||
<div class="splash-actions">
|
<div id="section-brand" class="setting-section" @mouseenter="activeTab = 'brand'">
|
||||||
<input
|
<div class="section-title-row">
|
||||||
ref="fileInputRef"
|
<div class="section-title">品牌logo</div>
|
||||||
type="file"
|
<img src="/icon/vipExclusive.png" alt="VIP专享" class="vip-exclusive-logo" />
|
||||||
accept="image/*"
|
</div>
|
||||||
@change="handleSplashImageUpload"
|
<div class="section-subtitle-text"> 支持 JPG、PNG 格式,最佳显示尺寸/比例 1200*736</div>
|
||||||
style="display: none;"
|
|
||||||
/>
|
<div class="setting-item">
|
||||||
<el-button
|
<div class="image-preview-wrapper" v-if="brandLogoUrl">
|
||||||
type="primary"
|
<img :src="brandLogoUrl" alt="品牌logo" class="preview-image" />
|
||||||
size="small"
|
<div class="image-buttons">
|
||||||
:loading="uploadingSplashImage"
|
<input ref="brandLogoInputRef" type="file" accept="image/*" @change="handleBrandLogoUpload" style="display: none;" />
|
||||||
:disabled="uploadingSplashImage"
|
<button class="img-btn" :disabled="uploadingBrandLogo" @click="brandLogoInputRef?.click()">
|
||||||
@click="triggerFileSelect">
|
{{ uploadingBrandLogo ? '上传中' : '更换' }}
|
||||||
{{ uploadingSplashImage ? '上传中...' : '选择图片' }}
|
</button>
|
||||||
</el-button>
|
<button class="img-btn" :disabled="deletingBrandLogo" @click="handleDeleteBrandLogo">
|
||||||
<el-button
|
{{ deletingBrandLogo ? '删除中' : '删除' }}
|
||||||
v-if="splashImageUrl"
|
</button>
|
||||||
size="small"
|
</div>
|
||||||
:loading="deletingSplashImage"
|
</div>
|
||||||
:disabled="deletingSplashImage"
|
<div class="splash-placeholder" v-else>
|
||||||
@click="handleDeleteSplashImage">
|
<span class="placeholder-icon">🏷️</span>
|
||||||
{{ deletingSplashImage ? '删除中...' : '删除' }}
|
<span class="placeholder-text">未设置品牌logo</span>
|
||||||
|
<input ref="brandLogoInputRef" type="file" accept="image/*" @change="handleBrandLogoUpload" style="display: none;" />
|
||||||
|
<el-button type="primary" size="small" style="margin-top: 8px;" :loading="uploadingBrandLogo" @click="brandLogoInputRef?.click()">
|
||||||
|
{{ uploadingBrandLogo ? '上传中' : '选择图片' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-desc" style="margin-top: 6px;">
|
|
||||||
支持 JPG、PNG 格式,大小不超过 5MB,建议尺寸 1200x675
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -737,6 +860,9 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
|
<!-- VIP过期提示弹窗 -->
|
||||||
|
<TrialExpiredDialog v-model="showTrialExpiredDialog" :expired-type="trialExpiredType" />
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -940,9 +1066,21 @@ onMounted(() => {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #1F2937;
|
color: #1F2937;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vip-exclusive-logo {
|
||||||
|
width: 60px;
|
||||||
|
height: auto;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
.section-header .section-title {
|
.section-header .section-title {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
@@ -1100,6 +1238,53 @@ onMounted(() => {
|
|||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 图片预览容器 */
|
||||||
|
.image-preview-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-buttons {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-preview-wrapper:hover .image-buttons {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-btn {
|
||||||
|
padding: 6px 14px;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-btn:hover:not(:disabled) {
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.feedback-form :deep(.el-textarea__inner) {
|
.feedback-form :deep(.el-textarea__inner) {
|
||||||
border-color: #E5E6EB;
|
border-color: #E5E6EB;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -1312,22 +1497,7 @@ onMounted(() => {
|
|||||||
color: #1F2937;
|
color: #1F2937;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 开屏图片样式 */
|
/* 占位符 */
|
||||||
.splash-preview-container {
|
|
||||||
width: 100%;
|
|
||||||
height: 180px;
|
|
||||||
border-radius: 6px;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid #E5E6EB;
|
|
||||||
background: #F8F9FA;
|
|
||||||
}
|
|
||||||
|
|
||||||
.splash-preview-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.splash-placeholder {
|
.splash-placeholder {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 180px;
|
height: 180px;
|
||||||
@@ -1350,12 +1520,6 @@ onMounted(() => {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #86909C;
|
color: #86909C;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splash-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ public class ClientAccountController extends BaseController {
|
|||||||
private Auth auth;
|
private Auth auth;
|
||||||
|
|
||||||
private static final String SPLASH_IMAGE_CACHE_KEY = "splash_image:";
|
private static final String SPLASH_IMAGE_CACHE_KEY = "splash_image:";
|
||||||
|
private static final String BRAND_LOGO_CACHE_KEY = "brand_logo:";
|
||||||
|
|
||||||
private AjaxResult checkDeviceLimit(Long accountId, String deviceId, int deviceLimit) {
|
private AjaxResult checkDeviceLimit(Long accountId, String deviceId, int deviceLimit) {
|
||||||
int activeDeviceCount = accountDeviceMapper.countActiveDevicesByAccountId(accountId);
|
int activeDeviceCount = accountDeviceMapper.countActiveDevicesByAccountId(accountId);
|
||||||
@@ -181,6 +182,11 @@ public class ClientAccountController extends BaseController {
|
|||||||
redisCache.setCacheObject(SPLASH_IMAGE_CACHE_KEY + username, account.getSplashImage());
|
redisCache.setCacheObject(SPLASH_IMAGE_CACHE_KEY + username, account.getSplashImage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新品牌logo缓存到 Redis
|
||||||
|
if (StringUtils.isNotEmpty(account.getBrandLogo())) {
|
||||||
|
redisCache.setCacheObject(BRAND_LOGO_CACHE_KEY + username, account.getBrandLogo());
|
||||||
|
}
|
||||||
|
|
||||||
String token = Jwts.builder()
|
String token = Jwts.builder()
|
||||||
.setHeaderParam("kid", jwtRsaKeyService.getKeyId())
|
.setHeaderParam("kid", jwtRsaKeyService.getKeyId())
|
||||||
.setSubject(username)
|
.setSubject(username)
|
||||||
@@ -413,4 +419,66 @@ public class ClientAccountController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传品牌logo
|
||||||
|
*/
|
||||||
|
@PostMapping("/brand-logo/upload")
|
||||||
|
public AjaxResult uploadBrandLogo(@RequestParam("file") MultipartFile file, @RequestParam("username") String username) {
|
||||||
|
try {
|
||||||
|
ClientAccount account = clientAccountService.selectClientAccountByUsername(username);
|
||||||
|
if (account == null) return AjaxResult.error("账号不存在");
|
||||||
|
if (!file.getContentType().startsWith("image/")) return AjaxResult.error("只支持图片文件");
|
||||||
|
if (file.getSize() > 5 * 1024 * 1024) return AjaxResult.error("图片大小不能超过5MB");
|
||||||
|
|
||||||
|
String fileName = "brand-logo/" + DateUtil.format(new Date(), "yyyy/MM/") + IdUtil.simpleUUID() + "." + FileUtil.extName(file.getOriginalFilename());
|
||||||
|
try (InputStream is = file.getInputStream()) {
|
||||||
|
Response res = uploadManager.put(is, fileName, auth.uploadToken(qiniu.getBucket()), null, "");
|
||||||
|
if (!res.isOK()) return AjaxResult.error("上传失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = qiniu.getResourcesUrl() + fileName;
|
||||||
|
account.setBrandLogo(url);
|
||||||
|
clientAccountService.updateClientAccount(account);
|
||||||
|
redisCache.setCacheObject(BRAND_LOGO_CACHE_KEY + username, url);
|
||||||
|
return AjaxResult.success().put("url", url).put("fileName", fileName);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return AjaxResult.error("上传失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取品牌logo
|
||||||
|
*/
|
||||||
|
@GetMapping("/brand-logo")
|
||||||
|
public AjaxResult getBrandLogo(@RequestParam("username") String username) {
|
||||||
|
String url = redisCache.getCacheObject(BRAND_LOGO_CACHE_KEY + username);
|
||||||
|
if (StringUtils.isEmpty(url)) {
|
||||||
|
ClientAccount account = clientAccountService.selectClientAccountByUsername(username);
|
||||||
|
if (account != null && StringUtils.isNotEmpty(account.getBrandLogo())) {
|
||||||
|
url = account.getBrandLogo();
|
||||||
|
redisCache.setCacheObject(BRAND_LOGO_CACHE_KEY + username, url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AjaxResult.success(Map.of("url", url != null ? url : ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除品牌logo
|
||||||
|
*/
|
||||||
|
@PostMapping("/brand-logo/delete")
|
||||||
|
public AjaxResult deleteBrandLogo(@RequestParam("username") String username) {
|
||||||
|
try {
|
||||||
|
ClientAccount account = clientAccountService.selectClientAccountByUsername(username);
|
||||||
|
if (account == null) return AjaxResult.error("账号不存在");
|
||||||
|
|
||||||
|
account.setBrandLogo(null);
|
||||||
|
clientAccountService.updateClientAccount(account);
|
||||||
|
redisCache.deleteObject(BRAND_LOGO_CACHE_KEY + username);
|
||||||
|
return AjaxResult.success("品牌logo已删除");
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return AjaxResult.error("删除失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -58,6 +58,9 @@ public class ClientAccount extends BaseEntity
|
|||||||
/** 开屏图片URL */
|
/** 开屏图片URL */
|
||||||
private String splashImage;
|
private String splashImage;
|
||||||
|
|
||||||
|
/** 品牌logo URL */
|
||||||
|
private String brandLogo;
|
||||||
|
|
||||||
public void setId(Long id)
|
public void setId(Long id)
|
||||||
{
|
{
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@@ -174,4 +177,14 @@ public class ClientAccount extends BaseEntity
|
|||||||
{
|
{
|
||||||
return splashImage;
|
return splashImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setBrandLogo(String brandLogo)
|
||||||
|
{
|
||||||
|
this.brandLogo = brandLogo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBrandLogo()
|
||||||
|
{
|
||||||
|
return brandLogo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="deviceLimit" column="device_limit" />
|
<result property="deviceLimit" column="device_limit" />
|
||||||
<result property="accountType" column="account_type" />
|
<result property="accountType" column="account_type" />
|
||||||
<result property="splashImage" column="splash_image" />
|
<result property="splashImage" column="splash_image" />
|
||||||
|
<result property="brandLogo" column="brand_logo" />
|
||||||
<result property="createBy" column="create_by" />
|
<result property="createBy" column="create_by" />
|
||||||
<result property="createTime" column="create_time" />
|
<result property="createTime" column="create_time" />
|
||||||
<result property="updateBy" column="update_by" />
|
<result property="updateBy" column="update_by" />
|
||||||
@@ -25,7 +26,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
|
|
||||||
<sql id="selectClientAccountVo">
|
<sql id="selectClientAccountVo">
|
||||||
select id, account_name, username, password, status, expire_time,
|
select id, account_name, username, password, status, expire_time,
|
||||||
allowed_ip_range, remark, permissions, device_limit, account_type, splash_image, create_by, create_time, update_by, update_time
|
allowed_ip_range, remark, permissions, device_limit, account_type, splash_image, brand_logo, create_by, create_time, update_by, update_time
|
||||||
from client_account
|
from client_account
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
@@ -63,6 +64,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="deviceLimit != null">device_limit,</if>
|
<if test="deviceLimit != null">device_limit,</if>
|
||||||
<if test="accountType != null">account_type,</if>
|
<if test="accountType != null">account_type,</if>
|
||||||
<if test="splashImage != null">splash_image,</if>
|
<if test="splashImage != null">splash_image,</if>
|
||||||
|
<if test="brandLogo != null">brand_logo,</if>
|
||||||
<if test="createBy != null">create_by,</if>
|
<if test="createBy != null">create_by,</if>
|
||||||
create_time
|
create_time
|
||||||
</trim>
|
</trim>
|
||||||
@@ -78,6 +80,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="deviceLimit != null">#{deviceLimit},</if>
|
<if test="deviceLimit != null">#{deviceLimit},</if>
|
||||||
<if test="accountType != null">#{accountType},</if>
|
<if test="accountType != null">#{accountType},</if>
|
||||||
<if test="splashImage != null">#{splashImage},</if>
|
<if test="splashImage != null">#{splashImage},</if>
|
||||||
|
<if test="brandLogo != null">#{brandLogo},</if>
|
||||||
<if test="createBy != null">#{createBy},</if>
|
<if test="createBy != null">#{createBy},</if>
|
||||||
sysdate()
|
sysdate()
|
||||||
</trim>
|
</trim>
|
||||||
@@ -97,6 +100,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="deviceLimit != null">device_limit = #{deviceLimit},</if>
|
<if test="deviceLimit != null">device_limit = #{deviceLimit},</if>
|
||||||
<if test="accountType != null">account_type = #{accountType},</if>
|
<if test="accountType != null">account_type = #{accountType},</if>
|
||||||
<if test="splashImage != null">splash_image = #{splashImage},</if>
|
<if test="splashImage != null">splash_image = #{splashImage},</if>
|
||||||
|
<if test="brandLogo != null">brand_logo = #{brandLogo},</if>
|
||||||
<if test="updateBy != null">update_by = #{updateBy},</if>
|
<if test="updateBy != null">update_by = #{updateBy},</if>
|
||||||
update_time = sysdate()
|
update_time = sysdate()
|
||||||
</trim>
|
</trim>
|
||||||
|
|||||||
Reference in New Issue
Block a user