feat(splash): 添加全局开屏图片功能并优化客户端启动流程

- 新增全局开屏图片上传、获取、删除接口
- 实现客户端全局开屏图片优先加载机制
- 集成七牛云存储配置从 pxdj 切换到 bydj
- 优化 splash 窗口显示逻辑确保至少显示 2 秒
- 添加全局开屏图片管理界面组件
- 更新应用配置和构建设置
- 修复多处图片缓存和加载问题
- 调整服务端 API 地址配置
- 修改微信客服联系方式
This commit is contained in:
2026-01-19 17:33:43 +08:00
parent 02858146b3
commit 358203b11d
19 changed files with 12157 additions and 175 deletions

View File

@@ -1,77 +0,0 @@
<div align="center">
# Electron Vue Template
<img width="794" alt="image" src="https://user-images.githubusercontent.com/32544586/222748627-ee10c9a6-70d2-4e21-b23f-001dd8ec7238.png">
A simple starter template for a **Vue3** + **Electron** TypeScript based application, including **ViteJS** and **Electron Builder**.
</div>
## About
This template utilizes [ViteJS](https://vitejs.dev) for building and serving your (Vue powered) front-end process, it provides Hot Reloads (HMR) to make development fast and easy ⚡
Building the Electron (main) process is done with [Electron Builder](https://www.electron.build/), which makes your application easily distributable and supports cross-platform compilation 😎
## Getting started
Click the green **Use this template** button on top of the repository, and clone your own newly created repository.
**Or..**
Clone this repository: `git clone git@github.com:Deluze/electron-vue-template.git`
### Install dependencies ⏬
```bash
npm install
```
### Start developing ⚒️
```bash
npm run dev
```
## Additional Commands
```bash
npm run dev # starts application with hot reload
npm run build # builds application, distributable files can be found in "dist" folder
# OR
npm run build:win # uses windows as build target
npm run build:mac # uses mac as build target
npm run build:linux # uses linux as build target
```
Optional configuration options can be found in the [Electron Builder CLI docs](https://www.electron.build/cli.html).
## Project Structure
```bash
- scripts/ # all the scripts used to build or serve your application, change as you like.
- src/
- main/ # Main thread (Electron application source)
- renderer/ # Renderer thread (VueJS application source)
```
## Using static files
If you have any files that you want to copy over to the app directory after installation, you will need to add those files in your `src/main/static` directory.
Files in said directory are only accessible to the `main` process, similar to `src/renderer/assets` only being accessible to the `renderer` process. Besides that, the concept is the same as to what you're used to in your other front-end projects.
#### Referencing static files from your main process
```ts
/* Assumes src/main/static/myFile.txt exists */
import {app} from 'electron';
import {join} from 'path';
import {readFileSync} from 'fs';
const path = join(app.getAppPath(), 'static', 'myFile.txt');
const buffer = readFileSync(path);
```

7281
electron-vue-template/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "erpClient",
"version": "0.1.0",
"description": "A minimal Electron + Vue application",
"main": "main/main.js",
"main": "build/main/main.js",
"scripts": {
"dev": "node scripts/dev-server.js",
"build": "node scripts/build.js && electron-builder --dir",
@@ -17,6 +17,7 @@
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.4.1",
"binary-extensions": "^3.1.0",
"chalk": "^4.1.2",
"chokidar": "^3.5.3",
"electron": "^32.1.2",
@@ -32,5 +33,26 @@
"element-plus": "^2.11.3",
"exceljs": "^4.4.0",
"vue": "^3.3.8"
},
"build": {
"appId": "com.tashow.erp",
"productName": "天骄智能电商",
"files": [
"build/**/*",
"node_modules/**/*",
"package.json"
],
"directories": {
"buildResources": "assets",
"output": "dist"
},
"win": {
"target": [
{
"target": "dir",
"arch": ["x64"]
}
]
}
}
}

4417
electron-vue-template/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@ let springProcess: ChildProcess | null = null;
let mainWindow: BrowserWindow | null = null;
let splashWindow: BrowserWindow | null = null;
let appOpened = false;
let splashStartTime = 0; // 记录 splash 窗口显示时间
let downloadProgress = {percentage: 0, current: '0 MB', total: '0 MB'};
let isDownloading = false;
let downloadedFilePath: string | null = null;
@@ -24,8 +25,9 @@ function openAppIfNotOpened() {
return;
}
appOpened = true;
const url = `http://localhost:${process.argv[2] || 8083}`;
isDev
? mainWindow.loadURL(`http://localhost:${process.argv[2] || 8083}`)
? mainWindow.loadURL(url)
: mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
mainWindow.webContents.once('did-finish-load', () => {
@@ -33,7 +35,11 @@ function openAppIfNotOpened() {
splashWindow.webContents.send('splash-complete');
}
// 先显示主窗口再关闭splash避免白屏
// 计算 splash 已显示的时间,确保至少显示 2 秒
const splashElapsed = Date.now() - splashStartTime;
const minSplashTime = 2000;
const remainingTime = Math.max(0, minSplashTime - splashElapsed);
setTimeout(() => {
if (mainWindow && !mainWindow.isDestroyed()) {
const shouldMinimize = loadConfig().launchMinimized || false;
@@ -41,17 +47,16 @@ function openAppIfNotOpened() {
mainWindow.show();
mainWindow.focus();
}
if (isDev) mainWindow.webContents.openDevTools();
}
// 延迟关闭splash,确保主窗口已显示
// 延迟关闭 splash
setTimeout(() => {
if (splashWindow && !splashWindow.isDestroyed()) {
splashWindow.close();
splashWindow = null;
}
}, 100);
}, 200);
}, remainingTime + 200);
});
}
@@ -93,31 +98,34 @@ const getImageCacheDir = () => {
};
// 下载图片到本地
async function downloadImageToLocal(imageUrl: string, username: string, type: 'splash' | 'logo'): Promise<void> {
async function downloadImageToLocal(imageUrl: string, username: string, type: 'splash' | 'logo' | 'global_splash'): Promise<void> {
return new Promise((resolve) => {
const protocol = imageUrl.startsWith('https') ? https : http;
protocol.get(imageUrl, (res) => {
const handleResponse = (res: http.IncomingMessage) => {
if (res.statusCode !== 200) return resolve();
const chunks: Buffer[] = [];
res.on('data', (chunk) => chunks.push(chunk));
res.on('end', () => {
const buffer = Buffer.concat(chunks);
const ext = imageUrl.match(/\.(jpg|jpeg|png|gif|webp)$/i)?.[1] || 'png';
const filepath = join(getImageCacheDir(), `${username}_${type}.${ext}`);
const filename = type === 'global_splash' ? `global_splash.${ext}` : `${username}_${type}.${ext}`;
const filepath = join(getImageCacheDir(), filename);
writeFileSync(filepath, buffer);
console.log(`[图片缓存] 已保存: ${username}_${type}.${ext}`);
console.log(`[图片缓存] 已保存: ${filename}`);
resolve();
});
res.on('error', () => resolve());
}).on('error', () => resolve());
};
const req = imageUrl.startsWith('https') ? https.get(imageUrl, handleResponse) : http.get(imageUrl, handleResponse);
req.on('error', () => resolve());
});
}
// 加载本地缓存图片
function loadCachedImage(username: string, type: 'splash' | 'logo'): string | null {
function loadCachedImage(username: string, type: 'splash' | 'logo' | 'global_splash'): string | null {
try {
const files = readdirSync(getImageCacheDir());
const file = files.find(f => f.startsWith(`${username}_${type}.`));
const prefix = type === 'global_splash' ? 'global_splash.' : `${username}_${type}.`;
const file = files.find(f => f.startsWith(prefix));
if (file) {
const buffer = readFileSync(join(getImageCacheDir(), file));
const ext = extname(file).slice(1);
@@ -129,10 +137,11 @@ function loadCachedImage(username: string, type: 'splash' | 'logo'): string | nu
}
// 删除本地缓存图片
function deleteCachedImage(username: string, type: 'splash' | 'logo'): void {
function deleteCachedImage(username: string, type: 'splash' | 'logo' | 'global_splash'): void {
try {
const files = readdirSync(getImageCacheDir());
const file = files.find(f => f.startsWith(`${username}_${type}.`));
const prefix = type === 'global_splash' ? 'global_splash.' : `${username}_${type}.`;
const file = files.find(f => f.startsWith(prefix));
if (file) {
const filepath = join(getImageCacheDir(), file);
if (existsSync(filepath)) {
@@ -143,6 +152,66 @@ function deleteCachedImage(username: string, type: 'splash' | 'logo'): void {
} catch (err) {}
}
// 从服务器同步获取全局开屏图片URL带超时
async function fetchGlobalSplashImageUrl(): Promise<string | null> {
return new Promise((resolve) => {
const config = loadConfig();
const serverUrl = config.serverUrl || 'http://8.138.23.49:8085';
const url = `${serverUrl}/monitor/account/global-splash-image`;
const handleResponse = (res: http.IncomingMessage) => {
if (res.statusCode !== 200) return resolve(null);
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const json = JSON.parse(data);
resolve(json.code === 200 && json.data?.url ? json.data.url : null);
} catch {
resolve(null);
}
});
res.on('error', () => resolve(null));
};
const req = url.startsWith('https') ? https.get(url, handleResponse) : http.get(url, handleResponse);
req.on('error', () => resolve(null));
req.setTimeout(3000, () => {
req.destroy();
resolve(null);
});
});
}
// 从远程URL下载图片并转为base64
async function downloadImageAsBase64(imageUrl: string): Promise<string | null> {
return new Promise((resolve) => {
const handleResponse = (res: http.IncomingMessage) => {
if (res.statusCode !== 200) return resolve(null);
const chunks: Buffer[] = [];
res.on('data', (chunk) => chunks.push(chunk));
res.on('end', () => {
try {
const buffer = Buffer.concat(chunks);
const ext = imageUrl.match(/\.(jpg|jpeg|png|gif|webp)$/i)?.[1] || 'png';
const mime = { jpg: 'jpeg', jpeg: 'jpeg', png: 'png', gif: 'gif', webp: 'webp' }[ext] || 'png';
resolve(`url('data:image/${mime};base64,${buffer.toString('base64')}')`);
} catch {
resolve(null);
}
});
res.on('error', () => resolve(null));
};
const req = imageUrl.startsWith('https') ? https.get(imageUrl, handleResponse) : http.get(imageUrl, handleResponse);
req.on('error', () => resolve(null));
req.setTimeout(5000, () => {
req.destroy();
resolve(null);
});
});
}
// 获取默认开屏图片
function getDefaultSplashImage(): string {
const path = getResourcePath('../../public/image/splash_screen.png', 'public/image/splash_screen.png');
@@ -179,6 +248,7 @@ interface AppConfig {
lastUsername?: string;
splashImageUrl?: string;
brandLogoUrl?: string;
serverUrl?: string;
}
function getConfigPath(): string {
@@ -246,11 +316,10 @@ function startSpringBoot() {
springProcess = spawn(javaPath, springArgs, {
cwd: dataDir,
detached: false,
stdio: 'ignore'
stdio: 'ignore',
windowsHide: true
});
let startupCompleted = false;
springProcess.on('close', () => mainWindow ? mainWindow.close() : app.quit());
springProcess.on('error', (error) => {
dialog.showErrorBox('启动失败', error.message.includes('ENOENT')
@@ -388,7 +457,7 @@ if (!gotTheLock) {
});
}
app.whenReady().then(() => {
app.whenReady().then(async () => {
if (!isDev) {
protocol.interceptFileProtocol('file', (request, callback) => {
// 使用 fileURLToPath 正确解码 URL处理空格和特殊字符
@@ -436,17 +505,50 @@ app.whenReady().then(() => {
if (!shouldMinimize) {
const config = loadConfig();
const username = config.lastUsername || '';
const imageUrl = config.splashImageUrl || '';
const userSplashUrl = config.splashImageUrl || '';
// 图片加载:本地缓存 > 默认图片
let splashImage = (imageUrl && username && loadCachedImage(username, 'splash')) || getDefaultSplashImage();
let splashImage: string | null = null;
// 如果有URL但缓存不存在后台下载
if (imageUrl && username && !loadCachedImage(username, 'splash')) {
downloadImageToLocal(imageUrl, username, 'splash');
// 开屏图片加载优先级:全局图片(实时) > 用户自定义图片 > 默认本地图片
console.log('[开屏图片] 开始获取全局开屏图片...');
// 1. 获取全局开屏图片
const globalUrl = await fetchGlobalSplashImageUrl();
if (globalUrl) {
console.log('[开屏图片] 获取到全局图片URL:', globalUrl);
splashImage = await downloadImageAsBase64(globalUrl);
if (splashImage) {
console.log('[开屏图片] 使用实时全局图片');
downloadImageToLocal(globalUrl, '', 'global_splash').catch(() => {});
} else {
splashImage = loadCachedImage('', 'global_splash');
}
} else {
splashImage = loadCachedImage('', 'global_splash');
}
// 2. 如果没有全局图片,尝试用户自定义图片
if (!splashImage && userSplashUrl && username) {
console.log('[开屏图片] 使用用户自定义图片');
splashImage = loadCachedImage(username, 'splash');
if (!splashImage) {
downloadImageToLocal(userSplashUrl, username, 'splash').catch(() => {});
}
}
// 3. 使用默认本地图片
if (!splashImage) {
console.log('[开屏图片] 使用默认本地图片');
splashImage = getDefaultSplashImage();
}
// 将图片数据写入临时文件,避免 data URL 过长
const tempSplashPath = join(app.getPath('temp'), 'splash-temp.html');
const splashHtml = readFileSync(getSplashPath(), 'utf-8').replace('__SPLASH_IMAGE__', splashImage);
writeFileSync(tempSplashPath, splashHtml);
// 记录 splash 显示时间
splashStartTime = Date.now();
splashWindow = new BrowserWindow({
width: 1200,
@@ -454,8 +556,8 @@ app.whenReady().then(() => {
frame: false,
transparent: false,
resizable: false,
alwaysOnTop: false,
show: false,
alwaysOnTop: true, // 设置为置顶,确保在主窗口之上
show: false, // 创建时不显示,等待内容加载完成
center: true,
icon: getIconPath(),
backgroundColor: '#ffffff',
@@ -466,15 +568,24 @@ app.whenReady().then(() => {
});
splashWindow.on('closed', () => splashWindow = null);
// 加载预注入的 HTML图片已base64内联无跨域问题
splashWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(splashHtml)}`);
splashWindow.once('ready-to-show', () => splashWindow?.show());
// 监听页面加载完成后显示
splashWindow.webContents.on('did-finish-load', () => {
setTimeout(() => {
if (splashWindow && !splashWindow.isDestroyed()) {
splashWindow.show();
}
}, 50);
});
// 加载临时 HTML 文件
splashWindow.loadFile(tempSplashPath);
}
// 已手动启动后端
// setTimeout(() => {
// startSpringBoot();
// }, 200);
setTimeout(() => {
startSpringBoot();
}, 200);
setTimeout(() => {
openAppIfNotOpened();
@@ -955,9 +1066,7 @@ ipcMain.handle('load-config', () => {
async function getFileSize(url: string): Promise<number> {
return new Promise((resolve) => {
const protocol = url.startsWith('https') ? https : http;
const request = protocol.get(url, {method: 'HEAD'}, (response) => {
const handleResponse = (response: http.IncomingMessage) => {
if (response.statusCode === 301 || response.statusCode === 302 || response.statusCode === 307) {
const redirectUrl = response.headers.location;
if (redirectUrl) {
@@ -968,8 +1077,13 @@ async function getFileSize(url: string): Promise<number> {
const size = parseInt(response.headers['content-length'] || '0', 10);
resolve(size);
}).on('error', () => resolve(0));
};
const request = url.startsWith('https')
? https.get(url, {method: 'HEAD'}, handleResponse)
: http.get(url, {method: 'HEAD'}, handleResponse);
request.on('error', () => resolve(0));
request.setTimeout(10000, () => {
request.destroy();
resolve(0);
@@ -979,9 +1093,9 @@ async function getFileSize(url: string): Promise<number> {
async function downloadFile(url: string, filePath: string, onProgress: (progress: {downloaded: number, total: number}) => void): Promise<void> {
return new Promise((resolve, reject) => {
const protocol = url.startsWith('https') ? https : http;
let request: http.ClientRequest;
const request = protocol.get(url, (response) => {
const handleResponse = (response: http.IncomingMessage) => {
if (response.statusCode === 301 || response.statusCode === 302 || response.statusCode === 307) {
const redirectUrl = response.headers.location;
if (redirectUrl) {
@@ -1024,7 +1138,12 @@ async function downloadFile(url: string, filePath: string, onProgress: (progress
fs.unlink(filePath).catch(() => {});
reject(error);
});
};
}).on('error', reject);
request = url.startsWith('https')
? https.get(url, handleResponse)
: http.get(url, handleResponse);
request.on('error', reject);
});
}

View File

@@ -656,8 +656,8 @@ onUnmounted(() => {
</div>
</div>
<div v-if="brandLogoUrl" class="brand-logo-section">
<img :src="brandLogoUrl" alt="品牌 Banner" class="brand-logo"/>
<div class="brand-logo-section">
<img src="https://qiniu.bydj.tashowz.com/brand-logo/2026/01/4bfd767a56a54351a6db284563b4f83d.png" alt="品牌 Banner" class="brand-logo"/>
</div>
<div class="menu-group-title">电商平台</div>

View File

@@ -45,6 +45,11 @@ export const splashApi = {
// 删除品牌logo
async deleteBrandLogo(username: string) {
return http.post<{ data: string }>(`/monitor/account/brand-logo/delete?username=${username}`)
},
// 获取全局开屏图片
async getGlobalSplashImage() {
return http.get<{ data: { url: string } }>('/monitor/account/global-splash-image')
}
}

View File

@@ -607,12 +607,14 @@ onMounted(() => {
<span class="sidebar-text">启动</span>
</div>
<div
v-show="false"
:class="['sidebar-item', { active: activeTab === 'splash' }]"
@click="scrollToSection('splash')">
<span class="sidebar-icon">🖼</span>
<span class="sidebar-text">开屏图片</span>
</div>
<div
v-show="false"
:class="['sidebar-item', { active: activeTab === 'brand' }]"
@click="scrollToSection('brand')">
<span class="sidebar-icon">🏷</span>
@@ -754,8 +756,8 @@ onMounted(() => {
</div>
</div>
<!-- 开屏图片设置 -->
<div id="section-splash" class="setting-section" @mouseenter="activeTab = 'splash'">
<!-- 开屏图片设置 (暂时隐藏) -->
<div id="section-splash" class="setting-section" @mouseenter="activeTab = 'splash'" style="display: none;">
<div class="section-title-row">
<div class="section-title">开屏图片</div>
<img src="/icon/vipExclusive.png" alt="VIP专享" class="vip-exclusive-logo" />
@@ -785,8 +787,8 @@ onMounted(() => {
</div>
</div>
<!-- 品牌logo页面 -->
<div id="section-brand" class="setting-section" @mouseenter="activeTab = 'brand'">
<!-- 品牌logo页面 (暂时隐藏) -->
<div id="section-brand" class="setting-section" @mouseenter="activeTab = 'brand'" style="display: none;">
<div class="section-title-row">
<div class="section-title">品牌 Banner</div>
<img src="/icon/vipExclusive.png" alt="VIP专享" class="vip-exclusive-logo" />

View File

@@ -38,7 +38,7 @@ function handleConfirm() {
}
function copyWechat() {
navigator.clipboard.writeText('_linhong').then(() => {
navigator.clipboard.writeText('butaihaoba001').then(() => {
ElMessage.success('微信号已复制')
}).catch(() => {
ElMessage.error('复制失败,请手动复制')
@@ -75,7 +75,7 @@ function copyWechat() {
</div>
<div class="wechat-info">
<div class="wechat-label">客服微信</div>
<div class="wechat-id">_linhong</div>
<div class="wechat-id">butaihaoba001</div>
</div>
<div class="copy-icon">📋</div>
</div>

View File

@@ -3,7 +3,7 @@
*/
export const AppConfig = {
CLIENT_BASE: 'http://localhost:8081',
RUOYI_BASE: 'http://192.168.1.89:8085',
RUOYI_BASE: 'http://8.138.23.49:8085',
get SSE_URL() {
return `${this.RUOYI_BASE}/monitor/account/events`
}

View File

@@ -10,7 +10,6 @@ import java.util.List;
*/
@Component
public class ProxyPool {
private static final String API_URL = "http://api.tianqiip.com/getip?secret=y0thbcco1rgxn9e9&num=%d&type=txt&port=2&time=3&mr=1&sign=9be780c7e27aea815f1e0874446b9e35";
/**

View File

@@ -13,8 +13,8 @@ public class QiniuUtil {
// 七牛配置
private static final String ACCESS_KEY = "M1I8ItQjEYOYXyJYieloSSaIG8Ppi4lfCAyZ8BaF";
private static final String SECRET_KEY = "Xvi0SwtL9WVOl28h6DNRLKP9MnZZqsKBWrC8shAl";
private static final String BUCKET = "pxdj-prod";
private static final String DOMAIN = "https://qiniu.pxdj.tashowz.com/"; // 开启HTTPS的外链域名
private static final String BUCKET = "bydj-prod";
private static final String DOMAIN = "https://qiniu.bydj.tashowz.com/"; // 开启HTTPS的外链域名
public static String uploadFromUrl(String imageUrl, String key) throws Exception {
// 1. 下载远程图片

View File

@@ -46,9 +46,9 @@ server:
api:
server:
# 主服务器API配置
#base-url: "http://8.138.23.49:8085"
base-url: "http://8.138.23.49:8085"
#base-url: "http://192.168.1.89:8085"
base-url: "http://127.0.0.1:8085"
#base-url: "http://127.0.0.1:8085"
paths:
monitor: "/monitor/client/api"
login: "/monitor/account/login"

View File

@@ -74,6 +74,8 @@ public class ClientAccountController extends BaseController {
private static final String SPLASH_IMAGE_CACHE_KEY = "splash_image:";
private static final String BRAND_LOGO_CACHE_KEY = "brand_logo:";
private static final String GLOBAL_SPLASH_IMAGE_KEY = "global_splash_image";
private static final String GLOBAL_BRAND_LOGO_KEY = "global_brand_logo";
private AjaxResult checkDeviceLimit(Long accountId, String deviceId, int deviceLimit) {
int activeDeviceCount = accountDeviceMapper.countActiveDevicesByAccountId(accountId);
@@ -474,4 +476,53 @@ public class ClientAccountController extends BaseController {
}
}
/**
* 上传全局开屏图片
*/
@PreAuthorize("@ss.hasPermi('system:version:upload')")
@PostMapping("/global-splash-image/upload")
public AjaxResult uploadGlobalSplashImage(@RequestParam("file") MultipartFile file) {
try {
if (!file.getContentType().startsWith("image/")) return AjaxResult.error("只支持图片文件");
if (file.getSize() > 5 * 1024 * 1024) return AjaxResult.error("图片大小不能超过5MB");
String fileName = "splash/global/" + 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;
redisCache.setCacheObject(GLOBAL_SPLASH_IMAGE_KEY, url);
return AjaxResult.success().put("url", url).put("fileName", fileName);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("上传失败: " + e.getMessage());
}
}
/**
* 获取全局开屏图片
*/
@GetMapping("/global-splash-image")
public AjaxResult getGlobalSplashImage() {
String url = redisCache.getCacheObject(GLOBAL_SPLASH_IMAGE_KEY);
return AjaxResult.success(Map.of("url", url != null ? url : ""));
}
/**
* 删除全局开屏图片
*/
@PreAuthorize("@ss.hasPermi('system:version:upload')")
@PostMapping("/global-splash-image/delete")
public AjaxResult deleteGlobalSplashImage() {
try {
redisCache.deleteObject(GLOBAL_SPLASH_IMAGE_KEY);
return AjaxResult.success("全局开屏图片已删除");
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("删除失败: " + e.getMessage());
}
}
}

View File

@@ -18,9 +18,9 @@ qiniu:
accessKey: M1I8ItQjEYOYXyJYieloSSaIG8Ppi4lfCAyZ8BaF
secretKey: Xvi0SwtL9WVOl28h6DNRLKP9MnZZqsKBWrC8shAl
# 七牛空间名
bucket: pxdj-prod
bucket: bydj-prod
# 资源地址
resourcesUrl: https://qiniu.pxdj.tashowz.com/
resourcesUrl: https://qiniu.bydj.tashowz.com/
# 七牛云机房
zone: HUA_NAN

View File

@@ -1,7 +1,7 @@
shop.qiniu.resourcesUrl=https://qiniu.pxdj.tashowz.com/
shop.qiniu.resourcesUrl=https://qiniu.bydj.tashowz.com/
shop.qiniu.accessKey=M1I8ItQjEYOYXyJYieloSSaIG8Ppi4lfCAyZ8BaF
shop.qiniu.secretKey=Xvi0SwtL9WVOl28h6DNRLKP9MnZZqsKBWrC8shAl
shop.qiniu.bucket=pxdj-prod
shop.qiniu.bucket=bydj-prod
# \u5177\u4F53\u67E5\u770BQiniuZone.java
shop.qiniu.zone=HUA_NAN

View File

@@ -1,35 +0,0 @@
const { run } = require('runjs')
const chalk = require('chalk')
const config = require('../vue.config.js')
const rawArgv = process.argv.slice(2)
const args = rawArgv.join(' ')
if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
const report = rawArgv.includes('--report')
run(`vue-cli-service build ${args}`)
const port = 9526
const publicPath = config.publicPath
var connect = require('connect')
var serveStatic = require('serve-static')
const app = connect()
app.use(
publicPath,
serveStatic('./dist', {
index: ['index.html', '/']
})
)
app.listen(port, function () {
console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
if (report) {
console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
}
})
} else {
run(`vue-cli-service build ${args}`)
}

View File

@@ -0,0 +1,31 @@
import request from '@/utils/request'
// 上传全局开屏图片
export function uploadGlobalSplashImage(file) {
const formData = new FormData()
formData.append('file', file)
return request({
url: '/monitor/account/global-splash-image/upload',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
// 获取全局开屏图片
export function getGlobalSplashImage() {
return request({
url: '/monitor/account/global-splash-image',
method: 'get'
})
}
// 删除全局开屏图片
export function deleteGlobalSplashImage() {
return request({
url: '/monitor/account/global-splash-image/delete',
method: 'post'
})
}

View File

@@ -6,6 +6,63 @@
<el-button type="success" icon="el-icon-upload" size="mini" @click="handleUpload" v-hasPermi="['system:version:upload']">上传新版本</el-button>
</el-form-item>
</el-form>
<!-- 开屏图片设置卡片 -->
<el-row :gutter="20" class="mb8">
<el-col :span="24">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>开屏图片设置</span>
<span style="color: #909399; font-size: 12px; margin-left: 10px;">客户端启动时显示的图片</span>
</div>
<div class="splash-setting">
<el-row :gutter="20">
<el-col :span="8">
<div class="splash-preview">
<div class="preview-label">当前开屏图片</div>
<div class="preview-box" v-loading="splashLoading">
<img v-if="globalSplashImage" :src="globalSplashImage" alt="开屏图片" class="preview-image" />
<div v-else class="preview-empty">
<i class="el-icon-picture-outline"></i>
<span>暂未设置</span>
</div>
</div>
</div>
</el-col>
<el-col :span="16">
<div class="splash-actions">
<el-upload
ref="splashUpload"
action="#"
:limit="1"
accept="image/*"
:show-file-list="false"
:auto-upload="false"
:on-change="handleSplashFileChange">
<el-button type="primary" icon="el-icon-upload" :loading="splashUploading">
{{ splashUploading ? '上传中...' : '上传开屏图片' }}
</el-button>
</el-upload>
<el-button
type="danger"
icon="el-icon-delete"
@click="handleDeleteSplash"
:disabled="!globalSplashImage"
style="margin-left: 10px;">
删除图片
</el-button>
<div class="upload-tip">
<p>建议尺寸1200 x 800 像素支持 jpg/png/gif 格式大小不超过 5MB</p>
<p>此图片将作为所有客户端的默认开屏图片</p>
</div>
</div>
</el-col>
</el-row>
</div>
</el-card>
</el-col>
</el-row>
<!-- 版本信息卡片 -->
<el-row :gutter="20" class="mb8">
<el-col :span="12">
@@ -106,6 +163,7 @@
<script>
import { getVersionInfo, uploadFile, updateVersion } from "@/api/monitor/version"
import { getGlobalSplashImage, uploadGlobalSplashImage, deleteGlobalSplashImage } from "@/api/monitor/splash"
export default {
name: "Version",
@@ -164,13 +222,72 @@ export default {
// 上传加载状态
uploadLoading: false,
// 上传进度
uploadProgress: 0
uploadProgress: 0,
// 开屏图片相关
globalSplashImage: '',
splashLoading: false,
splashUploading: false
};
},
created() {
this.getVersionInfo();
this.getGlobalSplashImage();
},
methods: {
/** 获取全局开屏图片 */
getGlobalSplashImage() {
this.splashLoading = true;
getGlobalSplashImage().then(response => {
this.splashLoading = false;
if (response.code === 200 && response.data) {
this.globalSplashImage = response.data.url || '';
}
}).catch(() => {
this.splashLoading = false;
});
},
/** 处理开屏图片文件选择 */
handleSplashFileChange(file) {
if (!file || !file.raw) return;
// 验证文件类型
if (!file.raw.type.startsWith('image/')) {
this.$modal.msgError("只支持图片文件");
return;
}
// 验证文件大小
if (file.raw.size > 5 * 1024 * 1024) {
this.$modal.msgError("图片大小不能超过5MB");
return;
}
this.splashUploading = true;
uploadGlobalSplashImage(file.raw).then(response => {
this.splashUploading = false;
if (response.code === 200) {
this.$modal.msgSuccess("开屏图片上传成功");
this.globalSplashImage = response.url;
} else {
this.$modal.msgError(response.msg || "上传失败");
}
}).catch(() => {
this.splashUploading = false;
this.$modal.msgError("上传失败");
});
},
/** 删除开屏图片 */
handleDeleteSplash() {
this.$modal.confirm('确定要删除全局开屏图片吗?删除后客户端将使用默认图片。').then(() => {
deleteGlobalSplashImage().then(response => {
if (response.code === 200) {
this.$modal.msgSuccess("删除成功");
this.globalSplashImage = '';
} else {
this.$modal.msgError(response.msg || "删除失败");
}
});
}).catch(() => {});
},
/** 查询版本信息 */
getVersionInfo() {
getVersionInfo().then(response => {
@@ -316,4 +433,54 @@ export default {
.box-card {
margin-bottom: 20px;
}
/* 开屏图片设置样式 */
.splash-setting {
padding: 10px 0;
}
.splash-preview {
text-align: center;
}
.preview-label {
font-size: 14px;
color: #606266;
margin-bottom: 10px;
}
.preview-box {
width: 100%;
height: 200px;
border: 1px dashed #dcdfe6;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
background-color: #fafafa;
overflow: hidden;
}
.preview-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.preview-empty {
display: flex;
flex-direction: column;
align-items: center;
color: #909399;
}
.preview-empty i {
font-size: 48px;
margin-bottom: 10px;
}
.splash-actions {
padding: 20px 0;
}
.upload-tip {
margin-top: 15px;
color: #909399;
font-size: 12px;
line-height: 1.8;
}
.upload-tip p {
margin: 0;
}
</style>