first commit

This commit is contained in:
2026-08-26 14:25:17 +08:00
commit 06debfd1fc
10595 changed files with 1836924 additions and 0 deletions
+386
View File
@@ -0,0 +1,386 @@
const act = {
template: /*language=HTML*/ `
<!-- 活动 -->
<div class="activity-section-wrapper">
<div class="section-act">
<!-- Swiper容器 -->
<div v-if="actList && actList.length > 0" class="activity-swiper-container">
<!-- 活动数据 - Swiper -->
<div ref="activitySwiper" class="swiper activity-swiper">
<div class="swiper-wrapper">
<div class="swiper-slide" v-for="(activity, index) in actList" :key="activity.id">
<div class="item" @click="go(activity)">
<div class="img-box">
<img :src="activity.cover || 'https://www.ncu.edu.cn/__local/8/52/2C/644986B4C9A030F7B65300178A6_8679CB08_18467.jpg'"
alt="">
<span class="status">
{{getStatusText(activity)}}
</span>
</div>
<h4 class="title">
{{ activity.name}}
</h4>
<!--报名时间-->
<div class="time">
<p>
<i class="fa fa-clock-o"></i>
时间:{{ activity.startDate }}至{{ activity.endDate }}
</p>
</div>
</div>
</div>
</div>
</div>
<!-- 导航按钮 -->
<div ref="nextButton" class="swiper-button-next"></div>
<div ref="prevButton" class="swiper-button-prev"></div>
<!-- 进度条 -->
<div ref="pagination" class="swiper-pagination"></div>
</div>
<div v-else class="no-activity-placeholder">
<div class="icon">📅</div>
<p>暂无近期活动</p>
<p class="subtext">敬请关注后续通知</p>
</div>
</div>
</div>
`,
data() {
return {
actList: [],
swiper: null
}
},
methods: {
/*查询活动*/
listAct() {
this.$axios.post("/platform/home/listHomeActivity").then((res) => {
if (res.code === 0) {
// for (let i = 0; i < 10; i++) {
// this.actList = this.actList.concat(res.data)
// }
this.actList = res.data
// 数据加载完成后初始化Swiper
this.$nextTick(() => {
this.initSwiper()
})
}
})
},
/*初始化Swiper*/
initSwiper() {
if (this.swiper) {
this.swiper.destroy(true, true)
this.swiper = null
}
const swiperElement = this.$refs.activitySwiper
if (!swiperElement) {
return
}
// 仅操作当前组件的轮播节点,避免页面其他轮播或尺寸观察器影响卡片宽度。
this.swiper = new Swiper(swiperElement, {
slidesPerView: 1,
spaceBetween: 18,
centeredSlides: false,
loop: false,
watchOverflow: true,
observer: false,
observeParents: false,
resizeObserver: false,
// 鼠标停留在活动区域时可横向浏览,到首尾后释放页面滚动。
mousewheel: {
enabled: true,
forceToAxis: false,
releaseOnEdges: true,
sensitivity: 0.8,
},
navigation: {
nextEl: this.$refs.nextButton,
prevEl: this.$refs.prevButton,
},
pagination: {
el: this.$refs.pagination,
type: 'progressbar',
},
breakpoints: {
768: {
slidesPerView: 2,
},
1024: {
slidesPerView: 4,
},
1200: {
slidesPerView: 5,
}
}
})
},
// 活动状态
getStatusText(item) {
if (this.$moment().unix() > this.$moment(item.endDate).unix()) {
return "已结束"
} else if (this.$moment().unix() < this.$moment(item.startDate).unix()) {
return "未开始"
} else {
return "进行中"
}
},
go(act) {
if (!act.url) {
this.$message.warning("管理员未配置活动链接")
return
}
if (act.url === "/platform/activity/apply"
&& !this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN", "BRANCH_UNION_CHAIRMAN", "BRANCH_UNION_WENTI_SPORTS"])) {
return this.$message.warning("PC端暂未开放报名,请前往移动端报名!")
}
window.open(act.url)
}
},
mounted() {
this.listAct()
},
beforeDestroy() {
if (this.swiper) {
this.swiper.destroy(true, true)
this.swiper = null
}
},
style: /*language=CSS*/ `
.activity-section-wrapper {
width: 100%;
min-width: 0;
max-width: 100%;
box-sizing: border-box;
overflow: hidden;
}
.section-act {
width: 100%;
min-width: 0;
max-width: 100%;
}
.section-act .section-act-title span {
font-size: 30px;
color: #000000;
display: block;
font-weight: bold;
position: relative;
text-align: center;
padding: 40px 0;
}
/*.section-act .section-act-title span:hover {
color: var(--color-primary);
cursor: pointer;
}*/
/deep/ .section-act .section-act-title span::before {
content: '';
width: 24px;
height: 11px;
background: url(https://www.ncu.edu.cn/images/titl.svg) no-repeat center;
background-size: 24px 11px;
display: inline-block;
margin-right: 0;
vertical-align: middle;
}
/deep/ .section-act .section-act-title span::after {
content: '';
width: 24px;
height: 11px;
background: url(https://www.ncu.edu.cn/images/titr.svg) no-repeat center;
background-size: 24px 11px;
display: inline-block;
margin-left: 0;
vertical-align: middle;
}
/* Swiper容器样式 */
.activity-swiper-container {
position: relative;
width: 100%;
min-width: 0;
max-width: 100%;
box-sizing: border-box;
padding: 9px 54px 12px;
/*margin-top: 30px;*/
overflow: hidden;
}
.activity-swiper {
width: 100%;
min-width: 0;
/*padding: 0 50px 20px 50px;*/
overflow: hidden;
}
.activity-swiper .swiper-slide {
height: auto;
min-width: 0;
}
/* 导航按钮样式 */
.activity-swiper-container .swiper-button-next,
.activity-swiper-container .swiper-button-prev {
color: var(--color-primary);
background: white;
width: 40px;
height: 40px;
top: 72px;
margin-top: 0;
border-radius: 50%;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.activity-swiper-container .swiper-button-next:hover,
.activity-swiper-container .swiper-button-prev:hover {
background: var(--color-primary);
color: white;
transform: scale(1.1);
}
/deep/ .activity-swiper-container .swiper-button-next::after,
/deep/ .activity-swiper-container .swiper-button-prev::after {
font-size: 16px;
font-weight: bold;
}
/* 进度条不展示,活动通过左右箭头或区域内滚轮浏览。 */
.activity-swiper-container .swiper-pagination {
display: none;
}
.activity-swiper-container .swiper-pagination-progressbar {
background: #e0e0e0;
border-radius: 2px;
}
.activity-swiper-container .swiper-pagination-progressbar-fill {
background: var(--color-primary);
border-radius: 2px;
}
.activity-swiper .swiper-slide .item {
height: auto;
position: relative;
cursor: pointer;
min-width: 0;
overflow: hidden;
}
.activity-swiper .swiper-slide .item .img-box {
width: 100%;
height: 128px;
border-radius: 8px;
overflow: hidden;
position: relative;
}
.activity-swiper .swiper-slide .item .img-box .status {
position: absolute;
left: 0;
top: 0;
background: #c11623;
padding: 4px 2px;
min-width: 42px;
color: #ffffff;
border-radius: 0 0 50% 0;
font-size: 12px;
line-height: 1.2;
}
.activity-swiper .swiper-slide .item .img-box img {
display: block;
width: 100% !important;
height: 100% !important;
max-width: 100% !important;
max-height: 128px !important;
object-fit: cover !important;
transition: transform 0.3s ease;
}
.activity-swiper .swiper-slide .item:hover .img-box img {
transform: scale(1.1);
}
.activity-swiper .swiper-slide .item h4 {
font-size: 15px;
margin: 8px 0 0;
height: 42px;
color: #333;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.4;
text-align: left;
transition: color 0.3s ease;
}
.activity-swiper .swiper-slide .item:hover h4 {
color: var(--color-primary);
}
.activity-swiper .swiper-slide .item .time {
/*margin-top: 8px;*/
}
.activity-swiper .swiper-slide .item .time p {
font-size: 12px;
margin: 0;
line-height: 1.4;
color: #666;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.activity-swiper .swiper-slide .item .time i {
margin-right: 4px;
color: var(--color-primary);
}
/* 无活动占位提示 */
.no-activity-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 20px;
color: #94a3b8;
text-align: center;
}
.no-activity-placeholder .icon {
font-size: 48px;
margin-bottom: 12px;
opacity: 0.6;
}
.no-activity-placeholder p {
margin: 4px 0 0;
font-size: 16px;
font-weight: 500;
}
.no-activity-placeholder .subtext {
font-size: 13px;
opacity: 0.8;
}
`
};
@@ -0,0 +1,274 @@
const entry = {
template: /*language=HTML*/ `
<div class="entry-wrapper">
<div :class="['entry-section', 'entry-section-' + section.key]" v-for="section in entrySections" :key="section.key">
<div class="entry-section-header">
<div class="entry-section-title">
<i :class="section.icon"></i>
<span>{{ section.title }}</span>
<em>/Applications</em>
</div>
</div>
<div class="apps-container">
<div class="app-grid">
<div class="app-item"
v-for="(item, index) in section.list"
:key="item.id || index"
@click="openService(item, section.key)">
<div class="app-icon">
<img v-if="item.picIcon" class="app-icon-img" :src="item.picIcon" alt="" />
<i v-else class="fa fa-cube app-icon-fallback"></i>
</div>
<div class="app-name">{{ item.name }}</div>
</div>
<el-empty class="app-empty"
v-if="!section.list || section.list.length === 0"
description="暂无数据"
:image-size="72">
</el-empty>
</div>
</div>
</div>
</div>
`,
data() {
return {
hasMore: true,
entries: {
serv: [],
app: [],
fav: []
},
}
},
computed: {
entrySections() {
return [
{ key: 'serv', title: '推荐服务', icon: 'fa fa-star', list: this.entries.serv },
{ key: 'app', title: '推荐应用', icon: 'fa fa-fire', list: this.entries.app },
{ key: 'fav', title: '我的收藏', icon: 'fa fa-heart', list: this.entries.fav }
];
}
},
created() {
this.getRecommendService()
this.getRecommendApp()
this.getFavorite()
},
methods: {
// 查询推荐服务
async getRecommendService() {
this.$axios.post('/platform/home/listRecommendService', {platform: "PC"}).then(res => {
if (res.code === 0) {
this.entries.serv = res.data
}
})
},
// 获取推荐应用
async getRecommendApp() {
this.$axios.post('/platform/home/listRecommendApp', {platform: "PC"}).then(res => {
if (res.code === 0) {
this.entries.app = res.data
}
})
},
// 获取我的收藏
async getFavorite() {
this.$axios.post('/platform/home/listFavorite', {platform: "PC"}).then(res => {
if (res.code === 0) {
this.entries.fav = res.data
}
})
},
// 点击服务或应用
openService(service, category) {
if (category === 'serv') {
if (!service.href) {
this.$message.error('无效的链接地址')
return
}
window.open(service.href)
return
}
if (category === 'app') {
window.sessionStorage.setItem("zhgh_sub_app", JSON.stringify(service))
window.open("/platform/v4/subApp?appId=" + service.id, "_blank")
return
}
if (category === 'fav') {
if (service.href) {
window.open(service.href)
return
}
if (!service.parentId) {
window.sessionStorage.setItem("zhgh_sub_app", JSON.stringify(service))
window.open("/platform/v4/subApp?appId=" + service.id, "_blank")
return
}
this.$message.error('无效的链接地址')
}
}
},
style: /*language=CSS*/ `
.entry-wrapper {
width: 100%;
box-sizing: border-box;
}
.entry-section + .entry-section {
margin-top: 22px;
}
/* 临时屏蔽“我的收藏”分组,保留原节点和数据逻辑便于恢复 */
.entry-section-fav {
display: none;
}
.entry-section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.entry-section-title {
display: inline-flex;
align-items: baseline;
gap: 6px;
color: #19324d;
font-size: 18px;
font-weight: 700;
}
.entry-section-title i {
color: #409eff;
font-size: 16px;
}
.entry-section-title em {
color: #b5c0d6;
font-size: 14px;
font-style: normal;
font-weight: 500;
}
.apps-container {
display: flex;
flex-direction: column;
gap: 20px;
}
.app-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(108px, 1fr));
justify-items: center;
justify-content: start;
gap: 12px 18px;
min-height: 116px;
padding: 18px 20px;
background: #fff;
border: 1px solid #edf1f7;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
overflow-x: hidden;
}
.app-item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
border-radius: 8px;
background: #fff;
width: 100%;
max-width: 108px;
min-width: 0;
box-sizing: border-box;
padding: 6px;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
overflow: hidden;
}
.app-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.app-icon {
width: 56px;
height: 56px;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 56px;
overflow: hidden;
border-radius: 8px;
background: #f5f8ff;
}
.app-icon img,
.app-icon-img {
display: block;
width: 56px !important;
height: 56px !important;
max-width: 56px !important;
max-height: 56px !important;
object-fit: contain !important;
border-radius: 8px;
}
.app-icon-fallback {
color: #409eff;
font-size: 24px;
}
.app-name {
min-height: 34px;
width: 100%;
font-size: 12px;
color: #333;
line-height: 1.4;
word-break: break-word;
text-align: center;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.app-empty {
grid-column: 1 / -1;
width: 100%;
}
@media (max-width: 768px) {
.app-grid {
grid-template-columns: repeat(3, minmax(72px, 1fr));
gap: 12px;
padding: 14px 10px;
}
.app-item {
padding: 8px 4px;
}
}
@media (max-width: 480px) {
.app-grid {
grid-template-columns: repeat(2, minmax(72px, 1fr));
gap: 12px;
}
}
`
};
@@ -0,0 +1,204 @@
<!--#
layout("/layouts/v4/baseLayout.html"){
#-->
<style>
.section-banner {
position: relative;
overflow: hidden;
}
.section-banner .banner-img {
width: 100%;
height: auto;
}
.section-banner .banner-img img {
width: 100%;
height: auto;
display: block;
}
.section-wrapper {
width: 100%;
}
.home-grid {
width: 80%;
margin: 20px auto;
display: grid;
grid-template-columns: 1fr;
gap: 24px;
align-items: stretch;
row-gap: 24px;
}
.entry-card {
display: flex;
/*align-items: center;*/
min-width: 320px;
background: transparent;
border-radius: 0;
box-shadow: none;
padding: 0;
transition: transform 0.2s, box-shadow 0.2s;
}
.entry-card:hover {
transform: none;
box-shadow: none;
}
.act-card {
display: flex;
align-items: center;
width: 100%;
min-width: 0;
max-width: 100%;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
padding: 22px 11px;
box-sizing: border-box;
overflow: hidden;
transition: transform 0.2s, box-shadow 0.2s;
}
.home-section {
width: 100%;
min-width: 0;
max-width: 100%;
box-sizing: border-box;
}
.home-section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.home-section-title {
display: inline-flex;
align-items: baseline;
gap: 6px;
color: #19324d;
font-size: 18px;
font-weight: 700;
}
.home-section-title i {
color: #409eff;
font-size: 16px;
}
.home-section-title em {
color: #b5c0d6;
font-size: 14px;
font-style: normal;
font-weight: 500;
}
/* 推荐入口的 scoped 样式会在组件挂载后注入,此处提前限制图片尺寸,避免刷新时原图短暂撑开页面。 */
#v4-home-app .entry-card .app-icon {
width: 56px;
height: 56px;
overflow: hidden;
}
#v4-home-app .entry-card .app-icon img {
display: block;
width: 56px;
height: 56px;
max-width: 56px;
max-height: 56px;
object-fit: contain;
}
</style>
<div class="v4-container" id="v4-home-app">
<!-- 首页banner -->
<div class="section-banner">
<div class="banner-img">
<img :src="homeBannerList[0] || ''" alt=""/>
<stats></stats>
</div>
</div>
<div class="" style="padding-bottom: 20px">
<user></user>
<div class="home-grid">
<div class="entry-card">
<entry></entry>
</div>
</div>
<div class="home-grid">
<div class="home-section">
<div class="home-section-header">
<div class="home-section-title">
<i class="fa fa-calendar"></i>
<span>最新活动</span>
<em>/Activities</em>
</div>
</div>
<div class="act-card">
<act></act>
</div>
</div>
</div>
<work-template v-if="$auth.hasRoleOr(['SYSADMIN','SCHOOL_UNION_ADMIN'])"></work-template>
<jcdt :list="websiteNews"></jcdt>
</div>
</div>
<script nonce="${cspNonce!}">
<!--#include("act.js"){}#-->
<!--#include("template.js"){}#-->
<!--#include("jcdt.js"){}#-->
<!--#include("entry.js"){}#-->
<!--#include("user.js"){}#-->
<!--#include("stats.js"){}#-->
new Vue({
el: '#v4-home-app',
data(){
return{
websiteNews: [],
homeBannerList: "${config.AppHomeImg!}".split(",").map(item => item.trim()).filter(item => item)
}
},
components: {
'act': act,
'work-template': workTemplate,
'jcdt': jcdt,
'entry': entry,
'user': user,
'stats': stats,
},
mounted() {
this.getWebSiteNews()
},
methods: {
getWebSiteNews(){
this.$axios.post('/platform/home/getNews').then(res => {
if (res.code === 0) {
this.websiteNews = res.data
}
})
}
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,182 @@
const jcdt = {
template: /*language=HTML*/ `
<div class="jcdt-wrapper">
<div class="jcdt-container">
<el-carousel height="360px" :interval="10000" indicator-position="none">
<el-carousel-item v-for="item,index in list" :key="index">
<div class="jcdt-title">
<span>{{ item.label }}</span>
</div>
<div class="jcdt-content">
<div class="jcdt-list">
<div class="jcdt-item" v-for="o,i in item.value" :key="i" @click="onLink(o)">
<div class="date" v-if="o.time">
<i class="el-icon-alarm-clock"></i>
<span>{{o.time}}</span>
</div>
<div style="display: flex; align-items: center">
<div>
</div>
<div class="title">{{o.text}}</div>
</div>
</div>
</div>
</div>
</el-carousel-item>
</el-carousel>
</div>
</div>
`,
data() {
return {}
},
props: {
list: {
type: Array,
default: []
}
},
methods: {
onLink(item) {
const url = this.isHttpOrHttps(item.href) ? item.href : "https://xgh.cug.edu.cn/" + item.href
window.open(url)
},
isHttpOrHttps(url) {
return /^(http:\/\/|https:\/\/)/i.test(url)
}
},
style: /*language=CSS*/ `
.jcdt-wrapper {
width: 100%;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.jcdt-container {
width: 80%;
max-width: 80%;
margin: 0 auto 10px;
background-color: #FFFFFF;
border-radius: 12px;
padding: 20px;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.06);
}
/deep/ .jcdt-title{
font-size: 30px;
color: #000000;
display: block;
font-weight: bold;
position: relative;
text-align: center;
padding: 20px 0;
}
/deep/ .jcdt-container .jcdt-title span::before {
content: '';
width: 24px;
height: 11px;
background: url(https://www.ncu.edu.cn/images/titl.svg) no-repeat center;
background-size: 24px 11px;
display: inline-block;
margin-right: 0;
vertical-align: middle;
}
/deep/ .jcdt-container .jcdt-title span::after {
content: '';
width: 24px;
height: 11px;
background: url(https://www.ncu.edu.cn/images/titr.svg) no-repeat center;
background-size: 24px 11px;
display: inline-block;
margin-left: 0;
vertical-align: middle;
}
/deep/ .jcdt-list {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
}
/deep/ .jcdt-item {
display: flex;
flex-direction: column;
padding: 16px;
border: none;
border-bottom: 3px solid transparent;
transition: all 0.3s ease;
cursor: pointer;
height: auto;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
border-bottom-color: var(--color-primary);
background: #ffffff;
}
/deep/ .jcdt-item:hover{
transform: translateY(-4px);
}
/deep/ .jcdt-item .date {
font-size: 20px;
font-weight: bold;
white-space: nowrap;
text-align: left;
margin-bottom: 8px;
transition: opacity 0.3s ease;
}
/deep/ .jcdt-item .title {
font-size: 16px;
font-weight: 600;
/*margin-bottom: 12px;*/
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
/*height: 45px;*/
}
/deep/ .jcdt-item .image {
width: 100%;
height: 180px;
margin: 0 0 16px 0;
border-radius: 8px;
overflow: hidden;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
}
/deep/ .jcdt-item .image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
/deep/ .jcdt-item .summary {
font-size: 14px;
color: #666;
line-height: 1.6;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-align: justify;
margin-bottom: 12px;
}
/deep/ .jcdt-item i {
color: #007aff;
}
`
};
@@ -0,0 +1,232 @@
const stats = {
template: /*language=HTML*/ `
<div class="stats-section">
<div class="stats-family">
<img src="/assets/platform/img/home/family.png"/>
</div>
<div class="stats-grid">
<!-- 待办 -->
<div
class="stat-item pending"
@click="handleStatClick('todo')"
>
<div class="stat-content">
<div class="stat-label">待办</div>
<div class="stat-number">{{ stats.todoCount }}</div>
</div>
<div class="stat-icon">
<i class="fa fa-clock-o"></i>
</div>
</div>
<!-- 已办 -->
<div
class="stat-item completed"
@click="handleStatClick('done')"
>
<div class="stat-content">
<div class="stat-label">已办</div>
<div class="stat-number">{{ stats.doneCount }}</div>
</div>
<div class="stat-icon">
<i class="fa fa-check-circle"></i>
</div>
</div>
<!-- 消息 -->
<div
class="stat-item messages"
@click="handleStatClick('notification')"
>
<div class="stat-content">
<div class="stat-label">消息</div>
<div class="stat-number">{{ stats.notifications }}</div>
</div>
<div class="stat-icon">
<i class="fa fa-bell"></i>
</div>
</div>
<!-- 发起 -->
<div
class="stat-item reminders"
@click="handleStatClick('started')"
>
<div class="stat-content">
<div class="stat-label">发起</div>
<div class="stat-number">{{ stats.startedCount }}</div>
</div>
<div class="stat-icon">
<i class="fa fa-file-text"></i>
</div>
</div>
</div>
</div>
`,
data() {
return {
stats: {
todoCount: 0,
doneCount: 0,
notifications: 0,
startedCount: 0
}
}
},
methods: {
handleStatClick(type) {
if (['todo', 'done', 'started'].includes(type)) {
window.open('/flow/todoCenter?status=' + type)
} else if (type === 'notification') {
window.open('/platform/v4/msg')
}
},
loadStats() {
this.$axios.post("/flow/todoCenter/statistics").then(res => {
if (res.code === 0) {
this.stats = res.data;
}
})
}
},
mounted() {
this.loadStats();
},
style: /*language=CSS*/ `
/* 右侧统计区域 */
.stats-section {
/*background: white;*/
color: #333;
padding: 20px;
border-radius: 12px;
z-index: 10;
width: 80%;
display: flex;
justify-content: space-between;
align-items: center;
margin: 20px auto 0;
/*border: 1px solid #e8e8e8;*/
}
/* 子内容临时屏蔽时,同步收起统计容器,避免首页 banner 下方出现空白 */
.stats-section {
display: none;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
width: 32%;
min-width: 360px;
margin-left: auto;
}
/* 临时屏蔽首页右上角“待办、已办、消息、发起”统计卡片,保留原模板便于恢复 */
.stats-grid {
display: none;
}
.stat-item {
background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
color: #1f2937;
padding: 20px;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
cursor: pointer;
transition: background 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.12);
width: 100%;
height: 90px;
}
.stat-item:hover {
background: rgba(255, 255, 255, 0.62);
border-color: rgba(255, 255, 255, 0.55);
transform: translateY(-5px);
box-shadow: 0 16px 36px rgba(15, 23, 42, 0.18);
}
.stat-icon {
font-size: 26px;
width: 44px;
height: 44px;
background: rgba(255, 255, 255, 0.42);
color: rgba(31, 41, 55, 0.72);
border: 1px solid rgba(255, 255, 255, 0.35);
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.stat-item.pending .stat-icon,
.stat-item.completed .stat-icon,
.stat-item.messages .stat-icon,
.stat-item.reminders .stat-icon {
background: rgba(255, 255, 255, 0.42);
}
.stat-item.pending .stat-icon {
color: #2563eb;
}
.stat-item.completed .stat-icon {
color: #16a34a;
}
.stat-item.messages .stat-icon {
color: #d97706;
}
.stat-item.reminders .stat-icon {
color: #0891b2;
}
.stat-content {
display: flex;
flex-direction: column;
margin-left: 0;
min-width: 0;
text-align: left;
}
.stat-label {
font-size: 14px;
color: rgba(31, 41, 55, 0.78);
text-transform: uppercase;
letter-spacing: 1px;
line-height: 1.2;
}
.stat-number {
font-size: 28px;
font-weight: 700;
color: #111827;
margin-top: 6px;
line-height: 1;
}
.stats-family {
width: 68%;
}
.stats-family img {
height: 190px !important;
border-radius: 30px;
}
/* 临时屏蔽“欢迎来到职工之家”图片,保留原节点便于恢复 */
.stats-family {
display: none;
}
`
};
@@ -0,0 +1,214 @@
const workTemplate = {
template: /*language=HTML*/ `
<div class="section-wrapper work-template-wrapper">
<div class="work-template-header">
<div class="work-template-title">
<i class="fa fa-clipboard"></i>
<span>工作模板</span>
<em>/Templates</em>
</div>
</div>
<div class="work-template-card">
<div v-if="templateList && templateList.length > 0" class="work-template-grid">
<div class="work-template-item"
v-for="template in templateList"
:key="template.id"
@click="go(template)">
<div class="work-template-icon">
<img :src="template.templateIcon || template.cover || 'https://www.ncu.edu.cn/__local/8/52/2C/644986B4C9A030F7B65300178A6_8679CB08_18467.jpg'"
alt="">
</div>
<div class="work-template-name">{{ template.templateName || template.name }}</div>
</div>
</div>
<div v-else class="no-activity-placeholder">
<div class="icon"><i class="fa fa-clipboard"></i></div>
<p>暂无工作模板</p>
<p class="subtext">敬请关注后续更新</p>
</div>
</div>
</div>
`,
data() {
return {
templateList: []
}
},
methods: {
listTemplate() {
this.$axios.post("/platform/home/listHomeTemplate").then((res) => {
if (res.code === 0) {
this.templateList = res.data
}
})
},
go(template) {
if (!template.url) {
this.$message.warning("管理员未配置模板链接")
return
}
const url = new URL(template.url, window.location.origin)
if (!url.searchParams.get("mode")) {
url.searchParams.set("mode", "edit")
}
if (!url.searchParams.get("id") && template.id) {
url.searchParams.set("id", template.id)
}
window.localStorage.setItem("zhgh_home_template_edit", JSON.stringify({
path: url.pathname,
id: url.searchParams.get("id"),
expiresAt: Date.now() + 60000
}))
window.open(url.href)
}
},
mounted() {
this.listTemplate()
},
style: /*language=CSS*/ `
.work-template-wrapper {
width: 80%;
margin: 20px auto 28px;
}
.work-template-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.work-template-title {
display: inline-flex;
align-items: baseline;
gap: 6px;
color: #19324d;
font-size: 18px;
font-weight: 700;
}
.work-template-title i {
color: #409eff;
font-size: 16px;
}
.work-template-title em {
color: #b5c0d6;
font-size: 14px;
font-style: normal;
font-weight: 500;
}
.work-template-card {
background: #ffffff;
border: 1px solid #edf1f7;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
min-height: 116px;
padding: 18px 20px;
}
.work-template-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
justify-items: center;
justify-content: start;
gap: 12px 18px;
overflow-x: hidden;
}
.work-template-item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 8px;
background: #fff;
width: 100%;
max-width: 100%;
padding: 6px;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.work-template-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.work-template-icon {
width: 55px;
height: 55px;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.work-template-icon img {
width: 55px !important;
height: 55px !important;
max-width: none;
max-height: none;
object-fit: cover;
border-radius: 8px;
}
.work-template-name {
min-height: 34px;
font-size: 12px !important;
font-weight: 400;
color: #333;
line-height: 1.4;
word-break: break-word;
text-align: center;
}
.work-template-wrapper .no-activity-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 20px;
color: #94a3b8;
text-align: center;
}
.work-template-wrapper .no-activity-placeholder .icon {
font-size: 44px;
margin-bottom: 12px;
opacity: 0.6;
}
.work-template-wrapper .no-activity-placeholder p {
margin: 4px 0 0;
font-size: 16px;
font-weight: 500;
}
.work-template-wrapper .no-activity-placeholder .subtext {
font-size: 13px;
opacity: 0.8;
}
@media (max-width: 768px) {
.work-template-grid {
grid-template-columns: repeat(3, minmax(72px, 1fr));
gap: 12px;
}
.work-template-item {
padding: 8px 4px;
}
}
@media (max-width: 480px) {
.work-template-grid {
grid-template-columns: repeat(2, minmax(72px, 1fr));
gap: 12px;
}
}
`
};
@@ -0,0 +1,553 @@
const user = {
template: /*language=HTML*/ `
<div class="user-wrapper">
<!-- 用户容器:左右布局 -->
<div class="user-container">
<!-- 左侧:用户信息卡片 -->
<div class="user-info-card">
<div class="user-details">
<div class="user-name">${@auth.getPrincipalProperty('username')}</div>
<div class="user-meta-grid">
<div class="meta-row">
<span class="meta-icon">🆔</span>
<div>
<span class="meta-label">工号</span>
<span class="meta-value">${@auth.getPrincipalProperty('loginname')}</span>
</div>
</div>
<div class="meta-row">
<span class="meta-icon">🏢</span>
<div>
<span class="meta-label">单位信息</span>
<span class="meta-value">
<!--#if(!isEmpty(@auth.getPrincipalProperty('unit'))){#-->
${@auth.getPrincipalProperty('unit').getName()}
<!--#}#-->
(
<!--#if(!isEmpty(@auth.getPrincipalProperty('union'))){#-->
${@auth.getPrincipalProperty('union').getName()}
<!--#}#-->
)
</span>
</div>
</div>
<div class="meta-row">
<span class="meta-icon">🎭</span>
<div>
<span class="meta-label">角色</span>
<span class="meta-value">{{ roleNames || '—' }}</span>
</div>
</div>
</div>
</div>
<!-- 天气区域 -->
<div v-if="weatherData" class="weather-section" :class="weatherData.bgClass">
<div style="display: flex; justify-content: space-around">
<div class="weather-header">
<span class="weather-location">{{ weatherData.name }}</span>
<span class="weather-text">{{ weatherData.text }}</span>
</div>
<div class="weather-main">
<span class="weather-emoji">{{ weatherData.emoji }}</span>
<span class="weather-temp">{{ weatherData.temp }}°</span>
</div>
</div>
<div class="weather-tip">{{ weatherData.tip }}</div>
</div>
<div v-else-if="weatherError" class="weather-error">
🌤️ 天气服务暂时不可用
</div>
</div>
<!-- 右侧:待办中心 -->
<div class="todo-center">
<!-- 标题和标签 -->
<div class="header">
<h3>待办中心</h3>
<el-tabs v-model="activeTab" type="card" @tab-click="handleTabClick">
<el-tab-pane :label="tabLabel('待办事宜', taskStats.todoCount)" name="todo"></el-tab-pane>
<el-tab-pane :label="tabLabel('已办事宜', taskStats.doneCount)" name="done"></el-tab-pane>
<el-tab-pane :label="tabLabel('我的发起', taskStats.startedCount)" name="started"></el-tab-pane>
<el-tab-pane :label="tabLabel('办结事务', taskStats.completedCount)" name="completed"></el-tab-pane>
</el-tabs>
</div>
<!-- 表格 -->
<el-table
:data="todoList"
style="width: 100%"
border
stripe
v-loading="loading"
:cell-style="{ padding: '8px' }"
:header-cell-style="{ background: '#f8f9fa', color: '#444', fontWeight: '500' }"
>
<el-table-column label="序号" type="index" :index="indexMethod" width="100px"></el-table-column>
<el-table-column prop="categoryName" label="流程分类" width="100"></el-table-column>
<el-table-column prop="title" label="流程名称" min-width="300">
<template slot-scope="scope">
<el-tooltip effect="dark" :content="scope.row.instanceName" placement="top">
<span class="table-title">{{ scope.row.instanceName }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="sender" label="申请人" width="120">
<template slot-scope="{row}">{{row.variable.initiatorName}}</template>
</el-table-column>
<el-table-column prop="taskName" label="当前流程节点" width="140" show-overflow-tooltip></el-table-column>
<el-table-column prop="date" label="发起日期" width="120">
<template slot-scope="{row}">
{{$moment(row.createdAt).format('YYYY-MM-DD')}}
</template>
</el-table-column>
<el-table-column label="操作" width="100px" fixed="right">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="openView(scope.row)">查看</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</div>
</div>
</div>
`,
mixins: [initTableMixins],
data() {
return {
activeTab: 'todo',
todoList: [],
loading: false,
pageForm: {
pageNum: 1,
pageSize: 5,
totalCount: 0
},
taskStats: {
todoCount: 0,
doneCount: 0,
startedCount: 0,
completedCount: 0
},
// 🔆 天气相关数据
weatherData: null,
weatherError: false,
// 🔑 和风天气 API Key(请替换为你自己的)
QWEATHER_KEY: 'f916578cbb0e4b55b778b0de5847ecf5', // ←← 务必替换!
roleNames: ""
};
},
methods: {
getWeatherInfo(iconCode, temp) {
const info = {
emoji: '🌤️',
tip: '今日天气宜人,心情也该放晴啦!',
bgClass: 'weather-sunny' // 用于 CSS 类
};
const tempNum = parseFloat(temp);
const isCold = tempNum < 10;
const isHot = tempNum > 28;
// 按照和风天气 iconCode 分类
if (['100'].includes(iconCode)) {
info.emoji = '☀️';
info.tip = isHot ? '高温预警!注意防暑降温,多喝水~' : '阳光灿烂,适合外出走走!';
info.bgClass = 'weather-sunny';
} else if (['101', '102', '103', '104'].includes(iconCode)) {
info.emoji = ['101', '103'].includes(iconCode) ? '⛅' : '☁️';
info.tip = isCold ? '天凉加衣,别让感冒找上门!' : '云层温柔,适合静心工作。';
info.bgClass = 'weather-cloudy';
} else if (['300', '301', '302', '305', '306', '399'].some(code => code === iconCode)) {
info.emoji = '🌧️';
info.tip = '🌧️ 下雨啦!记得带伞,小心路滑~';
info.bgClass = 'weather-rainy';
} else if (['307', '308', '309', '310'].includes(iconCode)) {
info.emoji = '⛈️';
info.tip = '⛈️ 雷雨天气,请减少外出,注意安全!';
info.bgClass = 'weather-storm';
} else if (['400', '401', '402', '403'].includes(iconCode)) {
info.emoji = '❄️';
info.tip = '❄️ 天寒地冻,保暖最重要!';
info.bgClass = 'weather-snowy';
} else if (['404', '405', '406', '407', '408', '409', '410'].includes(iconCode)) {
info.emoji = '🌫️';
info.tip = '🌫️ 能见度较低,出行请注意安全。';
info.bgClass = 'weather-foggy';
} else if (['500', '501', '502', '504'].includes(iconCode)) {
info.emoji = '🌀';
info.tip = '🌀 极端天气预警!请关注官方通知。';
info.bgClass = 'weather-extreme';
} else {
info.emoji = '🌤️';
info.tip = '今日天气未知,但愿你心情晴朗!';
info.bgClass = 'weather-default';
}
return info;
},
async getConfigKey(key) {
const resp = await this.$axios.post("/open/common/getConfigKey", { key })
return resp.data
},
// 🔆 获取天气
async fetchWeather() {
try {
// 存localStorage 112.13,32.01 广州 113.27,23.14
const AppMapCenterPointX = await this.getConfigKey("AppMapCenterPointX")
const AppMapCenterPointY = await this.getConfigKey("AppMapCenterPointY")
if (!AppMapCenterPointX || !AppMapCenterPointY){
return
}
// 1. 先通过城市名获取 locationId
this.$axios.get("https://mx5rk62kvj.re.qweatherapi.com/geo/v2/city/lookup?location=" + AppMapCenterPointX + "," + AppMapCenterPointY + "&key=" + this.QWEATHER_KEY).then((geoRes) => {
if (geoRes.code !== '200' || !geoRes.location?.[0]) {
console.log('城市未找到')
}
const locationId = geoRes.location[0].id;
// 2. 获取当前天气
this.$axios.get("https://mx5rk62kvj.re.qweatherapi.com/v7/weather/now?location=" + locationId + "&key=" + this.QWEATHER_KEY).then((weatherRes) => {
if (weatherRes.code !== '200') {
console.warn('天气数据异常:', weatherRes);
}
const weatherInfo = this.getWeatherInfo(weatherRes.now.icon, weatherRes.now.temp);
this.weatherData = {
...weatherInfo,
temp: weatherRes.now.temp,
text: weatherRes.now.text,
name: geoRes.location[0].name
};
this.weatherError = false;
})
})
} catch (err) {
console.warn('天气加载失败:', err);
this.weatherError = true;
this.weatherData = null;
}
},
// 获取任务列表
tabLabel(title, count) {
return title + "" + (count || 0) + ""
},
async getStatistics() {
try {
const res = await $.post("/flow/todoCenter/statistics")
if (res.code === 0) {
this.taskStats = Object.assign({}, this.taskStats, res.data)
}
} catch (error) {
console.error("获取待办统计失败:", error)
}
},
async getTasks() {
this.loading = true
try {
const res = await $.post("/flow/todoCenter/" + this.activeTab, this.pageForm)
if (res.code === 0) {
this.todoList = res.data.list
this.pageForm.totalCount = res.data.totalCount
}
} catch (error) {
this.$message.error("获取任务列表失败")
console.error("获取任务列表失败:", error)
} finally {
this.loading = false
}
},
pageData() {
this.getTasks()
this.getStatistics()
},
handleTabClick(tab) {
this.pageData()
console.log('切换到:', tab.name);
},
// 处理任务
async openView(task) {
console.log(task)
const {taskId, taskKey, taskState, instanceId, businessNo, formKey} = task
if (!formKey) {
this.$message.warning("当前流程没有配置地址")
return
}
window.open(formKey + "?taskId=" + taskId + "&bizId=" + businessNo + "&taskKey=" + taskKey + "&tab=" + this.activeTab)
},
getRoleNames() {
$.post("/platform/sys/role/getRoleNames").then(res => {
if (res.code === 0) {
this.roleNames = res.data.roleNames
}
})
}
},
created() {
this.pageData()
this.getRoleNames()
},
mounted() {
this.fetchWeather()
},
style: /*language=CSS*/ `
.user-wrapper {
width: 100%;
background-color: #f5f7fa;
}
.user-container {
width: 80%;
margin: 12px auto 20px;
display: grid;
grid-template-columns: 0.7fr 2.3fr;
gap: 24px;
align-items: stretch;
row-gap: 24px;
}
.user-info-card {
background: white;
border-radius: 16px;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.06);
padding: 24px;
display: flex;
flex-direction: column;
gap: 20px;
transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.user-info-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
}
/* 头像占位符 */
.user-avatar-placeholder {
width: 64px;
height: 64px;
border-radius: 50%;
background: linear-gradient(135deg, #409eff, #1877f2);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: 600;
margin: 0 auto;
box-shadow: 0 4px 10px rgba(24, 127, 255, 0.3);
}
/* 用户名 */
.user-name {
font-size: 20px;
font-weight: 700;
color: #1f2937;
text-align: center;
margin: 8px 0 0;
}
/* 元信息网格 */
.user-meta-grid {
display: flex;
flex-direction: column;
gap: 14px;
margin-top: 8px;
}
.meta-row {
display: flex;
align-items: flex-start;
gap: 12px;
}
.meta-icon {
font-size: 18px;
min-width: 24px;
text-align: center;
color: #409eff;
margin-top: 2px;
}
.meta-row > div {
flex: 1;
}
.meta-label {
font-size: 12px;
color: #6b7280;
font-weight: 500;
display: block;
margin-bottom: 2px;
}
.meta-value {
font-size: 15px;
color: #1f2937;
font-weight: 600;
word-break: break-word;
}
/* === 天气卡片 - 情感化设计 === */
.weather-section {
border-radius: 16px;
padding: 20px;
text-align: center;
color: white;
background: linear-gradient(135deg, #a8edea, #fed6e3); /* 默认柔和渐变 */
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
transition: all 0.3s ease;
}
/* 不同天气的背景主题 */
.weather-sunny {
background: linear-gradient(135deg, #FFD700, #FFA500);
color: #2c3e50;
}
.weather-cloudy {
background: linear-gradient(135deg, #B0C4DE, #708090);
color: white;
}
.weather-rainy {
background: linear-gradient(135deg, #4A6FA5, #1E3F66);
color: white;
}
.weather-storm {
background: linear-gradient(135deg, #2F2F2F, #0D0D0D);
color: #FFD700;
}
.weather-snowy {
background: linear-gradient(135deg, #E0F7FA, #B3E5FC);
color: #01579B;
}
.weather-foggy {
background: linear-gradient(135deg, #D7CCC8, #A1887F);
color: white;
}
.weather-extreme {
background: linear-gradient(135deg, #FF5252, #B71C1C);
color: white;
}
.weather-default {
background: linear-gradient(135deg, #E0E0E0, #90A4AE);
color: #333;
}
/* 天气头部:城市 + 描述 */
.weather-header {
margin-bottom: 12px;
}
.weather-location {
font-weight: 700;
font-size: 16px;
display: block;
}
.weather-text {
font-size: 13px;
opacity: 0.9;
display: block;
margin-top: 4px;
}
/* 主体:图标 + 温度 */
.weather-main {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
position: relative;
bottom: 8px;
}
.weather-emoji {
font-size: 36px;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
}
.weather-temp {
font-size: 28px;
font-weight: 800;
}
/* 贴心提示语 */
.weather-tip {
font-size: 14px;
line-height: 1.5;
opacity: 0.95;
margin-top: 8px;
padding: 0 8px;
}
/* 错误状态 */
.weather-error {
text-align: center;
color: #94a3b8;
font-size: 14px;
padding: 16px;
background: #f8fafc;
border-radius: 12px;
border: 1px dashed #cbd5e1;
}
/* 右侧待办中心 */
.todo-center {
min-width: 320px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
padding: 16px;
transition: transform 0.2s, box-shadow 0.2s;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.header h3 {
font-size: 18px;
color: #333;
margin: 0;
border-left: 4px solid #1890ff;
padding-left: 10px;
font-weight: 600;
}
.el-tabs {
margin-top: 10px;
}
.table-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 280px;
display: inline-block;
}
.pagination {
margin-top: 20px;
text-align: right;
}
/* 响应式:小屏下堆叠 */
@media (max-width: 768px) {
.user-container {
flex-direction: column;
width: 95%;
}
.user-info-card, .todo-center {
min-width: 100%;
width: 100%;
}
}
`
};