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 {authApi} from './api/auth'
|
||||
import {deviceApi, type DeviceItem, type DeviceQuota} from './api/device'
|
||||
import {splashApi} from './api/splash'
|
||||
import {getOrCreateDeviceId} from './utils/deviceId'
|
||||
import {getToken, setToken, removeToken, getUsernameFromToken, getClientIdFromToken} from './utils/token'
|
||||
import {CONFIG} from './api/http'
|
||||
@@ -61,12 +62,12 @@ const vipExpireTime = ref<Date | null>(null)
|
||||
const deviceTrialExpired = ref(false)
|
||||
const accountType = ref<string>('trial')
|
||||
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 expire = new Date(vipExpireTime.value)
|
||||
const msLeft = expire.getTime() - now.getTime()
|
||||
|
||||
|
||||
// 精确判断:当前时间 >= 过期时间,则已过期(与后端逻辑一致)
|
||||
if (msLeft <= 0) {
|
||||
const accountExpired = true
|
||||
@@ -75,22 +76,22 @@ const vipStatus = computed(() => {
|
||||
if (deviceExpired && accountExpired) expiredType = 'both'
|
||||
else if (accountExpired) expiredType = 'account'
|
||||
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 daysLeft = Math.floor(msLeft / (1000 * 60 * 60 * 24))
|
||||
|
||||
|
||||
let expiredType: 'device' | 'account' | 'both' | 'subscribe' = 'subscribe'
|
||||
if (accountType.value === 'trial' && deviceTrialExpired.value) {
|
||||
expiredType = 'device' // 试用账号且设备过期
|
||||
}
|
||||
|
||||
if (daysLeft === 0) 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 }
|
||||
return { isVip: true, daysLeft, hoursLeft, status: 'active', 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 <= 30) return {isVip: true, daysLeft, hoursLeft, status: 'normal', expiredType}
|
||||
return {isVip: true, daysLeft, hoursLeft, status: 'active', expiredType}
|
||||
})
|
||||
|
||||
// 功能可用性(账号VIP + 设备试用期)
|
||||
@@ -118,6 +119,9 @@ const showAccountManager = ref(false)
|
||||
// 当前版本
|
||||
const currentVersion = ref('')
|
||||
|
||||
// 品牌logo
|
||||
const brandLogoUrl = ref('')
|
||||
|
||||
// 菜单配置 - 复刻ERP客户端格式
|
||||
const menuConfig = [
|
||||
{key: 'rakuten', name: 'Rakuten', index: 'rakuten', icon: 'R', iconImage: rakutenIcon},
|
||||
@@ -198,19 +202,25 @@ function handleMenuSelect(key: string) {
|
||||
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 {
|
||||
setToken(data.token)
|
||||
isAuthenticated.value = true
|
||||
showAuthDialog.value = false
|
||||
showRegDialog.value = false
|
||||
|
||||
|
||||
currentUsername.value = getUsernameFromToken(data.token)
|
||||
userPermissions.value = data.permissions || ''
|
||||
vipExpireTime.value = data.expireTime ? new Date(data.expireTime) : null
|
||||
accountType.value = data.accountType || 'trial'
|
||||
deviceTrialExpired.value = data.deviceTrialExpired || false
|
||||
|
||||
|
||||
const deviceId = await getOrCreateDeviceId()
|
||||
await deviceApi.register({
|
||||
username: currentUsername.value,
|
||||
@@ -218,7 +228,7 @@ async function handleLoginSuccess(data: { token: string; permissions?: string; e
|
||||
os: navigator.platform
|
||||
})
|
||||
SSEManager.connect()
|
||||
|
||||
|
||||
// 同步当前账号的设置到 Electron 主进程
|
||||
syncSettingsToElectron()
|
||||
|
||||
@@ -231,7 +241,7 @@ async function handleLoginSuccess(data: { token: string; permissions?: string; e
|
||||
isAuthenticated.value = false
|
||||
showAuthDialog.value = true
|
||||
removeToken()
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,7 +261,7 @@ function clearLocalAuth() {
|
||||
async function logout() {
|
||||
try {
|
||||
const deviceId = getClientIdFromToken()
|
||||
if (deviceId) await deviceApi.offline({ deviceId, username: currentUsername.value })
|
||||
if (deviceId) await deviceApi.offline({deviceId, username: currentUsername.value})
|
||||
} catch (error) {
|
||||
console.warn('离线通知失败:', error)
|
||||
}
|
||||
@@ -270,7 +280,8 @@ async function handleUserClick() {
|
||||
cancelButtonText: '取消'
|
||||
})
|
||||
await logout()
|
||||
} catch {}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
function showRegisterDialog() {
|
||||
@@ -280,7 +291,7 @@ function showRegisterDialog() {
|
||||
|
||||
function backToLogin() {
|
||||
showRegDialog.value = false
|
||||
|
||||
|
||||
showAuthDialog.value = true
|
||||
}
|
||||
|
||||
@@ -300,13 +311,13 @@ async function checkAuth() {
|
||||
userPermissions.value = res.data.permissions || ''
|
||||
deviceTrialExpired.value = res.data.deviceTrialExpired || false
|
||||
accountType.value = res.data.accountType || 'trial'
|
||||
|
||||
|
||||
if (res.data.expireTime) {
|
||||
vipExpireTime.value = new Date(res.data.expireTime)
|
||||
}
|
||||
|
||||
|
||||
SSEManager.connect()
|
||||
|
||||
|
||||
// 同步当前账号的设置到 Electron 主进程
|
||||
syncSettingsToElectron()
|
||||
} catch {
|
||||
@@ -322,7 +333,7 @@ async function refreshVipStatus() {
|
||||
try {
|
||||
const token = getToken()
|
||||
if (!token) return false
|
||||
|
||||
|
||||
const res = await authApi.verifyToken(token)
|
||||
deviceTrialExpired.value = res.data.deviceTrialExpired || false
|
||||
accountType.value = res.data.accountType || 'trial'
|
||||
@@ -346,10 +357,10 @@ async function syncSettingsToElectron() {
|
||||
try {
|
||||
const username = getUsernameFromToken()
|
||||
const settings = getSettings(username)
|
||||
|
||||
|
||||
// 同步关闭行为
|
||||
await (window as any).electronAPI.setCloseAction(settings.closeAction || 'quit')
|
||||
|
||||
|
||||
// 同步启动配置
|
||||
await (window as any).electronAPI.setLaunchConfig({
|
||||
autoLaunch: settings.autoLaunch || false,
|
||||
@@ -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('vipStatus', vipStatus)
|
||||
@@ -518,24 +542,32 @@ async function confirmRemoveDevice(row: DeviceItem) {
|
||||
onMounted(async () => {
|
||||
showContent()
|
||||
await checkAuth()
|
||||
|
||||
|
||||
// 检查是否有待安装的更新
|
||||
await checkPendingUpdate()
|
||||
|
||||
|
||||
// 加载当前版本
|
||||
try {
|
||||
currentVersion.value = await (window as any).electronAPI.getJarVersion()
|
||||
} catch (error) {
|
||||
console.warn('获取当前版本失败:', error)
|
||||
}
|
||||
|
||||
|
||||
// 加载品牌logo
|
||||
loadBrandLogo()
|
||||
|
||||
// 监听品牌logo变化
|
||||
window.addEventListener('brandLogoChanged', (e: any) => {
|
||||
brandLogoUrl.value = e.detail
|
||||
})
|
||||
|
||||
// 全局阻止文件拖拽到窗口(避免意外打开文件)
|
||||
// 只在指定的 dropzone 区域处理拖拽上传
|
||||
document.addEventListener('dragover', (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}, false)
|
||||
|
||||
|
||||
document.addEventListener('drop', (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
@@ -584,6 +616,12 @@ onUnmounted(() => {
|
||||
<div class="user-avatar">
|
||||
<img src="/icon/icon.png" alt="logo"/>
|
||||
</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>
|
||||
<ul class="menu">
|
||||
<li
|
||||
@@ -595,7 +633,7 @@ onUnmounted(() => {
|
||||
>
|
||||
<span class="menu-text">
|
||||
<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>
|
||||
</span>
|
||||
{{ item.name }}
|
||||
@@ -604,7 +642,8 @@ onUnmounted(() => {
|
||||
</ul>
|
||||
|
||||
<!-- 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-status-text">
|
||||
<template v-if="vipStatus.isVip">
|
||||
@@ -616,7 +655,13 @@ onUnmounted(() => {
|
||||
</template>
|
||||
</div>
|
||||
<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>
|
||||
@@ -672,16 +717,20 @@ onUnmounted(() => {
|
||||
@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
|
||||
@@ -693,7 +742,9 @@ onUnmounted(() => {
|
||||
<template #header>
|
||||
<div class="device-dialog-header">
|
||||
<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>
|
||||
</template>
|
||||
@@ -829,6 +880,24 @@ onUnmounted(() => {
|
||||
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 {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
@@ -952,6 +1021,7 @@ onUnmounted(() => {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.device-dialog-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -959,31 +1029,45 @@ onUnmounted(() => {
|
||||
padding: 12px 0 4px 0;
|
||||
margin-left: 40px;
|
||||
}
|
||||
|
||||
.device-dialog :deep(.el-dialog__header) {
|
||||
text-align: center;
|
||||
}
|
||||
.device-dialog :deep(.el-dialog__body) { padding-top: 0; }
|
||||
|
||||
.device-dialog :deep(.el-dialog__body) {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.device-illustration {
|
||||
width: 180px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.device-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
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 {
|
||||
position: fixed;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
background: rgba(255,255,255,0.9);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
|
||||
@@ -27,6 +27,24 @@ export const splashApi = {
|
||||
// 删除自定义开屏图片(恢复默认)
|
||||
async deleteSplashImage(username: string) {
|
||||
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}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,16 +35,17 @@ async function handleAuth() {
|
||||
try {
|
||||
// 获取或生成设备ID
|
||||
const deviceId = await getOrCreateDeviceId()
|
||||
|
||||
|
||||
// 登录
|
||||
const loginRes: any = await authApi.login({
|
||||
...authForm.value,
|
||||
clientId: deviceId
|
||||
})
|
||||
|
||||
// 保存开屏图片配置(不阻塞登录)
|
||||
// 保存开屏图片配置和品牌logo(不阻塞登录)
|
||||
saveSplashConfigInBackground(authForm.value.username)
|
||||
|
||||
saveBrandLogoInBackground(authForm.value.username)
|
||||
|
||||
emit('loginSuccess', {
|
||||
token: loginRes.data.accessToken || loginRes.data.token,
|
||||
permissions: loginRes.data.permissions,
|
||||
@@ -90,6 +91,18 @@ async function saveSplashConfigInBackground(username: string) {
|
||||
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>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<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 {
|
||||
getSettings,
|
||||
saveSettings,
|
||||
import {
|
||||
getSettings,
|
||||
saveSettings,
|
||||
savePlatformSettings,
|
||||
type Platform,
|
||||
type PlatformExportSettings
|
||||
type Platform,
|
||||
type PlatformExportSettings
|
||||
} from '../../utils/settings'
|
||||
import { feedbackApi } from '../../api/feedback'
|
||||
import { getToken, getUsernameFromToken } from '../../utils/token'
|
||||
@@ -14,8 +14,13 @@ import { getOrCreateDeviceId } from '../../utils/deviceId'
|
||||
import { updateApi } from '../../api/update'
|
||||
import { splashApi } from '../../api/splash'
|
||||
|
||||
const TrialExpiredDialog = defineAsyncComponent(() => import('./TrialExpiredDialog.vue'))
|
||||
const refreshVipStatus = inject<() => Promise<boolean>>('refreshVipStatus')
|
||||
const vipStatus = inject<any>('vipStatus')
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
isVip?: boolean
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
@@ -75,6 +80,16 @@ const uploadingSplashImage = ref(false)
|
||||
const deletingSplashImage = ref(false)
|
||||
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({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
@@ -86,6 +101,7 @@ watch(() => props.modelValue, (newVal) => {
|
||||
loadAllSettings()
|
||||
loadCurrentVersion()
|
||||
loadSplashImage()
|
||||
loadBrandLogo()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -379,9 +395,9 @@ async function loadSplashImage() {
|
||||
try {
|
||||
const username = getUsernameFromToken()
|
||||
if (!username) return
|
||||
|
||||
|
||||
const res = await splashApi.getSplashImage(username)
|
||||
splashImageUrl.value = res?.data?.data?.url || res?.data?.url || ''
|
||||
splashImageUrl.value = res.data.url
|
||||
} catch (error) {
|
||||
splashImageUrl.value = ''
|
||||
}
|
||||
@@ -391,22 +407,28 @@ async function loadSplashImage() {
|
||||
async function handleSplashImageUpload(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 {
|
||||
uploadingSplashImage.value = true
|
||||
const res = await splashApi.uploadSplashImage(file, username)
|
||||
const url = res?.data?.data?.url || res?.data?.url
|
||||
if (url) {
|
||||
splashImageUrl.value = url
|
||||
await (window as any).electronAPI.saveSplashConfig(username, url)
|
||||
ElMessage.success('开屏图片设置成功,重启应用后生效')
|
||||
}
|
||||
splashImageUrl.value = res.url
|
||||
await (window as any).electronAPI.saveSplashConfig(username, res.url)
|
||||
ElMessage.success('开屏图片设置成功,重启应用后生效')
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.message || '上传失败')
|
||||
} finally {
|
||||
@@ -423,11 +445,11 @@ async function handleDeleteSplashImage() {
|
||||
'确认删除',
|
||||
{ confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
|
||||
)
|
||||
|
||||
|
||||
deletingSplashImage.value = true
|
||||
const username = getUsernameFromToken()
|
||||
if (!username) return ElMessage.warning('请先登录')
|
||||
|
||||
|
||||
await splashApi.deleteSplashImage(username)
|
||||
await (window as any).electronAPI.saveSplashConfig(username, '')
|
||||
splashImageUrl.value = ''
|
||||
@@ -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(() => {
|
||||
loadAllSettings()
|
||||
loadLogDates()
|
||||
loadCurrentVersion()
|
||||
loadSplashImage()
|
||||
loadBrandLogo()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -480,12 +578,18 @@ onMounted(() => {
|
||||
<span class="sidebar-icon">🚀</span>
|
||||
<span class="sidebar-text">启动</span>
|
||||
</div>
|
||||
<div
|
||||
<div
|
||||
:class="['sidebar-item', { active: activeTab === 'splash' }]"
|
||||
@click="scrollToSection('splash')">
|
||||
<span class="sidebar-icon">🖼️</span>
|
||||
<span class="sidebar-text">开屏图片</span>
|
||||
</div>
|
||||
<div
|
||||
:class="['sidebar-item', { active: activeTab === 'brand' }]"
|
||||
@click="scrollToSection('brand')">
|
||||
<span class="sidebar-icon">🏷️</span>
|
||||
<span class="sidebar-text">品牌logo</span>
|
||||
</div>
|
||||
<div
|
||||
:class="['sidebar-item', { active: activeTab === 'feedback' }]"
|
||||
@click="scrollToSection('feedback')">
|
||||
@@ -624,48 +728,67 @@ onMounted(() => {
|
||||
|
||||
<!-- 开屏图片设置 -->
|
||||
<div id="section-splash" class="setting-section" @mouseenter="activeTab = 'splash'">
|
||||
<div class="section-title">开屏图片</div>
|
||||
<div class="section-subtitle-text">自定义应用启动时的开屏图片</div>
|
||||
|
||||
<div class="section-title-row">
|
||||
<div class="section-title">开屏图片</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="splash-preview-container" v-if="splashImageUrl">
|
||||
<img :src="splashImageUrl" alt="开屏图片预览" class="splash-preview-image" />
|
||||
<div class="image-preview-wrapper" v-if="splashImageUrl">
|
||||
<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 class="splash-placeholder" v-else>
|
||||
<span class="placeholder-icon">🖼️</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 class="setting-item" style="margin-top: 10px;">
|
||||
<div class="splash-actions">
|
||||
<input
|
||||
ref="fileInputRef"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
@change="handleSplashImageUpload"
|
||||
style="display: none;"
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="uploadingSplashImage"
|
||||
:disabled="uploadingSplashImage"
|
||||
@click="triggerFileSelect">
|
||||
{{ uploadingSplashImage ? '上传中...' : '选择图片' }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="splashImageUrl"
|
||||
size="small"
|
||||
:loading="deletingSplashImage"
|
||||
:disabled="deletingSplashImage"
|
||||
@click="handleDeleteSplashImage">
|
||||
{{ deletingSplashImage ? '删除中...' : '删除' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 品牌logo页面 -->
|
||||
<div id="section-brand" class="setting-section" @mouseenter="activeTab = 'brand'">
|
||||
<div class="section-title-row">
|
||||
<div class="section-title">品牌logo</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="image-preview-wrapper" v-if="brandLogoUrl">
|
||||
<img :src="brandLogoUrl" alt="品牌logo" class="preview-image" />
|
||||
<div class="image-buttons">
|
||||
<input ref="brandLogoInputRef" type="file" accept="image/*" @change="handleBrandLogoUpload" style="display: none;" />
|
||||
<button class="img-btn" :disabled="uploadingBrandLogo" @click="brandLogoInputRef?.click()">
|
||||
{{ uploadingBrandLogo ? '上传中' : '更换' }}
|
||||
</button>
|
||||
<button class="img-btn" :disabled="deletingBrandLogo" @click="handleDeleteBrandLogo">
|
||||
{{ deletingBrandLogo ? '删除中' : '删除' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="splash-placeholder" v-else>
|
||||
<span class="placeholder-icon">🏷️</span>
|
||||
<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>
|
||||
</div>
|
||||
<div class="setting-desc" style="margin-top: 6px;">
|
||||
支持 JPG、PNG 格式,大小不超过 5MB,建议尺寸 1200x675
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -737,6 +860,9 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- VIP过期提示弹窗 -->
|
||||
<TrialExpiredDialog v-model="showTrialExpiredDialog" :expired-type="trialExpiredType" />
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
@@ -940,9 +1066,21 @@ onMounted(() => {
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.section-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.vip-exclusive-logo {
|
||||
width: 60px;
|
||||
height: auto;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.section-header .section-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -1100,6 +1238,53 @@ onMounted(() => {
|
||||
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) {
|
||||
border-color: #E5E6EB;
|
||||
border-radius: 6px;
|
||||
@@ -1312,22 +1497,7 @@ onMounted(() => {
|
||||
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 {
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
@@ -1350,12 +1520,6 @@ onMounted(() => {
|
||||
font-size: 13px;
|
||||
color: #86909C;
|
||||
}
|
||||
|
||||
.splash-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
@@ -76,6 +76,7 @@ public class ClientAccountController extends BaseController {
|
||||
private Auth auth;
|
||||
|
||||
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) {
|
||||
int activeDeviceCount = accountDeviceMapper.countActiveDevicesByAccountId(accountId);
|
||||
@@ -180,6 +181,11 @@ public class ClientAccountController extends BaseController {
|
||||
if (StringUtils.isNotEmpty(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()
|
||||
.setHeaderParam("kid", jwtRsaKeyService.getKeyId())
|
||||
@@ -402,7 +408,7 @@ public class ClientAccountController extends BaseController {
|
||||
try {
|
||||
ClientAccount account = clientAccountService.selectClientAccountByUsername(username);
|
||||
if (account == null) return AjaxResult.error("账号不存在");
|
||||
|
||||
|
||||
account.setSplashImage(null);
|
||||
clientAccountService.updateClientAccount(account);
|
||||
redisCache.deleteObject(SPLASH_IMAGE_CACHE_KEY + 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 */
|
||||
private String splashImage;
|
||||
|
||||
/** 品牌logo URL */
|
||||
private String brandLogo;
|
||||
|
||||
public void setId(Long id)
|
||||
{
|
||||
this.id = id;
|
||||
@@ -174,4 +177,14 @@ public class ClientAccount extends BaseEntity
|
||||
{
|
||||
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="accountType" column="account_type" />
|
||||
<result property="splashImage" column="splash_image" />
|
||||
<result property="brandLogo" column="brand_logo" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
@@ -25,7 +26,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
|
||||
<sql id="selectClientAccountVo">
|
||||
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
|
||||
</sql>
|
||||
|
||||
@@ -63,6 +64,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="deviceLimit != null">device_limit,</if>
|
||||
<if test="accountType != null">account_type,</if>
|
||||
<if test="splashImage != null">splash_image,</if>
|
||||
<if test="brandLogo != null">brand_logo,</if>
|
||||
<if test="createBy != null">create_by,</if>
|
||||
create_time
|
||||
</trim>
|
||||
@@ -78,6 +80,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="deviceLimit != null">#{deviceLimit},</if>
|
||||
<if test="accountType != null">#{accountType},</if>
|
||||
<if test="splashImage != null">#{splashImage},</if>
|
||||
<if test="brandLogo != null">#{brandLogo},</if>
|
||||
<if test="createBy != null">#{createBy},</if>
|
||||
sysdate()
|
||||
</trim>
|
||||
@@ -97,6 +100,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="deviceLimit != null">device_limit = #{deviceLimit},</if>
|
||||
<if test="accountType != null">account_type = #{accountType},</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>
|
||||
update_time = sysdate()
|
||||
</trim>
|
||||
|
||||
Reference in New Issue
Block a user