Merge branch 'feature_程诚_0831问题修复' into release_20260831

This commit is contained in:
2026-09-01 16:00:47 +08:00
8 changed files with 781 additions and 288 deletions
@@ -109,15 +109,27 @@ layout("/layouts/platform.html"){
if (!value) return []
try {
const data = typeof value === "string" ? JSON.parse(value) : value
return Array.isArray(data) ? data : []
if (Array.isArray(data)) return data
if (data && typeof data === "object") {
return Object.keys(data).map(item => ({item, result: data[item]}))
}
return []
} catch (e) {
return []
}
},
voteOptionText(item) {
const value = item.result || item.vote || item.selected || item.option || item.value
if (Array.isArray(value)) return value.join("、")
if (value && typeof value === "object") {
return value.label || value.name || value.value || "-"
}
return value || "-"
},
voteResultText(value) {
return this.parseVoteResult(value).map(item => {
return (item.item || item.itemName || "投票项") + "" +
(item.selected || item.option || item.value || "-")
this.voteOptionText(item)
}).join("")
},
buildSummary(records) {
@@ -125,7 +137,7 @@ layout("/layouts/platform.html"){
records.forEach(record => {
this.parseVoteResult(record.voteResult).forEach(item => {
const itemName = item.item || item.itemName || "投票项"
const option = item.selected || item.option || item.value || "-"
const option = this.voteOptionText(item)
if (!counter[itemName]) counter[itemName] = {}
counter[itemName][option] = (counter[itemName][option] || 0) + 1
})
@@ -14,12 +14,11 @@ const welfareOption = {
<el-table-column align="center" header-align="center" label="系统默认选择" width="140">
<template slot-scope="{row}">
<el-radio
v-model="systemDefaultOption"
:label="row"
@change="setSystemDefault(row)">
<el-checkbox
:value="!!row.isSystemDefault"
@change="setSystemDefault(row, $event)">
&nbsp;
</el-radio>
</el-checkbox>
</template>
</el-table-column>
@@ -124,9 +123,10 @@ const welfareOption = {
},
// 每个福利项目只允许配置一个系统默认选项,保存时会随福利选项一并提交。
setSystemDefault(defaultOption) {
setSystemDefault(defaultOption, checked = true) {
this.systemDefaultOption = checked ? defaultOption : null;
this.welfareList.forEach((item) => {
this.$set(item, 'isSystemDefault', item === defaultOption);
this.$set(item, 'isSystemDefault', checked && item === defaultOption);
});
},
@@ -200,7 +200,7 @@ const welfareOption = {
transform: translate(-50%, -50%);
}
/deep/ .welfare-option .el-radio__label {
/deep/ .welfare-option .el-checkbox__label {
padding-left: 0;
}
@@ -397,6 +397,19 @@ layout("/layouts/platform_h5.html"){
justify-content: flex-end;
}
.sports-event-union-action-hint {
display: inline-flex;
align-items: center;
color: #ed6a0c;
font-size: 12px;
line-height: 18px;
text-align: right;
}
.sports-event-union-action-hint.is-authorized {
color: #1989fa;
}
.sports-event-page .table-card .table-card-footer .van-cell__value {
display: flex;
align-items: center;
@@ -700,6 +713,12 @@ layout("/layouts/platform_h5.html"){
取消报名
</van-button>
</div>
<span
v-if="row.applyWay.includes(2) && !row.applyWay.includes(1)"
class="sports-event-union-action-hint"
:class="{'is-authorized': canUnionApplyOnPc}">
{{ canUnionApplyOnPc ? '分工会报名请在PC端进行' : '该活动为分工会报名,暂无权限' }}
</span>
</div>
</div>
</div>
@@ -731,6 +750,12 @@ layout("/layouts/platform_h5.html"){
<i class="fa fa-trash"></i>取消报名
</van-button>
</div>
<span
v-if="row.applyWay.includes(2) && !row.applyWay.includes(1)"
class="sports-event-union-action-hint"
:class="{'is-authorized': canUnionApplyOnPc}">
{{ canUnionApplyOnPc ? '分工会报名请在PC端进行' : '该活动为分工会报名,暂无权限' }}
</span>
</div>
</div>
</div>
@@ -768,6 +793,16 @@ layout("/layouts/platform_h5.html"){
}
},
components: {},
computed: {
canUnionApplyOnPc() {
return this.$auth.hasRoleOr([
"SYSADMIN",
"SCHOOL_UNION_ADMIN",
"BRANCH_UNION_CHAIRMAN",
"BRANCH_UNION_WENTI_SPORTS"
]) && this.$auth.hasPermission("activity.apply")
}
},
methods: {
toggleViewMode() {
this.viewMode = this.viewMode === "card" ? "list" : "card"
@@ -324,11 +324,37 @@ layout("/layouts/platform_h5.html"){
}
.brand-course-card__detail:nth-child(-n + 2) .brand-course-card__detail-label,
.brand-course-card__detail:nth-child(-n + 2) .brand-course-card__detail-value {
.brand-course-card__detail:nth-child(2) .brand-course-card__detail-value {
overflow: hidden;
text-overflow: ellipsis;
overflow-wrap: normal;
white-space: nowrap;
}
.brand-course-card__detail:first-child .brand-course-card__detail-value {
display: block;
max-width: 100%;
overflow: hidden;
overflow-wrap: normal;
text-overflow: ellipsis;
white-space: nowrap;
}
.brand-course-contact-popover {
display: block;
min-width: 0;
max-width: 100%;
}
.brand-course-contact-popover__content {
max-width: 240px;
padding: 8px 12px;
overflow-wrap: anywhere;
color: #33445a;
font-size: 12px;
line-height: 18px;
}
.brand-course-card__links {
display: flex;
min-width: 0;
@@ -913,7 +939,17 @@ layout("/layouts/platform_h5.html"){
<van-icon name="friends-o"></van-icon>
<div class="brand-course-card__detail-copy">
<div class="brand-course-card__detail-label">联系人</div>
<div class="brand-course-card__detail-value">{{row.courseInstructor}}</div>
<van-popover
class="brand-course-contact-popover"
:value="contactPopoverIndex === index"
placement="top-start"
trigger="click"
@input="setContactPopover(index, $event)">
<div class="brand-course-contact-popover__content">{{row.courseInstructor || '暂无'}}</div>
<template #reference>
<div class="brand-course-card__detail-value">{{row.courseInstructor || '暂无'}}</div>
</template>
</van-popover>
</div>
</div>
<div class="brand-course-card__detail">
@@ -1009,6 +1045,7 @@ layout("/layouts/platform_h5.html"){
activeAssort: null,
filterOpened: false,
viewMode: 'card',
contactPopoverIndex: -1,
weekdayCNMap: {0: '周日', 1: '周一', 2: '周二', 3: '周三', 4: '周四', 5: '周五', 6: '周六'},
}
@@ -1017,6 +1054,9 @@ layout("/layouts/platform_h5.html"){
toggleViewMode() {
this.viewMode = this.viewMode === 'card' ? 'list' : 'card'
},
setContactPopover(index, visible) {
this.contactPopoverIndex = visible ? index : -1
},
onAssortChange(name) {
this.pageForm.assortTypes = name ? JSON.stringify([name]) : []
this.doSearch()
@@ -278,11 +278,6 @@ layout("/layouts/platform_h5.html"){
.welfare-list-card__choices-arrow {
margin-left: 10px;
flex: none;
transition: transform .2s ease;
}
.welfare-list-card__choices-arrow.is-expanded {
transform: rotate(90deg);
}
.welfare-list-card__choices > .welfare-inline-loading,
@@ -446,32 +441,6 @@ layout("/layouts/platform_h5.html"){
white-space: nowrap;
}
.welfare-list-card__actions {
display: flex;
min-height: 42px;
margin-top: 10px;
padding-top: 10px;
align-items: center;
justify-content: flex-end;
border-top: 1px solid #edf1f6;
box-sizing: border-box;
}
.welfare-list-card__detail-button {
display: inline-flex;
height: 30px;
padding: 0 14px;
align-items: center;
justify-content: center;
border: 1px solid #1989fa;
border-radius: 16px;
color: #1989fa;
background-color: #fff;
font-size: 13px;
line-height: 28px;
box-sizing: border-box;
}
.welfare-list-card__time .van-icon {
margin-right: 5px;
flex: none;
@@ -486,76 +455,6 @@ layout("/layouts/platform_h5.html"){
font-size: 17px;
}
.welfare-list-card__expanded {
padding: 12px;
border-top: 1px solid #edf1f6;
background-color: #f8fbff;
}
.welfare-inline-card {
margin-bottom: 10px;
padding: 0 12px;
overflow: hidden;
border-radius: 10px;
background-color: #fff;
box-shadow: 0 4px 14px rgba(55, 85, 126, .06);
}
.welfare-inline-card:last-child {
margin-bottom: 0;
}
.welfare-inline-card__title {
position: relative;
height: 42px;
padding-left: 8px;
border-bottom: 1px solid #edf1f6;
color: #263548;
font-size: 14px;
font-weight: 600;
line-height: 42px;
}
.welfare-inline-card__title::before {
position: absolute;
top: 14px;
bottom: 14px;
left: 0;
width: 3px;
border-radius: 2px;
background-color: #1989fa;
content: '';
}
.welfare-inline-row {
display: flex;
min-height: 40px;
padding: 6px 0;
align-items: center;
border-bottom: 1px solid #edf1f6;
box-sizing: border-box;
}
.welfare-inline-row:last-child {
border-bottom: 0;
}
.welfare-inline-row__label {
width: 72px;
flex: none;
color: #7f8da1;
font-size: 12px;
}
.welfare-inline-row__value {
min-width: 0;
flex: 1;
color: #263548;
font-size: 13px;
line-height: 19px;
word-break: break-word;
}
.welfare-inline-selection {
display: flex;
min-height: 72px;
@@ -780,8 +679,10 @@ layout("/layouts/platform_h5.html"){
v-for="row in tableData"
:key="row.id"
class="welfare-list-card"
role="button"
@click="modifySelection(row)"
>
<div class="welfare-list-card__summary" @click="toggleRow(row)">
<div class="welfare-list-card__summary">
<div class="welfare-list-card__order-head">
<div class="welfare-list-card__order-info">
<div class="welfare-list-card__title">{{ row.name }}</div>
@@ -812,66 +713,9 @@ layout("/layouts/platform_h5.html"){
</template>
<div v-else class="welfare-inline-empty">暂无福利选择记录</div>
</div>
<van-icon class="welfare-list-card__arrow welfare-list-card__choices-arrow" :class="{'is-expanded': row.expanded}" name="arrow"></van-icon>
<van-icon class="welfare-list-card__arrow welfare-list-card__choices-arrow" name="arrow"></van-icon>
</div>
</div>
<div class="welfare-list-card__expanded" v-show="row.expanded">
<div class="welfare-inline-loading" v-if="row.detailLoading">
<van-loading size="20" color="#1989fa">加载中...</van-loading>
</div>
<template v-else-if="row.projectInfo">
<section class="welfare-inline-card">
<div class="welfare-inline-card__title">项目基本信息</div>
<div class="welfare-inline-row">
<span class="welfare-inline-row__label">项目名称</span>
<span class="welfare-inline-row__value">{{ row.projectInfo.name || row.name }}</span>
</div>
<div class="welfare-inline-row">
<span class="welfare-inline-row__label">选择时间</span>
<span class="welfare-inline-row__value">{{ formatRange(row.projectInfo.choiceTimeStart, row.projectInfo.choiceTimeEnd) }}</span>
</div>
<div class="welfare-inline-row">
<span class="welfare-inline-row__label">发放时间</span>
<span class="welfare-inline-row__value">{{ formatRange(row.projectInfo.provideTimeStart, row.projectInfo.provideTimeEnd) }}</span>
</div>
<div class="welfare-inline-row">
<span class="welfare-inline-row__label">发放地点</span>
<span class="welfare-inline-row__value">{{ row.projectInfo.provideAddress || '暂无' }}</span>
</div>
</section>
<section class="welfare-inline-card">
<div class="welfare-inline-card__title">联系信息</div>
<div class="welfare-inline-row">
<span class="welfare-inline-row__label">联系电话</span>
<span class="welfare-inline-row__value">{{ firstSelection(row).mobile || '暂无' }}</span>
</div>
<div class="welfare-inline-row">
<span class="welfare-inline-row__label">收货地址</span>
<span class="welfare-inline-row__value">{{ firstSelection(row).receiveAddress || '暂无' }}</span>
</div>
</section>
<section class="welfare-inline-card">
<div class="welfare-inline-card__title">我的选择</div>
<div v-if="row.selections.length > 0">
<div class="welfare-inline-selection" v-for="item in row.selections" :key="item.selectId || item.id">
<van-image class="welfare-inline-selection__image" :src="item.imgUrl" fit="cover" @click.stop="previewSelectionImage(item.imgUrl)">
<template #error><van-icon name="photo-o" size="24"></van-icon></template>
</van-image>
<div class="welfare-inline-selection__content">
<div class="welfare-inline-selection__name">{{ item.optionName || '未知选项' }}</div>
<div class="welfare-inline-selection__meta">
<span class="welfare-inline-selection__tag">× {{ item.selectNum || 0 }}</span>
</div>
</div>
</div>
</div>
<div v-else class="welfare-inline-empty">暂无福利选择记录</div>
</section>
</template>
</div>
</article>
</div>
</van-list>
@@ -928,10 +772,8 @@ layout("/layouts/platform_h5.html"){
if (res.code !== 0 || !res.data) return
const list = (res.data.list || []).map((row) => ({
...row,
expanded: false,
detailLoading: false,
detailLoaded: false,
projectInfo: null,
selections: []
}))
this.tableData = this.tableData.concat(list)
@@ -993,7 +835,6 @@ layout("/layouts/platform_h5.html"){
description: option.description
}
}) : []
this.$set(row, "projectInfo", projectInfo)
this.$set(row, "selections", selections)
this.$set(row, "detailLoaded", true)
} finally {
@@ -1001,36 +842,8 @@ layout("/layouts/platform_h5.html"){
}
},
/**
* 展开或收起当前福利卡片的内容详情。
* @param row 当前福利项目
*/
toggleRow(row) {
row.expanded = !row.expanded
},
previewSelectionImage(imageUrl) {
if (imageUrl) vant.ImagePreview([imageUrl])
},
/**
* 获取当前福利的首条选择记录,用于展示联系电话和收货地址。
* @param row 当前福利项目
* @return {Object} 首条选择记录;无记录时返回空对象
*/
firstSelection(row) {
return row.selections && row.selections.length > 0 ? row.selections[0] : {}
},
/**
* 格式化项目起止时间。
* @param startTime 开始时间
* @param endTime 结束时间
* @return {string} 起止时间文本
*/
formatRange(startTime, endTime) {
if (!startTime || !endTime) return "暂无"
return this.$moment(startTime).format("YYYY-MM-DD HH:mm") + " 至 " + this.$moment(endTime).format("YYYY-MM-DD HH:mm")
modifySelection(row) {
this.$pjaxReplace("/platform/h5/welfare/userSelect/index?id=" + (row.projectId || row.id) + "&from=mine")
},
/**
@@ -26,6 +26,10 @@ layout("/layouts/platform_h5.html"){
background: var(--bg-color);
}
.page-container.has-footer-contact {
padding-bottom: calc(126px + env(safe-area-inset-bottom));
}
.page-container .van-nav-bar {
background-color: #fff;
}
@@ -383,27 +387,6 @@ layout("/layouts/platform_h5.html"){
text-align: center;
}
.welfare-option-selected-mark {
position: absolute;
right: 0;
top: 0;
z-index: 2;
display: flex;
width: 32px;
height: 32px;
padding: 0 0 4px 4px;
align-items: center;
justify-content: center;
border-radius: 0 11px 0 18px;
color: #fff;
background: #35b878;
box-shadow: none;
box-sizing: border-box;
font-size: 14px;
line-height: 32px;
text-align: center;
}
.welfare-deadline {
margin: 0 12px 8px;
padding: 9px 11px;
@@ -742,10 +725,6 @@ layout("/layouts/platform_h5.html"){
backdrop-filter: blur(8px);
}
.van-sticky--fixed .welfare-section-title {
margin-top: 8px;
}
.welfare-section-title .title-text {
position: relative;
display: flex;
@@ -773,10 +752,482 @@ layout("/layouts/platform_h5.html"){
font-weight: 600;
}
/* 项目封面保持单图全宽展示,下拉时通过动态高度逐步露出完整画面。 */
.welfare-header {
height: 232px;
margin: 0;
overflow: hidden;
border-radius: 0;
box-shadow: none;
transition: height .28s ease-out;
}
.welfare-header.is-pulling {
transition: none;
}
.welfare-header.is-expanded {
background-color: #f3f7fd;
}
.welfare-header-img .van-image__img {
object-position: center;
}
.welfare-cover-pull-hint {
position: absolute;
right: 0;
bottom: 28px;
left: 0;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, .92);
font-size: 12px;
line-height: 18px;
text-shadow: 0 1px 5px rgba(20, 50, 88, .5);
pointer-events: none;
}
.welfare-cover-pull-hint::before {
position: absolute;
right: 0;
bottom: -28px;
left: 0;
z-index: -1;
height: 76px;
background: linear-gradient(180deg, rgba(18, 45, 82, 0), rgba(18, 45, 82, .42));
content: "";
}
.welfare-cover-pull-hint .van-icon {
margin-right: 6px;
font-size: 16px;
}
.main-content {
z-index: 3;
}
.welfare-project-card {
margin: -20px 12px 12px;
padding: 16px;
border-radius: 14px;
background-color: #fff;
box-shadow: 0 8px 24px rgba(50, 83, 126, .12);
box-sizing: border-box;
}
.welfare-project-card__head {
display: flex;
min-width: 0;
align-items: center;
}
.welfare-project-card__title-wrap {
display: flex;
min-width: 0;
flex: 1;
align-items: center;
flex-wrap: wrap;
}
.welfare-project-card__title {
min-width: 0;
overflow: hidden;
color: var(--text-primary);
font-size: 18px;
line-height: 25px;
text-overflow: ellipsis;
white-space: nowrap;
}
.welfare-project-card__festival {
height: 22px;
margin-left: 8px;
padding: 0 7px;
flex: none;
border: 1px solid #acd3ff;
border-radius: 5px;
color: #1989fa;
background-color: #f4f9ff;
font-size: 12px;
line-height: 20px;
box-sizing: border-box;
}
.welfare-project-card__status {
display: flex;
margin-top: 8px;
align-items: center;
color: #1989fa;
font-size: 12px;
line-height: 18px;
}
.welfare-project-card__status::before {
width: 6px;
height: 6px;
margin-right: 5px;
border-radius: 50%;
background-color: currentColor;
content: "";
}
.welfare-project-card__status.is-ended {
color: #a8b0bc;
}
.welfare-project-card__facts {
display: grid;
margin-top: 12px;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.welfare-project-fact {
display: flex;
min-width: 0;
padding: 10px;
align-items: center;
border-radius: 8px;
background: #f7faff;
box-sizing: border-box;
}
.welfare-project-fact__icon {
display: flex;
width: 32px;
height: 32px;
margin-right: 9px;
flex: none;
align-items: center;
justify-content: center;
border-radius: 8px;
color: #fff;
background: #4c9cff;
box-shadow: 0 4px 10px rgba(25, 137, 250, .18);
font-size: 18px;
}
.welfare-project-fact__content {
min-width: 0;
}
.welfare-project-fact__label {
color: #8491a5;
font-size: 11px;
line-height: 17px;
}
.welfare-project-fact__value {
margin-top: 2px;
overflow: hidden;
color: #1989fa;
font-size: 13px;
font-weight: 600;
line-height: 19px;
text-overflow: ellipsis;
white-space: nowrap;
}
.welfare-project-card__meta {
display: flex;
min-width: 0;
margin-top: 10px;
align-items: center;
}
.welfare-project-card__address {
display: flex;
min-width: 0;
flex: 1;
align-items: center;
overflow: hidden;
color: #4c596b;
font-size: 13px;
line-height: 20px;
text-overflow: ellipsis;
white-space: nowrap;
}
.welfare-project-card__address .van-icon {
margin-right: 5px;
flex: none;
color: #66758a;
font-size: 15px;
}
.welfare-project-card__tags {
display: flex;
margin-top: 8px;
flex-wrap: wrap;
column-gap: 5px;
row-gap: 5px;
}
.welfare-project-card__tag {
height: 22px;
padding: 0 7px;
border: 1px solid #b8d9ff;
border-radius: 4px;
color: #1989fa;
background-color: #f7fbff;
font-size: 11px;
line-height: 20px;
box-sizing: border-box;
}
.welfare-deadline {
margin-top: 0;
}
.welfare-section-title {
min-height: 42px;
margin: 0 12px 8px;
padding: 7px 0;
border-radius: 0;
background: rgba(243, 247, 253, .96);
box-shadow: none;
}
.welfare-section-title .title-text {
align-self: stretch;
}
.welfare-section-title .title-label {
color: var(--text-primary);
font-size: 16px;
line-height: 28px;
}
.welfare-section-title .title-count {
margin-left: 5px;
color: #7d899b;
font-size: 13px;
}
.welfare-option {
border-color: #e2eaf4;
box-shadow: none;
}
.welfare-option.selected {
border-color: #c5dfff;
background: linear-gradient(90deg, #f5faff 0%, #fff 100%);
box-shadow: none;
}
.welfare-option-main {
min-height: 128px;
}
.welfare-option-image {
width: 94px;
height: 94px;
margin: 16px 0 16px 14px;
}
.welfare-option-content {
padding: 14px 12px 10px;
}
.welfare-option-title {
margin-bottom: 4px;
}
.welfare-option-supplier {
overflow: hidden;
margin-bottom: 4px;
color: #7a8799;
font-size: 11px;
line-height: 17px;
text-overflow: ellipsis;
white-space: nowrap;
}
.welfare-option-rank {
margin-bottom: 5px;
}
.welfare-option-desc {
-webkit-line-clamp: 1;
}
.welfare-option-meta {
display: flex;
min-width: 0;
margin-top: 7px;
align-items: center;
overflow: hidden;
}
.welfare-option-meta__tags {
display: flex;
min-width: 0;
flex: 1;
column-gap: 5px;
overflow: hidden;
}
.welfare-option-meta__tag {
height: 19px;
padding: 0 5px;
flex: none;
border: 1px solid #b8d9ff;
border-radius: 3px;
color: #1989fa;
background-color: #f7fbff;
font-size: 10px;
line-height: 17px;
box-sizing: border-box;
}
.welfare-option-meta__total {
margin-left: 6px;
flex: none;
color: #8a96a8;
font-size: 10px;
line-height: 18px;
}
.welfare-option-select-row {
min-height: 44px;
padding: 7px 12px;
justify-content: space-between;
background: transparent;
}
.welfare-footer {
display: block;
}
.welfare-footer.has-contact {
padding-top: 8px;
padding-bottom: calc(8px + env(safe-area-inset-bottom));
}
.welfare-footer__actions {
display: flex;
align-items: center;
column-gap: 12px;
}
.welfare-footer__selection {
min-width: 92px;
color: #66758a;
font-size: 13px;
white-space: nowrap;
}
.welfare-footer__selection strong {
margin-left: 3px;
color: #1989fa;
font-size: 20px;
font-weight: 600;
}
.welfare-footer__contact {
display: flex;
min-width: 0;
min-height: 50px;
margin-bottom: 8px;
padding: 5px 8px;
align-items: center;
color: #66758a;
background-color: #fff;
font-size: 12px;
line-height: 18px;
box-sizing: border-box;
}
.welfare-footer__contact-icon {
display: flex;
width: 36px;
height: 36px;
margin-right: 10px;
flex: none;
align-items: center;
justify-content: center;
border-radius: 8px;
color: #1989fa;
background-color: #edf5ff;
font-size: 20px;
}
.welfare-footer__contact-info {
min-width: 0;
flex: 1;
}
.welfare-footer__contact-primary,
.welfare-footer__contact-address {
display: flex;
min-width: 0;
align-items: baseline;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.welfare-footer__contact-primary {
color: #1f2937;
font-size: 12px;
line-height: 20px;
}
.welfare-footer__contact-address {
margin-top: 2px;
font-size: 11px;
line-height: 16px;
}
.welfare-footer__contact-label {
margin-right: 3px;
flex: none;
color: #929cab;
}
.welfare-footer__contact-name,
.welfare-footer__contact-mobile,
.welfare-footer__contact-address-value {
min-width: 0;
overflow: hidden;
color: #1f2937;
font-weight: 500;
text-overflow: ellipsis;
}
.welfare-footer__contact-name {
flex: none;
}
.welfare-footer__contact-mobile {
margin-left: 12px;
flex: none;
}
.welfare-footer__contact-address-value {
flex: 1;
}
.welfare-submit-btn {
width: auto;
flex: 1;
}
.welfare-submit-btn.is-deadline-passed.van-button--disabled {
color: #a8b0bc;
background: #edf0f4;
border-color: #edf0f4;
opacity: 1;
}
</style>
<div id="app" v-cloak>
<div class="page-container">
<div class="page-container" :class="{'has-footer-contact': fromMine && hasSubmittedBefore}">
<van-nav-bar title="福利选择" left-text="返回" left-arrow @click-left="handlePageBack" fixed
placeholder></van-nav-bar>
@@ -794,8 +1245,17 @@ layout("/layouts/platform_h5.html"){
<van-empty v-else-if="!projectInfo.id" description="暂无福利信息"></van-empty>
<!-- 项目头部信息 -->
<div class="welfare-header" v-if="!pageLoading && projectInfo.id">
<van-image v-if="projectInfo.cover" :src="projectInfo.cover" class="welfare-header-img" fit="cover">
<div
class="welfare-header"
:class="{'is-pulling': coverPullEnabled, 'is-expanded': coverExpanded}"
:style="coverHeaderStyle"
v-if="!pageLoading && projectInfo.id"
@touchstart="handleCoverTouchStart"
@touchmove="handleCoverTouchMove"
@touchend="handleCoverTouchEnd"
@touchcancel="handleCoverTouchEnd"
>
<van-image v-if="projectInfo.cover" :src="projectInfo.cover" class="welfare-header-img" :fit="coverImageFit">
<template #error>
<div class="welfare-image-state">
<van-icon name="photo-o"></van-icon>
@@ -807,24 +1267,67 @@ layout("/layouts/platform_h5.html"){
<van-icon name="photo-o"></van-icon>
<span>暂无图片</span>
</div>
<div class="welfare-cover-pull-hint" v-if="projectInfo.cover">
<van-icon :name="coverExpanded ? 'arrow-up' : 'arrow-down'"></van-icon>
<span>{{ coverExpanded ? '上滑收起封面' : '下拉查看完整封面' }}</span>
</div>
</div>
<div class="main-content" v-if="!pageLoading && projectInfo.id">
<section class="welfare-project-card"
@touchstart="handleCoverTouchStart"
@touchmove="handleCoverTouchMove"
@touchend="handleCoverTouchEnd"
@touchcancel="handleCoverTouchEnd">
<div class="welfare-project-card__head">
<div class="welfare-project-card__title-wrap">
<div class="welfare-project-card__title">{{ projectInfo.name }}</div>
<span class="welfare-project-card__festival" v-if="projectInfo.festival">{{ projectInfo.festival }}</span>
</div>
</div>
<div class="welfare-project-card__status" :class="{'is-ended': isDeadlinePassed}">{{ projectChoiceStatus }}</div>
<div class="welfare-project-card__facts">
<div class="welfare-project-fact">
<van-icon class="welfare-project-fact__icon" name="calendar-o"></van-icon>
<div class="welfare-project-fact__content">
<div class="welfare-project-fact__label">选择截止</div>
<div class="welfare-project-fact__value">{{ formatProjectDateTime(projectInfo.choiceTimeEnd) }}</div>
</div>
</div>
<div class="welfare-project-fact">
<van-icon class="welfare-project-fact__icon" name="clock-o"></van-icon>
<div class="welfare-project-fact__content">
<div class="welfare-project-fact__label">发放时间</div>
<div class="welfare-project-fact__value">{{ formatProjectDateRange(projectInfo.provideTimeStart, projectInfo.provideTimeEnd) }}</div>
</div>
</div>
</div>
<div class="welfare-project-card__meta">
<div class="welfare-project-card__address">
<van-icon name="location-o"></van-icon>
<span>发放地点:{{ projectInfo.provideAddress || '待定' }}</span>
</div>
</div>
<div class="welfare-project-card__tags">
<span class="welfare-project-card__tag">{{ projectInfo.isCheckBox === 'radio' ? '单选' : '多选' }}</span>
<span class="welfare-project-card__tag">最多{{ selectionLimit }}份</span>
<span class="welfare-project-card__tag" v-if="hasExpressOption">支持快递</span>
</div>
</section>
<!-- 截止时间提醒 -->
<div class="welfare-deadline" v-if="isDeadlineSoon">
<van-icon name="warning-o"/>
<span>选择截止时间即将到期,请尽快选择!</span>
</div>
<!-- 标题到达顶部导航栏下方后固定,福利卡片继续在标题下方滚动。 -->
<van-sticky :offset-top="46" :z-index="30">
<div class="welfare-section-title">
<div class="title-text">
<span class="title-count">共 {{ projectInfo.options.length }} 项</span>
</div>
<div class="title-selection">已选 <strong>{{ selectedCount }}</strong>/{{ selectionLimit }}</div>
<div class="welfare-section-title">
<div class="title-text">
<span class="title-label">福利选项</span>
<span class="title-count">{{ projectInfo.options.length }}</span>
</div>
</van-sticky>
<div class="title-selection">已选 <strong>{{ selectedCount }}</strong>/{{ selectionLimit }}</div>
</div>
<!-- 福利选项列表 -->
<div class="welfare-options-container">
@@ -834,12 +1337,6 @@ layout("/layouts/platform_h5.html"){
class="welfare-option"
:class="{ selected: projectInfo.isCheckBox === 'radio' ? selectedRadioId === option.id : option.selectNum > 0 }"
>
<van-icon
v-if="projectInfo.isCheckBox === 'radio' ? selectedRadioId === option.id : option.selectNum > 0"
class="welfare-option-selected-mark"
name="success"
></van-icon>
<div class="welfare-option-main">
<div class="welfare-option-image" role="button" aria-label="查看福利图片"
@click.stop="previewOptionImage(option)">
@@ -862,6 +1359,10 @@ layout("/layouts/platform_h5.html"){
{{ option.optionName }}
</div>
<div class="welfare-option-supplier" v-if="option.supplier || option.shoppingName">
供应商:{{ option.supplier || option.shoppingName }}
</div>
<!-- <div class="welfare-option-tag-row">-->
<!-- <span class="welfare-option-type-tag">套餐</span>-->
<!-- </div>-->
@@ -880,10 +1381,21 @@ layout("/layouts/platform_h5.html"){
<van-icon name="arrow"/>
</div>
</div>
<div class="welfare-option-meta">
<div class="welfare-option-meta__tags">
<span class="welfare-option-meta__tag" v-if="option.isExpressDelivery">支持快递</span>
<span class="welfare-option-meta__tag" v-if="option.isSystemDefault">系统默认</span>
</div>
<span class="welfare-option-meta__total" v-if="option.selectedTotal !== undefined && option.selectedTotal !== null">
累计选择 {{ option.selectedTotal }} 份
</span>
</div>
</div>
</div>
<div class="welfare-option-select-row" @click.stop>
<div class="welfare-option-select-label">选择数量</div>
<div class="welfare-option-checkbox">
<van-stepper
:key="'input-number-'+index+'-'+(option.selectNumKey || 0)"
@@ -906,11 +1418,28 @@ layout("/layouts/platform_h5.html"){
</div>
<!-- 底部提交按钮 -->
<div class="welfare-footer" v-if="!pageLoading && projectInfo.id">
<van-button type="primary" class="welfare-submit-btn" :disabled="submitButtonDisabled"
@click="submitSelection" round>
{{ isDeadlinePassed ? '已截止' : '确认选择' }}
</van-button>
<div class="welfare-footer" :class="{'has-contact': fromMine && hasSubmittedBefore}" v-if="!pageLoading && projectInfo.id">
<div class="welfare-footer__contact" v-if="fromMine && hasSubmittedBefore">
<div class="welfare-footer__contact-icon"><van-icon name="location"></van-icon></div>
<div class="welfare-footer__contact-info">
<div class="welfare-footer__contact-primary">
<span class="welfare-footer__contact-label">收货人:</span>
<span class="welfare-footer__contact-name">{{ formData.userName || '暂无' }}</span>
<span class="welfare-footer__contact-mobile">{{ formData.mobile || '暂无' }}</span>
</div>
<div class="welfare-footer__contact-address">
<span class="welfare-footer__contact-label">收货地址:</span>
<span class="welfare-footer__contact-address-value">{{ formData.receiveAddress || '暂无' }}</span>
</div>
</div>
</div>
<div class="welfare-footer__actions">
<div class="welfare-footer__selection">已选 <strong>{{ selectedCount }}</strong>/{{ selectionLimit }}</div>
<van-button type="primary" class="welfare-submit-btn" :class="{'is-deadline-passed': isDeadlinePassed}" :disabled="submitButtonDisabled"
@click="submitSelection" round>
{{ isDeadlinePassed ? '已截止' : '确认选择' }}
</van-button>
</div>
</div>
<!-- 全屏确认页从右侧滑入;顶部导航栏固定,表单内容在中间区域独立滚动。 -->
@@ -1057,6 +1586,12 @@ layout("/layouts/platform_h5.html"){
projectInfo: {},
userSelection: [],
projectId: "",
fromMine: false,
coverPullStartY: 0,
coverPullStartDistance: 0,
coverPullDistance: 0,
coverPullEnabled: false,
coverExpanded: false,
selectedRadioId: "", // 单选模式下选中的选项ID
showConfirmDialog: false,
isSubmitting: false,
@@ -1069,6 +1604,7 @@ layout("/layouts/platform_h5.html"){
userSign: "", // 用户签名
mobile: "", // 手机号码
address: "",// 收货地址
receiveAddress: "", // 已提交的收货地址
userName: "",//收货人
},
@@ -1080,6 +1616,34 @@ layout("/layouts/platform_h5.html"){
computed: {
coverHeaderStyle() {
const viewportWidth = Math.min(document.documentElement.clientWidth || window.innerWidth || 390, 430)
const baseHeight = Math.max(230, Math.min(Math.round(viewportWidth * 0.66), 280))
return {height: baseHeight + this.coverPullDistance + "px"}
},
coverMaxPullDistance() {
const viewportHeight = document.documentElement.clientHeight || window.innerHeight || 800
return Math.max(170, Math.min(Math.round(viewportHeight * 0.25), 220))
},
coverImageFit() {
return this.coverPullDistance > 48 ? "contain" : "cover"
},
projectChoiceStatus() {
const now = Date.now()
const startTime = this.projectInfo.choiceTimeStart ? new Date(this.projectInfo.choiceTimeStart).getTime() : null
const endTime = this.projectInfo.choiceTimeEnd ? new Date(this.projectInfo.choiceTimeEnd).getTime() : null
if (startTime && now < startTime) return "选择未开始"
if (endTime && now > endTime) return "选择已截止"
return "选择进行中"
},
hasExpressOption() {
return !!(this.projectInfo.options || []).find((option) => option.isExpressDelivery)
},
// 系统默认福利必须被保留,管理端限制每个项目最多配置一个。
systemDefaultOption() {
if (!this.projectInfo.options) return null
@@ -1152,6 +1716,64 @@ layout("/layouts/platform_h5.html"){
},
methods: {
/**
* H5 布局由 page-wrapper 承担实际滚动,页面逻辑统一读取该容器而不是 window。
*/
getPageScroller() {
return document.querySelector("body > .main-page .page-wrapper") || document.querySelector(".page-wrapper")
},
resetPageScroll() {
const pageScroller = this.getPageScroller()
if (pageScroller) pageScroller.scrollTop = 0
window.scrollTo(0, 0)
},
/**
* 仅在页面位于顶部时启用封面下拉,避免与正常列表滚动冲突。
*/
handleCoverTouchStart(event) {
const pageScroller = this.getPageScroller()
const scrollTop = pageScroller ? pageScroller.scrollTop : window.pageYOffset
if (scrollTop > 0 || !event.touches || !event.touches.length) return
this.coverPullStartY = event.touches[0].clientY
this.coverPullStartDistance = this.coverPullDistance
this.coverPullEnabled = true
},
/**
* 封面展开后保留展示高度,主题信息卡片随封面底边移动;反向上滑可逐步收起。
*/
handleCoverTouchMove(event) {
if (!this.coverPullEnabled || !event.touches || !event.touches.length) return
const distance = event.touches[0].clientY - this.coverPullStartY
if (this.coverPullStartDistance === 0 && distance <= 0) return
this.coverPullDistance = Math.max(0, Math.min(
this.coverPullStartDistance + Math.round(distance * 0.65),
this.coverMaxPullDistance
))
if (event.cancelable) event.preventDefault()
},
handleCoverTouchEnd() {
if (!this.coverPullEnabled) return
const shouldExpand = this.coverPullDistance >= this.coverMaxPullDistance * 0.5
this.coverPullEnabled = false
this.coverExpanded = shouldExpand
this.coverPullDistance = shouldExpand ? this.coverMaxPullDistance : 0
},
formatProjectDateTime(value) {
return value ? this.$moment(value).format("MM/DD HH:mm") : "待定"
},
formatProjectDateRange(startTime, endTime) {
if (!startTime && !endTime) return "待定"
if (!startTime) return this.$moment(endTime).format("MM/DD")
if (!endTime) return this.$moment(startTime).format("MM/DD")
return this.$moment(startTime).format("MM/DD") + "" + this.$moment(endTime).format("MM/DD")
},
/**
* 注册当前福利页面的弹层历史。
* 页面标识由固定前缀和福利项目 ID 组成;回调参数是当前弹层名称数组,无返回值。
@@ -1724,6 +2346,7 @@ layout("/layouts/platform_h5.html"){
created() {
this.projectId = GetQueryString("id")
this.fromMine = GetQueryString("from") === "mine"
if (this.projectId) {
this.registerHistoryLayers()
this.loadPageData()
@@ -1735,6 +2358,11 @@ layout("/layouts/platform_h5.html"){
}, 1500)
}
},
mounted() {
this.$nextTick(() => {
this.resetPageScroll()
})
},
beforeDestroy() {
if (window.h5HistoryLayerManager) {
window.h5HistoryLayerManager.unregister(this.historyLayerPageKey)
@@ -265,35 +265,6 @@ layout("/layouts/platform_h5.html"){
white-space: nowrap;
}
.welfare-list-card__info {
display: flex;
min-width: 0;
margin-top: 8px;
align-items: center;
color: #718198;
font-size: 12px;
line-height: 18px;
}
.welfare-list-card__info .van-icon {
margin-right: 5px;
flex: none;
color: #8da2bd;
font-size: 14px;
}
.welfare-list-card__info-label {
flex: none;
}
.welfare-list-card__info-value {
min-width: 0;
overflow: hidden;
color: #42546c;
text-overflow: ellipsis;
white-space: nowrap;
}
.welfare-list-card__status {
display: inline-flex;
min-width: 54px;
@@ -517,11 +488,6 @@ layout("/layouts/platform_h5.html"){
{{ getChoiceModeText(row) }}
</span>
</div>
<div class="welfare-list-card__info" v-if="row.gift">
<van-icon name="gift-o"></van-icon>
<span class="welfare-list-card__info-label">福利:</span>
<span class="welfare-list-card__info-value">{{ row.gift }}</span>
</div>
<div class="welfare-list-card__time" v-if="row.choiceTimeStart && row.choiceTimeEnd">
<van-icon name="clock-o"></van-icon>
<span>选择时间:{{ $moment(row.choiceTimeStart).format('MM/DD HH:mm') }} - {{ $moment(row.choiceTimeEnd).format('MM/DD HH:mm') }}</span>
@@ -116,7 +116,6 @@ layout("/layouts/platform_h5.html"){
</van-cell>
<van-cell :border="false" :value="projectInfo.name" title="福利名称"></van-cell>
<van-cell :border="false" :value="projectInfo.festival" title="发放节日"></van-cell>
<van-cell :border="false" :value="projectInfo.gift" title="福利礼品" v-if="!projectInfo.flexible"></van-cell>
<van-cell :border="false" title="选择形式">{{projectInfo.isCheckBox==='checkBox'?'多选':'单选'}}</van-cell>
<van-cell
:border="false"