feat:职代会ui优化

This commit is contained in:
2026-08-30 14:34:42 +08:00
parent 35ea26b750
commit 7ae67a46a5
38 changed files with 2484 additions and 188 deletions
@@ -111,6 +111,13 @@ public class CongressH5ServiceImpl implements CongressH5Service {
cm.file_type AS fileType,
cm.file_size AS fileSize,
cm.create_time AS uploadTime,
CASE
WHEN cvi.id IS NULL THEN NULL
WHEN cvr.id IS NOT NULL THEN 'voted'
WHEN cvi.voting_start_time IS NOT NULL AND cvi.voting_start_time > NOW() THEN 'upcoming'
WHEN cvi.voting_end_time IS NOT NULL AND cvi.voting_end_time < NOW() THEN 'ended'
ELSE 'pending'
END AS voteStatus,
CASE
WHEN cvi.id IS NULL THEN NULL
WHEN cvr.id IS NOT NULL THEN true
@@ -132,8 +139,7 @@ public class CongressH5ServiceImpl implements CongressH5Service {
WHERE COALESCE(cm.deleted, 0) = 0
AND cm.type_id = @typeId
AND (cm.session_id = @sessionId OR cm.time_id = @sessionId)
AND (@searchKeyword = '' OR cm.name LIKE CONCAT('%%', @searchKeyword, '%%')
OR cm.file_name LIKE CONCAT('%%', @searchKeyword, '%%'))
AND (@searchKeyword = '' OR cm.name LIKE CONCAT('%%', @searchKeyword, '%%'))
AND (@fileType = '' OR FIND_IN_SET(LOWER(REPLACE(cm.file_type, '.', '')), @fileType) > 0
OR FIND_IN_SET(LOWER(SUBSTRING_INDEX(cm.file_name, '.', -1)), @fileType) > 0)
ORDER BY cm.create_time %s, cm.id DESC
@@ -349,6 +355,11 @@ public class CongressH5ServiceImpl implements CongressH5Service {
i.voting_type AS votingType,
i.voting_start_time AS votingStartTime,
i.voting_end_time AS votingEndTime,
CASE
WHEN i.voting_start_time IS NOT NULL AND i.voting_start_time > NOW() THEN 'upcoming'
WHEN i.voting_end_time IS NOT NULL AND i.voting_end_time < NOW() THEN 'ended'
ELSE 'ongoing'
END AS votingStatus,
i.voting_content AS votingContent,
i.voting_content_item_header AS votingContentItemHeader,
i.voting_content_option_header AS votingContentOptionHeader,
@@ -385,7 +396,14 @@ public class CongressH5ServiceImpl implements CongressH5Service {
return Result.error("投票参数不完整");
}
NutMap issue = fetchMap("""
SELECT id, session_id AS sessionId, COALESCE(time_id, session_id) AS timeId
SELECT id,
session_id AS sessionId,
COALESCE(time_id, session_id) AS timeId,
CASE
WHEN voting_start_time IS NOT NULL AND voting_start_time > NOW() THEN 'upcoming'
WHEN voting_end_time IS NOT NULL AND voting_end_time < NOW() THEN 'ended'
ELSE 'ongoing'
END AS votingStatus
FROM congress_voting_issue
WHERE id = @issueId
AND session_id = @sessionId
@@ -395,6 +413,12 @@ public class CongressH5ServiceImpl implements CongressH5Service {
if (issue == null) {
return Result.error("投票议题不存在");
}
if ("upcoming".equals(issue.getString("votingStatus"))) {
return Result.error("投票尚未开始");
}
if ("ended".equals(issue.getString("votingStatus"))) {
return Result.error("投票已结束,无法继续投票");
}
NutMap rep = fetchMap("""
SELECT id, userId, loginName, userName, delegationId
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

@@ -6,15 +6,29 @@ layout("/layouts/platform_h5.html"){
<van-nav-bar title="职代会" left-text="返回" left-arrow @click-left="returnH5Home('apps')" placeholder fixed></van-nav-bar>
<div class="congress-page">
<div class="empty-box" v-if="!loading && timesList.length === 0">
<div v-if="loading" class="congress-skeleton">
<div class="congress-skeleton__hero">
<van-skeleton title :row="2"></van-skeleton>
</div>
<div class="congress-skeleton__list">
<div v-for="index in 5" :key="'congress-skeleton-' + index" class="congress-skeleton__card">
<van-skeleton avatar avatar-size="56px" title :row="1"></van-skeleton>
</div>
</div>
</div>
<div class="empty-box" v-else-if="timesList.length === 0">
<van-icon name="notes-o" class="empty-icon"></van-icon>
<p class="empty-text">暂无参与届次信息</p>
</div>
<template v-else>
<div class="page-header">
<img class="page-header__bg" src="${base!}/images/congress/home/congress-hero-v8.png" alt="">
<div class="page-header__color-layer" aria-hidden="true"></div>
<div class="session-info-card">
<h2 class="session-title">{{ selectedSessionName }}</h2>
<h2 class="session-title">{{ selectedTimeText || selectedSessionName }}</h2>
<p class="session-subtitle">凝聚共识 · 共谋发展</p>
<button v-if="timesList.length > 1" type="button" class="header-switch-btn" @click="showTimePicker = true">
<span>切换届次</span>
<van-icon name="arrow-down" size="12"></van-icon>
@@ -31,9 +45,10 @@ layout("/layouts/platform_h5.html"){
:class="resolveTheme(module.name)"
@click="handleModuleClick(module)"
>
<img class="module-card__bg" :src="module.background" alt="">
<div class="module-header">
<div class="icon-wrapper">
<van-icon :name="module.icon" size="36"></van-icon>
<img :src="module.image" :alt="module.name">
</div>
<div class="module-info">
<h4 class="module-title">{{ module.name }}</h4>
@@ -85,7 +100,6 @@ layout("/layouts/platform_h5.html"){
},
methods: {
async initPage() {
const loading = this.$toast.loading({message: "加载中...", forbidClick: true, duration: 0})
try {
await this.fetchRepTimes()
if (this.timesList.length > 0) {
@@ -95,7 +109,6 @@ layout("/layouts/platform_h5.html"){
}
} finally {
this.loading = false
loading.close()
}
},
loginName() {
@@ -121,14 +134,16 @@ layout("/layouts/platform_h5.html"){
const modules = (res.data || []).map((item) => ({
id: item.id,
name: item.name,
icon: this.resolveIcon(item.name),
image: this.resolveImage(item.name),
background: this.resolveBackground(item.name),
desc: this.resolveDesc(item.name)
}))
const hasSignin = modules.some((item) => item.name && item.name.indexOf("会议签到") > -1)
this.moduleList = hasSignin ? modules : [{
id: "signin",
name: "会议签到",
icon: "completed",
image: this.resolveImage("会议签到"),
background: this.resolveBackground("会议签到"),
desc: "点击进行会议签到"
}].concat(modules)
},
@@ -184,15 +199,23 @@ layout("/layouts/platform_h5.html"){
"&timeId=" + encodeURIComponent(this.selectedTimeId) +
"&sessionId=" + encodeURIComponent(this.selectedSessionId))
},
resolveIcon(name) {
if (!name) return "orders-o"
if (name.indexOf("会议签到") > -1) return "completed"
if (name.indexOf("评议") > -1) return "star"
if (name.indexOf("议") > -1) return "todo-list"
if (name.indexOf("资料") > -1) return "notes"
if (name.indexOf("公告") > -1 || name.indexOf("结果") > -1) return "bell"
if (name.indexOf("文件") > -1) return "description"
return "orders"
resolveImage(name) {
const imageRoot = "${base!}/images/congress/home/"
if (!name) return imageRoot + "file-notice-v4.png"
if (name.indexOf("会议签到") > -1) return imageRoot + "meeting-sign-v4.png"
if (name.indexOf("议") > -1) return imageRoot + "evaluation-vote-v4.png"
if (name.indexOf("议题") > -1) return imageRoot + "topic-vote-v4.png"
if (name.indexOf("表决公告") > -1 || name.indexOf("结果") > -1) return imageRoot + "vote-notice-v4.png"
return imageRoot + "file-notice-v4.png"
},
resolveBackground(name) {
const imageRoot = "${base!}/images/congress/home/"
if (!name) return imageRoot + "file-notice-bg-v1.png"
if (name.indexOf("会议签到") > -1) return imageRoot + "meeting-sign-bg-v1.png"
if (name.indexOf("评议") > -1) return imageRoot + "evaluation-vote-bg-v1.png"
if (name.indexOf("议题") > -1) return imageRoot + "topic-vote-bg-v1.png"
if (name.indexOf("表决公告") > -1 || name.indexOf("结果") > -1) return imageRoot + "vote-notice-bg-v1.png"
return imageRoot + "file-notice-bg-v1.png"
},
resolveDesc(name) {
if (!name) return ""
@@ -223,18 +246,74 @@ layout("/layouts/platform_h5.html"){
<style id="style-congress-h5">
.congress-page {
height: calc(100vh - 46px);
background: #f4f8ff;
background: #f3f7fd;
overflow: hidden;
display: flex;
flex-direction: column;
}
.congress-skeleton {
flex: 1;
min-height: 0;
overflow: hidden;
background: #f3f7fd;
}
.congress-skeleton__hero {
height: 220px;
padding: 0 24px;
display: flex;
align-items: center;
box-sizing: border-box;
background: #e8eef7;
}
.congress-skeleton__hero .van-skeleton {
width: 52%;
padding: 0;
}
.congress-skeleton__list {
padding: 12px;
display: grid;
gap: 12px;
box-sizing: border-box;
}
.congress-skeleton__card {
min-height: 104px;
padding: 18px 16px;
display: flex;
align-items: center;
box-sizing: border-box;
border: 1px solid #e5edf8;
border-radius: 12px;
background: #fff;
box-shadow: 0 7px 20px rgba(55, 85, 126, 0.06);
}
.congress-skeleton__card .van-skeleton {
width: 100%;
padding: 0;
}
#app > .van-nav-bar .van-nav-bar__title {
color: #1f2937;
font-size: 16px;
font-weight: 600;
}
#app > .van-nav-bar .van-icon,
#app > .van-nav-bar .van-nav-bar__text {
color: #1989fa;
}
.page-header {
min-height: 192px;
background: #1686ff url("${base!}/images/congress/h5-congress-header.png") center center / cover no-repeat;
padding: 0px 15px 50px;
min-height: 220px;
background-color: #1458ad;
padding: 18px 24px 24px;
box-sizing: border-box;
color: #fff;
box-shadow: 0 12px 28px rgba(25, 137, 250, 0.18);
display: flex;
justify-content: flex-start;
align-items: center;
@@ -243,6 +322,30 @@ layout("/layouts/platform_h5.html"){
flex-shrink: 0;
}
.page-header__bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: 88% center;
display: block;
}
.page-header__color-layer {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 64%;
z-index: 1;
pointer-events: none;
background: linear-gradient(90deg,
rgba(22, 112, 232, 0.98) 0%,
rgba(22, 112, 232, 0.88) 52%,
rgba(22, 112, 232, 0) 100%);
}
.session-info-card {
display: flex;
flex-direction: column;
@@ -250,34 +353,41 @@ layout("/layouts/platform_h5.html"){
text-align: left;
position: relative;
z-index: 3;
max-width: 50%;
max-width: 52%;
}
.session-title {
font-size: 25px;
font-weight: 700;
line-height: 1.25;
line-height: 1.32;
color: #fff;
margin: 0 0 10px;
text-shadow: 0 4px 12px rgba(3, 77, 168, 0.18);
margin: 0;
text-shadow: 0 3px 10px rgba(3, 77, 168, 0.2);
}
.session-subtitle {
margin: 8px 0 0;
font-size: 14px;
line-height: 1.4;
color: rgba(255, 255, 255, 0.88);
white-space: nowrap;
}
.header-switch-btn {
margin-top: 6px;
height: 34px;
padding: 0 14px;
border: 0;
border-radius: 18px;
background: rgba(255, 255, 255, 0.18);
margin-top: 15px;
height: 36px;
padding: 0 15px;
border: 1px solid rgba(255, 255, 255, 0.56);
border-radius: 20px;
background: rgba(255, 255, 255, 0.12);
color: #fff;
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 14px;
font-weight: 600;
font-size: 13px;
line-height: 1;
cursor: pointer;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.36), 0 8px 18px rgba(0, 66, 160, 0.12);
box-shadow: 0 8px 18px rgba(0, 66, 160, 0.12);
backdrop-filter: blur(8px);
}
@@ -291,9 +401,9 @@ layout("/layouts/platform_h5.html"){
z-index: 2;
flex: 1;
min-height: 0;
margin: -72px 0 0;
padding: 18px 16px 24px;
background: #fff;
margin: 0;
padding: 12px 12px 24px;
background: #f3f7fd;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
@@ -305,14 +415,18 @@ layout("/layouts/platform_h5.html"){
}
.module-card {
min-height: 98px;
background: linear-gradient(135deg, #f8fbff 0%, #fff 100%);
border-radius: 14px;
box-shadow: 0 8px 18px rgba(27, 56, 105, 0.06);
--module-color: #1989fa;
--module-bg: #fff;
--module-icon-bg: #edf5ff;
--module-border: #e5edf8;
min-height: 104px;
background: var(--module-bg);
border-radius: 12px;
box-shadow: 0 7px 20px rgba(55, 85, 126, 0.08);
overflow: hidden;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid rgba(237, 242, 250, 0.88);
border: 1px solid var(--module-border);
position: relative;
}
@@ -325,25 +439,49 @@ layout("/layouts/platform_h5.html"){
box-shadow: 0 4px 16px rgba(25, 137, 250, 0.08);
}
.module-card__bg {
position: absolute;
top: 50%;
right: 38px;
z-index: 0;
width: 42%;
height: 88%;
display: block;
object-fit: contain;
object-position: right center;
opacity: 1;
pointer-events: none;
transform: translateY(-50%);
}
.module-header {
min-height: 60px;
padding: 14px;
min-height: 72px;
padding: 16px 15px;
display: flex;
align-items: center;
gap: 12px;
gap: 14px;
position: relative;
z-index: 2;
}
.icon-wrapper {
width: 54px;
height: 54px;
border-radius: 16px;
width: 58px;
height: 58px;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
background: #eef5ff;
flex-shrink: 0;
box-shadow: 0 8px 18px rgba(25, 137, 250, 0.1);
color: #1989fa;
overflow: hidden;
background: #eef3fb;
box-shadow: 0 4px 12px rgba(42, 74, 117, 0.1);
}
.icon-wrapper img {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
.module-info {
@@ -352,9 +490,9 @@ layout("/layouts/platform_h5.html"){
}
.module-title {
font-size: 16px;
font-weight: 700;
color: #111a3a;
font-size: 17px;
font-weight: 600;
color: #23344a;
margin: 0 0 6px;
line-height: 1.4;
overflow: hidden;
@@ -363,7 +501,7 @@ layout("/layouts/platform_h5.html"){
}
.module-desc {
font-size: 12px;
font-size: 13px;
color: #7d89a6;
margin: 0;
line-height: 1.5;
@@ -373,62 +511,38 @@ layout("/layouts/platform_h5.html"){
}
.arrow-icon {
width: 26px;
height: 26px;
border-radius: 50%;
display: flex;
width: 16px;
min-width: 16px;
height: 16px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #1989fa;
background: rgba(25, 137, 250, 0.1);
flex-shrink: 0;
transition: all 0.3s ease;
color: #aab6c5;
background: transparent;
font-size: 16px;
line-height: 16px;
}
.theme-green {
background: linear-gradient(135deg, #f3fff9 0%, #fff 100%);
}
.theme-green .icon-wrapper {
color: #17c979;
background: #eafbf2;
box-shadow: 0 8px 18px rgba(23, 201, 121, 0.12);
}
.theme-green .arrow-icon {
color: #17b977;
background: rgba(23, 201, 121, 0.12);
--module-color: #3d9182;
--module-bg: #fff;
--module-icon-bg: #edf6f3;
--module-border: #e4eee9;
}
.theme-purple {
background: linear-gradient(135deg, #fbf7ff 0%, #fff 100%);
}
.theme-purple .icon-wrapper {
color: #8756e9;
background: #f1eaff;
box-shadow: 0 8px 18px rgba(135, 86, 233, 0.12);
}
.theme-purple .arrow-icon {
color: #8756e9;
background: rgba(135, 86, 233, 0.12);
--module-color: #6c6fa3;
--module-bg: #fff;
--module-icon-bg: #f0f0f7;
--module-border: #e9e7f1;
}
.theme-orange {
background: linear-gradient(135deg, #fff8ed 0%, #fff 100%);
}
.theme-orange .icon-wrapper {
color: #ff9f1a;
background: #fff2df;
box-shadow: 0 8px 18px rgba(255, 159, 26, 0.12);
}
.theme-orange .arrow-icon {
color: #d9a05b;
background: rgba(255, 159, 26, 0.12);
--module-color: #a78345;
--module-bg: #fff;
--module-icon-bg: #f7f2e9;
--module-border: #eee9df;
}
.empty-box {
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -16,7 +16,7 @@ layout("/layouts/platform_h5.html"){
</div>
<div v-if="voteInfo" class="footer-bar">
<van-button type="primary" block @click="handleVoteClick">{{ isVoted(voteInfo.voted) ? '查看投票' : '投票' }}</van-button>
<van-button type="primary" block :disabled="voteDisabled" @click="handleVoteClick">{{ voteButtonText }}</van-button>
</div>
</div>
</div>
@@ -45,6 +45,20 @@ layout("/layouts/platform_h5.html"){
const source = name.indexOf(".") > -1 ? name : url
const index = source.lastIndexOf(".")
return index > -1 ? source.substring(index + 1).toLowerCase() : ""
},
voteState() {
if (!this.voteInfo) return ""
if (this.isVoted(this.voteInfo.voted)) return "voted"
return this.voteInfo.votingStatus || "ongoing"
},
voteDisabled() {
return this.voteState === "upcoming" || this.voteState === "ended"
},
voteButtonText() {
if (this.voteState === "voted") return "查看投票"
if (this.voteState === "upcoming") return "投票未开始"
if (this.voteState === "ended") return "投票已结束"
return "投票"
}
},
methods: {
@@ -159,6 +173,14 @@ layout("/layouts/platform_h5.html"){
this.$toast("投票信息未加载")
return
}
if (this.voteState === "upcoming") {
this.$toast("投票尚未开始")
return
}
if (this.voteState === "ended") {
this.$toast("投票已结束,无法继续投票")
return
}
window.localStorage.setItem("voteInfo", JSON.stringify(this.voteInfo))
this.$pjaxReplace(this.isVoted(this.voteInfo.voted) ? "/platform/h5/congress/voted" : "/platform/h5/congress/vote")
},
@@ -247,6 +269,13 @@ layout("/layouts/platform_h5.html"){
font-weight: 600;
box-shadow: 0 4px 12px rgba(25, 137, 250, 0.3);
}
.footer-bar .van-button.van-button--disabled {
color: #fff;
background: #b8c1cc;
box-shadow: none;
opacity: 1;
}
</style>
<!--#
@@ -157,6 +157,14 @@ layout("/layouts/platform_h5.html"){
return Math.ceil(item.options.length / 2)
},
submitVote() {
if (this.voteInfo.votingStatus === "upcoming") {
this.$toast("投票尚未开始")
return
}
if (this.voteInfo.votingStatus === "ended") {
this.$toast("投票已结束,无法继续投票")
return
}
const missing = this.voteItems.filter((_, index) => !this.selectedVotes[index])
if (missing.length > 0) {
this.$toast("请完成所有投票项的选择")