This commit is contained in:
2026-10-12 20:38:40 +08:00
parent f58a3b2caf
commit 22a0e8f383
54 changed files with 5152 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 985 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 KiB

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" fill="none">
<circle cx="64" cy="64" r="58" stroke="#F5222D" stroke-width="3"/>
<path d="M21 54A44 44 0 0 1 107 54" stroke="#F5222D" stroke-width="9" stroke-linecap="round"/>
<path d="M21 74A44 44 0 0 0 107 74" stroke="#F5222D" stroke-width="9" stroke-linecap="round"/>
<circle cx="64" cy="64" r="27" stroke="#F5222D" stroke-width="9"/>
<path d="M64 20V108" stroke="#F5222D" stroke-width="8" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 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: 462 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 KiB

@@ -0,0 +1,8 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="24" cy="24" r="22" fill="#EAF3FF"/>
<rect x="15" y="11" width="18" height="17" rx="4" fill="#2F7CF6"/>
<path d="M12 24C10.8954 24 10 24.8954 10 26V32C10 33.1046 10.8954 34 12 34H36C37.1046 34 38 33.1046 38 32V26C38 24.8954 37.1046 24 36 24C34.8954 24 34 24.8954 34 26V29H14V26C14 24.8954 13.1046 24 12 24Z" fill="#1769E8"/>
<rect x="14" y="28" width="20" height="7" rx="2.5" fill="#2F7CF6"/>
<path d="M16 34H20L19 38H15L16 34Z" fill="#1769E8"/>
<path d="M28 34H32L33 38H29L28 34Z" fill="#1769E8"/>
</svg>

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 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: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

File diff suppressed because one or more lines are too long
@@ -0,0 +1,634 @@
<template>
<div>
<van-action-sheet
v-model="visible"
class="activity-schedule-calendar"
:style="{ height: '78%' }"
:close-on-click-overlay="true"
title="活动日历"
closeable>
<div class="activity-schedule-calendar__body">
<div class="activity-schedule-calendar__month-bar">
<button type="button" class="activity-schedule-calendar__month-btn" @click="changeMonth(-1)">
<van-icon name="arrow-left"></van-icon>
</button>
<div class="activity-schedule-calendar__month-title">{{ currentYear }}年{{ currentMonth }}月</div>
<button type="button" class="activity-schedule-calendar__month-btn" @click="changeMonth(1)">
<van-icon name="arrow"></van-icon>
</button>
</div>
<div class="activity-schedule-calendar__weekdays">
<span v-for="item in weekdays" :key="item">{{ item }}</span>
</div>
<div class="activity-schedule-calendar__days">
<button
v-for="day in calendarDays"
:key="day.key"
type="button"
class="activity-schedule-calendar__day"
:class="{
'is-other-month': !day.currentMonth,
'is-selected': day.key === selectedDate,
'is-today': day.isToday
}"
@click="selectDay(day)">
<span>
<span class="activity-schedule-calendar__day-number">{{ day.day }}</span>
<small v-if="day.isToday" class="activity-schedule-calendar__today-label">今</small>
</span>
<i v-if="day.status" class="activity-schedule-calendar__day-status" :class="'is-' + day.status"></i>
</button>
</div>
<div class="activity-schedule-calendar__legend">
<span><i class="is-solid"></i>已签到</span>
<span><i class="is-outline"></i>可签到</span>
<span><i class="is-muted"></i>未开始</span>
</div>
<div v-if="showMap" :id="mapContainerId" class="activity-schedule-calendar__map"></div>
<section class="activity-schedule-calendar__schedule-card">
<div class="activity-schedule-calendar__schedule-title">
{{ selectedDate ? selectedDate + ' 的活动时段' : '活动时段' }}
</div>
<div v-if="selectedTimes.length" class="activity-schedule-calendar__schedule-list">
<div v-for="item in selectedTimes" :key="item.id" class="activity-schedule-calendar__schedule-item">
<div class="activity-schedule-calendar__schedule-main">
<van-icon :name="item.isAttend === true ? 'checked' : 'clock-o'"></van-icon>
<div class="activity-schedule-calendar__schedule-copy">
<div class="activity-schedule-calendar__schedule-time">
{{ formatTime(item.courseStartTime) }} - {{ formatTime(item.courseEndTime) }}
</div>
<div v-if="row && row.courseLocation" class="activity-schedule-calendar__schedule-location">
<van-icon name="location-o"></van-icon>
<span>{{ row.courseLocation }}</span>
</div>
</div>
</div>
<span v-if="item.isAttend === true" class="activity-schedule-calendar__status is-success">已签到</span>
<button
v-else-if="canSignItem(item)"
type="button"
class="activity-schedule-calendar__sign-btn"
@click.stop="onSign(item)">签到</button>
<span v-else class="activity-schedule-calendar__status" :class="{
'is-ended': getScheduleStatus(item) === '已结束',
'is-progressing': getScheduleStatus(item) === '进行中'
}">
{{ getScheduleStatus(item) }}
</span>
</div>
</div>
<div v-else class="activity-schedule-calendar__empty">
<img src="/assets/mobile/img/activity/schedule-empty.png?v=20260821-transparent" alt="">
<div class="activity-schedule-calendar__empty-title">当天暂无签到时段</div>
<div class="activity-schedule-calendar__empty-desc">当前日期没有需要签到的活动安排</div>
</div>
</section>
</div>
</van-action-sheet>
<van-popup
v-model="signVisible"
round
closeable
:safe-area-inset-bottom="true"
:close-on-click-overlay="false"
:style="{ width: '80%', height: '66%' }"
get-container="#app"
@close="onSignClose">
<scan-code ref="scanCodeRef"></scan-code>
</van-popup>
</div>
</template>
<script>
module.exports = {
name: "ActivityScheduleCalendar",
props: {
moduleType: {
type: String,
default: "brand"
}
},
components: {
"scan-code": httpVueLoader("/components/plugins/sysScanCode/index.vue?v=" + new Date().getTime())
},
data() {
return {
visible: false,
row: null,
currentYear: new Date().getFullYear(),
currentMonth: new Date().getMonth() + 1,
selectedDate: "",
weekdays: ["日", "一", "二", "三", "四", "五", "六"],
selectCourseTime: null,
signVisible: false,
markerLayer: null,
map: null,
circle: null,
mapContainerId: "activityScheduleMap_" + Math.random().toString(36).slice(2)
}
},
computed: {
courseTimes() {
return this.row && Array.isArray(this.row.courseTimes) ? this.row.courseTimes : []
},
scheduleDateMap() {
const map = {}
this.courseTimes.forEach(item => {
const key = this.dateKey(item)
if (!key) return
if (!map[key]) map[key] = []
map[key].push(item)
})
return map
},
selectedTimes() {
return this.scheduleDateMap[this.selectedDate] || []
},
canSign() {
return !!(this.row && this.row.isSign === true && this.row.isMobileSign === true)
},
showMap() {
return !!(this.moduleType === "brand" && this.row && this.row.isMobileSign === true && this.row.signType === 3)
},
calendarDays() {
const firstDay = new Date(this.currentYear, this.currentMonth - 1, 1)
const startDate = new Date(this.currentYear, this.currentMonth - 1, 1 - firstDay.getDay())
const todayKey = this.formatDateKey(new Date())
return Array.from({ length: 42 }, (_, index) => {
const date = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + index)
const key = this.formatDateKey(date)
return {
key,
day: date.getDate(),
currentMonth: date.getFullYear() === this.currentYear && date.getMonth() + 1 === this.currentMonth,
isToday: key === todayKey,
status: this.getDayStatus(this.scheduleDateMap[key] || [])
}
})
}
},
methods: {
onOpen(row) {
this.row = row
const initialDate = this.formatDateKey(new Date())
const date = this.$moment(initialDate)
this.currentYear = date.year()
this.currentMonth = date.month() + 1
this.selectedDate = initialDate
this.visible = true
this.$nextTick(() => {
if (this.showMap) this.initMap()
})
},
dateKey(item) {
const value = item && (item.courseDate || item.courseStartTime)
return value ? this.$moment(value).format("YYYY-MM-DD") : ""
},
formatDateKey(date) {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, "0")
const day = String(date.getDate()).padStart(2, "0")
return year + "-" + month + "-" + day
},
formatTime(value) {
return value ? this.$moment(value).format("HH:mm") : "--"
},
canSignItem(item) {
if (!this.canSign || !item || !item.courseStartTime || !item.courseEndTime) return false
const now = this.$moment()
return !now.isBefore(this.$moment(item.courseStartTime)) && !now.isAfter(this.$moment(item.courseEndTime))
},
getScheduleStatus(item) {
if (!item || !item.courseStartTime || !item.courseEndTime) return "待开始"
const now = this.$moment()
if (now.isBefore(this.$moment(item.courseStartTime))) return "待开始"
if (now.isAfter(this.$moment(item.courseEndTime))) return "已结束"
return "进行中"
},
getDayStatus(items) {
if (items.some(item => item.isAttend === true)) return "signed"
if (items.some(item => this.canSignItem(item))) return "available"
if (items.some(item => item.courseStartTime && this.$moment().isBefore(this.$moment(item.courseStartTime)))) return "pending"
return ""
},
changeMonth(offset) {
const date = new Date(this.currentYear, this.currentMonth - 1 + offset, 1)
this.currentYear = date.getFullYear()
this.currentMonth = date.getMonth() + 1
},
selectDay(day) {
this.selectedDate = day.key
if (!day.currentMonth) {
const date = this.$moment(day.key)
this.currentYear = date.year()
this.currentMonth = date.month() + 1
}
},
onSign(courseTime) {
this.selectCourseTime = courseTime
if (this.row.signType === 1) {
this.signVisible = true
this.$nextTick(() => this.$refs.scanCodeRef.init())
} else if (this.row.signType === 2) {
this.makeCode()
} else if (this.row.signType === 3 && this.moduleType === "brand") {
this.positionSign()
} else {
this.$toast("此签到模式正在升级中")
}
},
onSignClose() {
if (this.$refs.scanCodeRef) this.$refs.scanCodeRef.closeScan()
},
makeCode() {
const baseUrl = this.moduleType === "family"
? "/platform/family/mine/passiveScan"
: "/platform/trainSignUp/mine/passiveScan"
const content = jrQrcode.getQrBase64(baseUrl + "?id=" + this.selectCourseTime.id)
vant.ImagePreview([content])
},
positionSign() {
this.getLocation(coords => {
if (!coords) return
this.$axios.post("/platform/trainSignUp/mine/positionSign", {
timeId: this.selectCourseTime.id,
courseId: this.row.id,
lat: coords.lat,
lng: coords.lng
}).then(res => {
if (res.code === 0) {
this.selectCourseTime.isAttend = true
this.$toast("签到成功")
} else {
this.$toast(res.msg)
}
})
})
},
getLocation(callback) {
if (!navigator.geolocation) {
this.$toast("当前浏览器不支持定位功能")
callback(null)
return
}
const loading = vant.Toast.loading({ message: "获取定位中...", forbidClick: false, duration: 0 })
navigator.geolocation.getCurrentPosition(position => {
loading.close()
callback({ lat: position.coords.latitude, lng: position.coords.longitude })
}, error => {
loading.close()
const messageMap = {
1: "请允许浏览器获取位置信息",
2: "无法获取当前位置",
3: "定位超时,请重试"
}
this.$toast(messageMap[error.code] || "定位失败,请稍后重试")
callback(null)
}, { enableHighAccuracy: true, timeout: 10000, maximumAge: 60000 })
},
initMap() {
if (!this.row.courseLocationCoordinates || typeof TMap === "undefined") return
const position = JSON.parse(this.row.courseLocationCoordinates)
const center = new TMap.LatLng(position[0], position[1])
if (this.map) this.map.destroy()
this.map = new TMap.Map(this.mapContainerId, { zoom: 16, center })
this.markerLayer = new TMap.MultiMarker({
map: this.map,
geometries: [{ id: "activity", position: center }]
})
this.circle = new TMap.MultiCircle({
map: this.map,
geometries: [{ center, radius: this.row.radius || 100 }]
})
}
}
}
</script>
<style scoped>
.activity-schedule-calendar {
overflow: hidden;
border-radius: 18px 18px 0 0;
background: #f4f7fc;
}
.activity-schedule-calendar /deep/ .van-action-sheet__header {
height: 50px;
color: #1f2f43;
background: #fff;
font-size: 16px;
line-height: 50px;
}
.activity-schedule-calendar /deep/ .van-action-sheet__close {
top: 14px;
color: #7e899a;
font-size: 21px;
}
.activity-schedule-calendar__body {
height: 100%;
padding: 14px 14px 24px;
overflow-y: auto;
overflow-anchor: none;
box-sizing: border-box;
}
.activity-schedule-calendar__month-bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.activity-schedule-calendar__month-btn {
display: flex;
width: 36px;
height: 36px;
padding: 0;
align-items: center;
justify-content: center;
border: 1px solid #edf1f6;
border-radius: 50%;
color: #263548;
background: #fff;
box-shadow: 0 5px 14px rgba(55, 85, 126, .08);
}
.activity-schedule-calendar__month-title {
color: #1f2f43;
font-size: 18px;
font-weight: 600;
}
.activity-schedule-calendar__weekdays,
.activity-schedule-calendar__days {
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
text-align: center;
}
.activity-schedule-calendar__weekdays {
margin-bottom: 5px;
color: #58677b;
font-size: 12px;
line-height: 28px;
}
.activity-schedule-calendar__day {
position: relative;
display: flex;
width: 38px;
height: 48px;
margin: 1px auto;
padding: 0;
align-items: center;
flex-direction: column;
justify-content: center;
border: 0;
color: #263548;
background: transparent;
font-size: 14px;
line-height: 1;
box-sizing: border-box;
}
.activity-schedule-calendar__day > span {
position: absolute;
top: 4px;
left: 50%;
transform: translateX(-50%);
z-index: 1;
display: flex;
width: 40px;
height: 40px;
align-items: center;
justify-content: center;
border-radius: 50%;
box-sizing: border-box;
}
.activity-schedule-calendar__day-number {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
line-height: 14px;
}
.activity-schedule-calendar__today-label {
position: absolute;
top: 2px;
right: 0;
left: 0;
color: #1989fa;
font-size: 10px;
line-height: 10px;
text-align: center;
}
.activity-schedule-calendar__day.is-other-month > span {
color: #bdc5d0;
}
.activity-schedule-calendar__day.is-selected > span {
flex: 0 0 40px;
width: 40px;
min-width: 40px;
height: 40px;
min-height: 40px;
color: #fff;
background: #1989fa;
box-shadow: 0 5px 12px rgba(25, 137, 250, .22);
}
.activity-schedule-calendar__day.is-selected .activity-schedule-calendar__today-label {
top: 2px;
color: #fff;
}
.activity-schedule-calendar__day-status {
position: absolute;
bottom: -3px;
left: 50%;
width: 6px;
height: 6px;
transform: translateX(-50%);
border-radius: 50%;
box-sizing: border-box;
}
.activity-schedule-calendar__day-status.is-signed {
background: #1989fa;
}
.activity-schedule-calendar__day-status.is-available {
border: 1px solid #1989fa;
background: #fff;
}
.activity-schedule-calendar__day-status.is-pending {
background: #d8dde5;
}
.activity-schedule-calendar__legend {
display: flex;
margin: 12px 4px 16px;
align-items: center;
justify-content: space-around;
color: #78869a;
font-size: 11px;
}
.activity-schedule-calendar__legend span {
display: inline-flex;
align-items: center;
gap: 5px;
}
.activity-schedule-calendar__legend i {
width: 9px;
height: 9px;
border-radius: 50%;
box-sizing: border-box;
}
.activity-schedule-calendar__legend .is-solid { background: #1989fa; }
.activity-schedule-calendar__legend .is-outline { border: 1px solid #1989fa; }
.activity-schedule-calendar__legend .is-muted { background: #d8dde5; }
.activity-schedule-calendar__map {
width: 100%;
height: 150px;
margin-bottom: 12px;
overflow: hidden;
border-radius: 12px;
}
.activity-schedule-calendar__schedule-card {
overflow: hidden;
border-radius: 12px;
background: #fff;
box-shadow: 0 7px 20px rgba(55, 85, 126, .07);
}
.activity-schedule-calendar__schedule-title {
padding: 12px 14px;
border-bottom: 1px solid #edf1f6;
color: #263548;
font-size: 14px;
font-weight: 600;
}
.activity-schedule-calendar__schedule-item {
display: flex;
min-height: 70px;
padding: 12px 14px;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #edf1f6;
box-sizing: border-box;
}
.activity-schedule-calendar__schedule-item:last-child { border-bottom: 0; }
.activity-schedule-calendar__schedule-main {
display: flex;
min-width: 0;
align-items: flex-start;
gap: 9px;
}
.activity-schedule-calendar__schedule-main > .van-icon {
margin-top: 2px;
color: #1989fa;
font-size: 18px;
}
.activity-schedule-calendar__schedule-time {
color: #263548;
font-size: 15px;
line-height: 22px;
}
.activity-schedule-calendar__schedule-location {
display: flex;
margin-top: 5px;
align-items: center;
gap: 4px;
color: #7d899a;
font-size: 12px;
line-height: 18px;
}
.activity-schedule-calendar__status {
flex: 0 0 auto;
padding: 4px 8px;
border-radius: 5px;
color: #8b96a7;
background: #f1f4f8;
font-size: 11px;
}
.activity-schedule-calendar__status.is-success {
color: #19a965;
background: #eaf8f1;
}
.activity-schedule-calendar__status.is-progressing {
color: #ff8a00;
background: #fff4e6;
}
.activity-schedule-calendar__status.is-ended {
color: #98a2b2;
background: #eef1f5;
}
.activity-schedule-calendar__sign-btn {
display: inline-flex;
min-width: 42px !important;
height: 24px !important;
padding: 0 8px !important;
align-items: center;
justify-content: center;
border: 1px solid #1989fa !important;
border-radius: 5px !important;
color: #fff !important;
background-color: #1989fa !important;
box-shadow: none !important;
font-size: 11px !important;
}
.activity-schedule-calendar__empty {
padding: 18px 14px 22px;
color: #9aa5b5;
text-align: center;
}
.activity-schedule-calendar__empty img {
display: block;
width: 142px;
height: 94px;
margin: 0 auto 4px;
object-fit: contain;
}
.activity-schedule-calendar__empty-title {
color: #58677b;
font-size: 13px;
line-height: 20px;
}
.activity-schedule-calendar__empty-desc {
margin-top: 2px;
color: #a2adbc;
font-size: 11px;
line-height: 18px;
}
</style>
@@ -0,0 +1,221 @@
.club-user-join-popup {
overflow: hidden;
background: #f3f7fc;
}
.club-user-join-detail {
display: flex;
height: 100%;
padding-top: 46px;
overflow: hidden;
box-sizing: border-box;
background: #f3f7fc;
color: #1f2d3d;
flex-direction: column;
}
.club-user-join-detail > .van-nav-bar {
z-index: 20;
background: #fff;
}
.club-user-join-detail > .van-nav-bar .van-icon,
.club-user-join-detail > .van-nav-bar .van-nav-bar__text {
color: #1989fa;
}
.club-user-join-detail__scroll {
min-height: 0;
overflow-y: auto;
flex: 1;
-webkit-overflow-scrolling: touch;
}
.club-user-join-detail__content {
padding: 12px 15px calc(18px + env(safe-area-inset-bottom));
}
.club-user-join-card {
margin-bottom: 11px;
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background: #fff;
box-shadow: 0 3px 11px rgba(51, 87, 126, .08);
}
.club-user-join-card__title {
position: relative;
margin: 0;
padding: 12px 14px 8px 21px;
color: #354255;
font-size: 15px;
font-weight: 600;
line-height: 22px;
}
.club-user-join-card__title::before {
position: absolute;
top: 15px;
bottom: 11px;
left: 12px;
width: 2px;
border-radius: 2px;
background: #1989fa;
content: '';
}
.club-user-join-card__grid {
display: grid;
padding: 0 14px;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.club-user-join-card__item {
min-width: 0;
padding: 0 10px 13px 0;
}
.club-user-join-card__item--full {
grid-column: span 2;
}
.club-user-join-card__item span,
.club-user-join-material > span {
display: block;
margin-bottom: 4px;
color: #8b9bb0;
font-size: 12px;
}
.club-user-join-card__item strong {
display: block;
overflow: hidden;
color: #40526a;
font-size: 13px;
font-weight: 400;
line-height: 19px;
text-overflow: ellipsis;
white-space: nowrap;
}
.club-user-join-card__item--full strong {
white-space: normal;
}
.club-user-join-material {
display: flex;
padding: 0 14px;
align-items: center;
min-height: 40px;
margin-bottom: 12px;
}
.club-user-join-material > span {
flex: 0 0 82px;
margin: 0;
}
.club-user-join-material strong {
color: #40526a;
font-size: 13px;
font-weight: 400;
}
.club-user-join-material .van-image {
width: 56px;
height: 40px;
overflow: hidden;
border: 1px solid #edf2f8;
border-radius: 6px;
}
.club-user-join-info-card .van-cell-group {
margin: 0;
background: transparent;
}
.club-user-join-info-card .van-cell {
min-height: 44px;
padding: 10px 14px;
background: transparent;
}
.club-user-join-info-card .van-cell:not(:last-child)::after {
right: 14px;
left: 14px;
border-color: #eef2f7;
}
.club-user-join-info-card .van-cell__title {
width: 102px;
color: #596779;
font-size: 12px;
line-height: 24px;
flex: 0 0 102px;
}
.club-user-join-info-card .van-cell__value {
min-width: 0;
color: #4f5d70;
font-size: 12px;
line-height: 24px;
text-align: left;
overflow-wrap: anywhere;
}
.club-user-join-info-card .club-user-join-info-long-cell {
display: block;
}
.club-user-join-info-card .club-user-join-info-long-cell .van-cell__title {
width: auto;
max-width: 100%;
}
.club-user-join-info-card .club-user-join-info-long-cell .van-cell__value {
margin-top: 7px;
}
.club-user-join-info-image {
display: block;
width: 56px;
height: 40px;
overflow: hidden;
border: 1px solid #edf2f8;
border-radius: 6px;
}
.club-user-join-task-card .van-cell-group {
margin: 0;
background: transparent;
}
.club-user-join-task-card .van-cell {
min-height: 44px;
padding: 10px 14px;
background: transparent;
}
.club-user-join-task-card .van-cell:not(:last-child)::after {
right: 14px;
left: 14px;
border-color: #eef2f7;
}
.club-user-join-task-card .van-cell__title {
width: 102px;
color: #596779;
font-size: 12px;
line-height: 24px;
flex: 0 0 102px;
}
.club-user-join-task-card .van-cell__value {
min-width: 0;
color: #4f5d70;
font-size: 12px;
line-height: 24px;
text-align: left;
overflow-wrap: anywhere;
}
@@ -0,0 +1,283 @@
/* 疗休养移动端统一视觉主题:仅使用 tour-h5-theme 下的业务类,避免影响其他 H5 模块。 */
body {
background-color: #f3f7fd;
}
[v-cloak] {
display: none;
}
.tour-h5-theme {
min-height: 100vh;
color: #263548;
background-color: #f3f7fd;
box-sizing: border-box;
}
/* 页面接口首次加载时使用结构化骨架,避免先显示空字段或旧列表。 */
.tour-content-skeleton {
padding: 12px;
background: #f3f7fd;
box-sizing: border-box;
}
.tour-content-skeleton__cover,
.tour-content-skeleton__title,
.tour-content-skeleton__row,
.tour-content-skeleton__card {
background: linear-gradient(90deg, #edf2f8 25%, #f8fbff 37%, #edf2f8 63%);
background-size: 400% 100%;
animation: tour-content-skeleton-loading 1.35s ease infinite;
}
.tour-content-skeleton__cover { height: 168px; border-radius: 14px; }
.tour-content-skeleton__title { width: 46%; height: 18px; margin: 16px 2px 10px; border-radius: 9px; }
.tour-content-skeleton__row { width: 76%; height: 13px; margin: 10px 2px; border-radius: 7px; }
.tour-content-skeleton__card { height: 126px; margin-top: 12px; border-radius: 14px; }
@keyframes tour-content-skeleton-loading {
0% { background-position: 100% 50%; }
100% { background-position: 0 50%; }
}
.tour-h5-theme .van-nav-bar {
background-color: #fff;
}
.tour-h5-theme .van-nav-bar__title {
color: #172033;
font-size: 16px;
font-weight: 600;
}
.tour-h5-theme .van-nav-bar .van-icon,
.tour-h5-theme .van-nav-bar__text {
color: #1989fa;
}
.tour-h5-theme .van-nav-bar::after,
.tour-h5-theme .van-hairline--bottom::after {
border-bottom-color: #edf2f8;
}
.tour-h5-card {
overflow: hidden;
border-radius: 12px;
background-color: #fff;
box-shadow: 0 7px 20px rgba(55, 85, 126, .08);
}
.tour-h5-section-title {
position: relative;
display: flex;
min-height: 46px;
padding: 0 14px 0 20px;
align-items: center;
justify-content: space-between;
color: #202938;
font-size: 16px;
line-height: 22px;
font-weight: 600;
box-sizing: border-box;
}
.tour-h5-section-title::before {
position: absolute;
top: 15px;
bottom: 15px;
left: 10px;
width: 3px;
border-radius: 2px;
background-color: #1989fa;
content: "";
}
.tour-h5-tag {
display: inline-flex;
min-height: 22px;
padding: 2px 8px;
align-items: center;
justify-content: center;
border-radius: 11px;
color: #1989fa;
background-color: #eaf4ff;
font-size: 11px;
line-height: 18px;
box-sizing: border-box;
white-space: nowrap;
}
.tour-h5-tag.is-success {
color: #20a765;
background-color: #e9f8f0;
}
.tour-h5-tag.is-warning {
color: #e98018;
background-color: #fff3e4;
}
.tour-h5-tag.is-muted {
color: #7c899b;
background-color: #f0f2f5;
}
.tour-h5-search {
margin: 0 0 8px;
padding: 0;
overflow: hidden;
border-radius: 12px;
background-color: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
}
.tour-h5-search .van-search__content {
height: 42px;
padding-left: 12px;
align-items: center;
border-radius: 12px;
background-color: #fff;
}
.tour-h5-search .van-field__control {
color: #263548;
font-size: 14px;
}
.tour-h5-search .van-field__control::placeholder {
color: #a7b0bf;
}
.tour-h5-filter {
overflow: hidden;
border-radius: 10px;
background-color: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
}
.tour-h5-filter .van-dropdown-menu__bar,
.tour-h5-filter .van-dropdown-menu__item,
.tour-h5-filter .van-dropdown-menu__title {
height: 44px;
}
.tour-h5-filter .van-dropdown-menu__bar {
box-shadow: none;
}
.tour-h5-filter .van-dropdown-menu__title {
color: #66758a;
font-size: 14px;
}
.tour-h5-filter .van-dropdown-menu__title--active,
.tour-h5-filter .van-dropdown-item__option--active,
.tour-h5-filter .van-dropdown-item__option--active .van-dropdown-item__icon {
color: #1989fa;
}
.tour-h5-filter .van-dropdown-item__content {
width: calc(100% - 24px);
margin: 0 12px;
overflow: hidden;
border-radius: 0 0 12px 12px;
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
}
.tour-h5-empty {
display: flex;
min-height: 290px;
padding: 40px 18px 32px;
align-items: center;
justify-content: center;
flex-direction: column;
box-sizing: border-box;
text-align: center;
}
.tour-h5-empty img {
display: block;
width: 60%;
max-width: 190px;
height: auto;
object-fit: contain;
}
.tour-h5-empty__title {
margin-top: 8px;
color: #50627a;
font-size: 15px;
line-height: 22px;
font-weight: 600;
}
.tour-h5-empty__hint {
margin-top: 5px;
color: #9aa7b8;
font-size: 12px;
line-height: 18px;
}
.tour-h5-fixed-bar {
position: fixed;
right: 0;
bottom: 0;
left: 0;
z-index: 30;
display: flex;
gap: 10px;
padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
border-top: 1px solid #edf1f6;
background-color: rgba(255, 255, 255, .98);
box-shadow: 0 -6px 18px rgba(38, 66, 103, .08);
box-sizing: border-box;
}
.tour-h5-fixed-bar .van-button {
height: 42px;
border-radius: 8px;
font-size: 15px;
font-weight: 600;
}
.tour-h5-theme .van-cell {
min-height: 48px;
padding: 13px 14px;
color: #35445a;
font-size: 14px;
}
.tour-h5-theme .van-cell__title {
color: #65748a;
}
.tour-h5-theme .van-cell__value {
color: #263548;
}
.tour-h5-theme .van-field__label {
color: #65748a;
}
.tour-h5-theme .van-field__control {
color: #263548;
}
.tour-h5-theme .van-button--info,
.tour-h5-theme .van-button--primary {
border-color: #0f7bf3;
background-color: #0f7bf3;
}
.tour-h5-theme .van-button--plain.van-button--info {
color: #0f7bf3;
background-color: #fff;
}
@media (max-width: 350px) {
.tour-h5-fixed-bar {
gap: 7px;
padding-right: 10px;
padding-left: 10px;
}
}
@@ -0,0 +1,490 @@
.union-reimburse-page {
min-height: 100vh;
padding-bottom: calc(24px + env(safe-area-inset-bottom));
box-sizing: border-box;
background: #f3f7fd;
color: #40526a;
}
.union-reimburse-page .van-nav-bar,
.union-reimburse-page .van-sticky--fixed { background: #fff; }
.union-reimburse-page .van-nav-bar__title {
color: #172033;
font-size: 16px;
font-weight: 600;
}
.union-reimburse-page .van-nav-bar .van-icon,
.union-reimburse-page .van-nav-bar .van-nav-bar__text { color: #1989fa; }
.union-reimburse-sticky {
padding: 22px 12px 1px;
box-sizing: border-box;
background: #f3f7fd;
}
.union-reimburse-search {
margin: 0 0 8px;
padding: 0;
overflow: hidden;
border-radius: 12px;
background: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
}
.union-reimburse-search .van-search__content {
height: 42px;
padding-left: 12px;
align-items: center;
border-radius: 12px;
background: #fff;
}
.union-reimburse-search .van-field__control { color: #263548; font-size: 14px; }
.union-reimburse-search .van-field__control::placeholder { color: #a7b0bf; }
.union-reimburse-filter {
margin-bottom: 8px;
overflow: hidden;
border-radius: 10px;
background: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
}
.union-reimburse-filter .van-dropdown-menu__bar { height: 44px; box-shadow: none; }
.union-reimburse-filter .van-dropdown-menu__item,
.union-reimburse-filter .van-dropdown-menu__title { height: 44px; }
.union-reimburse-filter .van-dropdown-menu__item { align-items: center; justify-content: center; }
.union-reimburse-filter .van-dropdown-menu__title {
display: inline-flex;
align-items: center;
color: #66758a;
font-size: 14px;
line-height: 20px;
}
.union-reimburse-filter .van-dropdown-menu__title--active,
.union-reimburse-filter .van-dropdown-item__option--active,
.union-reimburse-filter .van-dropdown-item__option--active .van-dropdown-item__icon { color: #1989fa; }
.union-reimburse-filter .van-dropdown-item__content {
width: calc(100% - 24px);
margin: 0 12px;
overflow: hidden;
box-sizing: border-box;
border-radius: 0 0 12px 12px;
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
}
.union-reimburse-filter .van-dropdown-item__option {
display: flex;
min-height: 48px;
padding: 0 16px;
align-items: center;
color: #46566c;
font-size: 14px;
}
.union-reimburse-list-content { padding: 0 12px; }
.union-reimburse-page .table-list-container { margin-top: 0; padding-bottom: 2px; }
.union-reimburse-page .table-list-container .table-list-item {
margin-bottom: 12px;
padding: 14px;
border: 1px solid #e9eff7;
border-radius: 12px;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
}
.union-reimburse-card-header { display: flex; margin-bottom: 8px; align-items: flex-start; justify-content: space-between; }
.union-reimburse-card-header__heading { min-width: 0; padding-right: 10px; flex: 1; }
.union-reimburse-card-header__title {
overflow: hidden;
color: #253247;
font-size: 15px;
font-weight: 600;
line-height: 22px;
text-overflow: ellipsis;
white-space: nowrap;
}
.union-reimburse-card-header__sub { margin-top: 1px; color: #9ba6b6; font-size: 12px; line-height: 18px; }
.union-reimburse-card-header__status { display: inline-flex; flex: none; }
.union-reimburse-card-header__status.van-tag,
.union-reimburse-card-header__status .van-tag {
min-height: 24px;
padding: 3px 9px;
border: 0;
border-radius: 12px;
box-sizing: border-box;
font-size: 12px;
line-height: 18px;
}
.union-reimburse-card-header__status--pending { color: #1989fa; background: #eaf4ff; }
/* 报销成功与社团审核页 ProcessInstanceStateEnum 的“已完成”保持一致。 */
.union-reimburse-card-header__status--success { color: #67c23a; background: #f0f9eb; }
.union-reimburse-card-header__status--danger { color: #e35d5d; background: #fff0f0; }
.union-reimburse-page .table-list-container .table-list-item .item-actions {
margin-top: 10px;
padding-top: 10px;
border-top-color: #edf1f6;
}
.union-reimburse-page .table-list-container .table-list-item .action-btn {
min-height: 28px;
padding: 4px 11px;
border-radius: 14px;
color: #1989fa;
background: #eaf4ff;
font-size: 12px;
line-height: 18px;
}
.union-reimburse-page .table-list-container .table-list-item .action-btn.delete { color: #f04444; background: #fff0f0; }
.union-reimburse-page .table-list-container .table-list-item .action-btn.review { color: #1989fa; background: #eaf4ff; }
.union-reimburse-page .table-list-container .table-list-item .action-btn .van-icon { margin-right: 5px; font-size: 13px; }
.union-reimburse-page .empty-state { padding: 0; }
.union-reimburse-list-skeleton { padding: 0; }
.union-reimburse-skeleton-card {
margin-bottom: 12px;
padding: 14px;
border: 1px solid #e9eff7;
border-radius: 12px;
background: #fff;
}
.union-reimburse-skeleton-line {
height: 13px;
margin-bottom: 12px;
border-radius: 7px;
background: linear-gradient(90deg, #f2f3f5 25%, #e6e8eb 37%, #f2f3f5 63%);
background-size: 400% 100%;
animation: union-reimburse-skeleton-loading 1.4s ease infinite;
}
.union-reimburse-skeleton-line--title { width: 46%; height: 16px; }
.union-reimburse-skeleton-line--short { width: 60%; }
.union-reimburse-empty {
display: flex;
min-height: 280px;
padding: 46px 18px 36px;
align-items: center;
justify-content: center;
box-sizing: border-box;
flex-direction: column;
text-align: center;
}
.union-reimburse-empty img { display: block; width: 86%; max-width: 290px; height: auto; object-fit: contain; }
.union-reimburse-empty__title { margin-top: 14px; color: #50627a; font-size: 15px; font-weight: 600; line-height: 22px; }
.union-reimburse-empty__hint { margin-top: 5px; color: #9aa7b8; font-size: 12px; line-height: 18px; }
/* 申请页沿用社团申请的卡片层级,保留报销业务原有字段与交互。 */
.union-reimburse-form { padding-bottom: 0; }
.union-reimburse-form .custom-nav { box-shadow: none; }
.union-reimburse-form .form-container { padding: 12px 15px calc(86px + env(safe-area-inset-bottom)); }
.union-reimburse-form .form-section {
margin: 0 0 11px;
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background: #fff;
box-shadow: 0 3px 11px rgba(51, 87, 126, .08);
}
.union-reimburse-form .form-section .van-cell-group__title,
.union-reimburse-form .payment-toolbar,
.union-reimburse-form .invoice-toolbar {
position: relative;
min-height: 42px;
margin: 0;
padding: 12px 14px 8px 21px;
box-sizing: border-box;
color: #354255;
font-size: 15px;
font-weight: 600;
line-height: 22px;
background: #fff;
}
.union-reimburse-form .form-section__title {
position: relative;
min-height: 41px;
margin: 0;
padding: 13px 15px 8px 24px;
box-sizing: border-box;
color: #1f2d3d;
font-size: 14px;
font-weight: 600;
line-height: 20px;
background: #fff;
}
.union-reimburse-form .form-section__title::before {
position: absolute;
top: 16px;
left: 15px;
width: 3px;
height: 14px;
border-radius: 2px;
background: #1989fa;
content: '';
}
.union-reimburse-form .form-section .van-cell-group__title::before,
.union-reimburse-form .payment-toolbar::before,
.union-reimburse-form .invoice-toolbar::before {
position: absolute;
top: 15px;
bottom: 11px;
left: 12px;
width: 2px;
border-radius: 2px;
background: #1989fa;
content: '';
}
.union-reimburse-form .form-section .van-cell { min-height: 46px; padding: 11px 15px; background: transparent; }
.union-reimburse-form .form-section .van-cell::after { right: 15px; left: 15px; border-color: #eef2f7; }
.union-reimburse-form .form-section .van-field__label { width: 105px; color: #596779; font-size: 13px; line-height: 24px; }
.union-reimburse-form .payment-detail-item .van-cell--required::before { left: 6px; color: #f06464; }
.union-reimburse-form .form-section .van-field__control { color: #40526a; font-size: 14px; line-height: 24px; }
.union-reimburse-form .form-section .van-field__control::placeholder { color: #a7b0bf; }
.union-reimburse-form .more-text { display: block; }
.union-reimburse-form .more-text .van-field__label { width: auto; margin-bottom: 7px; }
.union-reimburse-form .more-text .van-field__control {
min-height: 88px;
padding: 10px 12px;
box-sizing: border-box;
border: 1px solid #c9d5e5;
border-radius: 10px;
background: #f7f9fd;
}
/* 附件上传区域与困难帮扶申请页保持一致,避免上传控件被文本域样式包裹。 */
.union-reimburse-form .form-section .reimburse-upload-field .van-field__control {
min-height: 0;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
}
.union-reimburse-form .payment-section,
.union-reimburse-form .invoice-section { padding: 0 0 14px; }
.union-reimburse-form .payment-toolbar,
.union-reimburse-form .invoice-toolbar { display: flex; align-items: center; justify-content: space-between; }
.union-reimburse-form .payment-toolbar .van-button,
.union-reimburse-form .invoice-toolbar .van-button {
height: 28px;
padding: 0 12px;
border: 0;
border-radius: 14px;
color: #1989fa;
background: #eaf4ff;
}
.union-reimburse-form .payment-detail-item,
.union-reimburse-form .invoice-detail-item {
margin: 8px 14px 0;
overflow: hidden;
border: 1px solid #e6ebf2;
border-radius: 10px;
background: #fff;
box-shadow: none;
}
.union-reimburse-form .invoice-detail-item { padding: 0; }
.union-reimburse-form .invoice-detail-title {
min-height: 38px;
padding: 0 12px;
box-sizing: border-box;
color: #40526a;
font-size: 14px;
font-weight: 600;
line-height: 22px;
background: #f7f9fd;
border-bottom: 1px solid #eef2f7;
}
.union-reimburse-form .invoice-detail-line {
margin: 0;
padding: 8px 12px 0;
color: #718096;
font-size: 13px;
line-height: 20px;
word-break: break-all;
}
.union-reimburse-form .invoice-detail-line:last-of-type { padding-bottom: 8px; }
.union-reimburse-form .invoice-detail-actions,
.union-reimburse-form .payment-detail-actions {
min-height: 28px;
margin-top: 8px;
padding: 9px 12px;
box-sizing: border-box;
border-top: 1px solid #eef2f7;
background: #fff;
}
.union-reimburse-form .invoice-detail-actions { justify-content: flex-end; gap: 6px; }
.union-reimburse-form .invoice-detail-actions .van-button,
.union-reimburse-form .payment-detail-actions .payment-delete-btn {
height: 28px;
min-width: 52px;
padding: 0 10px;
border: 0;
border-radius: 14px;
font-size: 12px;
line-height: 18px;
}
.union-reimburse-form .invoice-detail-actions .van-button--info,
.union-reimburse-form .invoice-detail-actions .van-button--primary {
color: #1989fa;
background: #eaf4ff;
}
.union-reimburse-form .invoice-detail-actions .van-button--danger,
.union-reimburse-form .payment-detail-actions .payment-delete-btn {
color: #f04444;
background: #fff0f0;
}
.union-reimburse-form .invoice-detail-actions .van-icon,
.union-reimburse-form .payment-detail-actions .payment-delete-btn .van-icon {
margin-right: 4px;
font-size: 13px;
}
.union-reimburse-form .payment-detail-item .van-cell { padding-right: 12px; padding-left: 12px; }
.union-reimburse-form .payment-detail-title {
min-height: 38px;
padding: 0 12px;
color: #40526a;
background: #f7f9fd;
border-bottom-color: #eef2f7;
}
.union-reimburse-form .invoice-tip { margin: 8px 14px 10px; border-color: #dce9f8; color: #718096; background: #f5f9ff; }
.union-reimburse-form .form-actions {
gap: 10px;
padding: 10px 15px calc(10px + env(safe-area-inset-bottom));
box-shadow: 0 -2px 12px rgba(43, 73, 112, .09);
}
.union-reimburse-form .form-actions .van-button { height: 40px; border-radius: 8px; font-size: 14px; font-weight: 500; }
.union-reimburse-form .form-actions .van-button--primary { border-color: #1989fa; background: #1989fa; box-shadow: none; }
.union-reimburse-form .form-actions .van-button--info.van-button--plain { color: #1989fa; border-color: #1989fa; }
/* 新增、编辑发票弹框与申请页共用卡片、字段和操作按钮视觉规范。 */
.union-reimburse-form .invoice-popup {
overflow: hidden;
background: #f3f7fd;
}
.union-reimburse-form .invoice-popup .van-nav-bar { flex: none; box-shadow: 0 1px 6px rgba(43, 73, 112, .08); }
.union-reimburse-form .invoice-popup .van-nav-bar__title { color: #1f2d3d; font-size: 16px; font-weight: 600; }
.union-reimburse-form .invoice-popup-body {
padding: 12px 15px;
box-sizing: border-box;
background: #f3f7fd;
}
.union-reimburse-form .invoice-popup-card {
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background: #fff;
box-shadow: 0 3px 11px rgba(51, 87, 126, .08);
}
.union-reimburse-form .invoice-popup-card .van-cell { min-height: 46px; padding: 11px 15px; background: transparent; }
.union-reimburse-form .invoice-popup-card .van-cell::after { right: 15px; left: 15px; border-color: #eef2f7; }
.union-reimburse-form .invoice-popup-card .van-field__label { width: 105px; color: #596779; font-size: 13px; line-height: 24px; }
.union-reimburse-form .invoice-popup-card .van-field__control { color: #40526a; font-size: 14px; line-height: 24px; }
.union-reimburse-form .invoice-popup-card .van-field__control::placeholder { color: #a7b0bf; }
.union-reimburse-form .invoice-popup-card .more-text { display: block; }
.union-reimburse-form .invoice-popup-card .more-text .van-field__label { width: auto; margin-bottom: 7px; }
.union-reimburse-form .invoice-popup-card .more-text .van-field__control {
min-height: 88px;
padding: 10px 12px;
box-sizing: border-box;
border: 1px solid #c9d5e5;
border-radius: 10px;
background: #f7f9fd;
}
/* 发票文件只保留上传控件本身,移除报销文本域的边框、背景和内边距。 */
.union-reimburse-form .invoice-popup-card .more-text.invoice-popup-file-field .van-field__control {
min-height: 0;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
}
.union-reimburse-form .invoice-popup-tip {
margin: 10px 0 0;
padding: 9px 11px;
border-color: #dce9f8;
color: #718096;
background: #f5f9ff;
}
.union-reimburse-form .invoice-popup-actions {
flex: none;
gap: 10px;
padding: 10px 15px calc(10px + env(safe-area-inset-bottom));
box-shadow: 0 -2px 12px rgba(43, 73, 112, .09);
}
.union-reimburse-form .invoice-popup-actions .van-button { height: 40px; border-radius: 8px; font-size: 14px; font-weight: 500; }
.union-reimburse-form .invoice-popup-actions .van-button--primary { border-color: #1989fa; background: #1989fa; box-shadow: none; }
.union-reimburse-form .invoice-popup-actions .van-button--info.van-button--plain { color: #1989fa; border-color: #1989fa; background: #fff; }
/* 审核卡片与社团审核保持相同的标题、必填标记和按钮规格。 */
.union-reimburse-approval { padding: 0; background: #f3f7fd; }
.union-reimburse-approval-card {
margin: 0 0 11px;
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background: #fff;
box-shadow: 0 3px 11px rgba(51, 87, 126, .08);
}
.union-reimburse-approval-card__title {
position: relative;
margin: 0;
padding: 12px 14px 8px 21px;
color: #354255;
font-size: 15px;
font-weight: 600;
line-height: 22px;
}
.union-reimburse-approval-card__title::before {
position: absolute;
top: 15px;
bottom: 11px;
left: 12px;
width: 2px;
border-radius: 2px;
background: #1989fa;
content: '';
}
.union-reimburse-approval-field-label {
position: relative;
margin: 0;
padding: 10px 14px 6px;
color: #596779;
font-size: 12px;
line-height: 24px;
}
.union-reimburse-approval-required { position: absolute; left: 6px; color: #ee0a24; font-size: 12px; }
.union-reimburse-approval-card .van-field.union-reimburse-approval-field {
width: calc(100% - 28px);
margin: 0 14px 14px;
padding: 12px;
box-sizing: border-box;
border: 1px solid #c9d5e5;
border-radius: 10px;
background: #f7f9fd;
}
.union-reimburse-approval-card .van-field.union-reimburse-approval-field .van-field__control {
min-height: 150px;
color: #253247;
font-size: 12px;
line-height: 22px;
}
.union-reimburse-approval-card .van-field.union-reimburse-approval-field::after { display: none; }
.union-reimburse-approval-actions {
display: flex;
column-gap: 10px;
padding: 10px 12px calc(12px + env(safe-area-inset-bottom));
}
.union-reimburse-approval-actions .van-button { height: 40px; border-radius: 14px; font-size: 14px; }
@keyframes union-reimburse-skeleton-loading {
0% { background-position: 100% 50%; }
100% { background-position: 0 50%; }
}
@@ -0,0 +1,534 @@
.proposal-view-page {
--proposal-view-primary: #1677ee;
--proposal-view-text: #354255;
--proposal-view-muted: #8a97a8;
min-height: 100%;
padding-bottom: calc(20px + env(safe-area-inset-bottom));
box-sizing: border-box;
color: var(--proposal-view-text);
background: #f3f7fc;
}
/* 提案 H5 列表统一使用全屏 Popup 承载详情和办理表单。 */
.proposal-full-popup {
width: 100%;
height: 100%;
overflow: hidden;
background: #f3f7fc;
}
.proposal-full-popup__page {
display: flex;
height: 100%;
padding-top: 46px;
overflow: hidden;
box-sizing: border-box;
flex-direction: column;
}
.proposal-full-popup__page > .van-nav-bar {
z-index: 20;
background: #ffffff;
}
.proposal-full-popup__scroll {
min-height: 0;
overflow-y: auto;
flex: 1;
-webkit-overflow-scrolling: touch;
}
.proposal-full-popup__scroll .proposal-view-main {
padding-top: 18px;
}
.proposal-popup-form-actions {
display: flex;
padding: 10px 12px calc(12px + env(safe-area-inset-bottom));
justify-content: space-between;
column-gap: 10px;
}
/* 提案页面按需启用的圆角按钮与分页样式,不影响未声明对应 class 的组件。 */
.proposal-soft-button,
.proposal-soft-pagination {
--proposal-soft-primary: #1989fa;
}
/* 提案审核弹框的次要操作按钮统一使用柔和圆角,不包含阴影或漫反射效果。 */
.proposal-rounded-button {
border-radius: 14px;
}
.proposal-soft-pagination {
column-gap: 2px;
}
.proposal-soft-button {
border-color: var(--proposal-soft-primary);
border-radius: 14px;
background-color: var(--proposal-soft-primary);
}
/* 保持危险操作原有红色语义。 */
.proposal-soft-button--danger {
border-color: #ff4d4f;
background-color: #ff4d4f;
}
/* 保持退回类操作原有橙色语义。 */
.proposal-soft-button--warning {
border-color: #ff976a;
background-color: #ff976a;
}
/* 校工会预审核四个操作按钮与 PC 端 Element UI 的 primary、danger、info 配色保持一致。 */
.proposal-pre-audit-actions .proposal-pre-audit-button--primary {
border-color: var(--color-primary);
background-color: var(--color-primary);
}
.proposal-pre-audit-actions .proposal-pre-audit-button--danger {
border-color: #f56c6c;
background-color: #f56c6c;
}
.proposal-pre-audit-actions .proposal-pre-audit-button--info {
border-color: #909399;
background-color: #909399;
}
.proposal-soft-pagination .van-pagination__item {
color: var(--proposal-soft-primary);
background-color: #f8fbff;
border-radius: 12px;
transition: color 0.2s ease, background-color 0.2s ease;
}
.proposal-soft-pagination .van-pagination__item::after {
border-color: rgba(25, 137, 250, 0.16);
border-radius: inherit;
}
.proposal-soft-pagination .van-pagination__item--active,
.proposal-soft-pagination .van-pagination__item:not(.van-pagination__item--disabled):active {
color: #fff;
background-color: var(--proposal-soft-primary);
}
.proposal-soft-pagination .van-pagination__item--disabled,
.proposal-soft-pagination .van-pagination__item--disabled:active {
color: #646566;
background-color: #f7f8fa;
}
.proposal-action-icon {
margin-right: 6px;
font-size: 15px;
}
.reply-unit-popup {
display: flex;
height: 100%;
flex-direction: column;
}
.reply-unit-toolbar {
display: grid;
min-height: 48px;
border-bottom: 1px solid #eee;
grid-template-columns: 64px 1fr 64px;
align-items: center;
}
.reply-unit-toolbar button {
height: 48px;
padding: 0;
color: #666;
font-size: 14px;
background: transparent;
border: 0;
}
.reply-unit-toolbar button:last-child {
color: #1989fa;
}
.reply-unit-toolbar-title {
color: #222;
font-size: 16px;
font-weight: 600;
text-align: center;
}
.reply-unit-list {
overflow-y: auto;
flex: 1;
-webkit-overflow-scrolling: touch;
}
.reply-unit-empty {
padding: 48px 16px;
color: #999;
font-size: 14px;
text-align: center;
}
.transfer-dialog {
padding: 18px 16px 76px;
box-sizing: border-box;
}
.proposal-info-card {
margin-bottom: 16px;
padding: 16px;
background: #fff;
border-radius: 10px;
}
.proposal-info-card .info-item {
display: flex;
margin-bottom: 10px;
flex-direction: column;
row-gap: 4px;
}
.proposal-info-card .info-item label {
color: #969799;
font-size: 12px;
}
.proposal-full-popup__page > .button-group {
display: flex;
padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
background: #fff;
column-gap: 10px;
}
.proposal-view-main {
position: relative;
z-index: 2;
padding: 12px 15px 0;
}
.proposal-view-card {
margin-bottom: 11px;
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background: #ffffff;
box-shadow: 0 3px 11px rgba(51, 87, 126, 0.08);
}
.proposal-view-card__title {
position: relative;
margin: 0;
padding: 12px 14px 8px 21px;
color: #354255;
font-size: 15px;
font-weight: 600;
line-height: 22px;
}
.proposal-view-card__title::before {
position: absolute;
top: 15px;
bottom: 11px;
left: 12px;
width: 2px;
border-radius: 2px;
background: var(--proposal-view-primary);
content: "";
}
/* 审核、附议、反馈及答复输入框统一复用提案查询 Card 的视觉规范。 */
.proposal-audit-card__required {
margin-left: 4px;
color: #ee0a24;
font-size: 14px;
}
.proposal-view-card .van-field.proposal-audit-field {
width: calc(100% - 28px);
margin: 0 14px 14px;
padding: 12px;
border: 1px solid #c9d5e5;
border-radius: 10px;
background-color: #f7f9fd;
box-sizing: border-box;
}
.proposal-view-card .van-field.proposal-audit-field .van-field__control {
min-height: 150px;
color: #253247;
line-height: 22px;
}
.proposal-view-card .van-field.proposal-audit-field .van-field__control::placeholder {
color: #9aa7b8;
}
.proposal-view-card .van-field.proposal-audit-field .van-field__word-limit {
color: #718096;
}
.proposal-view-card .van-field.proposal-audit-field::after {
display: none;
}
.proposal-view-card .van-cell-group {
margin: 0;
background: transparent;
}
.proposal-view-card .van-cell {
min-height: 44px;
padding: 10px 14px;
background: transparent;
}
.proposal-view-card .van-cell:not(:last-child)::after {
right: 14px;
left: 14px;
border-color: #eef2f7;
}
.proposal-view-card .van-cell__title {
flex: 0 0 92px;
width: 92px;
color: #354255;
font-size: 14px;
font-weight: 600;
line-height: 24px;
}
/* 纵向字段中 flex-basis 会转为标题高度,需要取消横向字段使用的 92px 固定占位。 */
.proposal-view-card .direction-column-field .van-cell__title {
flex: none;
width: auto;
}
.proposal-view-card .van-cell__value {
min-width: 0;
color: #4f5d70;
font-size: 14px;
line-height: 24px;
text-align: left;
overflow-wrap: anywhere;
}
.proposal-view-card .proposal-view-long-cell {
display: block;
}
.proposal-view-card .proposal-view-long-cell .van-cell__title {
width: auto;
max-width: 100%;
}
.proposal-view-card .proposal-view-long-cell .van-cell__value {
margin-top: 7px;
}
.proposal-view-rich-value,
.proposal-view-rich-value * {
max-width: 100%;
white-space: normal;
word-break: break-word;
}
.proposal-view-rich-value img,
.proposal-view-rich-value image {
height: auto;
max-width: 100%;
}
.proposal-view-card .van-tag {
font-size: 14px;
line-height: 18px;
white-space: normal;
}
.proposal-view-merge-cell .van-cell__title {
flex: 1;
width: auto;
}
.proposal-view-merge-cell .van-cell__label {
margin-top: 4px;
color: var(--proposal-view-muted);
font-size: 13px;
line-height: 20px;
white-space: normal;
}
.proposal-flow-card__header {
display: flex;
align-items: center;
justify-content: space-between;
}
.proposal-flow-card__hint {
display: inline-flex;
align-items: center;
padding-right: 14px;
color: var(--proposal-view-muted);
font-size: 13px;
line-height: 22px;
white-space: nowrap;
}
.proposal-flow-card__hint .van-icon {
margin-left: 4px;
color: #7f8da0;
font-size: 17px;
}
.proposal-flow-scroll {
width: 100%;
padding: 4px 14px 15px;
overflow-x: auto;
box-sizing: border-box;
scroll-behavior: smooth;
scroll-snap-type: x proximity;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
}
.proposal-flow-scroll::-webkit-scrollbar {
display: none;
}
.proposal-flow-track {
position: relative;
display: flex;
width: max-content;
min-width: 100%;
padding-top: 5px;
}
.proposal-flow-track::before {
position: absolute;
top: 24px;
right: 52px;
left: 52px;
height: 2px;
background: #d8e9ff;
content: "";
}
.proposal-flow-node {
position: relative;
z-index: 1;
display: flex;
flex: 0 0 108px;
width: 108px;
padding: 0 6px;
border: 0;
outline: none;
box-sizing: border-box;
align-items: center;
flex-direction: column;
color: var(--proposal-view-text);
background: transparent;
font-family: inherit;
scroll-snap-align: center;
-webkit-tap-highlight-color: transparent;
}
.proposal-flow-node__icon {
display: flex;
width: 38px;
height: 38px;
border: 3px solid #ffffff;
border-radius: 50%;
box-sizing: border-box;
align-items: center;
justify-content: center;
color: #ffffff;
background: var(--proposal-view-primary);
box-shadow: 0 2px 7px rgba(22, 119, 238, 0.22);
transition: box-shadow 0.2s ease, transform 0.2s ease;
}
.proposal-flow-node__icon .van-icon {
font-size: 20px;
}
.proposal-flow-node__title {
display: -webkit-box;
min-height: 40px;
margin-top: 8px;
overflow: hidden;
color: #354255;
font-size: 14px;
font-weight: 600;
line-height: 20px;
text-align: center;
word-break: break-word;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.proposal-flow-node__handler {
display: block;
width: 100%;
margin-top: 1px;
overflow: hidden;
color: var(--proposal-view-muted);
font-size: 13px;
line-height: 20px;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
}
.proposal-flow-node.is-active .proposal-flow-node__icon {
box-shadow: 0 0 0 5px #e7f2ff, 0 3px 9px rgba(22, 119, 238, 0.28);
transform: scale(1.06);
}
.proposal-flow-node.is-active .proposal-flow-node__title {
color: var(--proposal-view-primary);
}
.proposal-flow-detail__name {
margin: 0;
padding: 4px 14px 10px;
border-bottom: 1px solid #eef2f7;
color: #26364a;
font-size: 16px;
font-weight: 600;
line-height: 24px;
}
.proposal-flow-detail__tip {
padding: 9px 14px 12px;
border-top: 1px solid #eef2f7;
color: var(--proposal-view-muted);
font-size: 13px;
line-height: 20px;
text-align: center;
}
.proposal-view-table-wrap {
width: 100%;
overflow-x: auto;
}
.proposal-view-table-wrap .el-table {
min-width: 560px;
font-size: 14px;
}
.proposal-view-slot:empty {
display: none;
}
@media (min-width: 600px) {
.proposal-view-main {
max-width: 520px;
margin-right: auto;
margin-left: auto;
}
}
@@ -0,0 +1,457 @@
const PROPOSAL_TASK_POPUP = {
template: /*language=HTML*/ `
<van-popup v-model="visible" position="right" class="proposal-full-popup" :close-on-click-overlay="false"
get-container="#app">
<div class="proposal-full-popup__page">
<van-nav-bar @click-left="close" left-arrow left-text="返回" :title="title" placeholder
fixed></van-nav-bar>
<div class="proposal-full-popup__scroll">
<proposal-info v-if="bizId" :biz-id="bizId" :visible="visible">
<div v-if="isApproval">
<van-form ref="formRef">
<template
v-if="kind === 'delegation' || kind === 'preAudit' || kind === 'schoolLeaderApproval'">
<section class="proposal-view-card">
<h2 class="proposal-view-card__title">审批意见<span
class="proposal-audit-card__required">*</span></h2>
<van-field v-model="formData.tf_opinion" class="proposal-audit-field"
name="tf_opinion" type="textarea" rows="6" label=""
placeholder="请输入审批意见"
:rules="[{required:true,message:'请填写审批意见'}]" required
maxlength="100" show-word-limit></van-field>
</section>
</template>
<template v-if="kind === 'feedbackEvaluation'">
<section class="proposal-view-card">
<h2 class="proposal-view-card__title">反馈评价<span
class="proposal-audit-card__required">*</span></h2>
<van-field v-model="formData.tf_feedback_name" label="满意度"
placeholder="请选择满意度"
:rules="[{required:true,message:'请选择满意度'}]" required readonly
is-link @click="openLayer('feedback-picker')"></van-field>
<van-field v-model="formData.tf_opinion" class="proposal-audit-field"
name="tf_opinion" type="textarea" rows="6" label=""
placeholder="请输入反馈意见"
:rules="[{required:true,message:'请填写反馈意见'}]"
maxlength="100" show-word-limit></van-field>
</section>
</template>
<template v-if="kind === 'suggestion'">
<section class="proposal-view-card">
<van-field v-model="formData.result" label="承办意向"
placeholder="请选择承办意向"
:rules="[{required:true,message:'请选择承办意向'}]" required readonly
is-link @click="openLayer('result-picker')"></van-field>
<h2 class="proposal-view-card__title">反馈意见<span
class="proposal-audit-card__required">*</span></h2>
<van-field v-model="formData.opinion" class="proposal-audit-field"
name="opinion" type="textarea" rows="6" label=""
placeholder="请输入反馈意见"
:rules="[{required:true,message:'请输入反馈意见'}]" required
show-word-limit></van-field>
</section>
</template>
<template v-if="kind === 'unitReply'">
<section class="proposal-view-card">
<h2 class="proposal-view-card__title">承办单位答复<span
class="proposal-audit-card__required">*</span></h2>
<van-field :value="formData.underTakeName" label="承办单位"
readonly></van-field>
<van-field v-model="formData.tf_implementStateName" label="落实情况"
placeholder="请选择落实情况"
:rules="[{required:true,message:'请选择落实情况'}]" required readonly
is-link @click="openLayer('implement-picker')"></van-field>
<!-- 承办单位答复附件上传入口暂时停用,保留公用上传组件配置便于后续恢复。 -->
<!-- <van-field label="附件" name="tf_attachment" class="direction-column-field">-->
<!-- <template #input>-->
<!-- <h5-file-upload-->
<!-- :value.sync="formData.tf_attachment"-->
<!-- :upload_number="5"-->
<!-- upload_mode="file"-->
<!-- upload_result_category="array"-->
<!-- upload_result_type="url"-->
<!-- complete_result>-->
<!-- </h5-file-upload>-->
<!-- </template>-->
<!-- </van-field>-->
<van-field v-model="formData.tf_opinion" class="proposal-audit-field"
name="tf_opinion" type="textarea" rows="6" label=""
placeholder="请输入答复内容"
:rules="[{required:true,message:'请填写答复内容'}]"
maxlength="100" show-word-limit></van-field>
</section>
<van-field v-if="supportCandidate && formData.underTakeIsMaster"
v-model="formData.tf_nextNodeOperatorName" label="校领导"
placeholder="请选择校领导"
:rules="[{required:true,message:'请选择校领导'}]" required readonly
is-link @click="openLayer('candidate-picker')"></van-field>
</template>
<template v-if="kind === 'caseCheck'">
<van-field :value="formData.tf_username" label="审核人" readonly></van-field>
<van-field :value="formData.tf_auditTime" label="审核时间" readonly></van-field>
<van-field :value="formData.tf_caseCheckNeedSecondReply" label="二次答复" required
:rules="[{validator:validateSecondReply,message:'请选择是否需要二次答复'}]">
<template #input>
<van-radio-group v-model="formData.tf_caseCheckNeedSecondReply"
direction="horizontal" @change="onSecondReplyChange">
<van-radio :name="0">无需二次答复</van-radio>
<van-radio :name="1">需要二次答复</van-radio>
</van-radio-group>
</template>
</van-field>
<van-field v-if="formData.tf_caseCheckNeedSecondReply === 1"
:value="selectedReplyUnitNames" label="答复单位"
placeholder="请选择办理单位" readonly required is-link
:rules="[{validator:validateSecondReplyUnits,message:'请选择需要二次答复的办理单位'}]"
@click="openReplyUnitPicker"></van-field>
<section class="proposal-view-card">
<h2 class="proposal-view-card__title">审批意见<span
class="proposal-audit-card__required">*</span></h2>
<van-field v-model="formData.tf_opinion" class="proposal-audit-field"
name="tf_opinion" type="textarea" rows="6" label=""
placeholder="请输入审批意见" required maxlength="100" show-word-limit
:rules="[{required:true,message:'请填写审批意见'}]"></van-field>
</section>
</template>
</van-form>
<div class="proposal-popup-form-actions" v-if="kind === 'delegation'">
<van-button type="primary" block :loading="formLoading" class="proposal-soft-button"
@click="prepareSubmit(1)">同意
</van-button>
<van-button type="danger" block :loading="formLoading"
class="proposal-soft-button proposal-soft-button--danger"
@click="prepareSubmit(2)">不同意
</van-button>
<van-button type="danger" block :loading="formLoading"
class="proposal-soft-button proposal-soft-button--danger"
@click="prepareSubmit(6)">退回
</van-button>
</div>
<div class="proposal-popup-form-actions proposal-pre-audit-actions" v-else-if="kind === 'preAudit'"
style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr))">
<van-button type="primary" :loading="formLoading"
class="proposal-soft-button proposal-pre-audit-button--primary"
@click="prepareSubmit(1)">同意
</van-button>
<van-button type="danger" :loading="formLoading"
class="proposal-soft-button proposal-pre-audit-button--danger"
@click="prepareSubmit(2)">不同意
</van-button>
<van-button type="info" :loading="formLoading"
class="proposal-soft-button proposal-pre-audit-button--info"
@click="prepareSubmit(4)">退回(不审核)
</van-button>
<van-button type="info" :loading="formLoading"
class="proposal-soft-button proposal-pre-audit-button--info"
@click="prepareSubmit(6)">退回(审核)
</van-button>
</div>
<div class="proposal-popup-form-actions" v-else-if="kind === 'schoolLeaderApproval'">
<van-button type="primary" block :loading="formLoading" class="proposal-soft-button"
@click="prepareSubmit('agree')">同意答复
</van-button>
<van-button type="danger" block :loading="formLoading"
class="proposal-soft-button proposal-soft-button--danger"
@click="prepareSubmit('back')">退回答复
</van-button>
</div>
<div class="proposal-popup-form-actions" v-else>
<van-button type="primary" block :loading="formLoading" class="proposal-soft-button"
@click="prepareSubmit(1)">提交
</van-button>
<van-button block :disabled="formLoading" class="proposal-rounded-button"
@click="close">返回
</van-button>
</div>
</div>
</proposal-info>
</div>
</div>
<!-- 字典通过异步请求加载,初始化阶段可能为 null;使用空数组保证弹层首次渲染不执行 null.map。 -->
<van-popup v-model="feedbackPickerVisible" position="bottom" :close-on-click-overlay="false">
<van-picker show-toolbar
:columns="(dict.type.PROPOSAL_FEEDBACK || []).map((item) => ({text:item.label,value:item.code}))"
@confirm="confirmFeedback" @cancel="closeLayer('feedback-picker')"></van-picker>
</van-popup>
<van-popup v-model="resultPickerVisible" position="bottom" :close-on-click-overlay="false">
<van-picker show-toolbar :columns="(dict.type.UNDERTAKE_SUGGESTION || []).map((item) => item.code)"
@confirm="confirmResult" @cancel="closeLayer('result-picker')"></van-picker>
</van-popup>
<van-popup v-model="implementPickerVisible" position="bottom" :close-on-click-overlay="false">
<van-picker show-toolbar
:columns="(dict.type.PROPOSAL_REPLY_IMPLEMENT || []).map((item) => ({text:item.label,value:item.code}))"
@confirm="confirmImplement" @cancel="closeLayer('implement-picker')"></van-picker>
</van-popup>
<van-popup v-model="candidatePickerVisible" position="bottom" :close-on-click-overlay="false">
<van-picker show-toolbar :columns="candidates.map((item) => ({text:item.userName,value:item.userId}))"
@confirm="confirmCandidate" @cancel="closeLayer('candidate-picker')"></van-picker>
</van-popup>
<van-popup v-model="replyUnitPickerVisible" position="bottom" round :close-on-click-overlay="false"
:style="{height:'70%'}">
<div class="reply-unit-popup">
<div class="reply-unit-toolbar">
<button type="button" @click="closeLayer('reply-unit-picker')">取消</button>
<div class="reply-unit-toolbar-title">选择答复单位</div>
<button type="button" @click="confirmReplyUnits">确定</button>
</div>
<div class="reply-unit-list" v-if="replyUnitOptions.length">
<van-checkbox-group v-model="pendingReplyUnitIds">
<van-cell v-for="item in replyUnitOptions" :key="item.unitId" clickable
:title="item.unitName" :label="item.isMaster ? '主办单位' : '协办单位'"
@click="toggleReplyUnit(item)">
<template #right-icon>
<van-checkbox :name="item.unitId" @click.stop></van-checkbox>
</template>
</van-cell>
</van-checkbox-group>
</div>
<div class="reply-unit-empty" v-else>暂无可选办理单位</div>
</div>
</van-popup>
<van-dialog :value="confirmVisible" title="提示" message="您确定要提交吗?" show-cancel-button
@confirm="submit" @cancel="closeLayer('submit-confirm')"></van-dialog>
</van-popup>
`,
dicts: ["PROPOSAL_FEEDBACK", "UNDERTAKE_SUGGESTION", "PROPOSAL_REPLY_IMPLEMENT"],
components: {"proposal-info": PROPOSAL_INFO},
props: {kind: {type: String, required: true}, pageKey: {type: String, required: true}},
data() {
return {
visible: false,
bizId: "",
action: "view",
formData: {},
formLoading: false,
pendingSubmitType: 1,
feedbackPickerVisible: false,
resultPickerVisible: false,
implementPickerVisible: false,
candidatePickerVisible: false,
replyUnitPickerVisible: false,
confirmVisible: false,
supportCandidate: false,
candidates: [],
replyUnitOptions: [],
pendingReplyUnitIds: [],
unsubscribeHistoryLayer: null
}
},
computed: {
isApproval() {
return this.action === "approval"
},
title() {
const titles = {
delegation: "团长审核",
preAudit: "校工会预审核",
schoolLeaderApproval: "分管校领导审批",
feedbackEvaluation: "反馈评价",
suggestion: "承办单位意见",
unitReply: "承办单位答复",
caseCheck: "提案委员会审查"
};
return this.isApproval ? titles[this.kind] : "提案详情"
},
selectedReplyUnitNames() {
const ids = this.formData.tf_secondReplyUnitIds || [];
return this.replyUnitOptions.filter((item) => ids.indexOf(item.unitId) > -1).map((item) => item.unitName + "(" + (item.isMaster ? "主办" : "协办") + ")").join("、")
}
},
methods: {
syncLayers(stack) {
this.$set(this, "visible", stack.includes("detail"));
this.$set(this, "feedbackPickerVisible", stack.includes("feedback-picker"));
this.$set(this, "resultPickerVisible", stack.includes("result-picker"));
this.$set(this, "implementPickerVisible", stack.includes("implement-picker"));
this.$set(this, "candidatePickerVisible", stack.includes("candidate-picker"));
this.$set(this, "replyUnitPickerVisible", stack.includes("reply-unit-picker"));
this.$set(this, "confirmVisible", stack.includes("submit-confirm"))
},
openLayer(name) {
window.h5HistoryLayerManager.open(name)
}, closeLayer(name, callback) {
window.h5HistoryLayerManager.close(name, callback)
}, close() {
this.closeLayer("detail")
},
/** row 提供详情主键和当前 WF 任务参数,action 为 view 或 approval。 */
open(row, action) {
const defaultTaskNames = {
delegation: "团长审核",
preAudit: "校工会预审核",
feedbackEvaluation: "反馈评价",
unitReply: "承办单位答复",
caseCheck: "提案委员会审查"
}
const taskName = this.kind === "unitReply" ? (row.taskName || defaultTaskNames[this.kind]) : (row.curTaskName || row.taskName || defaultTaskNames[this.kind] || "")
this.$set(this, "bizId", row.id);
this.$set(this, "action", action);
this.$set(this, "formData", {
proposalId: row.id,
processTaskId: row.taskId || "",
taskKey: row.taskKey || "",
taskName: taskName,
instanceId: row.instanceId || "",
underTakeName: row.underTakeName || "",
underTakeIsMaster: !!row.underTakeIsMaster,
tf_opinion: "",
opinion: ""
})
if (action === "approval" && this.kind === "unitReply") {
// 承办单位答复附件上传入口暂时停用,保留初始化代码便于后续恢复。
// this.$set(this.formData, "tf_attachment", [])
this.loadCandidates(row.taskId || "")
}
if (action === "approval" && this.kind === "caseCheck") {
this.$set(this.formData, "tf_auditTime", moment().format("YYYY-MM-DD"));
this.$set(this.formData, "tf_username", (this.$store.state.user && this.$store.state.user.username) || "");
this.$set(this.formData, "tf_caseCheckNeedSecondReply", 0);
this.$set(this.formData, "tf_secondReplyUnitIds", []);
this.loadReplyUnits(row.id)
}
this.openLayer("detail")
},
loadCandidates(taskId) {
this.$axios.post("/flow/common/candidate", {taskId: taskId}).then((res) => {
if (res.code === 0) {
this.$set(this, "candidates", res.data.candidates || []);
this.$set(this, "supportCandidate", res.data.support)
}
})
},
confirmFeedback(value) {
this.$set(this.formData, "tf_feedback_name", value.text);
this.$set(this.formData, "tf_feedback", value.value);
this.closeLayer("feedback-picker")
},
confirmResult(value) {
this.$set(this.formData, "result", value);
this.closeLayer("result-picker")
},
confirmImplement(value) {
this.$set(this.formData, "tf_implementStateName", value.text);
this.$set(this.formData, "tf_implementState", value.value);
this.closeLayer("implement-picker")
},
confirmCandidate(value) {
this.$set(this.formData, "tf_nextNodeOperatorName", value.text);
this.$set(this.formData, "tf_nextNodeOperator", value.value);
this.closeLayer("candidate-picker")
},
validateSecondReply(value) {
return value === 0 || value === 1
},
validateSecondReplyUnits() {
return this.formData.tf_caseCheckNeedSecondReply !== 1 || (this.formData.tf_secondReplyUnitIds || []).length > 0
},
onSecondReplyChange(value) {
if (value !== 1) this.$set(this.formData, "tf_secondReplyUnitIds", [])
},
loadReplyUnits(proposalId) {
this.$axios.post("/platform/proposal/case/check/listReplyUnits", {proposalId: proposalId}).then((res) => {
if (res && res.code === 0) this.$set(this, "replyUnitOptions", res.data || [])
})
},
openReplyUnitPicker() {
this.$set(this, "pendingReplyUnitIds", [...(this.formData.tf_secondReplyUnitIds || [])]);
this.openLayer("reply-unit-picker")
},
toggleReplyUnit(item) {
const ids = [...this.pendingReplyUnitIds];
const index = ids.indexOf(item.unitId);
if (index > -1) ids.splice(index, 1); else ids.push(item.unitId);
this.$set(this, "pendingReplyUnitIds", ids)
},
confirmReplyUnits() {
this.$set(this.formData, "tf_secondReplyUnitIds", [...this.pendingReplyUnitIds]);
this.closeLayer("reply-unit-picker")
},
buildCaseCheckData() {
const data = {...this.formData};
const units = data.tf_caseCheckNeedSecondReply === 1 ? this.replyUnitOptions.filter((item) => data.tf_secondReplyUnitIds.indexOf(item.unitId) > -1) : [];
const masters = units.filter((item) => item.isMaster);
const slaves = units.filter((item) => !item.isMaster);
data.tf_masterUnitIds = masters.map((item) => item.unitId);
data.tf_masterUnitId = data.tf_masterUnitIds[0] || "";
data.tf_masterUnitNames = masters.map((item) => item.unitName);
data.tf_masterUnitName = data.tf_masterUnitNames[0] || "";
data.tf_masterUnitNameStr = data.tf_masterUnitNames.join(",");
data.tf_slaveUnitIds = slaves.map((item) => item.unitId);
data.tf_slaveUnitNames = slaves.map((item) => item.unitName);
data.tf_slaveUnitNameStr = data.tf_slaveUnitNames.join(",");
return data
},
prepareSubmit(value) {
// 各办理页面提交前统一校验意见字段;空白字符按未填写处理,提示后不再打开确认弹框。
const opinionConfig = this.kind === "suggestion"
? {field: "opinion", message: "请输入反馈意见"}
: this.kind === "unitReply"
? {field: "tf_opinion", message: "请填写答复内容"}
: {field: "tf_opinion", message: "请填写审批意见"}
if (!(this.formData[opinionConfig.field] || "").trim()) {
this.$toast(opinionConfig.message)
return
}
this.$refs.formRef.validate().then(() => {
this.$set(this, "pendingSubmitType", value);
this.openLayer("submit-confirm")
}).catch(() => {
})
},
/** 根据页面类型保留原接口和参数分支,成功后清理全部弹层历史并通知列表刷新。 */
submit() {
this.$set(this, "formLoading", true)
let request
if (this.kind === "suggestion") request = this.$axios.post("/platform/proposal/suggestion/suggest", this.formData)
else if (this.kind === "schoolLeaderApproval") {
const data = {...this.formData, tf_approval: this.pendingSubmitType};
delete data.taskKey;
delete data.taskName;
request = this.$axios.post("/platform/proposal/schoolLeaderApproval/executeTask", {data: JSON.stringify(data)})
} else {
const data = this.kind === "caseCheck" ? {
...this.buildCaseCheckData(),
submitType: this.pendingSubmitType
} : {...this.formData, submitType: this.pendingSubmitType}
if (this.kind === "preAudit") {
data.taskName = "startTask";
if (this.pendingSubmitType === 4) {
data.tf_sourceTaskKey = this.formData.taskKey;
data.tf_audit = false
}
}
if (this.kind === "unitReply") {
delete data.underTakeIsMaster;
delete data.underTakeName
}
if (this.kind === "feedbackEvaluation" && data.tf_feedback === "DISSATISFIED") request = this.$axios.post("/platform/proposal/feedbackEvaluation/caseInfo", {instId: data.instanceId}).then((res) => {
if (res.code !== 0) return Promise.reject(new Error(res.msg || "查询立案信息失败"));
data.tf_masterUnitId = res.data.tf_masterUnitId;
return this.$axios.post("/flow/common/executeTask", {data: JSON.stringify(data)})
})
else request = this.$axios.post("/flow/common/executeTask", {data: JSON.stringify(data)})
}
request.then((res) => {
if (res && res.code === 0) {
this.$toast.success(res.msg);
window.h5HistoryLayerManager.closeAll(() => this.$emit("success"))
}
}).catch((error) => {
if (error && error.message && error.message !== "cancel") this.$toast.fail(error.message)
}).finally(() => {
this.$set(this, "formLoading", false)
})
}
},
created() {
window.h5HistoryLayerManager.ensureRegistered(this.pageKey);
this.$set(this, "unsubscribeHistoryLayer", window.h5HistoryLayerManager.subscribe((stack) => this.syncLayers(stack)))
},
beforeDestroy() {
if (this.unsubscribeHistoryLayer) this.unsubscribeHistoryLayer();
if (window.h5HistoryLayerManager.pageKey === this.pageKey) window.h5HistoryLayerManager.unregister(this.pageKey)
}
}
@@ -0,0 +1,17 @@
/* 提案办理页统一使用 #1989fa 品牌蓝,并覆盖动态弹框及 Vant 组件继承的全局主题色。 */
:root {
--color-primary: #1989fa;
}
/* 公共提案详情组件拥有独立主题变量,需要在办理页内同步覆盖。 */
.proposal-view {
--proposal-view-primary: #1989fa;
}
.proposal-flow-node__icon {
box-shadow: 0 2px 7px rgba(25, 137, 250, 0.22);
}
.proposal-flow-node.is-active .proposal-flow-node__icon {
box-shadow: 0 0 0 5px #e7f2ff, 0 3px 9px rgba(25, 137, 250, 0.28);
}
@@ -0,0 +1,176 @@
.condolence-full-popup {
--color-primary: #1989fa;
width: 100%;
height: 100%;
overflow: hidden;
background: #f3f7fc;
}
.condolence-full-popup__page {
display: flex;
height: 100%;
padding-top: 46px;
overflow: hidden;
box-sizing: border-box;
flex-direction: column;
}
.condolence-full-popup__page > .van-nav-bar {
z-index: 20;
background: #fff;
}
.condolence-full-popup__page > .van-nav-bar .van-icon,
.condolence-full-popup__page > .van-nav-bar .van-nav-bar__text {
color: #1989fa;
}
.condolence-full-popup__scroll {
min-height: 0;
overflow-y: auto;
flex: 1;
-webkit-overflow-scrolling: touch;
}
.condolence-info-main {
padding: 12px 15px calc(18px + env(safe-area-inset-bottom));
}
.condolence-info-card {
margin-bottom: 11px;
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background: #fff;
box-shadow: 0 3px 11px rgba(51, 87, 126, .08);
}
.condolence-info-card__title {
position: relative;
margin: 0;
padding: 12px 14px 8px 21px;
color: #354255;
font-size: 15px;
font-weight: 600;
line-height: 22px;
}
.condolence-info-card__title::before {
position: absolute;
top: 15px;
bottom: 11px;
left: 12px;
width: 2px;
border-radius: 2px;
background: #1989fa;
content: "";
}
/* 审核意见复用详情 Card 标题,并与提案 delegation 审核输入区保持一致。 */
.condolence-audit-card__required {
margin-left: 4px;
color: #ee0a24;
font-size: 14px;
}
.condolence-info-card .van-field.condolence-audit-field {
width: calc(100% - 28px);
margin: 0 14px 14px;
padding: 12px;
box-sizing: border-box;
border: 1px solid #c9d5e5;
border-radius: 10px;
background: #f7f9fd;
}
.condolence-info-card .van-field.condolence-audit-field .van-field__control {
min-height: 150px;
color: #253247;
line-height: 22px;
}
.condolence-info-card .van-field.condolence-audit-field .van-field__control::placeholder {
color: #9aa7b8;
}
.condolence-info-card .van-field.condolence-audit-field .van-field__word-limit {
color: #718096;
}
.condolence-info-card .van-field.condolence-audit-field::after {
display: none;
}
.condolence-popup-actions {
display: flex;
padding: 10px 12px calc(12px + env(safe-area-inset-bottom));
justify-content: space-between;
column-gap: 10px;
background: transparent;
}
.condolence-popup-actions .van-button {
border-radius: 14px;
}
.condolence-popup-actions .condolence-action-primary {
border-color: #1989fa;
background: #1989fa;
}
.condolence-popup-actions .condolence-action-danger {
border-color: #ff4d4f;
background: #ff4d4f;
}
.condolence-info-card .van-cell-group {
margin: 0;
background: transparent;
}
.condolence-info-card .van-cell {
min-height: 44px;
padding: 10px 14px;
background: transparent;
}
.condolence-info-card .van-cell:not(:last-child)::after {
right: 14px;
left: 14px;
border-color: #eef2f7;
}
.condolence-info-card .van-cell__title {
width: 102px;
color: #596779;
font-size: 12px;
line-height: 24px;
flex: 0 0 102px;
}
.condolence-info-card .van-cell__value {
min-width: 0;
color: #4f5d70;
font-size: 12px;
line-height: 24px;
text-align: left;
overflow-wrap: anywhere;
}
.condolence-info-long-cell {
display: block;
}
.condolence-info-long-cell .van-cell__title {
width: auto;
max-width: 100%;
}
.condolence-info-long-cell .van-cell__value {
margin-top: 7px;
}
.condolence-signature-image {
display: block;
max-width: 100%;
}
@@ -0,0 +1,307 @@
#app {
--color-primary: #1989fa;
}
.difficult-help-approval-page {
min-height: 100vh;
padding-bottom: calc(24px + env(safe-area-inset-bottom));
box-sizing: border-box;
background: #f3f7fd;
}
.difficult-help-approval-page .van-nav-bar,
.difficult-help-approval-page .van-sticky--fixed {
background: #fff;
}
.difficult-help-approval-page .van-nav-bar__title {
color: #172033;
font-size: 16px;
font-weight: 600;
}
.difficult-help-approval-page .van-nav-bar .van-icon,
.difficult-help-approval-page .van-nav-bar__text {
color: #1989fa;
}
.difficult-help-approval-sticky {
padding: 22px 12px 1px;
box-sizing: border-box;
background: #f3f7fd;
}
.difficult-help-approval-search {
margin: 0 0 8px;
padding: 0;
overflow: hidden;
border-radius: 12px;
background: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
}
.difficult-help-approval-search .van-search__content {
height: 42px;
padding-left: 12px;
align-items: center;
border-radius: 12px;
background: #fff;
}
.difficult-help-approval-search .van-field__control {
color: #263548;
font-size: 14px;
}
.difficult-help-approval-search .van-field__control::placeholder {
color: #a7b0bf;
}
.difficult-help-approval-filter {
margin-bottom: 10px;
overflow: hidden;
border-radius: 10px;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
transition: border-radius .18s ease;
}
.difficult-help-approval-filter.is-open {
border-radius: 10px 10px 0 0;
}
.difficult-help-approval-filter .van-dropdown-menu__bar,
.difficult-help-approval-filter .van-dropdown-menu__item,
.difficult-help-approval-filter .van-dropdown-menu__title {
height: 44px;
}
.difficult-help-approval-filter .van-dropdown-menu__bar {
box-shadow: none;
}
.difficult-help-approval-filter .van-dropdown-menu__item {
min-width: 0;
align-items: center;
justify-content: center;
}
.difficult-help-approval-filter .van-dropdown-menu__title {
display: inline-flex;
max-width: calc(100% - 12px);
align-items: center;
color: #66758a;
font-size: 13px;
line-height: 20px;
}
.difficult-help-approval-filter .van-dropdown-menu__title--active,
.difficult-help-approval-filter .van-dropdown-item__option--active,
.difficult-help-approval-filter .van-dropdown-item__option--active .van-cell__title,
.difficult-help-approval-filter .van-dropdown-item__option--active .van-dropdown-item__icon {
color: #1989fa;
}
.difficult-help-approval-filter .van-dropdown-item__content {
width: calc(100% - 24px);
max-height: 50%;
margin: 0 12px;
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
border-radius: 0 0 12px 12px;
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
}
.difficult-help-approval-filter .van-dropdown-item__option {
display: flex;
min-height: 48px;
padding: 0 16px;
align-items: center;
color: #46566c;
font-size: 14px;
}
.difficult-help-approval-filter .van-dropdown-item__option .van-cell__title {
display: flex;
min-width: 0;
min-height: 20px;
align-items: center;
flex: 1 1 auto;
line-height: 20px;
}
.difficult-help-approval-filter .van-dropdown-item__option .van-cell__value {
display: flex;
width: 20px;
min-width: 0;
overflow: visible;
align-items: center;
justify-content: flex-end;
flex: 0 0 20px;
}
.difficult-help-approval-content {
padding: 0 12px;
}
.difficult-help-list-skeleton {
padding-bottom: 2px;
}
.difficult-help-list-skeleton__card {
margin-bottom: 12px;
padding: 14px;
overflow: hidden;
box-sizing: border-box;
border: 1px solid #e9eff7;
border-radius: 12px;
background: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
}
.difficult-help-list-skeleton__card .van-skeleton {
padding: 0;
}
.difficult-help-list-skeleton__card .van-skeleton__title {
width: 34%;
height: 17px;
margin-bottom: 13px;
}
.difficult-help-list-skeleton__card .van-skeleton__row {
height: 12px;
margin-top: 11px;
}
.difficult-help-list-skeleton__card .van-skeleton__row:nth-child(2n) {
width: 88%;
}
.difficult-help-list-skeleton__card .van-skeleton__row:last-child {
width: 48%;
margin-top: 18px;
}
.difficult-help-approval-page .table-list-container {
margin-top: 0;
padding-bottom: 2px;
}
.difficult-help-approval-page .table-list-container .table-list-item {
margin-bottom: 12px;
padding: 14px;
border: 1px solid #e9eff7;
border-radius: 12px;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
}
.difficult-help-approval-card-header {
display: flex;
margin-bottom: 8px;
align-items: flex-start;
justify-content: space-between;
}
.difficult-help-approval-card-heading {
min-width: 0;
padding-right: 10px;
flex: 1;
}
.difficult-help-approval-card-title {
overflow: hidden;
color: #253247;
font-size: 15px;
font-weight: 600;
line-height: 22px;
text-overflow: ellipsis;
white-space: nowrap;
}
.difficult-help-approval-card-code {
margin-top: 1px;
color: #9ba6b6;
font-size: 12px;
line-height: 18px;
}
.difficult-help-approval-card-status {
display: inline-flex;
flex: none;
}
.difficult-help-approval-card-status .van-tag {
min-height: 24px;
padding: 3px 9px;
border: 0;
border-radius: 12px;
box-sizing: border-box;
font-size: 12px;
line-height: 18px;
}
.difficult-help-approval-page .table-list-container .table-list-item .item-actions {
margin-top: 10px;
padding-top: 10px;
border-top-color: #edf1f6;
}
.difficult-help-approval-page .table-list-container .table-list-item .action-btn {
min-height: 28px;
padding: 4px 11px;
border-radius: 14px;
color: #1989fa;
background: #eaf4ff;
font-size: 12px;
line-height: 18px;
}
.difficult-help-approval-page .table-list-container .table-list-item .action-btn.delete {
color: #f04444;
background: #fff0f0;
}
.difficult-help-approval-page .table-list-container .table-list-item .action-btn .van-icon {
margin-right: 5px;
font-size: 13px;
}
.difficult-help-approval-page .empty-state {
padding: 0;
}
.difficult-help-approval-empty {
display: flex;
min-height: 280px;
padding: 46px 18px 36px;
align-items: center;
justify-content: center;
box-sizing: border-box;
flex-direction: column;
text-align: center;
}
.difficult-help-approval-empty img {
display: block;
width: 86%;
max-width: 290px;
height: auto;
object-fit: contain;
}
.difficult-help-approval-empty__title {
margin-top: 14px;
color: #50627a;
font-size: 15px;
font-weight: 600;
line-height: 22px;
}
.difficult-help-approval-empty__hint {
margin-top: 5px;
color: #9aa7b8;
font-size: 12px;
line-height: 18px;
}
@@ -0,0 +1,353 @@
.difficult-help-full-popup {
--color-primary: #1989fa;
width: 100%;
height: 100%;
overflow: hidden;
background: #f3f7fc;
}
.difficult-help-full-popup__page {
display: flex;
height: 100%;
padding-top: 46px;
overflow: hidden;
box-sizing: border-box;
flex-direction: column;
}
.difficult-help-full-popup__page > .van-nav-bar {
z-index: 20;
background: #fff;
}
.difficult-help-full-popup__page > .van-nav-bar .van-icon,
.difficult-help-full-popup__page > .van-nav-bar .van-nav-bar__text {
color: #1989fa;
}
.difficult-help-full-popup__scroll {
min-height: 0;
overflow-y: auto;
flex: 1;
-webkit-overflow-scrolling: touch;
}
.difficult-help-info-main {
padding: 12px 15px calc(18px + env(safe-area-inset-bottom));
}
.difficult-help-info-card {
margin-bottom: 11px;
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background: #fff;
box-shadow: 0 3px 11px rgba(51, 87, 126, .08);
}
.difficult-help-info-card__title {
position: relative;
margin: 0;
padding: 12px 14px 8px 21px;
color: #354255;
font-size: 15px;
font-weight: 600;
line-height: 22px;
}
.difficult-help-info-card__title::before {
position: absolute;
top: 15px;
bottom: 11px;
left: 12px;
width: 2px;
border-radius: 2px;
background: #1989fa;
content: "";
}
.difficult-help-audit-card__required {
margin-left: 4px;
color: #ee0a24;
font-size: 14px;
}
.difficult-help-audit-form {
padding-bottom: 4px;
}
.difficult-help-info-card .van-field.difficult-help-audit-select,
.difficult-help-info-card .van-field.difficult-help-audit-money {
min-height: 48px;
padding: 11px 14px;
}
.difficult-help-info-card .van-field.difficult-help-audit-select .van-field__label,
.difficult-help-info-card .van-field.difficult-help-audit-money .van-field__label {
width: 88px;
color: #596779;
font-size: 12px;
}
.difficult-help-info-card .van-field.difficult-help-audit-select .van-field__control,
.difficult-help-info-card .van-field.difficult-help-audit-money .van-field__control {
color: #253247;
font-size: 13px;
}
.difficult-help-info-card .van-field.difficult-help-audit-textarea {
width: calc(100% - 28px);
margin: 0 14px 14px;
padding: 12px;
box-sizing: border-box;
border: 1px solid #c9d5e5;
border-radius: 10px;
background: #f7f9fd;
}
.difficult-help-info-card .van-field.difficult-help-audit-textarea .van-field__control {
min-height: 132px;
color: #253247;
font-size: 13px;
line-height: 22px;
}
.difficult-help-info-card .van-field.difficult-help-audit-textarea .van-field__control::placeholder,
.difficult-help-info-card .van-field.difficult-help-audit-select .van-field__control::placeholder,
.difficult-help-info-card .van-field.difficult-help-audit-money .van-field__control::placeholder {
color: #9aa7b8;
}
.difficult-help-info-card .van-field.difficult-help-audit-textarea .van-field__word-limit {
color: #718096;
}
.difficult-help-info-card .van-field.difficult-help-audit-textarea::after {
display: none;
}
.difficult-help-popup-actions {
display: flex;
padding: 10px 0 calc(12px + env(safe-area-inset-bottom));
justify-content: space-between;
column-gap: 10px;
background: transparent;
}
.difficult-help-popup-actions .van-button {
min-width: 0;
border-radius: 14px;
}
.difficult-help-popup-actions .difficult-help-action-primary {
border-color: #1989fa;
background: #1989fa;
}
.difficult-help-popup-actions .difficult-help-action-danger {
border-color: #ff4d4f;
background: #ff4d4f;
}
.difficult-help-info-card .van-cell-group {
margin: 0;
background: transparent;
}
.difficult-help-info-card .van-cell {
min-height: 44px;
padding: 10px 14px;
background: transparent;
}
.difficult-help-info-card .van-cell:not(:last-child)::after {
right: 14px;
left: 14px;
border-color: #eef2f7;
}
.difficult-help-info-card .van-cell__title {
width: 112px;
color: #596779;
font-size: 12px;
line-height: 24px;
flex: 0 0 112px;
}
.difficult-help-info-card .van-cell__value {
min-width: 0;
color: #4f5d70;
font-size: 12px;
line-height: 24px;
text-align: left;
overflow-wrap: anywhere;
}
.difficult-help-info-long-cell {
display: block;
}
.difficult-help-info-long-cell .van-cell__title {
width: auto;
max-width: 100%;
}
.difficult-help-info-long-cell .van-cell__value {
margin-top: 7px;
}
.difficult-help-info-pre-line {
white-space: pre-line;
overflow-wrap: anywhere;
}
.difficult-help-family-list {
padding: 0 14px 14px;
}
.difficult-help-family-item {
overflow: hidden;
border: 1px solid #e5edf7;
border-radius: 10px;
background: #fff;
box-shadow: 0 2px 8px rgba(51, 87, 126, .06);
}
.difficult-help-family-item + .difficult-help-family-item {
margin-top: 10px;
}
.difficult-help-family-header {
display: flex;
min-height: 44px;
padding: 9px 11px;
align-items: center;
box-sizing: border-box;
background: linear-gradient(90deg, #f0f6ff 0%, #f8fbff 100%);
}
.difficult-help-family-index {
display: inline-flex;
width: 24px;
height: 24px;
margin-right: 8px;
align-items: center;
justify-content: center;
flex: 0 0 24px;
border-radius: 50%;
color: #fff;
background: #1989fa;
font-size: 12px;
font-weight: 600;
}
.difficult-help-family-name {
min-width: 0;
overflow: hidden;
color: #2f3d51;
font-size: 14px;
font-weight: 600;
line-height: 22px;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
.difficult-help-family-relation {
max-width: 92px;
margin-left: 8px;
padding: 2px 8px;
overflow: hidden;
border-radius: 10px;
color: #1989fa;
background: #e7f2ff;
font-size: 11px;
line-height: 18px;
text-overflow: ellipsis;
white-space: nowrap;
flex: none;
}
.difficult-help-family-summary {
display: grid;
margin: 0 11px;
padding: 11px 0;
grid-template-columns: repeat(2, minmax(0, 1fr));
border-bottom: 1px solid #eef2f7;
}
.difficult-help-family-summary__item {
min-width: 0;
padding: 0 8px;
}
.difficult-help-family-summary__item:first-child {
padding-left: 0;
border-right: 1px solid #eef2f7;
}
.difficult-help-family-summary__item span,
.difficult-help-family-summary__item strong,
.difficult-help-family-detail__row span,
.difficult-help-family-detail__row strong {
display: block;
overflow-wrap: anywhere;
}
.difficult-help-family-summary__item span,
.difficult-help-family-detail__row span {
color: #909bac;
font-size: 11px;
line-height: 17px;
}
.difficult-help-family-summary__item strong {
margin-top: 3px;
color: #435167;
font-size: 13px;
font-weight: 500;
line-height: 20px;
}
.difficult-help-family-detail {
padding: 4px 11px 7px;
}
.difficult-help-family-detail__row {
display: grid;
padding: 7px 0;
grid-template-columns: 68px minmax(0, 1fr);
column-gap: 8px;
}
.difficult-help-family-detail__row strong {
color: #536176;
font-size: 12px;
font-weight: 400;
line-height: 18px;
}
.difficult-help-info-card .van-empty {
padding: 12px 0 20px;
}
.difficult-help-info-promise {
color: #4f5d70;
font-size: 12px;
line-height: 21px;
}
.difficult-help-info-commitment {
display: flex;
margin-top: 8px;
align-items: flex-end;
color: #8995a6;
font-size: 11px;
line-height: 18px;
flex-direction: column;
}
.difficult-help-signature-image {
display: block;
max-width: 100%;
}
@@ -0,0 +1,524 @@
:root {
--member-primary: #1989fa;
--member-page-bg: #f3f7fd;
--member-text: #263548;
--member-muted: #8c98aa;
--member-border: #e8eef6;
--color-primary: #1989fa;
}
body {
background: var(--member-page-bg);
}
.member-h5-page {
min-height: 100vh;
padding-bottom: calc(24px + env(safe-area-inset-bottom));
box-sizing: border-box;
color: var(--member-text);
background: var(--member-page-bg);
}
.member-h5-page.has-fixed-actions {
padding-bottom: calc(78px + env(safe-area-inset-bottom));
}
.member-h5-page .van-nav-bar {
background: #fff;
}
.member-h5-page .van-nav-bar__title {
color: #1f2937;
font-size: 16px;
font-weight: 600;
}
.member-h5-page .van-nav-bar .van-icon,
.member-h5-page .van-nav-bar__text {
color: var(--member-primary);
}
.member-hero {
position: relative;
height: 116px;
padding: 18px 18px 0;
overflow: hidden;
box-sizing: border-box;
background-color: #edf5ff;
background-image: url("/assets/mobile/img/home/v4/hero-campus.png");
background-position: 66% 46%;
background-size: cover;
}
.member-hero::after {
position: absolute;
inset: 0;
background: rgba(242, 247, 255, .76);
content: "";
}
.member-hero__content {
position: relative;
z-index: 1;
}
.member-hero__title {
color: var(--member-primary);
font-size: 23px;
font-weight: 700;
line-height: 30px;
letter-spacing: 1px;
}
.member-hero__subtitle {
margin-top: 2px;
color: #718096;
font-size: 11px;
line-height: 18px;
}
.member-page-main {
position: relative;
z-index: 2;
margin-top: -34px;
padding: 0 12px;
}
.member-toolbar {
padding: 10px;
border: 1px solid var(--member-border);
border-radius: 12px;
background: rgba(255, 255, 255, .98);
box-shadow: 0 5px 16px rgba(43, 73, 112, .09);
}
.member-toolbar .van-search {
padding: 0;
background: transparent;
}
.member-toolbar .van-search__content {
height: 42px;
align-items: center;
border-radius: 10px;
background: #f5f8fc;
}
.member-toolbar .van-field__control {
color: var(--member-text);
font-size: 14px;
}
.member-toolbar .van-search__action {
padding: 0 4px 0 12px;
color: var(--member-primary);
font-size: 14px;
}
.member-toolbar .van-dropdown-menu {
margin-top: 8px;
/* Vant 下拉选项弹层位于菜单内部,吸顶状态下必须允许弹层超出筛选卡片显示。 */
overflow: visible;
border-top: 1px solid #edf1f6;
}
.member-toolbar .van-dropdown-menu__bar {
height: 42px;
background: transparent;
}
.member-toolbar .van-dropdown-menu__title {
color: #66758a;
font-size: 13px;
}
.member-list-section {
margin-top: 10px;
}
.member-list-section .table-list-container {
margin-top: 0;
}
.member-list-section .table-list-item {
border-color: var(--member-border) !important;
border-radius: 12px !important;
box-shadow: 0 7px 20px rgba(55, 85, 126, .08) !important;
}
.member-list-section .item-title {
color: #263548 !important;
font-size: 15px !important;
}
.member-list-section .action-btn {
color: var(--member-primary) !important;
background: #eaf4ff !important;
}
.member-list-section .action-btn.delete {
color: #e95757 !important;
background: #fff0f0 !important;
}
.member-empty {
display: flex;
min-height: 280px;
padding: 20px 18px 30px;
align-items: center;
justify-content: flex-start;
flex-direction: column;
box-sizing: border-box;
text-align: center;
}
.member-empty img {
display: block;
width: 84%;
max-width: 290px;
height: auto;
object-fit: contain;
}
.member-empty__title {
margin-top: -8px;
color: #50627a;
font-size: 15px;
line-height: 22px;
font-weight: 500;
}
.member-empty__hint {
margin-top: 6px;
color: #9aa7b8;
font-size: 12px;
line-height: 18px;
}
.member-list-skeleton {
padding-top: 10px;
}
.member-list-skeleton__card {
min-height: 154px;
margin-bottom: 10px;
padding: 14px;
border-radius: 12px;
background: #fff;
box-sizing: border-box;
box-shadow: 0 7px 20px rgba(55, 85, 126, .08);
}
.member-list-skeleton__card .van-skeleton {
padding: 0;
}
.member-list-skeleton__card .van-skeleton__title {
height: 17px;
}
.member-list-skeleton__card .van-skeleton__row {
height: 12px;
margin-top: 13px;
}
/* 页面首次进入时统一遮住尚未完成初始化的横幅、筛选栏和列表,避免返回页面时出现空筛选项。 */
.member-page-skeleton {
min-height: calc(100vh - 46px);
padding: 12px;
box-sizing: border-box;
background: var(--member-page-bg);
}
.member-page-skeleton__hero {
height: 82px;
margin-bottom: 10px;
padding: 18px;
border-radius: 12px;
background: linear-gradient(135deg, #e4f1ff 0%, #f2f7ff 100%);
box-sizing: border-box;
}
.member-page-skeleton__toolbar {
min-height: 96px;
margin-bottom: 10px;
padding: 14px;
border-radius: 12px;
background: #fff;
box-sizing: border-box;
box-shadow: 0 5px 16px rgba(43, 73, 112, .07);
}
.member-page-skeleton__toolbar .van-skeleton,
.member-page-skeleton__hero .van-skeleton {
padding: 0;
}
.member-page-skeleton__cards .member-list-skeleton {
padding-top: 0;
}
.member-count-chip {
display: inline-flex;
height: 28px;
margin: 10px 0 0;
padding: 0 11px;
align-items: center;
border-radius: 14px;
color: #397de0;
background: #edf5ff;
font-size: 12px;
}
.member-form-intro {
display: flex;
min-height: 58px;
margin-bottom: 10px;
padding: 11px 12px;
align-items: flex-start;
box-sizing: border-box;
border: 1px solid #dfeafb;
border-radius: 10px;
background: rgba(246, 250, 255, .98);
box-shadow: 0 3px 10px rgba(53, 98, 154, .08);
}
.member-form-intro .van-icon {
margin: 1px 9px 0 0;
color: var(--member-primary);
font-size: 19px;
}
.member-form-intro__title {
color: var(--member-primary);
font-size: 12px;
line-height: 18px;
font-weight: 600;
}
.member-form-intro__text {
margin-top: 2px;
color: #8a97a8;
font-size: 10px;
line-height: 17px;
}
.member-application-form.form-container {
padding-bottom: 0;
background: transparent;
}
.member-application-form .form-section {
margin-bottom: 11px;
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background: #fff;
box-shadow: 0 3px 11px rgba(51, 87, 126, .08);
}
.member-form-section__title {
position: relative;
margin: 0;
padding: 12px 14px 8px 21px;
color: #354255;
background: #fff;
font-size: 13px;
line-height: 20px;
font-weight: 600;
}
.member-form-section__title::before {
position: absolute;
top: 15px;
bottom: 11px;
left: 12px;
width: 2px;
border-radius: 2px;
background: var(--member-primary);
content: "";
}
.member-application-form .form-section > .van-cell-group {
margin: 0;
background: transparent;
}
.member-application-form .van-cell {
min-height: 44px;
padding: 10px 14px;
background: #fff;
}
.member-application-form .van-field__label {
width: 84px;
color: #596779;
font-size: 12px;
line-height: 24px;
}
.member-application-form .van-field__control,
.member-application-form .van-field__value {
color: #4f5d70;
font-size: 12px;
line-height: 24px;
}
.member-form-actions,
.member-approval-actions {
position: fixed;
right: 0;
bottom: 0;
left: 0;
z-index: 100;
display: flex;
gap: 9px;
padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
border-top: 1px solid #edf1f6;
background: rgba(255, 255, 255, .98);
box-sizing: border-box;
box-shadow: 0 -4px 14px rgba(50, 78, 112, .06);
}
.member-form-actions .van-button,
.member-approval-actions .van-button {
height: 42px;
margin: 0;
border-radius: 14px;
font-size: 14px;
}
.member-form-actions .member-save-button {
color: var(--member-primary);
border-color: var(--member-primary);
background: #fff;
}
.member-form-actions .member-submit-button,
.member-approval-actions .member-agree-button {
color: #fff;
border-color: var(--member-primary);
background: var(--member-primary);
}
.member-approval-actions .member-return-button {
color: #fff;
border-color: #e6a23c;
background: #e6a23c;
}
.member-form-skeleton {
padding: 0 12px 24px;
}
.member-form-skeleton__card {
min-height: 200px;
margin-bottom: 10px;
padding: 16px 14px;
border-radius: 12px;
background: #fff;
box-sizing: border-box;
}
.member-form-skeleton__card .van-skeleton {
padding: 0;
}
.member-full-popup {
width: 100%;
height: 100%;
overflow: hidden;
background: var(--member-page-bg);
}
.member-full-popup__page {
display: flex;
height: 100%;
padding-top: 46px;
overflow: hidden;
box-sizing: border-box;
flex-direction: column;
}
.member-full-popup__page > .van-nav-bar {
z-index: 20;
background: #fff;
}
.member-full-popup__page > .van-nav-bar .van-nav-bar__title {
color: #172033;
font-size: 16px;
font-weight: 600;
}
.member-full-popup__page > .van-nav-bar .van-icon,
.member-full-popup__page > .van-nav-bar .van-nav-bar__text {
color: var(--member-primary);
}
.member-info-scroll {
min-height: 0;
padding: 12px 12px calc(82px + env(safe-area-inset-bottom));
overflow-y: auto;
flex: 1;
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
}
.member-detail-card {
margin-bottom: 11px;
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background: #fff;
box-shadow: 0 3px 11px rgba(51, 87, 126, .08);
}
.member-detail-card__title {
position: relative;
padding: 12px 14px 8px 21px;
color: #354255;
font-size: 13px;
line-height: 20px;
font-weight: 600;
}
.member-detail-card__title::before {
position: absolute;
top: 15px;
bottom: 11px;
left: 12px;
width: 2px;
border-radius: 2px;
background: var(--member-primary);
content: "";
}
.member-detail-card .van-cell {
padding: 10px 14px;
color: #4f5d70;
font-size: 12px;
}
.member-detail-card .van-cell__title {
color: #7b899b;
}
@media (min-width: 600px) {
.member-hero__content,
.member-page-main,
.member-form-skeleton {
max-width: 520px;
margin-right: auto;
margin-left: auto;
box-sizing: border-box;
}
.member-form-actions,
.member-approval-actions {
right: 50%;
left: auto;
width: 520px;
transform: translateX(50%);
}
}
File diff suppressed because it is too large Load Diff
+58
View File
@@ -0,0 +1,58 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const root = path.resolve(__dirname, '../../..');
const Vue = require(path.join(root, 'src/main/resources/static/assets/platform/plugins/vue/vue.js'));
const roots = ['src/main/resources/views/platform/zhghh5', 'src/main/resources/views/layouts', 'src/main/resources/static/components'];
// 只检查模板事件,排除注释;methods 中绑定了实例的 this 不属于此回归。
const collectEvents = () => {
const records = [];
const walk = directory => {
for (const entry of fs.readdirSync(directory, {withFileTypes: true})) {
const file = path.join(directory, entry.name);
if (entry.isDirectory()) { walk(file); continue; }
if (!/\.(html|js|vue)$/.test(file)) continue;
const source = new TextDecoder('utf-8', {fatal: true}).decode(fs.readFileSync(file));
const active = source.replace(/<!--[\s\S]*?-->/g, text => text.replace(/[^\n\r]/g, ' '));
for (const match of active.matchAll(/(?:@|v-on:)([\w.:-]+)\s*=\s*(["'])([\s\S]*?)\2/g)) {
records.push({file: path.relative(root, file).replaceAll('\\', '/'), line: source.slice(0, match.index).split('\n').length, attribute: match[0], event: match[1], expression: match[3], start: match.index});
}
}
};
roots.forEach(directory => walk(path.join(root, directory)));
return records;
};
const run = () => {
const records = collectEvents();
const unsafe = records.filter(record => /\$set\(this\s*,/.test(record.expression) && !/=>/.test(record.expression));
assert.deepEqual(unsafe.map(record => record.file + ':' + record.line), [], '内联事件不能把调用时的 this 当作页面实例');
let checked = 0;
for (const record of records.filter(record => /\$set\(\$data\s*,/.test(record.expression))) {
const setters = [...record.expression.matchAll(/\$set\(\$data\s*,\s*['"]([^'"]+)['"]\s*,\s*(true|false|0|row)\s*\)/g)];
assert(setters.length > 0, record.attribute);
const row = {id: 'test-row'};
const data = {row, item: {id: 'test-address'}};
for (const setter of setters) data[setter[1]] = setter[2] === 'true' ? false : setter[2] === 'false' ? true : null;
const instance = new Vue({data: () => data, methods: {addressClick: () => {}}});
const compiled = Vue.compile('<test-event ' + record.attribute + '></test-event>');
instance.$options.render = compiled.render;
instance.$options.staticRenderFns = compiled.staticRenderFns;
const listener = instance._render().data.on[record.event];
// 模拟 Vue 组件事件的无实例调用,防止测试人为绑定 this 掩盖原缺陷。
listener.apply(null, []);
for (const setter of setters) {
const expected = setter[2] === 'row' ? row : JSON.parse(setter[2]);
assert.equal(instance[setter[1]], expected, record.file + ':' + record.line);
}
instance.$destroy();
checked++;
}
const activity = fs.readFileSync(path.join(root, 'src/main/resources/views/platform/zhghh5/activity/trainSignUp/apply/index.html'), 'utf8');
assert.match(activity, /title="活动介绍"[^>]*@click-left="historyBack"/, '活动介绍返回必须使用统一历史栈');
console.log(JSON.stringify({scannedEvents: records.length, checkedDataEvents: checked, unsafeInlineSetters: unsafe.length, activityBack: 'passed'}));
};
module.exports = {collectEvents};
if (require.main === module) run();