Browse Source

修改巡视计划,按照站内的要求修改表头

master
douyage 1 month ago
parent
commit
dca0df710f
11 changed files with 187 additions and 274 deletions
  1. BIN
      src/assets/images/header/framework-avatar.png
  2. BIN
      src/assets/images/header/xfdh-title-cropped.png
  3. BIN
      src/assets/images/header/xfdh-title-filled.png
  4. +1
    -0
      src/assets/images/header/xfdh-title.svg
  5. +2
    -2
      src/assets/styles/sidebar.scss
  6. +3
    -3
      src/layout/components/AppMain.vue
  7. +72
    -9
      src/layout/components/Navbar.vue
  8. +26
    -2
      src/permission.js
  9. BIN
      src/views/cards/assets/year-task.png
  10. +6
    -3
      src/views/cards/site_management/site_management.vue
  11. +77
    -255
      src/views/cards/tasksShow/yearDisplay.vue

BIN
src/assets/images/header/framework-avatar.png View File

Before After
Width: 58  |  Height: 61  |  Size: 5.4 KiB

BIN
src/assets/images/header/xfdh-title-cropped.png View File

Before After
Width: 1290  |  Height: 75  |  Size: 73 KiB

BIN
src/assets/images/header/xfdh-title-filled.png View File

Before After
Width: 1921  |  Height: 75  |  Size: 87 KiB

+ 1
- 0
src/assets/images/header/xfdh-title.svg
File diff suppressed because it is too large
View File


+ 2
- 2
src/assets/styles/sidebar.scss View File

@ -20,10 +20,10 @@
background-repeat: no-repeat;
background-position: center bottom;
background-size: 100% 100%;
height: calc(100% - 68px);
height: calc(100% - 50px);
position: fixed;
font-size: 0px;
top: 68px;
top: 50px;
bottom: 0;
left: 0;
z-index: 1001;


+ 3
- 3
src/layout/components/AppMain.vue View File

@ -44,8 +44,8 @@ export default {
<style lang="scss" scoped>
.app-main {
/* 68 = navbar */
height: calc(100vh - 68px);
/* 50 = navbar */
height: calc(100vh - 50px);
min-height: 0;
width: 100%;
position: relative;
@ -53,7 +53,7 @@ export default {
}
.fixed-header + .app-main {
padding-top: 68px;
padding-top: 50px;
}
.app-main.hasTagsView {


+ 72
- 9
src/layout/components/Navbar.vue View File

@ -30,8 +30,8 @@
trigger="click"
>
<div class="avatar-wrapper">
<img :src="loginAvatar" class="user-avatar" />
<i class="el-icon-caret-bottom" />
<span class="user-name">{{ displayName }}</span>
<img :src="displayAvatar" class="user-avatar" />
</div>
<el-dropdown-menu slot="dropdown">
<!-- <router-link to="/user/profile">
@ -51,9 +51,9 @@ import { mapGetters } from "vuex";
import TopNav from "@/components/TopNav";
import Screenfull from "@/components/Screenfull";
import headerLogo from "@/assets/images/header/logo.png";
import xfdhHeader from "@/assets/images/header/xfdh-title.png";
import xfdhHeader from "@/assets/images/header/xfdh-title-filled.png";
import xfdhBrand from "@/assets/images/header/xfdh-brand.png";
import loginAvatar from "@/assets/images/aa-logo.png";
import loginAvatar from "@/assets/images/header/framework-avatar.png";
export default {
components: {
TopNav,
@ -64,11 +64,13 @@ export default {
headerLogo,
xfdhHeader,
xfdhBrand,
loginAvatar
loginAvatar,
frameworkUser: null,
frameworkUserPoller: null
};
},
computed: {
...mapGetters(["avatar", "device"]),
...mapGetters(["name", "device"]),
topNav: {
get() {
return this.$store.state.settings.topNav;
@ -76,9 +78,47 @@ export default {
},
isXfdh() {
return true
},
displayName() {
return (this.frameworkUser && (this.frameworkUser.realName || this.frameworkUser.userName || this.frameworkUser.username)) || this.name || '超级管理员'
},
displayAvatar() {
return this.loginAvatar
}
},
mounted() {
this.loadFrameworkUser()
this.frameworkUserPoller = window.setInterval(this.loadFrameworkUser, 500)
},
beforeDestroy() {
if (this.frameworkUserPoller) window.clearInterval(this.frameworkUserPoller)
},
methods: {
loadFrameworkUser() {
try {
const contexts = [window]
try { if (window.parent && contexts.indexOf(window.parent) === -1) contexts.push(window.parent) } catch (e) {}
try { if (window.top && contexts.indexOf(window.top) === -1) contexts.push(window.top) } catch (e) {}
let cachedUser = null
for (let index = 0; index < contexts.length && !cachedUser; index += 1) {
try { cachedUser = contexts[index].sessionStorage.getItem('main_userInfo') } catch (e) {}
}
if (cachedUser) {
const user = JSON.parse(cachedUser)
if (user && typeof user === 'object') {
this.frameworkUser = user
if (this.frameworkUserPoller) {
window.clearInterval(this.frameworkUserPoller)
this.frameworkUserPoller = null
}
}
}
return
} catch (error) {
// 使
console.warn('[framework-auth] unable to load external user', error)
}
},
async logout() {
this.$confirm("确定注销并退出系统吗?", "提示", {
confirmButtonText: "确定",
@ -86,6 +126,16 @@ export default {
type: "warning",
})
.then(() => {
sessionStorage.removeItem('joinbright-token');
sessionStorage.removeItem('main_userInfo');
sessionStorage.removeItem('main_joinbright_token');
sessionStorage.removeItem('main_islogin');
if (window.parent && window.parent !== window) {
window.parent.postMessage(JSON.stringify({
event: { type: "LOGOUT" },
source: "xfdh"
}), "*");
}
this.$store.dispatch("LogOut").then(() => {
location.href = "/index";
});
@ -100,7 +150,7 @@ export default {
@import "~@/assets/styles/variables.scss";
.navbar {
height: 68px;
height: 50px;
overflow: hidden;
position: relative;
display: flex;
@ -112,6 +162,7 @@ export default {
&.xfdh-navbar {
background-size: 100% 100%;
background-position: center center;
.navbar-brand {
width: 470px;
@ -119,8 +170,8 @@ export default {
}
.header-logo {
width: 245px;
height: 48px;
width: 220px;
height: 40px;
}
.platform-title {
@ -239,6 +290,18 @@ export default {
align-items: center;
padding-right: 16px;
.user-name {
max-width: 150px;
margin-right: 12px;
overflow: hidden;
color: #fff;
font-size: 15px;
font-weight: 600;
line-height: 40px;
text-overflow: ellipsis;
white-space: nowrap;
}
.user-avatar {
cursor: pointer;
width: 40px;


+ 26
- 2
src/permission.js View File

@ -11,15 +11,39 @@ NProgress.configure({ showSpinner: false });
const whiteList = ["/login", "/auth-redirect", "/bind", "/register"];
// The embedded framework provides the authenticated user in sessionStorage.
// During the first navigation its token cookie may not be available yet, so
// treat this session as authenticated and avoid rendering the login route.
function hasEmbeddedSession() {
const contexts = [window];
try { if (window.parent && window.parent !== window) contexts.push(window.parent); } catch (e) {}
try { if (window.top && window.top !== window && contexts.indexOf(window.top) === -1) contexts.push(window.top); } catch (e) {}
for (let index = 0; index < contexts.length; index++) {
try {
const raw = contexts[index].sessionStorage.getItem("main_userInfo");
if (!raw) continue;
const user = JSON.parse(raw);
if (user && (user.id || user.username || user.realName)) return true;
} catch (e) {}
}
return false;
}
router.beforeEach(async (to, from, next) => {
// NProgress.start();
if (getToken()) {
const embeddedSession = hasEmbeddedSession();
if (getToken() || embeddedSession) {
to.meta.title && store.dispatch("settings/setTitle", to.meta.title);
/* has token*/
if (to.path === "/login") {
console.info("[GAB] to next");
next({ path: "" });
next({ path: "/xfdh/index", replace: true });
// NProgress.done();
} else if (embeddedSession && !getToken()) {
// The host framework already authenticated this embedded page. Avoid
// calling the standalone admin API, which would fail without its token
// and cause a redirect to the login screen during first paint.
next();
} else {
console.info("[GAB] to next 2");
if (store.getters.roles.length === 0) {


BIN
src/views/cards/assets/year-task.png View File

Before After
Width: 113  |  Height: 109  |  Size: 22 KiB

+ 6
- 3
src/views/cards/site_management/site_management.vue View File

@ -3639,7 +3639,7 @@ export default {
.sec-line {
width: 100%;
height: 100%;
padding: 10px;
padding: 0 10px 10px;
box-sizing: border-box;
overflow: hidden;
display: flex;
@ -3707,13 +3707,14 @@ export default {
.search {
width: 100%;
position: relative;
margin-top: 0;
margin-bottom: 10px;
.el-input__inner {
background-color: #1e3a4a;
border-color: #2a4a5a;
color: #ffffff;
height: 36px;
height: 32px;
padding-right: 45px;
}
@ -3721,7 +3722,7 @@ export default {
position: absolute;
top: 0;
right: 0;
height: 36px;
height: 32px;
width: 40px;
padding: 0;
border-radius: 0 4px 4px 0;
@ -3839,6 +3840,8 @@ export default {
gap: 12px;
flex-shrink: 0;
min-height: 34px;
padding-top: 3px;
box-sizing: border-box;
}
/* 右侧面板样式 - 完全独立不受影响 */


+ 77
- 255
src/views/cards/tasksShow/yearDisplay.vue View File

@ -2,46 +2,43 @@
<div class="year-view">
<div class="year-header">
<h2>{{ currentYear }}</h2>
<div class="year-controls">
<el-date-picker
v-model="selectedYear"
style="float: right; width: 120px"
type="year"
size="mini"
placeholder="选择年"
@change="onYearChange"
>
</el-date-picker>
</div>
<el-date-picker
v-model="selectedYear"
class="year-picker"
type="year"
size="mini"
placeholder="选择年份"
@change="onYearChange"
/>
</div>
<div class="months-grid">
<div class="month-card" v-for="month in 12" :key="month">
<div v-loading="loading" class="months-grid">
<div v-for="month in 12" :key="month" class="month-card">
<div class="month-title">{{ getChineseMonth(month) }}</div>
<!-- <div class="month-stats" v-if="hasData(month)"> -->
<div class="month-stats">
<div class="stat-item">
正在执行:
<span class="status-executing">{{
getMonthData(month).executing
}}</span>
</div>
<div class="stat-item">
已经完成:
<span class="status-completed">{{
getMonthData(month).executed
}}</span>
</div>
<div class="stat-item">
:
<span class="status-not-executed">{{
getMonthData(month).pending
}}</span>
<div class="month-body">
<div class="month-stats">
<div class="stat-item executing">
<i class="status-dot" />
<span class="stat-label">正在执行:</span>
<strong>{{ getMonthData(month).executing }}</strong>
</div>
<div class="stat-item completed">
<i class="status-dot" />
<span class="stat-label">已经完成:</span>
<strong>{{ getMonthData(month).executed }}</strong>
</div>
<div class="stat-item pending">
<i class="status-dot" />
<span class="stat-label">未执行:</span>
<strong>{{ getMonthData(month).pending }}</strong>
</div>
</div>
<img
class="month-icon"
:src="yearTaskIcon"
alt=""
/>
</div>
<!-- <div class="month-stats empty" v-else>
<div class="stat-item">无数据</div>
</div> -->
</div>
</div>
</div>
@ -49,265 +46,90 @@
<script>
import { calenderYear } from "@/api/videosMonitor/index";
import yearTaskIcon from "@/views/cards/assets/year-task.png";
export default {
name: "YearView",
props: {
year: {
type: Number,
required: true,
},
},
props: { year: { type: Number, required: true } },
data() {
//
const currentYear = new Date().getFullYear();
return {
selectedYear: new Date(currentYear, 0, 1),
// 使
currentYear: currentYear,
chineseMonths: [
"一月",
"二月",
"三月",
"四月",
"五月",
"六月",
"七月",
"八月",
"九月",
"十月",
"十一月",
"十二月",
],
//
currentYear,
chineseMonths: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
yearDataMap: {},
loading: false,
yearTaskIcon,
};
},
watch: {
// prop
year(newYear) {
this.currentYear = newYear;
this.selectedYear = new Date(newYear, 0, 1);
//
this.getYearData();
},
},
mounted() {
//
this.getYearData();
},
mounted() { this.getYearData(); },
methods: {
onYearChange() {
if (this.selectedYear) {
this.currentYear = this.selectedYear.getFullYear();
//
this.$emit("year-changed", this.currentYear);
//
this.getYearData();
}
if (!this.selectedYear) return;
this.currentYear = this.selectedYear.getFullYear();
this.$emit("year-changed", this.currentYear);
this.getYearData();
},
//
async getYearData() {
this.loading = true;
try {
this.loading = true;
const response = await calenderYear({
year: this.currentYear,
});
//
const response = await calenderYear({ year: this.currentYear });
const data = response?.rows || response || [];
//
const monthData = {};
data.forEach(item => {
monthData[item.month] = {
executed: item.executed || 0,
executing: item.executing || 0,
pending: item.pending || 0,
total: item.total || 0
const month = Number(item.month);
if (!month) return;
monthData[month] = {
executed: Number(item.executed) || 0,
executing: Number(item.executing) || 0,
pending: Number(item.pending) || 0,
total: Number(item.total) || 0,
};
});
//
this.yearDataMap = {
[this.currentYear]: monthData
};
this.yearDataMap = { [this.currentYear]: monthData };
} catch (error) {
console.error('获取年份数据失败:', error);
//
this.yearDataMap = {
[this.currentYear]: {}
};
} finally {
this.loading = false;
}
console.error("获取年份数据失败:", error);
this.yearDataMap = { [this.currentYear]: {} };
} finally { this.loading = false; }
},
getMonthData(month) {
// null
const yearData = this.yearDataMap[this.currentYear];
//
if (!yearData) {
return {
executed: 0,
executing: 0,
pending: 0,
total: 0
};
}
//
const monthStats = yearData[month];
if (!monthStats) {
return {
executed: 0,
executing: 0,
pending: 0,
total: 0
};
}
//
return monthStats;
return (this.yearDataMap[this.currentYear] || {})[month] || { executed: 0, executing: 0, pending: 0, total: 0 };
},
hasData(month) {
const data = this.getMonthData(month);
return data.executed > 0 || data.executing > 0 || data.pending > 0 || data.total > 0;
},
getChineseMonth(monthNumber) {
return this.chineseMonths[monthNumber - 1] || monthNumber + "月";
},
getChineseMonth(month) { return this.chineseMonths[month - 1]; },
},
};
</script>
<style scoped>
.year-view {
width: 100%;
min-height: 100%;
color: #e8f2ff;
}
.year-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.year-header h2 {
margin: 0;
font-size: 20px;
color: #b9d6ff;
}
.year-controls {
display: flex;
gap: 10px;
align-items: center;
}
.months-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 15px;
}
.month-card {
background: linear-gradient(135deg, rgba(18, 37, 105, 0.96), rgba(15, 31, 92, 0.96));
display: flex;
flex-direction: column;
min-height: 220px;
padding: 24px 15px 28px;
border-radius: 0;
border: 1px solid #2787ed;
box-shadow: inset 0 0 18px rgba(23, 91, 196, 0.12);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.month-card:hover {
border-color: #4fb5ff;
box-shadow: inset 0 0 22px rgba(31, 116, 229, 0.2), 0 0 8px rgba(39, 135, 237, 0.25);
}
.month-title {
font-size: 20px;
font-weight: bold;
margin-bottom: 15px;
color: #35bcff;
text-align: left;
}
.month-stats {
display: flex;
flex-direction: column;
gap: 8px;
align-items: flex-end;
margin-top: auto;
}
.month-stats.empty {
text-align: center;
padding: 20px 0;
}
.stat-item {
display: flex;
justify-content: flex-start;
gap: 8px;
min-width: 120px;
font-size: 14px;
color: #dceaff;
text-align: left;
}
.status-executing {
color: #fff;
font-weight: bold;
}
.status-completed {
color: #fff;
font-weight: bold;
}
.status-not-executed {
color: #fff;
font-weight: bold;
}
/* 覆盖Element UI的默认样式 */
:deep(.el-date-editor--year) {
background-color: #0b1d58;
border: 1px solid #438fe5;
border-radius: 0;
}
:deep(.el-input__inner) {
background-color: #0b1d58;
color: #dceaff;
border: 1px solid #438fe5;
}
:deep(.el-input__inner::placeholder) {
color: #7da8d8;
}
:deep(.el-date-picker__header-label) {
color: #dceaff;
}
:deep(.el-date-picker__year-btn) {
color: #dceaff;
}
:deep(.el-date-picker__year-btn:hover) {
color: #1a53ff;
}
:deep(.el-picker-panel) {
background-color: #0b1d58;
border: 1px solid #438fe5;
}
.year-view { width: 100%; min-height: 100%; color: #e8f2ff; }
.year-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; }
.year-header h2 { margin: 0; font-size: 16px; font-weight: 400; color: #b9d6ff; }
.year-picker { width: 100px; }
.months-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; }
.month-card { min-height: 220px; padding: 17px 22px; box-sizing: border-box; background: #17266e; border: 1px solid #2865c3; transition: border-color .2s, box-shadow .2s; }
.month-card:hover { border-color: #43b9ff; box-shadow: 0 0 12px rgba(39, 135, 237, .28); }
.month-title { color: #38b8ff; font-size: 25px; line-height: 1; font-weight: 500; }
.month-body { display: flex; align-items: center; justify-content: space-between; height: 165px; }
.month-stats { display: flex; flex-direction: column; gap: 8px; }
.stat-item { display: flex; align-items: center; min-width: 145px; height: 23px; padding: 0 8px; box-sizing: border-box; color: #dbe8ff; font-size: 14px; background: rgba(24, 56, 130, .85); }
.stat-item strong { margin-left: auto; font-weight: 400; color: #e5efff; }
.status-dot { width: 8px; height: 8px; margin-right: 8px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }
.executing { color: #147fc1; }.completed { color: #178a70; }.pending { color: #536c9f; }
.stat-label { color: #dbe8ff; }
.month-icon { width: 82px; height: auto; margin: 12px 4px 0 8px; object-fit: contain; }
:deep(.year-picker .el-input__inner) { height: 26px; padding: 0 8px; color: #dceaff; background: #10225f; border: 1px solid #438fe5; border-radius: 0; }
:deep(.year-picker .el-input__prefix), :deep(.year-picker .el-input__suffix) { color: #a8c9ef; }
@media (max-width: 1100px) { .months-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 760px) { .months-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }.month-card { padding: 14px; }.month-icon { width: 65px; }.stat-item { min-width: 125px; font-size: 12px; } }
</style>

Loading…
Cancel
Save