主页整改

This commit is contained in:
hongqiwei
2026-05-06 11:06:12 +08:00
parent 6331b736b1
commit e9e7974c4c
7 changed files with 214 additions and 262 deletions
@@ -5,7 +5,6 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.result.Result;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_menu;
@@ -263,48 +262,36 @@ public class SysHomeController {
// @CacheDefaults(cacheName = RedisConstant.PLATFORM_REDIS_WKCACHE_PREFIX + "web_news", isHash = true)
// @CacheResult(cacheKey = "news", ignoreNull = true, cacheLiveTime = 60 * 60 * 24)
public Result getNews() {
// 定义要爬取的URL
String domain = "https://gonghui.ypi.edu.cn";
try {
// 使用Jsoup连接到URL并获取页面内容
Document doc = Jsoup.connect(domain).get();
JSONObject root = new JSONObject();
// 3. 基层风采(来自 .pic-list1
List<JSONObject> grassrootsArray = new ArrayList<>();
Elements grassrootsItems = doc.select(".news #wp_news_w21 ul li");
List<NutMap> items = new ArrayList<>();
Elements grassrootsItems = doc.select(".news #wp_news_w21 ul li");
for (Element item : grassrootsItems) {
Element aTitle = item.select("a").first();
Element meta = item.select(".Article_PublishDate").first();
if (aTitle == null || meta == null) {
continue;
}
String title = aTitle.attr("title").trim();
String url = aTitle.attr("href").trim();
Element meta = item.select(".Article_PublishDate").first();
String date = meta.text().trim();
/*Element link = item.select(".nr").first();
String date = link.select(".date").text().trim();
String title = link.select(".news_title a").text().trim();
String url = link.select(".news_title a").attr("href").trim();
String summary = link.select(".news_text a").text().trim();
String imageUrl = "";
Element img = link.select(".news_imgs").first();
if (img != null) {
imageUrl = img.attr("src").trim();
}*/
JSONObject node = new JSONObject();
node.set("title", title);
node.set("date", date);
node.set("url", "https://gonghui.ypi.edu.cn/" + url);
//node.set("summary", summary);
//node.set("image", StrUtil.isNotBlank(imageUrl) ? (domain + imageUrl) : "");
grassrootsArray.add(node);
NutMap node = NutMap.NEW();
node.setv("text", title);
node.setv("time", date);
node.setv("href", url.startsWith("http://") || url.startsWith("https://") ? url : domain + "/" + url);
items.add(node);
}
root.set("grassroots", grassrootsArray);
return Result.success(root);
NutMap section = NutMap.NEW();
section.setv("label", "\u7efc\u5408\u65b0\u95fb");
section.setv("value", items);
List<NutMap> news = new ArrayList<>();
news.add(section);
return Result.success(news);
} catch (Exception e) {
log.error(e);
return Result.success();
Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

@@ -1,31 +1,31 @@
const entry = {
template: /*language=HTML*/ `
<div class="entry-wrapper">
<!-- 标签页导航 -->
<div class="tabs">
<div class="tab-item" :class="{ active: activeCategory === 'serv' }"
@click="setActiveCategory('serv')">
<i class="fa fa-star"></i> 推荐服务
<div class="entry-section" 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="tab-item" :class="{ active: activeCategory === 'app' }"
@click="setActiveCategory('app')">
<i class="fa fa-fire"></i> 推荐应用
</div>
<div class="tab-item" :class="{ active: activeCategory === 'fav' }"
@click="setActiveCategory('fav')">
<i class="fa fa-heart"></i> 我的收藏
</div>
</div>
<!-- 应用块区域 -->
<div class="apps-container">
<div class="app-grid">
<div class="app-item" v-for="(item, index) in currentServices" :key="index"
@click="openService(item)">
<div class="app-icon">
<img :src="item.picIcon" alt="" />
<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 :src="item.picIcon" alt="" />
</div>
<div class="app-name">{{ item.name }}</div>
</div>
<div class="app-name">{{ item.name }}</div>
<el-empty class="app-empty"
v-if="!section.list || section.list.length === 0"
description="暂无数据"
:image-size="72">
</el-empty>
</div>
</div>
</div>
@@ -33,7 +33,6 @@ const entry = {
`,
data() {
return {
activeCategory: 'serv',
hasMore: true,
entries: {
serv: [],
@@ -43,8 +42,12 @@ const entry = {
}
},
computed: {
currentServices() {
return this.entries[this.activeCategory] || [];
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() {
@@ -53,10 +56,6 @@ const entry = {
this.getFavorite()
},
methods: {
setActiveCategory(category) {
this.activeCategory = category;
},
// 查询推荐服务
async getRecommendService() {
this.$axios.post('/platform/home/listRecommendService', {platform: "PC"}).then(res => {
@@ -84,35 +83,37 @@ const entry = {
})
},
// 点击服务
openService(service) {
if(this.activeCategory === 'serv'){
if(!service.href) this.$message.error('无效的链接地址')
// 点击服务或应用
openService(service, category) {
if (category === 'serv') {
if (!service.href) {
this.$message.error('无效的链接地址')
return
}
window.open(service.href)
return
}
if(this.activeCategory === 'app'){
// 储存到缓存
if (category === 'app') {
window.sessionStorage.setItem("zhgh_sub_app", JSON.stringify(service))
// 新标签页打开
window.open("/platform/v4/subApp?appId=" + service.id, "_blank")
return
}
if(this.activeCategory === 'fav'){
if(!service.href) this.$message.error('无效的链接地址')
window.open(service.href)
return
if(!service.parentId){
// 储存到缓存
window.sessionStorage.setItem("zhgh_sub_app", JSON.stringify(app))
// 新标签页打开
window.open("/platform/v4/subApp?appId=" + app.id, "_blank")
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*/ `
@@ -121,37 +122,36 @@ const entry = {
box-sizing: border-box;
}
.tabs {
margin-top: 5px;
display: flex;
gap: 20px;
border-bottom: 2px solid #e8e8e8;
padding-bottom: 10px;
.entry-section + .entry-section {
margin-top: 22px;
}
.tab-item {
.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;
color: #666;
cursor: pointer;
padding-bottom: 8px;
transition: color 0.2s ease;
}
.tab-item:hover {
color: #1890ff;
}
.tab-item.active {
color: #1890ff;
font-weight: bold;
border-bottom: 2px solid #1890ff;
}
.tab-item i {
font-size: 14px;
font-style: normal;
font-weight: 500;
}
.apps-container {
@@ -162,19 +162,17 @@ const entry = {
.app-grid {
display: grid;
/* 改为自适应列数:每格最小 80px,自动换行 */
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
justify-items: center; /* 子项内容居中(图标和文字) */
justify-content: start; /* 整体网格靠左对齐,避免居中 */
gap: 4px;
padding: 12px 4px;
grid-template-columns: repeat(auto-fill, minmax(96px, 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;
max-height: calc(350px - 74px);
overflow-y: auto;
}
.app-item {
@@ -187,6 +185,7 @@ const entry = {
width: 100%;
max-width: 100%;
padding: 6px;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
@@ -208,6 +207,7 @@ const entry = {
}
.app-name {
min-height: 34px;
font-size: 12px;
color: #333;
line-height: 1.4;
@@ -215,31 +215,26 @@ const entry = {
text-align: center;
}
/* 响应式设计 */
.app-empty {
grid-column: 1 / -1;
width: 100%;
}
@media (max-width: 768px) {
.tabs {
flex-wrap: wrap;
gap: 10px;
}
.tab-item {
padding: 8px 0;
font-size: 12px;
}
.app-grid {
grid-template-columns: repeat(2, 1fr);
gap: 15px;
grid-template-columns: repeat(3, minmax(72px, 1fr));
gap: 12px;
padding: 14px 10px;
}
.app-item {
padding: 12px;
padding: 8px 4px;
}
}
@media (max-width: 480px) {
.app-grid {
grid-template-columns: 1fr;
grid-template-columns: repeat(2, minmax(72px, 1fr));
gap: 12px;
}
}
@@ -7,17 +7,20 @@ layout("/layouts/v4/baseLayout.html"){
.section-banner {
position: relative;
overflow: hidden;
display: flex;
align-items: center;
margin-bottom: 0;
}
.section-banner .banner-img {
width: 100%;
height: 100%;
height: auto;
}
.section-banner .banner-img img {
width: 100%;
height: 100%;
/*object-fit: cover;*/
display: block;
}
.section-wrapper {
@@ -28,7 +31,7 @@ layout("/layouts/v4/baseLayout.html"){
width: 80%;
margin: 20px auto;
display: grid;
grid-template-columns: 2.2fr 0.8fr;
grid-template-columns: 1fr;
gap: 24px;
align-items: stretch;
row-gap: 24px;
@@ -38,16 +41,16 @@ layout("/layouts/v4/baseLayout.html"){
display: flex;
/*align-items: center;*/
min-width: 320px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
padding: 16px;
background: transparent;
border-radius: 0;
box-shadow: none;
padding: 0;
transition: transform 0.2s, box-shadow 0.2s;
}
.entry-card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0,0,0,0.12);
transform: none;
box-shadow: none;
}
.act-card {
@@ -67,7 +70,7 @@ layout("/layouts/v4/baseLayout.html"){
<!-- 首页banner -->
<div class="section-banner">
<div class="banner-img">
<img src="${config.AppHomeImg!}" alt=""/>
<img src="/assets/platform/img/home/home.png" alt="应用首页"/>
<stats></stats>
</div>
</div>
@@ -76,16 +79,12 @@ layout("/layouts/v4/baseLayout.html"){
<user></user>
<div class="home-grid">
<div class="act-card">
<act></act>
</div>
<div class="entry-card">
<entry></entry>
</div>
</div>
<jcdt :list="websiteNews.grassroots"></jcdt>
<jcdt :list="websiteNews"></jcdt>
</div>
</div>
@@ -102,9 +101,7 @@ layout("/layouts/v4/baseLayout.html"){
el: '#v4-home-app',
data(){
return{
websiteNews:{
grassroots:[],
}
websiteNews: []
}
},
components: {
@@ -2,25 +2,22 @@ const jcdt = {
template: /*language=HTML*/ `
<div class="jcdt-wrapper">
<div class="jcdt-container">
<div class="jcdt-title">
<span>综合新闻</span>
</div>
<div class="jcdt-content">
<div class="jcdt-list">
<div class="jcdt-item" v-for="item in list" :key="item.title" @click="onLink(item)">
<div class="date" v-if="item.date">{{item.date}}</div>
<div style="display: flex; align-items: center">
<div >
<el-image style="width: 30px" src="/assets/platform/img/v4/xw.png" ></el-image>
<div class="jcdt-section" 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 class="title">{{item.title}}</div>
</div>
<!--<div class="image">
<img v-if="item.image" :src="item.image" alt="">
<img v-else src="/assets/platform/img/v4/not-image.png" alt="">
</div>-->
<!-- <div class="summary">{{item.summary}}</div>-->
</div>
</div>
</div>
@@ -38,9 +35,12 @@ const jcdt = {
},
methods: {
onLink(item) {
if(!item.url) return
window.open(item.url)
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 {
@@ -53,16 +53,14 @@ const jcdt = {
.jcdt-container {
width: 80%;
max-width: 80%;
margin: 0 auto;
margin: 0 auto 10px;
background-color: #FFFFFF;
border-radius: 12px;
padding: 20px;
margin-bottom: 10px;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.06);
}
.jcdt-title{
/deep/ .jcdt-title{
font-size: 30px;
color: #000000;
display: block;
@@ -72,6 +70,10 @@ const jcdt = {
padding: 20px 0;
}
.jcdt-section + .jcdt-section {
margin-top: 20px;
}
/deep/ .jcdt-container .jcdt-title span::before {
content: '';
width: 24px;
@@ -94,13 +96,13 @@ const jcdt = {
vertical-align: middle;
}
.jcdt-list {
/deep/ .jcdt-list {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
}
.jcdt-item {
/deep/ .jcdt-item {
display: flex;
flex-direction: column;
padding: 16px;
@@ -109,18 +111,17 @@ const jcdt = {
transition: all 0.3s ease;
cursor: pointer;
height: auto;
border-radius: 12px;
}
.jcdt-item:hover{
background: #ffffff;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
border-bottom-color: var(--color-primary);
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
background: #ffffff;
}
.jcdt-item .date {
/deep/ .jcdt-item:hover{
transform: translateY(-4px);
}
/deep/ .jcdt-item .date {
font-size: 20px;
font-weight: bold;
white-space: nowrap;
@@ -129,7 +130,7 @@ const jcdt = {
transition: opacity 0.3s ease;
}
.jcdt-item .title {
/deep/ .jcdt-item .title {
font-size: 16px;
font-weight: 600;
/*margin-bottom: 12px;*/
@@ -141,7 +142,7 @@ const jcdt = {
/*height: 45px;*/
}
.jcdt-item .image {
/deep/ .jcdt-item .image {
width: 100%;
height: 180px;
margin: 0 0 16px 0;
@@ -155,14 +156,14 @@ const jcdt = {
transition: transform 0.3s ease;
}
.jcdt-item .image img {
/deep/ .jcdt-item .image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.jcdt-item .summary {
/deep/ .jcdt-item .summary {
font-size: 14px;
color: #666;
line-height: 1.6;
@@ -174,5 +175,8 @@ const jcdt = {
margin-bottom: 12px;
}
/deep/ .jcdt-item i {
color: #007aff;
}
`
};
@@ -1,74 +1,12 @@
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')"
@mouseenter="onMouseEnter"
@mouseleave="onMouseLeave"
>
<div class="stat-icon">
<i class="fa fa-clock-o"></i>
</div>
<div class="stat-content">
<div class="stat-label">待办</div>
<div class="stat-number">{{ stats.todoCount }}</div>
</div>
</div>
<!-- 已办 -->
<div
class="stat-item completed"
@click="handleStatClick('done')"
@mouseenter="onMouseEnter"
@mouseleave="onMouseLeave"
>
<div class="stat-icon">
<i class="fa fa-check-circle"></i>
</div>
<div class="stat-content">
<div class="stat-label">已办</div>
<div class="stat-number">{{ stats.doneCount }}</div>
</div>
</div>
<!-- 消息 -->
<div
class="stat-item messages"
@click="handleStatClick('notification')"
@mouseenter="onMouseEnter"
@mouseleave="onMouseLeave"
>
<div class="stat-icon">
<i class="fa fa-bell"></i>
</div>
<div class="stat-content">
<div class="stat-label">消息</div>
<div class="stat-number">{{ stats.notifications }}</div>
</div>
</div>
<!-- 发起 -->
<div
class="stat-item reminders"
@click="handleStatClick('started')"
@mouseenter="onMouseEnter"
@mouseleave="onMouseLeave"
>
<div class="stat-icon">
<i class="fa fa-file-text"></i>
</div>
<div class="stat-content">
<div class="stat-label">发起</div>
<div class="stat-number">{{ stats.startedCount }}</div>
</div>
</div>
</div>
<!--<div class="stats-grid">
<div class="stat-item pending"></div>
<div class="stat-item completed"></div>
<div class="stat-item messages"></div>
<div class="stat-item reminders"></div>
</div>-->
</div>
`,
data() {
@@ -108,7 +46,7 @@ const stats = {
}
},
mounted() {
this.loadStats();
// this.loadStats();
},
style: /*language=CSS*/ `
/* 右侧统计区域 */
@@ -119,10 +57,10 @@ const stats = {
border-radius: 12px;
z-index: 10;
width: 80%;
display: flex;
display: none;
justify-content: space-between;
align-items: center;
margin: -130px auto 0;
margin: 20px auto 0;
/*border: 1px solid #e8e8e8;*/
}
@@ -131,7 +69,8 @@ const stats = {
grid-template-columns: repeat(2, 1fr);
gap: 16px;
width: 32%;
margin-left: 20px;
min-width: 360px;
margin-left: auto;
}
.stat-item {
@@ -162,6 +101,11 @@ const stats = {
justify-content: center;
}
/* 临时屏蔽待办、已办、消息、发起四个统计图标 */
.stat-icon-hidden {
display: none !important;
}
.stat-item.pending .stat-icon,
.stat-item.completed .stat-icon,
.stat-item.messages .stat-icon,
@@ -185,7 +129,7 @@ const stats = {
.stat-content {
display: flex;
flex-direction: column;
margin-left: 10px;
margin-left: 0;
}
.stat-label {
@@ -209,5 +153,10 @@ const stats = {
height: 190px !important;
border-radius: 30px;
}
/* 临时屏蔽“欢迎来到职工之家”图片,保留原节点便于恢复 */
.stats-family {
display: none;
}
`
};
@@ -68,10 +68,10 @@ const user = {
<div class="header">
<h3>待办中心</h3>
<el-tabs v-model="activeTab" type="card" @tab-click="handleTabClick">
<el-tab-pane label="待办事宜" name="todo"></el-tab-pane>
<el-tab-pane label="已办事宜" name="done"></el-tab-pane>
<el-tab-pane label="我的发起" name="started"></el-tab-pane>
<el-tab-pane label="办结事务" name="completed"></el-tab-pane>
<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>
@@ -124,6 +124,12 @@ const user = {
pageSize: 5,
totalCount: 0
},
taskStats: {
todoCount: 0,
doneCount: 0,
startedCount: 0,
completedCount: 0
},
// 🔆 天气相关数据
weatherData: null,
weatherError: false,
@@ -228,6 +234,19 @@ const user = {
}
},
// 获取任务列表
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 {
@@ -245,6 +264,7 @@ const user = {
},
pageData() {
this.getTasks()
this.getStatistics()
},
handleTabClick(tab) {
this.pageData()