项目初始化
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div ref="hostCardRef" class="host-card">
|
||||
<div class="card-title">
|
||||
<span>主机信息</span>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="gauge-list">
|
||||
<div v-for="item in gauges" :key="item.label" class="gauge-item">
|
||||
<div :ref="(el) => setChartRef(el, item.label)" class="gauge-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
const gauges = ref([
|
||||
{ label: 'CPU系统使用率', value: 79, color: '#3b82f6' },
|
||||
{ label: 'CPU用户使用率', value: 68, color: '#10b981' },
|
||||
{ label: 'CPU利用率', value: 82, color: '#ef4444' },
|
||||
]);
|
||||
|
||||
const chartInstances = new Map();
|
||||
const chartRefs = new Map();
|
||||
const hostCardRef = ref<HTMLElement>();
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
|
||||
function resizeCharts() {
|
||||
chartInstances.forEach((chart) => {
|
||||
chart.resize();
|
||||
});
|
||||
}
|
||||
|
||||
function setChartRef(el: any, label: string) {
|
||||
if (el) chartRefs.set(label, el);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gauges.value.forEach((item) => {
|
||||
const el = chartRefs.get(item.label);
|
||||
if (!el) return;
|
||||
const chart = echarts.init(el);
|
||||
chart.setOption({
|
||||
series: [
|
||||
{
|
||||
type: 'gauge',
|
||||
startAngle: 200,
|
||||
endAngle: -20,
|
||||
radius: '75%',
|
||||
center: ['50%', '50%'],
|
||||
min: 0,
|
||||
max: 100,
|
||||
splitNumber: 5,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
width: 10,
|
||||
color: [
|
||||
[item.value / 100, item.color],
|
||||
[1, '#e5e7eb'],
|
||||
],
|
||||
},
|
||||
},
|
||||
pointer: {
|
||||
length: '55%',
|
||||
width: 4,
|
||||
itemStyle: { color: item.color },
|
||||
},
|
||||
axisTick: { show: false },
|
||||
splitLine: {
|
||||
length: 8,
|
||||
lineStyle: { color: '#fff', width: 2 },
|
||||
},
|
||||
axisLabel: {
|
||||
show: false,
|
||||
},
|
||||
title: {
|
||||
show: true,
|
||||
offsetCenter: [0, '120%'],
|
||||
fontSize: 11,
|
||||
color: '#666',
|
||||
},
|
||||
detail: {
|
||||
valueAnimation: true,
|
||||
formatter: '{value}%',
|
||||
color: item.color,
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
offsetCenter: [0, '0%'],
|
||||
},
|
||||
data: [{ value: item.value, name: item.label }],
|
||||
},
|
||||
],
|
||||
});
|
||||
chartInstances.set(item.label, chart);
|
||||
});
|
||||
|
||||
if (hostCardRef.value) {
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
resizeCharts();
|
||||
});
|
||||
resizeObserver.observe(hostCardRef.value);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', resizeCharts);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
resizeObserver?.disconnect();
|
||||
window.removeEventListener('resize', resizeCharts);
|
||||
chartInstances.forEach((chart) => chart.dispose());
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@dark-bg: #141414;
|
||||
|
||||
.host-card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
.card-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
color: rgb(51 65 85);
|
||||
border-bottom: 1px solid rgb(226 232 240);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 16px;
|
||||
color: rgb(71 85 105);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
overflow: auto;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.gauge-list {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.gauge-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.gauge-chart {
|
||||
width: 100%;
|
||||
height: 140px;
|
||||
max-width: 140px;
|
||||
}
|
||||
|
||||
.gauge-label {
|
||||
font-size: 11px;
|
||||
color: rgb(100 116 139);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .host-card {
|
||||
box-shadow: none;
|
||||
|
||||
.card-title {
|
||||
color: rgb(203 213 225);
|
||||
border-bottom-color: rgb(51 65 85);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
color: rgb(148 163 184);
|
||||
}
|
||||
|
||||
.gauge-label {
|
||||
color: rgb(148 163 184);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,725 @@
|
||||
<template>
|
||||
<div class="notice-card">
|
||||
<div class="card-title">
|
||||
<span>通知消息</span>
|
||||
<div class="card-tabs">
|
||||
<button
|
||||
v-for="item in tabs"
|
||||
:key="item.key"
|
||||
:class="['tab-item', { active: activeTab === item.key }]"
|
||||
@click="activeTab = item.key"
|
||||
>
|
||||
{{ item.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div ref="tableWrapRef" class="table-container">
|
||||
<el-table :data="currentList" :height="tableHeight" :show-header="true" :border="false">
|
||||
<el-table-column prop="title" label="标题" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-button class="notice-title-button" link type="primary" @click="openNoticeDialog(row)">
|
||||
{{ row.title }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="time" label="时间" width="180" />
|
||||
<el-table-column prop="content" label="内容" min-width="200" show-overflow-tooltip="true" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogVisible" class="notice-info-dialog" title="通知详情" width="60%" destroy-on-close>
|
||||
<template v-if="selectedNotice">
|
||||
<div class="notice-dialog">
|
||||
<div class="notice-dialog__header">
|
||||
<div class="notice-dialog__title">{{ selectedNotice.title }}</div>
|
||||
<div class="notice-dialog__header-divider"></div>
|
||||
<div class="notice-dialog__time">{{ selectedNotice.time }}</div>
|
||||
</div>
|
||||
<el-divider class="notice-dialog__divider" />
|
||||
<div class="notice-dialog__content-panel">
|
||||
<div class="notice-dialog__content" v-html="selectedNotice.content"></div>
|
||||
</div>
|
||||
<div v-if="selectedNotice.attachments?.length" class="notice-dialog__attachments-panel">
|
||||
<div class="notice-dialog__attachments-title">附件区域</div>
|
||||
<div class="notice-dialog__attachments-list">
|
||||
<div
|
||||
v-for="attachment in selectedNotice.attachments"
|
||||
:key="attachment"
|
||||
class="notice-dialog__attachment-item"
|
||||
>
|
||||
<div class="notice-dialog__attachment-meta">
|
||||
<el-icon class="notice-dialog__attachment-icon"><Document /></el-icon>
|
||||
<span class="notice-dialog__attachment-name">{{ attachment }}</span>
|
||||
</div>
|
||||
<div class="notice-dialog__attachment-actions">
|
||||
<span class="notice-dialog__attachment-size">{{ getAttachmentSize(attachment) }}</span>
|
||||
<el-button
|
||||
class="notice-dialog__attachment-download"
|
||||
type="primary"
|
||||
link
|
||||
:icon="Download"
|
||||
@click.stop="handleAttachmentDownload(attachment)"
|
||||
>下载</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Document, Download } from '@element-plus/icons-vue';
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
|
||||
interface NoticeItem {
|
||||
title: string;
|
||||
time: string;
|
||||
content: string;
|
||||
attachments?: string[];
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{ key: '0', label: '未读' },
|
||||
{ key: '1', label: '已读' },
|
||||
];
|
||||
|
||||
const activeTab = ref('0');
|
||||
|
||||
const noticeData: Record<string, NoticeItem[]> = {
|
||||
0: [
|
||||
{
|
||||
title: '系统升级通知',
|
||||
time: '2025-03-25 10:00',
|
||||
content: '<p>系统将于今晚进行升级维护,请提前保存工作内容。</p><p><strong>维护时间:</strong>23:00 - 02:00</p>',
|
||||
attachments: [
|
||||
'升级说明.pdf',
|
||||
'影响范围清单.xlsx',
|
||||
'系统切换流程.docx',
|
||||
'升级回退预案.pdf',
|
||||
'服务影响通知单.xls',
|
||||
'值班安排表.doc',
|
||||
'数据库检查项.txt',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '安全检查提醒',
|
||||
time: '2025-03-25 09:30',
|
||||
content:
|
||||
'<p>请及时完成本周安全检查任务,确保系统运行稳定。</p><ul><li>检查设备运行状态</li><li>确认日志采集是否正常</li></ul>',
|
||||
attachments: ['安全检查模板.docx', '巡检项清单.pdf', '整改说明.docx', '告警记录.xlsx'],
|
||||
},
|
||||
|
||||
{
|
||||
title: '备份任务完成',
|
||||
time: '2025-03-25 08:15',
|
||||
content: '<p>数据库备份任务已完成,备份文件已存储至指定位置。</p>',
|
||||
attachments: [],
|
||||
},
|
||||
],
|
||||
1: [
|
||||
{
|
||||
title: '巡检报告提交',
|
||||
time: '2025-03-24 16:20',
|
||||
content: '<p>昨日巡检报告已提交并审核通过。</p><p>请相关负责人及时查看结果。</p>',
|
||||
attachments: ['巡检报告-0324.pdf'],
|
||||
},
|
||||
{
|
||||
title: '权限变更通知',
|
||||
time: '2025-03-24 14:10',
|
||||
content: '<p>用户权限调整已生效,请相关人员知悉。</p>',
|
||||
attachments: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const currentList = computed(() => noticeData[activeTab.value] || []);
|
||||
const tableWrapRef = ref<HTMLElement>();
|
||||
const tableHeight = ref(0);
|
||||
const dialogVisible = ref(false);
|
||||
const selectedNotice = ref<NoticeItem | null>(null);
|
||||
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
|
||||
function openNoticeDialog(notice: NoticeItem) {
|
||||
selectedNotice.value = notice;
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
|
||||
function handleAttachmentDownload(attachment: string) {
|
||||
ElMessage.info(`待接入下载地址:${attachment}`);
|
||||
}
|
||||
|
||||
function getAttachmentSize(attachment: string) {
|
||||
const seed = attachment.split('').reduce((total, char) => total + char.charCodeAt(0), 0);
|
||||
return `${((seed % 9000) / 100 + 0.8).toFixed(2)} MB`;
|
||||
}
|
||||
|
||||
function updateTableHeight() {
|
||||
nextTick(() => {
|
||||
const tableWrapEl = tableWrapRef.value;
|
||||
if (!tableWrapEl) return;
|
||||
tableHeight.value = Math.max(tableWrapEl.clientHeight, 0);
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateTableHeight();
|
||||
if (tableWrapRef.value) {
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
updateTableHeight();
|
||||
});
|
||||
resizeObserver.observe(tableWrapRef.value);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
resizeObserver?.disconnect();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@dark-bg: #141414;
|
||||
|
||||
.notice-card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
.card-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
color: rgb(51 65 85);
|
||||
border-bottom: 1px solid rgb(226 232 240);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.card-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
position: relative;
|
||||
padding: 4px 8px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: rgb(100 116 139);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.active {
|
||||
color: rgb(30 41 59);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: -2px;
|
||||
height: 2px;
|
||||
background: rgb(59 130 246);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 8px 12px 12px;
|
||||
color: rgb(71 85 105);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.table-container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.el-table {
|
||||
width: 100%;
|
||||
--el-table-border-color: transparent;
|
||||
--el-table-header-bg-color: rgb(248, 250, 252);
|
||||
--el-table-tr-bg-color: transparent;
|
||||
--el-table-row-hover-bg-color: rgb(241 245 249);
|
||||
--el-table-text-color: rgb(71 85 105);
|
||||
--el-table-header-text-color: rgb(51 65 85);
|
||||
background: transparent;
|
||||
|
||||
&::before,
|
||||
&::after,
|
||||
.el-table__inner-wrapper::before,
|
||||
.el-table__border-left-patch {
|
||||
display: none;
|
||||
}
|
||||
|
||||
th.el-table__cell,
|
||||
td.el-table__cell {
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
border-bottom: 1px solid rgb(226 232 240);
|
||||
}
|
||||
|
||||
th.el-table__cell {
|
||||
background: rgb(248, 250, 252);
|
||||
}
|
||||
|
||||
td.el-table__cell,
|
||||
th.el-table__cell {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.cell {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.notice-title-button {
|
||||
padding: 0;
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notice-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__header-divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: rgb(226 232 240);
|
||||
}
|
||||
|
||||
&__divider {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
&__content-panel,
|
||||
&__attachments-panel {
|
||||
padding: 2px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: rgb(248 252 255);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&__attachments-panel {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
border: 1px solid rgb(219 234 254);
|
||||
background: rgb(248 252 255);
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: rgb(30 41 59);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__time {
|
||||
font-size: 13px;
|
||||
color: rgb(100 116 139);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&__content {
|
||||
min-height: 220px;
|
||||
max-height: 320px;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid rgb(191 219 254);
|
||||
border-radius: 6px;
|
||||
background: rgb(255 255 255);
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
color: rgb(51 65 85);
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgb(191 219 254) transparent;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
border-radius: 999px;
|
||||
background: rgb(191 219 254);
|
||||
}
|
||||
|
||||
:deep(p) {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
:deep(ul) {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
:deep(li) {
|
||||
margin: 4px 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__attachments {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&__attachments-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-height: 36px;
|
||||
margin-bottom: 12px;
|
||||
padding: 0 12px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: rgb(239 246 255);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: rgb(51 65 85);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&__attachments-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
max-height: 132px;
|
||||
overflow-y: auto;
|
||||
padding-right: 2px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgb(191 219 254) transparent;
|
||||
}
|
||||
|
||||
&__attachments-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&__attachments-list::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&__attachments-list::-webkit-scrollbar-thumb {
|
||||
border-radius: 999px;
|
||||
background: rgb(191 219 254);
|
||||
}
|
||||
|
||||
&__attachment-item,
|
||||
&__attachments-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid rgb(219 234 254);
|
||||
border-radius: 6px;
|
||||
background: linear-gradient(180deg, rgb(255 255 255) 0%, rgb(248 250 252) 100%);
|
||||
box-shadow: 0 6px 16px rgb(148 163 184 / 12%);
|
||||
font-size: 13px;
|
||||
color: rgb(71 85 105);
|
||||
transition:
|
||||
transform 0.2s ease,
|
||||
box-shadow 0.2s ease,
|
||||
border-color 0.2s ease,
|
||||
background 0.2s ease,
|
||||
color 0.2s ease;
|
||||
}
|
||||
|
||||
&__attachment-name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__attachment-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__attachment-icon {
|
||||
flex-shrink: 0;
|
||||
font-size: 16px;
|
||||
color: rgb(59 130 246);
|
||||
}
|
||||
|
||||
&__attachment-download {
|
||||
flex-shrink: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&__attachment-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
&__attachment-size {
|
||||
font-size: 12px;
|
||||
color: rgb(100 116 139);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__attachment-item:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: rgb(147 197 253);
|
||||
background: linear-gradient(180deg, rgb(239 246 255) 0%, rgb(219 234 254) 100%);
|
||||
box-shadow: 0 10px 22px rgb(96 165 250 / 18%);
|
||||
color: rgb(30 41 59);
|
||||
}
|
||||
|
||||
&__attachment-item:hover :deep(.el-button) {
|
||||
color: rgb(29 78 216);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notice-info-dialog {
|
||||
.el-dialog {
|
||||
background: rgb(255 255 255) !important;
|
||||
--el-dialog-bg-color: rgb(255 255 255);
|
||||
--el-bg-color: rgb(255 255 255);
|
||||
box-shadow: 0 12px 32px rgb(15 23 42 / 18%);
|
||||
}
|
||||
|
||||
.el-dialog__header {
|
||||
margin-right: 0;
|
||||
padding: 12px 12px 10px;
|
||||
border-bottom: 1px solid rgb(226 232 240) !important;
|
||||
background: rgb(255 255 255) !important;
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 2px;
|
||||
background: rgb(255 255 255) !important;
|
||||
}
|
||||
|
||||
.el-divider {
|
||||
--el-border-color: rgb(226 232 240);
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .notice-card {
|
||||
box-shadow: none;
|
||||
|
||||
.card-title {
|
||||
color: rgb(203 213 225);
|
||||
border-bottom-color: rgb(51 65 85);
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
color: rgb(148 163 184);
|
||||
|
||||
&.active {
|
||||
color: rgb(226 232 240);
|
||||
|
||||
&::after {
|
||||
background: rgb(96 165 250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
color: rgb(148 163 184);
|
||||
|
||||
.table-container {
|
||||
.el-table {
|
||||
--el-table-border-color: transparent;
|
||||
--el-table-header-bg-color: rgb(20, 20, 20);
|
||||
--el-table-tr-bg-color: transparent;
|
||||
--el-table-row-hover-bg-color: rgb(30 41 59);
|
||||
--el-table-text-color: rgb(148 163 184);
|
||||
--el-table-header-text-color: rgb(226 232 240);
|
||||
background: transparent;
|
||||
|
||||
th.el-table__cell {
|
||||
background: rgb(20, 20, 20);
|
||||
}
|
||||
|
||||
th.el-table__cell,
|
||||
td.el-table__cell {
|
||||
border-bottom-color: rgb(51 65 85);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notice-dialog {
|
||||
&__title {
|
||||
color: rgb(226 232 240);
|
||||
}
|
||||
|
||||
&__time {
|
||||
color: rgb(148 163 184);
|
||||
}
|
||||
|
||||
&__header-divider {
|
||||
background: rgb(51 65 85);
|
||||
}
|
||||
|
||||
&__content-panel,
|
||||
&__attachments-panel {
|
||||
background: rgb(20, 20, 20);
|
||||
}
|
||||
|
||||
&__attachments-panel {
|
||||
border-color: rgb(51 65 85);
|
||||
background: rgb(20, 20, 20);
|
||||
}
|
||||
|
||||
&__content {
|
||||
color: rgb(226 232 240);
|
||||
background: rgb(20, 20, 20);
|
||||
border-color: rgb(51 65 85);
|
||||
}
|
||||
|
||||
&__attachment-item,
|
||||
&__attachments-empty {
|
||||
border-color: rgb(71 85 105);
|
||||
background: rgb(20, 20, 20);
|
||||
box-shadow: 0 6px 16px rgb(2 6 23 / 26%);
|
||||
color: rgb(226 232 240);
|
||||
}
|
||||
|
||||
&__attachment-icon {
|
||||
color: rgb(147 197 253);
|
||||
}
|
||||
|
||||
&__attachment-size {
|
||||
color: rgb(148 163 184);
|
||||
}
|
||||
|
||||
&__attachment-download:deep(.el-button) {
|
||||
color: rgb(147 197 253);
|
||||
}
|
||||
|
||||
&__attachment-item:hover {
|
||||
border-color: rgb(96 165 250);
|
||||
background: rgb(37 99 235 / 22%);
|
||||
box-shadow: 0 10px 24px rgb(59 130 246 / 20%);
|
||||
color: rgb(241 245 249);
|
||||
}
|
||||
|
||||
&__attachment-item:hover :deep(.el-button) {
|
||||
color: rgb(191 219 254);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .notice-info-dialog {
|
||||
--el-bg-color: rgb(20, 20, 20);
|
||||
--el-dialog-bg-color: rgb(20, 20, 20);
|
||||
--el-fill-color-blank: rgb(20, 20, 20);
|
||||
|
||||
.el-dialog {
|
||||
background: rgb(20, 20, 20) !important;
|
||||
--el-dialog-bg-color: rgb(20, 20, 20);
|
||||
--el-bg-color: rgb(20, 20, 20);
|
||||
--el-fill-color-blank: rgb(20, 20, 20);
|
||||
box-shadow: 0 14px 36px rgb(0 0 0 / 42%);
|
||||
}
|
||||
|
||||
.el-dialog__wrapper,
|
||||
.el-overlay-dialog,
|
||||
.el-dialog__content {
|
||||
background: rgb(20, 20, 20) !important;
|
||||
}
|
||||
|
||||
.el-dialog__header {
|
||||
border-bottom: 1px solid rgb(51 65 85) !important;
|
||||
background: rgb(20, 20, 20) !important;
|
||||
}
|
||||
|
||||
.el-dialog__title {
|
||||
color: rgb(226 232 240);
|
||||
}
|
||||
|
||||
.el-dialog__headerbtn .el-dialog__close {
|
||||
color: rgb(148 163 184);
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
background: rgb(20, 20, 20) !important;
|
||||
}
|
||||
|
||||
.el-dialog__header,
|
||||
.el-dialog__body,
|
||||
.el-dialog__footer {
|
||||
--el-bg-color: rgb(20, 20, 20);
|
||||
--el-fill-color-blank: rgb(20, 20, 20);
|
||||
}
|
||||
|
||||
.el-divider {
|
||||
--el-border-color: rgb(51 65 85);
|
||||
}
|
||||
|
||||
.notice-dialog {
|
||||
&__attachments-title {
|
||||
background: rgb(20, 20, 20);
|
||||
color: rgb(226 232 240);
|
||||
}
|
||||
|
||||
&__content {
|
||||
scrollbar-color: rgb(71 85 105) transparent;
|
||||
}
|
||||
|
||||
&__content::-webkit-scrollbar-thumb {
|
||||
background: rgb(71 85 105);
|
||||
}
|
||||
|
||||
&__attachments-list {
|
||||
scrollbar-color: rgb(71 85 105) transparent;
|
||||
}
|
||||
|
||||
&__attachments-list::-webkit-scrollbar-thumb {
|
||||
background: rgb(71 85 105);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<div class="notice-card">
|
||||
<div class="card-title">
|
||||
<span>待办信息</span>
|
||||
<div class="card-tabs">
|
||||
<button
|
||||
v-for="item in tabs"
|
||||
:key="item.key"
|
||||
:class="['tab-item', { active: activeTab === item.key }]"
|
||||
@click="activeTab = item.key"
|
||||
>
|
||||
{{ item.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div ref="tableWrapRef" class="table-container">
|
||||
<el-table :data="currentList" :height="tableHeight" :show-header="true" :border="false">
|
||||
<el-table-column prop="title" label="标题" min-width="120" />
|
||||
<el-table-column prop="time" label="时间" width="180" />
|
||||
<el-table-column prop="content" label="内容" min-width="200" show-overflow-tooltip="true" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
|
||||
const tabs = [
|
||||
{ key: '0', label: '待办' },
|
||||
{ key: '1', label: '已办' },
|
||||
];
|
||||
|
||||
const activeTab = ref('0');
|
||||
|
||||
const noticeData = {
|
||||
0: [
|
||||
{ title: '系统升级通知', time: '2025-03-25 10:00', content: '系统将于今晚进行升级维护,请提前保存工作内容。' },
|
||||
{ title: '安全检查提醒', time: '2025-03-25 09:30', content: '请及时完成本周安全检查任务,确保系统运行稳定。' },
|
||||
|
||||
{ title: '备份任务完成', time: '2025-03-25 08:15', content: '数据库备份任务已完成,备份文件已存储至指定位置。' },
|
||||
],
|
||||
1: [
|
||||
{ title: '巡检报告提交', time: '2025-03-24 16:20', content: '昨日巡检报告已提交并审核通过。' },
|
||||
{ title: '权限变更通知', time: '2025-03-24 14:10', content: '用户权限调整已生效,请相关人员知悉。' },
|
||||
],
|
||||
};
|
||||
|
||||
const currentList = computed(() => noticeData[activeTab.value] || []);
|
||||
const tableWrapRef = ref<HTMLElement>();
|
||||
const tableHeight = ref(0);
|
||||
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
|
||||
function updateTableHeight() {
|
||||
nextTick(() => {
|
||||
const tableWrapEl = tableWrapRef.value;
|
||||
if (!tableWrapEl) return;
|
||||
tableHeight.value = Math.max(tableWrapEl.clientHeight, 0);
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateTableHeight();
|
||||
if (tableWrapRef.value) {
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
updateTableHeight();
|
||||
});
|
||||
resizeObserver.observe(tableWrapRef.value);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
resizeObserver?.disconnect();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@dark-bg: #141414;
|
||||
|
||||
.notice-card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
.card-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
color: rgb(51 65 85);
|
||||
border-bottom: 1px solid rgb(226 232 240);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.card-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
position: relative;
|
||||
padding: 4px 8px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: rgb(100 116 139);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.active {
|
||||
color: rgb(30 41 59);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: -2px;
|
||||
height: 2px;
|
||||
background: rgb(59 130 246);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 8px 12px 12px;
|
||||
color: rgb(71 85 105);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.table-container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.el-table {
|
||||
width: 100%;
|
||||
--el-table-border-color: transparent;
|
||||
--el-table-header-bg-color: rgb(248, 250, 252);
|
||||
--el-table-tr-bg-color: transparent;
|
||||
--el-table-row-hover-bg-color: rgb(241 245 249);
|
||||
--el-table-text-color: rgb(71 85 105);
|
||||
--el-table-header-text-color: rgb(51 65 85);
|
||||
background: transparent;
|
||||
|
||||
&::before,
|
||||
&::after,
|
||||
.el-table__inner-wrapper::before,
|
||||
.el-table__border-left-patch {
|
||||
display: none;
|
||||
}
|
||||
|
||||
th.el-table__cell,
|
||||
td.el-table__cell {
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
border-bottom: 1px solid rgb(226 232 240);
|
||||
}
|
||||
|
||||
th.el-table__cell {
|
||||
background: rgb(248, 250, 252);
|
||||
}
|
||||
|
||||
td.el-table__cell,
|
||||
th.el-table__cell {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.cell {
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .notice-card {
|
||||
box-shadow: none;
|
||||
|
||||
.card-title {
|
||||
color: rgb(203 213 225);
|
||||
border-bottom-color: rgb(51 65 85);
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
color: rgb(148 163 184);
|
||||
|
||||
&.active {
|
||||
color: rgb(226 232 240);
|
||||
|
||||
&::after {
|
||||
background: rgb(96 165 250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
color: rgb(148 163 184);
|
||||
|
||||
.table-container {
|
||||
.el-table {
|
||||
--el-table-border-color: transparent;
|
||||
--el-table-header-bg-color: rgb(20, 20, 20);
|
||||
--el-table-tr-bg-color: transparent;
|
||||
--el-table-row-hover-bg-color: rgb(30 41 59);
|
||||
--el-table-text-color: rgb(148 163 184);
|
||||
--el-table-header-text-color: rgb(226 232 240);
|
||||
background: transparent;
|
||||
|
||||
th.el-table__cell {
|
||||
background: rgb(20, 20, 20);
|
||||
}
|
||||
|
||||
th.el-table__cell,
|
||||
td.el-table__cell {
|
||||
border-bottom-color: rgb(51 65 85);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,22 +1,68 @@
|
||||
<template>
|
||||
<div class="mySpring-analysis">
|
||||
<div class="analysis-layout">
|
||||
<div class="analysis-left">
|
||||
<section class="analysis-panel">左上</section>
|
||||
<section class="analysis-panel">左中</section>
|
||||
<section class="analysis-panel">左下</section>
|
||||
</div>
|
||||
<div class="analysis-right">
|
||||
<section class="analysis-panel">右上</section>
|
||||
<section class="analysis-panel">右下</section>
|
||||
<PageWrapper
|
||||
:contentFullHeight="true"
|
||||
title="false"
|
||||
contentClass="analysis-page-wrapper"
|
||||
>
|
||||
<div class="analysis-page">
|
||||
<div class="mySpring-analysis">
|
||||
<div class="analysis-layout">
|
||||
<div class="analysis-left">
|
||||
<section class="analysis-panel">
|
||||
<TodoInfo />
|
||||
</section>
|
||||
<section class="analysis-panel">
|
||||
<NoticeInfo />
|
||||
</section>
|
||||
<section class="analysis-panel">
|
||||
<HostInfo />
|
||||
</section>
|
||||
</div>
|
||||
<div class="analysis-right">
|
||||
<section class="analysis-panel">右上</section>
|
||||
<section class="analysis-panel">右下</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PageWrapper>
|
||||
</template>
|
||||
<script lang="ts" setup name="Analysis"></script>
|
||||
<script lang="ts" setup name="Analysis">
|
||||
import { PageWrapper } from '@jeesite/core/components/Page';
|
||||
import HostInfo from './components/HostInfo.vue';
|
||||
import TodoInfo from './components/TodoInfo.vue';
|
||||
import NoticeInfo from './components/NoticeInfo.vue';
|
||||
</script>
|
||||
<style lang="less">
|
||||
@dark-bg: #141414;
|
||||
|
||||
.analysis-page-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
padding: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
overflow: hidden !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.analysis-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.mySpring-analysis {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mySpring-analysis .ant-card {
|
||||
border-radius: 10px !important;
|
||||
}
|
||||
@@ -33,6 +79,7 @@
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
min-height: 0;
|
||||
gap: 12px;
|
||||
padding: 4px;
|
||||
@@ -47,6 +94,7 @@
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
min-height: 0;
|
||||
gap: 12px;
|
||||
box-sizing: border-box;
|
||||
@@ -63,14 +111,17 @@
|
||||
}
|
||||
|
||||
.mySpring-analysis .analysis-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgb(226 232 240);
|
||||
background: rgb(248 250 252);
|
||||
box-shadow: 0 1px 3px rgb(15 23 42 / 0.06);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
color: rgb(71 85 105);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user