feat: 添加公告功能,支持查看公告和在系统浏览器中打开链接
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { app, BrowserWindow, ipcMain, dialog } from 'electron'
|
import { app, BrowserWindow, ipcMain, dialog, shell } from 'electron'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import { spawn } from 'child_process'
|
import { spawn } from 'child_process'
|
||||||
@@ -74,7 +74,6 @@ function createWindow() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
mainWindow.setMenuBarVisibility(false)
|
mainWindow.setMenuBarVisibility(false)
|
||||||
|
|
||||||
const devServer = process.env.VITE_DEV_SERVER_URL
|
const devServer = process.env.VITE_DEV_SERVER_URL
|
||||||
if (devServer) {
|
if (devServer) {
|
||||||
mainWindow.loadURL(devServer)
|
mainWindow.loadURL(devServer)
|
||||||
@@ -121,6 +120,17 @@ ipcMain.handle('select-file', async (event, filters = []) => {
|
|||||||
return result.canceled ? null : result.filePaths[0]
|
return result.canceled ? null : result.filePaths[0]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 在系统默认浏览器中打开URL
|
||||||
|
ipcMain.handle('open-url', async (event, url: string) => {
|
||||||
|
try {
|
||||||
|
await shell.openExternal(url)
|
||||||
|
return { success: true }
|
||||||
|
} catch (error) {
|
||||||
|
console.error('打开链接失败:', error)
|
||||||
|
return { success: false, error: error.message }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 环境检查
|
// 环境检查
|
||||||
ipcMain.handle('check-environment', async () => {
|
ipcMain.handle('check-environment', async () => {
|
||||||
const appRoot = getAppRoot()
|
const appRoot = getAppRoot()
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
openDevTools: () => ipcRenderer.invoke('open-dev-tools'),
|
openDevTools: () => ipcRenderer.invoke('open-dev-tools'),
|
||||||
selectFolder: () => ipcRenderer.invoke('select-folder'),
|
selectFolder: () => ipcRenderer.invoke('select-folder'),
|
||||||
selectFile: (filters?: any[]) => ipcRenderer.invoke('select-file', filters),
|
selectFile: (filters?: any[]) => ipcRenderer.invoke('select-file', filters),
|
||||||
|
openUrl: (url: string) => ipcRenderer.invoke('open-url', url),
|
||||||
|
|
||||||
// 初始化相关API
|
// 初始化相关API
|
||||||
checkEnvironment: () => ipcRenderer.invoke('check-environment'),
|
checkEnvironment: () => ipcRenderer.invoke('check-environment'),
|
||||||
|
|||||||
@@ -23,7 +23,12 @@
|
|||||||
class="notice-tab-pane"
|
class="notice-tab-pane"
|
||||||
>
|
>
|
||||||
<div class="notice-content">
|
<div class="notice-content">
|
||||||
<div class="markdown-content" v-html="renderMarkdown(content)"></div>
|
<div
|
||||||
|
ref="markdownContentRef"
|
||||||
|
class="markdown-content"
|
||||||
|
v-html="renderMarkdown(content)"
|
||||||
|
@click="handleLinkClick"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
@@ -116,6 +121,33 @@ const confirmNotices = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理链接点击
|
||||||
|
const handleLinkClick = async (event: MouseEvent) => {
|
||||||
|
const target = event.target as HTMLElement
|
||||||
|
if (target.tagName === 'A') {
|
||||||
|
event.preventDefault()
|
||||||
|
const url = target.getAttribute('href')
|
||||||
|
if (url) {
|
||||||
|
try {
|
||||||
|
// 检查是否在Electron环境中
|
||||||
|
if (window.electronAPI && window.electronAPI.openUrl) {
|
||||||
|
const result = await window.electronAPI.openUrl(url)
|
||||||
|
if (!result.success) {
|
||||||
|
console.error('打开链接失败:', result.error)
|
||||||
|
message.error('打开链接失败,请手动复制链接地址')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果不在Electron环境中,使用普通的window.open
|
||||||
|
window.open(url, '_blank')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('打开链接失败:', error)
|
||||||
|
message.error('打开链接失败,请手动复制链接地址')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 监听公告数据变化,设置默认选中第一个公告
|
// 监听公告数据变化,设置默认选中第一个公告
|
||||||
watch(
|
watch(
|
||||||
() => props.noticeData,
|
() => props.noticeData,
|
||||||
|
|||||||
1
frontend/src/types/electron.d.ts
vendored
1
frontend/src/types/electron.d.ts
vendored
@@ -2,6 +2,7 @@ export interface ElectronAPI {
|
|||||||
openDevTools: () => Promise<void>
|
openDevTools: () => Promise<void>
|
||||||
selectFolder: () => Promise<string | null>
|
selectFolder: () => Promise<string | null>
|
||||||
selectFile: (filters?: any[]) => Promise<string | null>
|
selectFile: (filters?: any[]) => Promise<string | null>
|
||||||
|
openUrl: (url: string) => Promise<{ success: boolean; error?: string }>
|
||||||
|
|
||||||
// 初始化相关API
|
// 初始化相关API
|
||||||
checkEnvironment: () => Promise<{
|
checkEnvironment: () => Promise<{
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<a-typography-title>{{ greeting }}</a-typography-title>
|
<a-typography-title>{{ greeting }}</a-typography-title>
|
||||||
|
<!-- 右上角公告按钮 -->
|
||||||
|
<div class="header-actions">
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
ghost
|
||||||
|
@click="showNotice"
|
||||||
|
:loading="noticeLoading"
|
||||||
|
class="notice-button"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<BellOutlined />
|
||||||
|
</template>
|
||||||
|
查看公告
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 公告模态框 -->
|
<!-- 公告模态框 -->
|
||||||
@@ -230,7 +245,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import { ReloadOutlined, ClockCircleOutlined, UserOutlined } from '@ant-design/icons-vue'
|
import { ReloadOutlined, ClockCircleOutlined, UserOutlined, BellOutlined } from '@ant-design/icons-vue'
|
||||||
import { Service } from '@/api/services/Service'
|
import { Service } from '@/api/services/Service'
|
||||||
import NoticeModal from '@/components/NoticeModal.vue'
|
import NoticeModal from '@/components/NoticeModal.vue'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
@@ -286,6 +301,7 @@ const proxyData = ref<Record<string, ProxyInfo>>({})
|
|||||||
// 公告系统相关状态
|
// 公告系统相关状态
|
||||||
const noticeVisible = ref(false)
|
const noticeVisible = ref(false)
|
||||||
const noticeData = ref<Record<string, string>>({})
|
const noticeData = ref<Record<string, string>>({})
|
||||||
|
const noticeLoading = ref(false)
|
||||||
|
|
||||||
// 获取当前活动信息
|
// 获取当前活动信息
|
||||||
const currentActivity = computed(() => {
|
const currentActivity = computed(() => {
|
||||||
@@ -462,7 +478,6 @@ const fetchNoticeData = async () => {
|
|||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
// 检查是否需要显示公告
|
// 检查是否需要显示公告
|
||||||
if (response.if_need_show && response.data && Object.keys(response.data).length > 0) {
|
if (response.if_need_show && response.data && Object.keys(response.data).length > 0) {
|
||||||
// if (response.data && Object.keys(response.data).length > 0) {
|
|
||||||
noticeData.value = response.data
|
noticeData.value = response.data
|
||||||
noticeVisible.value = true
|
noticeVisible.value = true
|
||||||
}
|
}
|
||||||
@@ -480,6 +495,31 @@ const onNoticeConfirmed = () => {
|
|||||||
// message.success('公告已确认')
|
// message.success('公告已确认')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示公告的处理函数
|
||||||
|
const showNotice = async () => {
|
||||||
|
noticeLoading.value = true
|
||||||
|
try {
|
||||||
|
const response = await Service.getNoticeInfoApiInfoNoticeGetPost()
|
||||||
|
|
||||||
|
if (response.code === 200) {
|
||||||
|
// 忽略 if_need_show 字段,只要有公告数据就显示
|
||||||
|
if (response.data && Object.keys(response.data).length > 0) {
|
||||||
|
noticeData.value = response.data
|
||||||
|
noticeVisible.value = true
|
||||||
|
} else {
|
||||||
|
message.info('暂无公告信息')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.error(response.message || '获取公告失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('显示公告失败:', error)
|
||||||
|
message.error('显示公告失败,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
noticeLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchActivityData()
|
fetchActivityData()
|
||||||
fetchNoticeData()
|
fetchNoticeData()
|
||||||
@@ -489,6 +529,9 @@ onMounted(() => {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.header {
|
.header {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header h1 {
|
.header h1 {
|
||||||
@@ -498,6 +541,21 @@ onMounted(() => {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-button {
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 公告相关样式 */
|
||||||
|
.notice-modal {
|
||||||
|
/* 自定义公告模态框样式 */
|
||||||
|
}
|
||||||
|
|
||||||
.activity-card {
|
.activity-card {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user