综合服务大屏

This commit is contained in:
zjf committed 2025-07-08 18:33:46 +08:00
1 parent d4f0205c4d
commit 94665f63f8
5 files changed
+596

No files matched your search

+7
View File
@@ -39,3 +39,10 @@ export function putObj(obj?: Object) {
})
}
export function getScreenFeedbackList() {
return request({
url: '/integrated/feedback/screen/list',
method: 'get'
})
}
+7
View File
@@ -39,3 +39,10 @@ export function putObj(obj?: Object) {
})
}
export function getScreenLostAndFoundList() {
return request({
url: '/integrated/lostAndFound/screen/list',
method: 'get'
})
}
+7
View File
@@ -39,3 +39,10 @@ export function putObj(obj?: Object) {
})
}
export function getScreenMaintenanceOrderList() {
return request({
url: '/integrated/maintenanceOrder/screen/list',
method: 'get'
})
}
+8
View File
@@ -39,3 +39,11 @@ export function putObj(obj?: Object) {
})
}
export function getScreenNoticeList(params?: Object) {
return request({
url: '/integrated/notice/screen/list',
method: 'get',
params
})
}
+567
View File
@@ -0,0 +1,567 @@
<template>
<div class="tech-bg" ref="techBgRef">
<div class="fullscreen-btn" @click="toggleFullscreen">
<svg v-if="!isFullscreen" width="24" height="24" viewBox="0 0 24 24"><path fill="#00eaff" d="M4 4h7V2H2v9h2V4zm16 0v7h2V2h-9v2h7zm0 16h-7v2h9v-9h-2v7zM4 20v-7H2v9h9v-2H4z"/></svg>
<svg v-else width="24" height="24" viewBox="0 0 24 24"><path fill="#00eaff" d="M15 5V3h6v6h-2V6.41l-5.29 5.3-1.42-1.42L17.59 5H15zm-6 14v2H3v-6h2v3.59l5.29-5.3 1.42 1.42L6.41 19H9zm10-6h2v6a2 2 0 0 1-2 2h-6v-2h6v-6zm-8-8H5a2 2 0 0 0-2 2v6h2V5h6V3z"/></svg>
</div>
<div class="screen-header">
<div class="screen-header-bar"></div>
<div class="screen-header-title-wrap">
<div class="screen-header-title">智慧服务综合大屏</div>
</div>
</div>
<div class="tech-container">
<div class="tech-grid">
<div class="tech-panel tech-panel-feedback" style="grid-row: span 2;">
<div class="tech-panel-title"><span class="tech-panel-title-bar"></span>意见反馈</div>
<div class="tech-panel-content">
<div class="feedback-header">
<span class="feedback-title">标题</span>
<span class="feedback-type">类型</span>
<span class="feedback-time">反馈时间</span>
</div>
<ul v-if="feedbackList.length" class="feedback-list">
<li v-for="item in feedbackList" :key="item.title + item.createTime" class="feedback-item">
<span class="feedback-title">{{ item.title }}</span>
<span class="feedback-type">{{ item.typeLabel }}</span>
<span class="feedback-time">{{ item.createTime?.slice(0, 16) }}</span>
</li>
</ul>
<div v-else class="feedback-empty">暂无意见反馈</div>
</div>
</div>
<div class="tech-panel tech-panel-notice" style="grid-row: span 2;">
<div class="tech-panel-title"><span class="tech-panel-title-bar"></span>通知</div>
<div class="tech-panel-content">
<div class="tech-notice-bar">
<span class="tech-notice-icon">
<svg width="22" height="22" viewBox="0 0 1024 1024"><path fill="#00eaff" d="M192 416v192h192l224 192V224L384 416H192zm576 96c0-77.6-54.4-142.4-128-156.8v313.6c73.6-14.4 128-79.2 128-156.8zM704 96v96c141.6 15.2 256 135.2 256 288s-114.4 272.8-256 288v96c196.8-15.2 352-185.6 352-384S900.8 111.2 704 96z"/></svg>
</span>
<div class="tech-notice-content">
<div v-for="(item, idx) in noticeList" :key="item.createTime + item.title" class="tech-notice-item-vertical">
<div class="tech-notice-title-row">
<span class="tech-notice-time">{{ item.createTime?.slice(5, 16) }}</span>
<span class="tech-notice-title" @click="toggleNoticeDetail(idx)">{{ item.title }}</span>
</div>
<div v-if=" hasValidDescription(item.description)" class="tech-notice-description" v-html="item.description"></div>
</div>
</div>
</div>
</div>
</div>
<div class="tech-panel">
<div class="tech-panel-title"><span class="tech-panel-title-bar"></span>失物招领</div>
<div class="tech-panel-content">
<div class="lost-header">
<span class="lost-title">标题</span>
<span class="lost-location">发现地点</span>
<span class="lost-date">发现日期</span>
</div>
<ul v-if="lostList.length" class="lost-list">
<li v-for="item in lostList" :key="item.title + item.foundDate" class="lost-item">
<span class="lost-title">{{ item.title }}</span>
<span class="lost-location">{{ item.foundLocation }}</span>
<span class="lost-date">{{ item.foundDate }}</span>
</li>
</ul>
<div v-else class="lost-empty">暂无失物招领</div>
</div>
</div>
<div class="tech-panel">
<div class="tech-panel-title"><span class="tech-panel-title-bar"></span>维修工单</div>
<div class="tech-panel-content">
<div class="maintenance-header">
<span class="maintenance-title">标题</span>
<span class="maintenance-date">报修日期</span>
<span class="maintenance-location">报修地点</span>
<span class="maintenance-urgency">紧急程度</span>
</div>
<ul v-if="maintenanceList.length" class="maintenance-list">
<li v-for="item in maintenanceList" :key="item.title + (item.dateFound || '') + (item.location || '')" class="maintenance-item">
<span class="maintenance-title">{{ item.title }}</span>
<span class="maintenance-date">{{ item.dateFound || '-' }}</span>
<span class="maintenance-location">{{ item.location || '-' }}</span>
<span class="maintenance-urgency">{{ item.urgencyLabel || '-' }}</span>
</li>
</ul>
<div v-else class="maintenance-empty">暂无维修工单</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { getScreenNoticeList } from '/@/api/integrated/notice';
import { getScreenLostAndFoundList } from '/@/api/integrated/lostAndFound';
import { getScreenFeedbackList } from '/@/api/integrated/feedback';
import { getScreenMaintenanceOrderList } from '/@/api/integrated/maintenanceOrder';
const noticeList = ref<any[]>([]);
const lostList = ref<any[]>([]);
const feedbackList = ref<any[]>([]);
const maintenanceList = ref<any[]>([]);
const isFullscreen = ref(false);
const techBgRef = ref<HTMLElement | null>(null);
const noticeOpenIndex = ref<number | null>(null);
const gridBlocks = ref([
{ title: '失物招领' },
{ title: '意见反馈' },
{ title: '维修工单' },
{ title: '区块四', content: '内容区4,可放图表/列表/指标等' },
{ title: '区块五', content: '内容区5,可放图表/列表/指标等' },
{ title: '区块六', content: '内容区6,可放图表/列表/指标等' },
]);
function toggleFullscreen() {
const el = techBgRef.value;
if (!isFullscreen.value && el) {
if (el.requestFullscreen) el.requestFullscreen();
else if ((el as any).webkitRequestFullscreen) (el as any).webkitRequestFullscreen();
else if ((el as any).mozRequestFullScreen) (el as any).mozRequestFullScreen();
else if ((el as any).msRequestFullscreen) (el as any).msRequestFullscreen();
} else {
if (document.exitFullscreen) document.exitFullscreen();
else if ((document as any).webkitExitFullscreen) (document as any).webkitExitFullscreen();
else if ((document as any).mozCancelFullScreen) (document as any).mozCancelFullScreen();
else if ((document as any).msExitFullscreen) (document as any).msExitFullscreen();
}
}
document.addEventListener('fullscreenchange', () => {
isFullscreen.value = document.fullscreenElement === techBgRef.value;
});
function toggleNoticeDetail(idx: number) {
noticeOpenIndex.value = noticeOpenIndex.value === idx ? null : idx;
}
function hasValidDescription(desc: string | undefined | null) {
if (!desc || typeof desc !== 'string') return false;
// 去除所有 HTML 标签和空白
const text = desc.replace(/<[^>]*>/g, '').replace(/\s/g, '');
return text.length > 0;
}
onMounted(async () => {
const res = await getScreenNoticeList();
noticeList.value = Array.isArray(res.data) ? res.data : [];
// 获取失物招领数据
const lostRes = await getScreenLostAndFoundList();
lostList.value = Array.isArray(lostRes.data) ? lostRes.data : [];
// 获取意见反馈数据
const feedbackRes = await getScreenFeedbackList();
feedbackList.value = Array.isArray(feedbackRes.data) ? feedbackRes.data : [];
// 获取维修工单数据
const maintenanceRes = await getScreenMaintenanceOrderList();
maintenanceList.value = Array.isArray(maintenanceRes.data) ? maintenanceRes.data : [];
});
</script>
<style scoped>
.tech-bg {
min-height: 100vh;
width: 100vw;
background: radial-gradient(ellipse at center, #0a1a2a 0%, #081529 100%);
box-sizing: border-box;
padding: 24px;
position: relative;
overflow: auto;
border: 2px solid #00eaff;
border-radius: 18px;
box-shadow:
0 0 16px 2px #00eaff33,
0 0 0 4px #00334455 inset;
transition: box-shadow 0.3s, border-color 0.3s;
}
.screen-header {
width: 100%;
background: rgba(0,34,68,0.92);
border-bottom: 2px solid #00eaff;
box-shadow: none;
border-top-left-radius: 18px;
border-top-right-radius: 18px;
position: relative;
margin-bottom: 18px;
z-index: 10;
}
.screen-header-bar {
position: absolute;
left: 60px; right: 60px; top: 0;
height: 4px;
background: #003344;
border-radius: 2px;
box-shadow: none;
opacity: 0.5;
z-index: 2;
}
.screen-header-title-wrap {
display: flex;
align-items: center;
justify-content: center;
position: relative;
padding: 32px 0 18px 0;
}
.screen-header-title {
font-size: 3.2vw;
color: #00eaff;
font-weight: bold;
letter-spacing: 0.6vw;
text-shadow: 0 0 24px #00eaff, 0 0 48px #0ff;
z-index: 3;
position: relative;
}
.screen-header-decoration,
.screen-header-decoration.left,
.screen-header-decoration.right {
display: none !important;
}
.tech-container {
width: calc(1920px - 48px);
margin: 0 auto;
padding-top: 0;
position: relative;
}
.tech-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(2, 1fr);
gap: 32px;
width: 100%;
margin: 0 auto;
grid-auto-flow: dense;
}
.tech-panel {
background: linear-gradient(135deg, #0b223a 80%, #0a1a2a 100%);
border: 3px solid #00eaff;
border-radius: 22px;
box-shadow: 0 0 40px #00eaff33, 0 0 16px #0ff2, 0 0 0 3px #003344 inset;
position: relative;
display: flex;
flex-direction: column;
min-height: 220px;
overflow: hidden;
transition: box-shadow 0.2s, border-color 0.2s;
}
.tech-panel::before {
content: '';
position: absolute;
top: 0; left: 0; width: 60px; height: 24px;
background: linear-gradient(120deg, #00eaff 0 80%, transparent 100%);
clip-path: polygon(0 0, 100% 0, 80% 100%, 0 100%);
opacity: 0.22;
z-index: 2;
}
.tech-panel-title {
display: flex;
align-items: center;
font-size: 1.1vw;
font-weight: bold;
color: #00eaff;
padding: 18px 0 10px 24px;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
background: rgba(0,34,68,0.85);
letter-spacing: 2px;
box-shadow: none;
}
.tech-panel-title-bar {
display: inline-block;
width: 14px;
height: 32px;
background: linear-gradient(180deg, #00eaff 0%, #0ff 100%);
border-radius: 5px;
margin-right: 18px;
box-shadow: 0 0 12px #00eaff99;
}
.tech-panel-title::after {
content: '';
display: block;
height: 4px;
width: 120px;
background: linear-gradient(90deg, #00eaff 0%, #0ff 100%);
border-radius: 2px;
margin-left: 18px;
opacity: 0.18;
box-shadow: 0 0 8px #00eaff99;
}
.tech-panel-content {
flex: 1;
padding: 32px 40px 28px 40px;
color: #b6eaff;
font-size: 1vw;
display: block;
background: linear-gradient(135deg, #0a1a2a 60%, #0b223a 100%);
border-radius: 0 0 18px 18px;
box-shadow: 0 0 32px #00eaff22 inset;
position: relative;
z-index: 1;
}
.tech-panel:first-child .tech-panel-content {
height: 260px;
overflow-y: auto;
}
.tech-panel:nth-child(2) .tech-panel-content,
.feedback-panel {
height: 260px;
overflow-y: auto;
}
.tech-panel:nth-child(3) .tech-panel-content,
.maintenance-panel {
height: 260px;
overflow-y: auto;
}
.lost-list {
list-style: none;
margin: 0;
padding: 0;
width: 100%;
}
.lost-header, .lost-item {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
font-size: 1vw;
}
.lost-header {
font-weight: bold;
color: #00eaff;
border-bottom: 1.5px solid #00eaff44;
margin-bottom: 4px;
padding: 0 0 8px 0;
}
.lost-title {
flex: 2 1 0;
min-width: 0;
text-align: left;
}
.lost-location {
flex: 2 1 0;
min-width: 0;
text-align: left;
}
.lost-date {
flex: 1 1 0;
min-width: 0;
text-align: left;
}
.lost-item {
color: #b6eaff;
border-bottom: 1px solid #00eaff33;
padding: 8px 0;
}
.lost-empty {
text-align: center;
color: #b6eaff;
font-size: 1vw;
padding: 32px 0;
}
.feedback-header, .feedback-item {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
font-size: 1vw;
}
.feedback-header {
font-weight: bold;
color: #00eaff;
border-bottom: 1.5px solid #00eaff44;
margin-bottom: 4px;
padding: 0 0 8px 0;
}
.feedback-title {
flex: 2 1 0;
min-width: 0;
text-align: left;
}
.feedback-type {
flex: 2 1 0;
min-width: 0;
text-align: left;
}
.feedback-time {
flex: 2 1 0;
min-width: 0;
text-align: left;
}
.feedback-item {
color: #b6eaff;
border-bottom: 1px solid #00eaff33;
padding: 8px 0;
}
.feedback-empty {
text-align: center;
color: #b6eaff;
font-size: 1vw;
padding: 32px 0;
}
.maintenance-header, .maintenance-item {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
font-size: 1vw;
}
.maintenance-header {
font-weight: bold;
color: #00eaff;
border-bottom: 1.5px solid #00eaff44;
margin-bottom: 4px;
padding: 0 0 8px 0;
}
.maintenance-title {
flex: 2 1 0;
min-width: 0;
text-align: left;
}
.maintenance-date {
flex: 2 1 0;
min-width: 0;
text-align: left;
}
.maintenance-location {
flex: 2 1 0;
min-width: 0;
text-align: left;
}
.maintenance-urgency {
flex: 1 1 0;
min-width: 0;
text-align: left;
}
.maintenance-item {
color: #b6eaff;
border-bottom: 1px solid #00eaff33;
padding: 8px 0;
}
.maintenance-empty {
text-align: center;
color: #b6eaff;
font-size: 1vw;
padding: 32px 0;
}
@media (max-width: 1600px) {
.tech-container { width: 100vw; }
.tech-grid { gap: 18px; }
}
.fullscreen-btn {
position: fixed;
top: 32px;
right: 48px;
z-index: 9999;
background: rgba(0,34,68,0.85);
border: 2px solid #00eaff;
border-radius: 8px;
box-shadow: 0 0 12px #00eaff44;
padding: 12px 16px;
cursor: pointer;
transition: background 0.2s, box-shadow 0.2s;
display: flex;
align-items: center;
}
.fullscreen-btn:hover {
background: #003344;
box-shadow: 0 0 24px #00eaff99;
}
.tech-panel-feedback {
grid-row: span 2;
}
.tech-panel-notice {
grid-row: span 2;
}
.tech-panel-feedback .tech-panel-content,
.tech-panel-notice .tech-panel-content {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
padding: 32px 40px 28px 40px;
overflow: hidden;
}
.feedback-list,
.tech-notice-content {
flex: 1 1 0;
min-height: 0;
overflow-y: auto;
}
.tech-notice-item-vertical {
margin-bottom: 18px;
}
.tech-notice-title-row {
display: flex;
align-items: center;
margin-bottom: 6px;
}
.tech-notice-time {
color: #b6eaff;
margin-right: 16px;
min-width: 90px;
font-size: 0.98vw;
}
.tech-notice-title {
font-size: 1.15vw;
font-weight: bold;
color: #00eaff;
cursor: pointer;
text-decoration: underline;
transition: color 0.2s;
}
.tech-notice-title:hover {
color: #0ff;
}
.tech-notice-description {
margin-left: 0;
margin-top: 6px;
padding: 16px 18px;
background: rgba(0,34,68,0.92);
border-left: 3px solid #00eaff;
border-radius: 12px;
color: #b6eaff;
font-size: 0.98vw;
box-shadow: 0 0 12px #00eaff22 inset;
word-break: break-all;
max-width: 520px;
}
.tech-notice-description img {
max-width: 100%;
height: auto;
display: block;
margin: 8px 0;
border-radius: 6px;
box-shadow: 0 0 8px #00eaff33;
}
/* 失物招领、维修工单、意见反馈、通知内容区滚动与高度控制 */
.tech-panel-content {
/* 其它区块默认样式 */
}
.lost-list,
.maintenance-list {
max-height: 260px;
overflow-y: auto;
}
.lost-list li,
.maintenance-list li {
/* 保证每条高度一致,便于5条可见 */
min-height: 38px;
}
.feedback-list {
/* max-height: 420px; */
}
.tech-panel-feedback .tech-panel-content {
/* max-height: 480px; min-height: 420px; overflow-y: auto; */
}
.tech-panel-notice .tech-panel-content {
/* max-height: 480px; min-height: 420px; overflow-y: auto; */
}
.tech-notice-content {
/* max-height: 420px; */
}
</style>