This commit is contained in:
2026-04-01 18:53:08 +08:00
parent dc71dc22d8
commit d799d11f69
34 changed files with 5509 additions and 0 deletions
@@ -0,0 +1,887 @@
new Vue({
el: '#app',
store,
data() {
return {
siteId: GetQueryString('siteId'),
row: {},
siteLoaded: false,
clubOptions: [],
timeLimitConfig: {
filterHolidays: false,
holidayList: [],
notApplyTimeList: [],
},
formData: {
reserveType: 'union',
applyUnionId: '',
applyUnionName: '',
clubId: '',
clubName: '',
applyMobile: '',
reserveStartTime: '',
reserveEndTime: '',
joinCount: 1,
applyCause: '',
},
showReserveTypePicker: false,
showClubPicker: false,
showTimePicker: false,
timePickerField: '',
timePickerColumns: [],
timePickerParts: {
date: '',
hour: null,
minute: null,
},
timePickerSyncing: false,
pickerFilterCache: {},
}
},
computed: {
reserveTypeColumns() {
return ['分工会预约', '协会预约']
},
reserveTypeText() {
if (this.formData.reserveType === 'club') {
return '协会预约'
}
if (this.formData.reserveType === 'union') {
return '分工会预约'
}
return ''
},
clubColumns() {
return this.clubOptions.map(item => item.clubName)
},
timePickerTitle() {
return this.timePickerField === 'reserveEndTime' ? '选择预约结束时间' : '选择预约开始时间'
},
},
methods: {
historyBack,
clearTimePickerCache() {
this.pickerFilterCache = {}
},
buildDefaultFormData() {
const user = this.$store.state.user || {}
return {
siteId: this.row.id || '',
applyUserId: user.id || '',
applyUserName: user.username || '',
applySex: user.sex || '',
applyLoginName: user.loginname || '',
applyUnitId: user.unit ? user.unit.id : '',
applyUnitName: user.unit ? user.unit.name : '',
reserveType: 'union',
applyUnionId: user.union ? user.union.id : '',
applyUnionName: user.union ? user.union.name : '',
clubId: '',
clubName: '',
applyMobile: user.mobile || '',
reserveStartTime: '',
reserveEndTime: '',
joinCount: 1,
applyCause: '',
}
},
getNormalizedMoment(value) {
return this.$moment(value).seconds(0).milliseconds(0)
},
getFieldMinMoment(field) {
const now = this.$moment().add(1, 'minute').seconds(0).milliseconds(0)
if (field === 'reserveEndTime' && this.formData.reserveStartTime) {
const start = this.getNormalizedMoment(this.formData.reserveStartTime).add(1, 'minute')
return start.isAfter(now) ? start : now
}
return now
},
getFieldLatestMoment(field) {
if (field === 'reserveStartTime' && this.formData.reserveEndTime) {
return this.getNormalizedMoment(this.formData.reserveEndTime).subtract(1, 'minute')
}
return null
},
getFieldMaxMoment(field) {
const defaultMax = this.$moment().add(365, 'day').endOf('day').seconds(0).milliseconds(0)
const latest = this.getFieldLatestMoment(field)
if (latest && latest.isBefore(defaultMax)) {
return latest.clone().seconds(0).milliseconds(0)
}
return defaultMax
},
getContinuousEndBounds() {
if (!this.formData.reserveStartTime) {
return null
}
const minMoment = this.getFieldMinMoment('reserveEndTime')
let maxMoment = this.getFieldMaxMoment('reserveEndTime')
if (maxMoment.isBefore(minMoment)) {
return null
}
let boundary = null
const blockedDayCursor = minMoment.clone().startOf('day').add(1, 'day')
while (blockedDayCursor.isSameOrBefore(maxMoment, 'day')) {
if (this.isBlockedDay(blockedDayCursor)) {
boundary = blockedDayCursor.clone().startOf('day')
break
}
blockedDayCursor.add(1, 'day')
}
const list = Array.isArray(this.timeLimitConfig.notApplyTimeList) ? this.timeLimitConfig.notApplyTimeList : []
list.forEach(item => {
if (!item || !item.date || !item.startTime || !item.endTime) {
return
}
const dayStr = this.$moment(item.date).format('YYYY-MM-DD')
const rangeStart = this.$moment(dayStr + ' ' + this.normalizeTime(item.startTime))
const rangeEnd = this.$moment(dayStr + ' ' + this.normalizeTime(item.endTime))
if (rangeEnd.isSameOrBefore(minMoment)) {
return
}
if (rangeStart.isSameOrBefore(minMoment) && rangeEnd.isAfter(minMoment)) {
boundary = minMoment.clone()
return
}
if (rangeStart.isAfter(minMoment) && rangeStart.isSameOrBefore(maxMoment)) {
if (!boundary || rangeStart.isBefore(boundary)) {
boundary = rangeStart.clone()
}
}
})
if (boundary && boundary.isSameOrBefore(maxMoment)) {
maxMoment = boundary.clone().subtract(1, 'minute')
}
if (maxMoment.isBefore(minMoment)) {
return null
}
return {
minMoment: minMoment,
maxMoment: maxMoment,
}
},
getEffectiveFieldMaxMoment(field) {
if (field === 'reserveEndTime' && this.formData.reserveStartTime) {
const bounds = this.getContinuousEndBounds()
return bounds ? bounds.maxMoment.clone() : this.getFieldMinMoment(field).clone().subtract(1, 'minute')
}
return this.getFieldMaxMoment(field)
},
buildPickerMoment(parts) {
if (parts && parts.date) {
return this.$moment(parts.date, 'YYYY-MM-DD')
.hour(parts.hour || 0)
.minute(parts.minute || 0)
.second(0)
.millisecond(0)
}
return this.$moment({
year: parts.year,
month: parts.month - 1,
date: parts.day,
hour: parts.hour,
minute: parts.minute,
second: 0,
millisecond: 0,
})
},
extractTimePickerPartsFromMoment(momentValue) {
const pickerMoment = this.getNormalizedMoment(momentValue)
return {
date: pickerMoment.format('YYYY-MM-DD'),
hour: pickerMoment.hour(),
minute: pickerMoment.minute(),
}
},
getPickerPartValue(item, fallback) {
if (item && typeof item === 'object' && item !== null && typeof item.value !== 'undefined') {
return item.value
}
if (item === '' || item === null || typeof item === 'undefined') {
return fallback
}
return item
},
extractTimePickerParts(values) {
const fallback = this.timePickerParts || {}
return {
date: this.getPickerPartValue(values && values[0], fallback.date),
hour: Number(this.getPickerPartValue(values && values[1], fallback.hour)),
minute: Number(this.getPickerPartValue(values && values[2], fallback.minute)),
}
},
isValidCalendarMoment(momentValue, month, day) {
return momentValue.isValid() && momentValue.month() + 1 === month && momentValue.date() === day
},
isBlockedDay(momentValue) {
if (!momentValue || !momentValue.isValid()) {
return true
}
if (momentValue.day() === 0 || momentValue.day() === 6) {
return true
}
return this.timeLimitConfig.filterHolidays && this.timeLimitConfig.holidayList.includes(momentValue.format('YYYY-MM-DD'))
},
isRangeSelectable(start, end) {
if (!start || !end || !start.isValid() || !end.isValid()) {
return false
}
if (!end.isAfter(start)) {
return false
}
if (this.hasHolidayInRange(start, end)) {
return false
}
if (this.overlapsDisabledRange(start, end)) {
return false
}
return true
},
isExactTimeSelectable(momentValue, field) {
if (!momentValue || !momentValue.isValid()) {
return false
}
const candidate = momentValue.clone().seconds(0).milliseconds(0)
const minMoment = this.getFieldMinMoment(field)
if (candidate.isBefore(minMoment)) {
return false
}
const latestMoment = this.getFieldLatestMoment(field)
if (latestMoment && candidate.isAfter(latestMoment)) {
return false
}
if (field === 'reserveEndTime' && this.formData.reserveStartTime) {
const bounds = this.getContinuousEndBounds()
if (!bounds) {
return false
}
return candidate.isSameOrAfter(bounds.minMoment) && candidate.isSameOrBefore(bounds.maxMoment)
}
if (this.isDateTimeBlocked(candidate.format('YYYY-MM-DD HH:mm:ss'))) {
return false
}
if (field === 'reserveStartTime' && this.formData.reserveEndTime) {
const end = this.getNormalizedMoment(this.formData.reserveEndTime)
if (!candidate.isBefore(end)) {
return false
}
return this.isRangeSelectable(candidate, end)
}
return true
},
dayHasAvailableTime(year, month, day, field) {
const cacheKey = ['day', field, year, month, day, this.formData.reserveStartTime || '', this.formData.reserveEndTime || ''].join('|')
if (cacheKey in this.pickerFilterCache) {
return this.pickerFilterCache[cacheKey]
}
const dayMoment = this.buildPickerMoment({ year: year, month: month, day: day, hour: 0, minute: 0 })
if (!this.isValidCalendarMoment(dayMoment, month, day) || this.isBlockedDay(dayMoment)) {
this.pickerFilterCache[cacheKey] = false
return false
}
const minMoment = this.getFieldMinMoment(field)
const maxMoment = this.getEffectiveFieldMaxMoment(field)
const dayStart = dayMoment.clone().startOf('day')
const dayEnd = dayMoment.clone().endOf('day')
if (dayEnd.isBefore(minMoment) || dayStart.isAfter(maxMoment)) {
this.pickerFilterCache[cacheKey] = false
return false
}
if (field === 'reserveEndTime' && this.formData.reserveStartTime) {
this.pickerFilterCache[cacheKey] = true
return true
}
for (let hour = 0; hour < 24; hour++) {
if (this.hourHasAvailableTime(year, month, day, hour, field)) {
this.pickerFilterCache[cacheKey] = true
return true
}
}
this.pickerFilterCache[cacheKey] = false
return false
},
hourHasAvailableTime(year, month, day, hour, field) {
const cacheKey = ['hour', field, year, month, day, hour, this.formData.reserveStartTime || '', this.formData.reserveEndTime || ''].join('|')
if (cacheKey in this.pickerFilterCache) {
return this.pickerFilterCache[cacheKey]
}
const hourMoment = this.buildPickerMoment({ year: year, month: month, day: day, hour: hour, minute: 0 })
if (!this.isValidCalendarMoment(hourMoment, month, day)) {
this.pickerFilterCache[cacheKey] = false
return false
}
const minMoment = this.getFieldMinMoment(field)
const maxMoment = this.getEffectiveFieldMaxMoment(field)
const hourStart = hourMoment.clone().startOf('hour')
const hourEnd = hourMoment.clone().endOf('hour')
if (hourEnd.isBefore(minMoment) || hourStart.isAfter(maxMoment)) {
this.pickerFilterCache[cacheKey] = false
return false
}
if (field === 'reserveEndTime' && this.formData.reserveStartTime) {
this.pickerFilterCache[cacheKey] = true
return true
}
for (let minute = 0; minute < 60; minute++) {
const candidate = this.buildPickerMoment({ year: year, month: month, day: day, hour: hour, minute: minute })
if (this.isExactTimeSelectable(candidate, field)) {
this.pickerFilterCache[cacheKey] = true
return true
}
}
this.pickerFilterCache[cacheKey] = false
return false
},
findFirstSelectableDateTime(field, baseMoment) {
let startMoment = this.getNormalizedMoment(baseMoment)
const minMoment = this.getFieldMinMoment(field)
const maxMoment = this.getEffectiveFieldMaxMoment(field)
if (maxMoment.isBefore(minMoment)) {
return null
}
if (startMoment.isBefore(minMoment)) {
startMoment = minMoment.clone()
}
for (let dayOffset = 0; dayOffset <= 366; dayOffset++) {
const dayMoment = startMoment.clone().startOf('day').add(dayOffset, 'day')
if (dayMoment.isAfter(maxMoment, 'day')) {
break
}
if (this.isBlockedDay(dayMoment)) {
continue
}
const startHour = dayMoment.isSame(startMoment, 'day') ? startMoment.hour() : 0
for (let hour = startHour; hour < 24; hour++) {
const startMinute = dayMoment.isSame(startMoment, 'day') && hour === startMoment.hour() ? startMoment.minute() : 0
for (let minute = startMinute; minute < 60; minute++) {
const candidate = dayMoment.clone().hour(hour).minute(minute).second(0).millisecond(0)
if (candidate.isAfter(maxMoment)) {
return null
}
if (this.isExactTimeSelectable(candidate, field)) {
return candidate
}
}
}
}
return null
},
formatPickerNumber(value) {
return value < 10 ? '0' + value : '' + value
},
getWeekdayText(momentValue) {
const weekdayList = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
return weekdayList[momentValue.day()]
},
createPickerOption(text, value, disabled) {
return {
text: text,
value: value,
disabled: !!disabled,
}
},
findNearestEnabledIndex(options, preferredValue) {
if (!Array.isArray(options) || !options.length) {
return -1
}
const enabledIndexes = []
options.forEach((item, index) => {
if (!item.disabled) {
enabledIndexes.push(index)
}
})
if (!enabledIndexes.length) {
return -1
}
if (preferredValue === '' || preferredValue === null || typeof preferredValue === 'undefined' || Number.isNaN(preferredValue)) {
return enabledIndexes[0]
}
let targetIndex = enabledIndexes[0]
let targetDistance = Math.abs(Number(options[targetIndex].value) - Number(preferredValue))
enabledIndexes.forEach(index => {
const distance = Math.abs(Number(options[index].value) - Number(preferredValue))
if (distance < targetDistance) {
targetIndex = index
targetDistance = distance
}
})
return targetIndex
},
buildDateOptions(field) {
const minMoment = this.getFieldMinMoment(field).clone().startOf('day')
const maxMoment = this.getEffectiveFieldMaxMoment(field).clone().startOf('day')
const options = []
const cursor = minMoment.clone()
while (cursor.isSameOrBefore(maxMoment, 'day')) {
const text = cursor.format('MM-DD') + ' ' + this.getWeekdayText(cursor)
options.push(this.createPickerOption(text, cursor.format('YYYY-MM-DD'), !this.dayHasAvailableTime(cursor.year(), cursor.month() + 1, cursor.date(), field)))
cursor.add(1, 'day')
}
return options
},
buildHourOptionsByDate(field, dateValue) {
const dateMoment = this.$moment(dateValue, 'YYYY-MM-DD')
const year = dateMoment.year()
const month = dateMoment.month() + 1
const day = dateMoment.date()
const options = []
for (let hour = 0; hour < 24; hour++) {
options.push(this.createPickerOption(this.formatPickerNumber(hour) + '时', hour, !this.hourHasAvailableTime(year, month, day, hour, field)))
}
return options
},
buildMinuteOptionsByDateHour(field, dateValue, hour) {
const dateMoment = this.$moment(dateValue, 'YYYY-MM-DD')
const options = []
for (let minute = 0; minute < 60; minute++) {
const candidate = this.buildPickerMoment({
year: dateMoment.year(),
month: dateMoment.month() + 1,
day: dateMoment.date(),
hour: hour,
minute: minute,
})
options.push(this.createPickerOption(this.formatPickerNumber(minute) + '分', minute, !this.isExactTimeSelectable(candidate, field)))
}
return options
},
buildTimePickerColumns(field, preferredParts) {
const dateOptions = this.buildDateOptions(field)
const enabledDateIndex = dateOptions.findIndex(item => !item.disabled)
if (enabledDateIndex < 0) {
return null
}
let dateIndex = enabledDateIndex
if (preferredParts && preferredParts.date) {
const exactIndex = dateOptions.findIndex(item => item.value === preferredParts.date && !item.disabled)
if (exactIndex >= 0) {
dateIndex = exactIndex
}
}
const selectedDate = dateOptions[dateIndex].value
const hourOptions = this.buildHourOptionsByDate(field, selectedDate)
const hourIndex = this.findNearestEnabledIndex(hourOptions, preferredParts ? preferredParts.hour : null)
if (hourIndex < 0) {
return null
}
const selectedHour = hourOptions[hourIndex].value
const minuteOptions = this.buildMinuteOptionsByDateHour(field, selectedDate, selectedHour)
const minuteIndex = this.findNearestEnabledIndex(minuteOptions, preferredParts ? preferredParts.minute : null)
if (minuteIndex < 0) {
return null
}
const selectedMinute = minuteOptions[minuteIndex].value
return {
parts: {
date: selectedDate,
hour: selectedHour,
minute: selectedMinute,
},
indexes: [dateIndex, hourIndex, minuteIndex],
columns: [
{ values: dateOptions, defaultIndex: dateIndex },
{ values: hourOptions, defaultIndex: hourIndex },
{ values: minuteOptions, defaultIndex: minuteIndex },
],
}
},
sameTimePickerParts(left, right) {
if (!left || !right) {
return false
}
return left.date === right.date
&& left.hour === right.hour
&& left.minute === right.minute
},
syncTimePickerColumns(picker, built) {
if (!picker || !built) {
this.timePickerSyncing = false
return
}
this.timePickerSyncing = true
built.columns.forEach((column, index) => {
picker.setColumnValues(index, column.values)
})
picker.setIndexes(built.indexes)
this.timePickerParts = built.parts
setTimeout(() => {
this.timePickerSyncing = false
}, 0)
},
applyTimePickerState(built, picker, syncColumns = true) {
if (!built) {
return false
}
const currentPicker = picker || this.$refs.timePickerRef
if (syncColumns) {
this.timePickerColumns = built.columns
this.$nextTick(() => {
this.syncTimePickerColumns(currentPicker || this.$refs.timePickerRef, built)
})
return true
}
this.syncTimePickerColumns(currentPicker, built)
return true
},
async init() {
if (!this.siteId) {
this.$toast('未获取到场馆信息')
this.historyBack()
return
}
const siteRes = await this.$axios.post('/platform/siteCug/manage/info', { id: this.siteId })
if (siteRes.code !== 0 || !siteRes.data) {
this.$toast(siteRes.msg || '场馆信息加载失败')
this.historyBack()
return
}
this.row = siteRes.data
this.formData = this.buildDefaultFormData()
await this.queryTimeLimitConfig()
this.siteLoaded = true
},
async queryTimeLimitConfig() {
const fallback = {
filterHolidays: !!this.row.filterHolidays,
holidayList: [],
notApplyTimeList: Array.isArray(this.row.notApplyTimeList) ? this.row.notApplyTimeList : [],
}
try {
const res = await this.$axios.post('/platform/siteCug/apply/timeLimitConfig', {
siteId: this.row.id,
})
if (res.code === 0 && res.data) {
this.timeLimitConfig = {
filterHolidays: !!res.data.filterHolidays,
holidayList: Array.isArray(res.data.holidayList) ? res.data.holidayList : [],
notApplyTimeList: Array.isArray(res.data.notApplyTimeList) ? res.data.notApplyTimeList : [],
}
this.clearTimePickerCache()
return
}
} catch (e) {
}
this.timeLimitConfig = fallback
this.clearTimePickerCache()
},
async reserveTypeChange(value) {
const user = this.$store.state.user || {}
if (value === 'union') {
this.formData.applyUnionId = user.union ? user.union.id : ''
this.formData.applyUnionName = user.union ? user.union.name : ''
this.formData.clubId = ''
this.formData.clubName = ''
return
}
if (value === 'club') {
this.formData.applyUnionId = ''
this.formData.applyUnionName = ''
this.formData.clubId = ''
this.formData.clubName = ''
await this.loadManagedClubs()
}
},
async loadManagedClubs() {
try {
const res = await this.$axios.post('/platform/club/examine/apply/getClubsByRole')
this.clubOptions = Array.isArray(res.data) ? res.data : []
if (!this.clubOptions.length) {
this.$toast('您当前没有可预约的协会管理权限')
}
} catch (e) {
this.clubOptions = []
this.$toast('协会列表加载失败,请稍后重试')
}
},
onReserveTypeConfirm(value) {
this.showReserveTypePicker = false
const reserveType = value === '协会预约' ? 'club' : 'union'
this.$set(this.formData, 'reserveType', reserveType)
this.reserveTypeChange(reserveType)
},
async openClubPicker() {
if (this.formData.reserveType !== 'club') {
this.$toast('请先选择协会预约')
return
}
if (!this.clubOptions.length) {
await this.loadManagedClubs()
}
if (!this.clubOptions.length) {
return
}
this.showClubPicker = true
},
onClubConfirm(value, index) {
this.showClubPicker = false
const club = this.clubOptions[index]
this.formData.clubId = club ? club.id : ''
this.formData.clubName = club ? club.clubName : value
},
openTimePicker(field) {
this.timePickerField = field
this.timePickerSyncing = false
this.clearTimePickerCache()
let defaultTime = this.formData[field] ? this.getNormalizedMoment(this.formData[field]) : null
if (!defaultTime || !defaultTime.isValid()) {
defaultTime = field === 'reserveEndTime' && this.formData.reserveStartTime
? this.getNormalizedMoment(this.formData.reserveStartTime).add(1, 'hour')
: this.$moment().add(1, 'hour').startOf('hour')
}
const availableTime = this.findFirstSelectableDateTime(field, defaultTime)
if (!availableTime) {
this.$toast('当前没有可预约的时间')
return
}
const built = this.buildTimePickerColumns(field, this.extractTimePickerPartsFromMoment(availableTime))
if (!built) {
this.$toast('当前没有可预约的时间')
return
}
this.showTimePicker = true
this.applyTimePickerState(built, null, true)
},
onTimePickerChange(picker, values) {
if (!this.timePickerField || this.timePickerSyncing) {
return
}
const parts = this.extractTimePickerParts(values)
const built = this.buildTimePickerColumns(this.timePickerField, parts)
if (!built) {
return
}
if (this.sameTimePickerParts(parts, built.parts)) {
this.timePickerParts = built.parts
return
}
this.applyTimePickerState(built, picker, false)
},
onTimeConfirm(values) {
const parts = this.extractTimePickerParts(values)
const candidate = this.buildPickerMoment(parts)
if (!this.isExactTimeSelectable(candidate, this.timePickerField)) {
this.$toast('该时间不可预约,请重新选择')
const built = this.buildTimePickerColumns(this.timePickerField, parts)
this.applyTimePickerState(built, this.$refs.timePickerRef)
return
}
const formatted = candidate.format('YYYY-MM-DD HH:mm:ss')
this.timePickerSyncing = false
this.showTimePicker = false
this.$set(this.formData, this.timePickerField, formatted)
this.clearTimePickerCache()
this.timeFieldChange(this.timePickerField)
},
async normalizeReserveTypeData() {
const user = this.$store.state.user || {}
if (this.formData.reserveType === 'union') {
this.formData.applyUnionId = user.union ? user.union.id : ''
this.formData.applyUnionName = user.union ? user.union.name : ''
this.formData.clubId = ''
this.formData.clubName = ''
return true
}
if (this.formData.reserveType === 'club') {
if (!this.clubOptions.length) {
await this.loadManagedClubs()
}
const club = this.clubOptions.find(item => item.id === this.formData.clubId)
this.formData.clubName = club ? club.clubName : ''
this.formData.applyUnionId = ''
this.formData.applyUnionName = ''
return true
}
return false
},
normalizeTime(timeStr) {
if (!timeStr) {
return '00:00:00'
}
return timeStr.length === 5 ? timeStr + ':00' : timeStr
},
getDisabledRangesByDay(dayStr) {
const list = Array.isArray(this.timeLimitConfig.notApplyTimeList) ? this.timeLimitConfig.notApplyTimeList : []
return list
.filter(item => item && item.date && this.$moment(item.date).format('YYYY-MM-DD') === dayStr)
.sort((a, b) => this.normalizeTime(a.startTime).localeCompare(this.normalizeTime(b.startTime)))
},
isDateTimeBlocked(dateTimeStr) {
if (!dateTimeStr) {
return false
}
const target = this.$moment(dateTimeStr)
const dayStr = target.format('YYYY-MM-DD')
if (target.day() === 0 || target.day() === 6) {
return true
}
if (this.timeLimitConfig.filterHolidays && this.timeLimitConfig.holidayList.includes(dayStr)) {
return true
}
const disabledRanges = this.getDisabledRangesByDay(dayStr)
return disabledRanges.some(item => {
const start = this.$moment(dayStr + ' ' + this.normalizeTime(item.startTime))
const end = this.$moment(dayStr + ' ' + this.normalizeTime(item.endTime))
return target.isSameOrAfter(start) && target.isBefore(end)
})
},
hasHolidayInRange(start, end) {
const current = start.clone().startOf('day')
const endDay = end.clone().startOf('day')
while (current.isSameOrBefore(endDay)) {
if (current.day() === 0 || current.day() === 6) {
return true
}
if (this.timeLimitConfig.filterHolidays && this.timeLimitConfig.holidayList.includes(current.format('YYYY-MM-DD'))) {
return true
}
current.add(1, 'day')
}
return false
},
overlapsDisabledRange(start, end) {
const list = Array.isArray(this.timeLimitConfig.notApplyTimeList) ? this.timeLimitConfig.notApplyTimeList : []
return list.some(item => {
if (!item || !item.date || !item.startTime || !item.endTime) {
return false
}
const rangeStart = this.$moment(this.$moment(item.date).format('YYYY-MM-DD') + ' ' + this.normalizeTime(item.startTime))
const rangeEnd = this.$moment(this.$moment(item.date).format('YYYY-MM-DD') + ' ' + this.normalizeTime(item.endTime))
return start.isBefore(rangeEnd) && end.isAfter(rangeStart)
})
},
showSubmitError(message) {
return this.$dialog.alert({
title: '提示',
message: message || '提交失败',
}).catch(() => {})
},
validateTimeLimit(showMessage = true, useDialog = false) {
const reserveStartTime = this.formData.reserveStartTime
const reserveEndTime = this.formData.reserveEndTime
if (!reserveStartTime || !reserveEndTime) {
return true
}
const start = this.$moment(reserveStartTime)
const end = this.$moment(reserveEndTime)
const showError = (message) => {
if (!showMessage) {
return
}
if (useDialog) {
this.showSubmitError(message)
return
}
this.$toast(message)
}
if (this.isDateTimeBlocked(reserveStartTime) || this.isDateTimeBlocked(reserveEndTime)) {
showError('预约时间不能选择周末、节假日或禁用时间')
return false
}
if (this.hasHolidayInRange(start, end)) {
showError('预约时间范围内包含周末或节假日,请重新选择')
return false
}
if (this.overlapsDisabledRange(start, end)) {
showError('预约时间范围与禁用时间冲突,请重新选择')
return false
}
return true
},
timeFieldChange(field) {
const value = this.formData[field]
this.clearTimePickerCache()
if (!value) {
return
}
if (this.isDateTimeBlocked(value)) {
this.$toast('该时间点不可预约,请重新选择')
this.$set(this.formData, field, '')
return
}
if (field === 'reserveStartTime' && this.formData.reserveEndTime) {
const start = this.$moment(value)
const end = this.$moment(this.formData.reserveEndTime)
if (!end.isAfter(start)) {
this.$set(this.formData, 'reserveEndTime', '')
this.$toast('结束时间需晚于开始时间,请重新选择')
return
}
}
if (field === 'reserveEndTime' && this.formData.reserveStartTime) {
const start = this.$moment(this.formData.reserveStartTime)
const end = this.$moment(value)
if (!end.isAfter(start)) {
this.$toast('预约结束时间必须晚于开始时间')
this.$set(this.formData, field, '')
return
}
}
if (this.formData.reserveStartTime && this.formData.reserveEndTime && !this.validateTimeLimit()) {
this.$set(this.formData, field, '')
}
},
onSubmit() {
this.$refs.formRef.validate().then(async () => {
if (!this.formData.reserveType) {
await this.showSubmitError('请选择预约类型')
return
}
await this.normalizeReserveTypeData()
if (this.formData.reserveType === 'union' && !this.formData.applyUnionId) {
await this.showSubmitError('当前用户未关联分工会,不能发起分工会预约')
return
}
if (this.formData.reserveType === 'club' && !this.formData.clubId) {
await this.showSubmitError('请选择您管理的协会')
return
}
if (!this.formData.reserveStartTime || !this.formData.reserveEndTime) {
await this.showSubmitError('请选择预约开始和结束时间')
return
}
const start = this.$moment(this.formData.reserveStartTime)
const end = this.$moment(this.formData.reserveEndTime)
if (!start.isAfter(this.$moment())) {
await this.showSubmitError('预约开始时间必须晚于当前时间')
return
}
if (!end.isAfter(start)) {
await this.showSubmitError('预约结束时间必须晚于开始时间')
return
}
if (!this.validateTimeLimit(true, true)) {
return
}
this.$dialog.confirm({
title: '提示',
message: '您确定要提交吗?',
}).then(() => {
const loading = this.$toast.loading({
message: '提交中...',
forbidClick: true,
overlay: true,
duration: 0,
})
this.$axios.post('/platform/siteCug/apply/submit', {
data: JSON.stringify(this.formData),
}).then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg || '提交成功')
this.$pjaxReplace('/platform/siteCug/apply/h5')
}
}).catch((err) => {
this.showSubmitError((err && err.msg) || '提交失败')
}).finally(() => {
loading.close()
})
}).catch(() => {})
}).catch(() => {})
},
},
created() {
this.init()
}
})
@@ -0,0 +1,167 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
.page-container {
padding-bottom: 84px;
background: #f7f8fa;
min-height: calc(100vh - 46px);
}
.footer-actions {
position: fixed;
left: 0;
right: 0;
bottom: 0;
display: flex;
gap: 12px;
padding: 10px 12px calc(env(safe-area-inset-bottom) + 10px);
background: #ffffff;
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.06);
}
.footer-actions .van-button {
flex: 1;
}
.disabled-time-item {
line-height: 20px;
margin-bottom: 4px;
}
/deep/ .direction-column-cell .van-cell__value {
white-space: normal;
text-align: left;
}
</style>
<div id="app" v-cloak>
<van-nav-bar title="场馆申请" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
<div class="page-container" v-if="siteLoaded">
<van-cell-group title="场馆信息">
<van-cell title="场馆名称" :value="row.name || '-' "></van-cell>
<van-cell title="场地地址" class="direction-column-cell">
<template #default>
{{ row.address || '-' }}
</template>
</van-cell>
<van-cell title="联系人" :value="row.contactName || '-' "></van-cell>
<van-cell title="联系电话" :value="row.contactPhone || '-' "></van-cell>
<van-cell title="场地类型" :value="row.typeName || '-' "></van-cell>
</van-cell-group>
<van-cell-group title="预约须知">
<van-cell title="可预约日期" value="仅限工作日"></van-cell>
<van-cell title="节假日限制"
:value="timeLimitConfig.filterHolidays ? '节假日不可预约' : '不排除节假日'"></van-cell>
<van-cell title="禁用时段" class="direction-column-cell">
<template #default>
<div v-if="timeLimitConfig.notApplyTimeList.length">
<div
class="disabled-time-item"
v-for="(item, index) in timeLimitConfig.notApplyTimeList"
:key="item.date + item.startTime + item.endTime + index">
{{ item.date }} {{ item.startTime }} - {{ item.endTime }}
</div>
</div>
<div v-else>暂无禁用时段</div>
</template>
</van-cell>
</van-cell-group>
<van-form ref="formRef" class="form-container" :show-error-message="false">
<van-cell-group title="申请信息">
<van-field v-model="formData.applyUserName" label="预约人" name="applyUserName" readonly
:rules="[{ required: true, message: '请确认预约人' }]"></van-field>
<van-field v-model="formData.applyLoginName" label="工号" name="applyLoginName" readonly
:rules="[{ required: true, message: '请确认工号' }]"></van-field>
<van-field v-model="formData.applyUnitName" label="所属单位" name="applyUnitName" readonly
:rules="[{ required: true, message: '请确认所属单位' }]"></van-field>
<van-field
:value="reserveTypeText"
label="预约类型"
name="reserveType"
readonly
clickable
is-link
required
placeholder="请选择预约类型"
@click="showReserveTypePicker = true"
:rules="[{ required: true, message: '请选择预约类型' }]">
</van-field>
<van-field
v-if="formData.reserveType === 'union'"
v-model="formData.applyUnionName"
label="分工会"
name="applyUnionName"
readonly
placeholder="自动读取当前登录人的分工会">
</van-field>
<van-field
v-if="formData.reserveType === 'club'"
v-model="formData.clubName"
label="协会"
name="clubName"
readonly
clickable
is-link
required
placeholder="请选择您管理的协会"
@click="openClubPicker"
:rules="[{ required: true, message: '请选择您管理的协会' }]">
</van-field>
<van-field v-model="formData.applyMobile" label="联系电话" name="applyMobile" type="tel" maxlength="11"
required placeholder="请输入联系电话"
:rules="[{ required: true, message: '请输入联系电话' }]"></van-field>
<van-field v-model="formData.reserveStartTime" label="开始时间" name="reserveStartTime" readonly
clickable is-link required placeholder="请选择预约开始时间"
@click="openTimePicker('reserveStartTime')"
:rules="[{ required: true, message: '请选择预约开始时间' }]"></van-field>
<van-field v-model="formData.reserveEndTime" label="结束时间" name="reserveEndTime" readonly clickable
is-link required placeholder="请选择预约结束时间" @click="openTimePicker('reserveEndTime')"
:rules="[{ required: true, message: '请选择预约结束时间' }]"></van-field>
<van-field v-model="formData.applyCause" label="预约事由" name="applyCause" required rows="4" autosize
type="textarea" maxlength="1000" show-word-limit placeholder="请输入预约事由"
:rules="[{ required: true, message: '请输入预约事由' }]"></van-field>
</van-cell-group>
</van-form>
</div>
<div class="footer-actions" v-if="siteLoaded">
<van-button plain round type="info" @click="historyBack">取消</van-button>
<van-button round type="primary" color="#246fb4" @click="onSubmit">提交预约</van-button>
</div>
<van-popup v-model="showReserveTypePicker" position="bottom" round>
<van-picker show-toolbar :columns="reserveTypeColumns" @confirm="onReserveTypeConfirm"
@cancel="showReserveTypePicker = false"></van-picker>
</van-popup>
<van-popup v-model="showClubPicker" position="bottom" round>
<van-picker show-toolbar :columns="clubColumns" @confirm="onClubConfirm"
@cancel="showClubPicker = false"></van-picker>
</van-popup>
<van-popup v-model="showTimePicker" position="bottom" round>
<van-picker
ref="timePickerRef"
show-toolbar
value-key="text"
:title="timePickerTitle"
:columns="timePickerColumns"
@change="onTimePickerChange"
@confirm="onTimeConfirm"
@cancel="timePickerSyncing = false; showTimePicker = false">
</van-picker>
</van-popup>
</div>
<script nonce="${cspNonce!}">
<!--#include('apply.js'){}#-->
</script>
<!--#
}
#-->
@@ -0,0 +1,106 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
</style>
<div id="app">
<van-nav-bar title="场馆预约" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
<van-sticky offset-top="46px">
<van-search
v-model="pageForm.searchKeyword"
:show-action="false"
:reverse-color="false"
input-align="left"
placeholder="请输入名称或地址搜索"
@search="doSearch"
></van-search>
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
<van-dropdown-item v-model="pageForm.type" :options="typeOptions" :multiple="false" @change="doSearch"></van-dropdown-item>
</van-dropdown-menu>
</van-sticky>
<table-list api="/platform/siteCug/apply/pageData" :page_form.sync="pageForm" ref="tableListRef" title="name" @ready="onReady">
<template v-slot="{ row }">
<table-column label="场地地址">{{ row.address }}</table-column>
<table-column label="联系人">{{ row.contactName }}</table-column>
<table-column label="联系方式">{{ row.contactPhone }}</table-column>
<table-column label="场地类型">{{ row.typeName }}</table-column>
</template>
<template #actions="{ row }">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<span>查看</span>
</div>
<div class="action-btn" @click="onApply(row)">
<i class="fa fa-edit"></i>
<span>预约</span>
</div>
</template>
</table-list>
<info ref="infoRef"></info>
</div>
<script nonce="${cspNonce!}">
<!--#include('../common/siteInfo.js'){}#-->
new Vue({
el: '#app',
store,
components: {
info: siteInfo,
},
data() {
return {
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: '',
type: null,
},
typeOptions: [],
}
},
methods: {
historyBack,
onView(row) {
this.$refs.infoRef.onOpen(row)
},
onApply(row) {
this.$pjaxReplace('/platform/siteCug/apply/form/h5?siteId=' + row.id)
},
async onReady() {
const typeList = await this.querySiteType()
this.typeOptions = [
{
text: '全部类型',
value: null,
}
].concat(typeList.map((item) => ({ text: item.name, value: item.id })))
if (this.typeOptions.length > 0) {
this.pageForm.type = this.typeOptions[0].value
this.doSearch()
}
},
async querySiteType() {
const res = await this.$axios.post('/platform/siteCug/function/type/queryFunctionType')
return Array.isArray(res.data) ? res.data : []
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
},
},
})
</script>
<!--#
}
#-->
@@ -0,0 +1,80 @@
const siteCugApplyInfoH5 = {
template:
/*language=HTML*/
`
<van-action-sheet v-model="visible" :title="sheetTitle">
<div class="detail-container">
<van-cell-group title="基本信息">
<van-cell title="预约场地">{{ viewData.siteName }}</van-cell>
<van-cell title="预约人">{{ viewData.applyUserName }}</van-cell>
<van-cell title="预约人工号">{{ viewData.applyLoginName }}</van-cell>
<van-cell title="所属单位">{{ viewData.applyUnitName }}</van-cell>
<van-cell title="预约类型">{{ reserveTypeLabel }}</van-cell>
<van-cell title="预约主体">{{ reserveTargetName }}</van-cell>
<van-cell title="开始时间">{{ viewData.reserveStartTime }}</van-cell>
<van-cell title="结束时间">{{ viewData.reserveEndTime }}</van-cell>
<van-cell title="联系电话">{{ viewData.applyMobile }}</van-cell>
<van-cell title="预约事由" class="direction-column-cell">{{ viewData.applyCause }}</van-cell>
</van-cell-group>
<template v-for="task in doneTasks">
<van-cell-group :title="task.displayName" v-if="task.ext.isFirstTaskNode" :key="task.id + '-first'">
<van-cell title="申请用户">{{ task.ext.initiatorName }}({{task.ext.initiatorAccount}})</van-cell>
<van-cell title="申请时间">{{ task.finishTime }}</van-cell>
<van-cell title="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</van-cell>
</van-cell-group>
<van-cell-group :title="task.displayName" v-else :key="task.id + '-done'">
<van-cell title="办理用户">{{ task.taskFormData.userName }}({{task.taskFormData.loginName}})</van-cell>
<van-cell title="办理时间">{{ task.finishTime }}</van-cell>
<van-cell title="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</van-cell>
<van-cell title="办理意见" class="direction-column-cell">
<div v-html="task.taskFormData.opinion || task.taskFormData.tf_opinion || '-' "></div>
</van-cell>
</van-cell-group>
</template>
</div>
<slot></slot>
</van-action-sheet>
`,
dicts: ['PROCESS_TASK_SUBMIT_TYPE'],
data() {
return {
visible: false,
viewData: {},
doneTasks: [],
row: null,
}
},
computed: {
reserveTypeLabel() {
return this.viewData.reserveType === 'club' ? '协会预约' : '分工会预约'
},
reserveTargetName() {
return this.viewData.reserveType === 'club' ? (this.viewData.clubName || '-') : (this.viewData.applyUnionName || '-')
},
sheetTitle() {
return this.visible && this.$slots.default ? '审核预约' : '预约详情'
},
},
methods: {
onOpen(row) {
this.row = row
this.visible = true
this.viewData = row
this.getDoneTasks()
},
onClose() {
this.visible = false
},
getDoneTasks() {
this.$axios.post('/flow/common/doneTasks', { bizId: this.row.id }).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data
}
})
},
},
};
@@ -0,0 +1,105 @@
const siteInfo = {
template: /*language=HTML*/ `
<div>
<van-action-sheet v-model="visible" title="场馆信息">
<div class="detail-container">
<van-cell-group title="基本信息">
<van-cell title="创建人" :value="viewData.createUserName || '-' "></van-cell>
<van-cell title="场馆名称" :value="viewData.name || '-' "></van-cell>
<van-cell title="场地地址" class="direction-column-cell">
<template #default>
{{ viewData.address || '-' }}
</template>
</van-cell>
<van-cell title="联系人" :value="viewData.contactName || '-' "></van-cell>
<van-cell title="联系电话" :value="viewData.contactPhone || '-' "></van-cell>
<van-cell title="排序编号" :value="viewData.sortNum || '-' "></van-cell>
<van-cell title="容纳人数" :value="viewData.maxNum || '-' "></van-cell>
<van-cell title="场地类型" :value="viewData.typeName || '-' "></van-cell>
<van-cell title="性别限制">
<template #default>
<span v-if="viewData.sexLimit === 1">男</span>
<span v-else-if="viewData.sexLimit === 2">女</span>
<span v-else>不限制</span>
</template>
</van-cell>
<van-cell title="开启状态">
<template #default>
<span v-if="viewData.state">开启</span>
<span v-else>禁用</span>
</template>
</van-cell>
<van-cell title="排除节假日">
<template #default>
<span v-if="viewData.filterHolidays">是</span>
<span v-else>否</span>
</template>
</van-cell>
<van-cell title="场地介绍" class="direction-column-cell">
<template #default>
<div v-if="viewData.introduce" v-html="viewData.introduce"></div>
<div v-else>暂无场地介绍</div>
</template>
</van-cell>
</van-cell-group>
<van-cell-group title="禁用时间" v-if="viewData.notApplyTimeList && viewData.notApplyTimeList.length > 0">
<table class="table-class">
<thead>
<tr>
<th>日期</th>
<th>开始时间</th>
<th>结束时间</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in viewData.notApplyTimeList" :key="item.date + item.startTime + item.endTime + index">
<td>{{ item.date }}</td>
<td>{{ item.startTime }}</td>
<td>{{ item.endTime }}</td>
</tr>
</tbody>
</table>
</van-cell-group>
</div>
</van-action-sheet>
</div>
`,
data() {
return {
viewData: {},
visible: false,
}
},
methods: {
onOpen(row) {
this.viewData = row
this.visible = true
},
},
style: /*language=CSS*/ `
/deep/ .direction-column-cell .van-cell__value {
white-space: normal;
text-align: left;
}
/deep/ .table-class {
width: 100%;
border-radius: 5px;
overflow: hidden;
line-height: 1.5rem;
font-size: 13px;
table-layout: fixed;
border-collapse: collapse;
}
/deep/ .table-class th {
background-color: #f2f2f2;
border: 1px solid #dddddd;
}
/deep/ .table-class tr {
text-align: center;
border-bottom: 1px solid #dddddd;
}
/deep/ .table-class td {
border: 1px solid #dddddd;
}
`
}
@@ -0,0 +1,151 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
</style>
<div id="app" v-cloak>
<van-nav-bar title="我的预约" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
<van-sticky offset-top="46px">
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
<van-dropdown-item v-model="pageForm.siteType" :options="typeOptions" :multiple="false" @change="doSearch"></van-dropdown-item>
<van-dropdown-item v-model="pageForm.siteId" :options="siteOptions" :multiple="false" @change="doSearch"></van-dropdown-item>
</van-dropdown-menu>
</van-sticky>
<table-list api="/platform/siteCug/mine/pageData" :page_form.sync="pageForm" ref="tableListRef" title="siteName" @ready="onReady">
<template v-slot="{index,row}">
<table-column label="场地名称">{{row.siteName}}</table-column>
<table-column label="预约类型">{{row.reserveType === 'club' ? '协会预约' : '分工会预约'}}</table-column>
<table-column label="预约主体">{{row.reserveType === 'club' ? (row.clubName || '-') : (row.applyUnionName || '-')}}</table-column>
<table-column label="所属单位">{{row.applyUnitName}}</table-column>
<table-column label="开始时间">{{row.reserveStartTime}}</table-column>
<table-column label="结束时间">{{row.reserveEndTime}}</table-column>
<table-column label="当前节点">{{row.taskName || '-'}}</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<span>查看</span>
</div>
<div class="action-btn delete" @click="onDelete(row)" v-if="row.canCancel">
<i class="fa fa-trash"></i>
<span>取消预约</span>
</div>
</template>
</table-list>
<info ref="infoRef"></info>
</div>
<script nonce="${cspNonce!}">
<!--#include('../common/applyInfo.js'){}#-->
new Vue({
el: '#app',
store,
components: {
info: siteCugApplyInfoH5,
},
data() {
return {
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
siteType: null,
siteId: null,
},
typeOptions: [
{
text: '全部类型',
value: null,
}
],
siteOptions: [
{
text: '全部场地',
value: null,
}
],
}
},
methods: {
historyBack,
async onReady() {
await this.querySiteType()
await this.querySites()
this.doSearch()
},
onView(row) {
this.$refs.infoRef.onOpen(row)
},
onDelete(row) {
this.$dialog.confirm({
title: '提示',
message: row.yearlyBatch
? ('该记录属于“预约本年”批量预约,确认后将一并删除同批次的 ' + (row.batchDeleteCount || 0) + ' 条预约记录,是否继续?')
: '您确定要取消该预约吗?',
}).then(() => {
this.$axios.post('/platform/siteCug/mine/delete', { id: row.id }).then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg || '取消成功')
this.doSearch()
} else {
this.$dialog.alert({
title: '提示',
message: res.msg || '取消失败',
}).catch(() => {})
}
}).catch((err) => {
this.$dialog.alert({
title: '提示',
message: (err && err.msg) || '取消失败',
}).catch(() => {})
})
}).catch(() => {})
},
querySiteType() {
return this.$axios.post('/platform/siteCug/function/type/queryFunctionType').then((res) => {
const list = Array.isArray(res.data) ? res.data : []
this.typeOptions = [
{
text: '全部类型',
value: null,
}
].concat(list.map((item) => ({
text: item.name,
value: item.id,
})))
})
},
querySites() {
return this.$axios.post('/platform/siteCug/manage/querySites').then((res) => {
const list = Array.isArray(res.data) ? res.data : []
this.siteOptions = [
{
text: '全部场地',
value: null,
}
].concat(list.map((item) => ({
text: item.name,
value: item.id,
})))
})
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
},
},
})
</script>
<!--#
}
#-->
@@ -0,0 +1,272 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
.action-btn.disabled {
opacity: 0.45;
pointer-events: none;
}
.action-btn.loading {
opacity: 0.9;
}
.action-btn .van-loading {
margin-right: 4px;
}
</style>
<div id="app" v-cloak>
<van-nav-bar title="校工会审核" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
<van-sticky offset-top="46px">
<van-search
v-model="pageForm.searchKeyword"
:show-action="false"
:reverse-color="false"
input-align="left"
placeholder="请输入姓名/工号/场地搜索"
@search="doSearch"
></van-search>
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
<van-dropdown-item v-model="pageForm.siteType" :options="typeOptions" :multiple="false" @change="doSearch"></van-dropdown-item>
<van-dropdown-item v-model="pageForm.siteId" :options="siteOptions" :multiple="false" @change="doSearch"></van-dropdown-item>
</van-dropdown-menu>
<van-tabs v-model="pageForm.approvalText" @change="onApprovalTabChange">
<van-tab title="已审核" name="1"></van-tab>
<van-tab title="未审核" name="0"></van-tab>
</van-tabs>
</van-sticky>
<table-list api="/platform/siteCug/schoolUnionAudit/pageData" :page_form.sync="pageForm" ref="tableListRef" title="applyUserName" @ready="onReady">
<template v-slot="{index,row}">
<table-column label="场地名称">{{row.siteName}}</table-column>
<table-column label="预约类型">{{row.reserveType === 'club' ? '协会预约' : '分工会预约'}}</table-column>
<table-column label="预约主体">{{row.reserveType === 'club' ? (row.clubName || '-') : (row.applyUnionName || '-')}}</table-column>
<table-column label="所属单位">{{row.applyUnitName}}</table-column>
<table-column label="开始时间">{{row.reserveStartTime}}</table-column>
<table-column label="结束时间">{{row.reserveEndTime}}</table-column>
<table-column label="当前节点">{{row.curTaskName || '-'}}</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<span>查看</span>
</div>
<div class="action-btn" v-if="row.taskState === 10" @click="onAudit(row)">
<i class="fa fa-edit"></i>
<span>审核</span>
</div>
<div class="action-btn delete" v-if="canRevoke(row)" :class="{ loading: revokeLoading }" @click="onRevoke(row)">
<van-loading v-if="revokeLoading" size="14px" color="#fff"></van-loading>
<i v-else class="fa fa-reply"></i>
<span>撤回</span>
</div>
</template>
</table-list>
<info ref="infoRef">
<div v-if="showApprovalForm">
<div class="process-title">{{formData.taskName}}</div>
<van-form ref="formRef">
<van-field
v-model="formData.tf_opinion"
name="tf_opinion"
label="审批意见"
placeholder="请输入审批意见"
:rules="[{ required: true, message: '请填写审批意见' }]"
required
maxlength="100"
show-word-limit
></van-field>
</van-form>
<div style="display:flex;justify-content:space-between;column-gap:10px;padding:10px;">
<van-button type="info" block :loading="auditLoading" :disabled="auditLoading" @click="handleTaskAction(6)">退回</van-button>
<van-button type="danger" block :loading="auditLoading" :disabled="auditLoading" @click="handleTaskAction(2)">不同意</van-button>
<van-button type="primary" block :loading="auditLoading" :disabled="auditLoading" @click="handleTaskAction(1)">同意</van-button>
</div>
</div>
</info>
</div>
<script nonce="${cspNonce!}">
<!--#include('../common/applyInfo.js'){}#-->
new Vue({
el: '#app',
store,
components: {
info: siteCugApplyInfoH5,
},
data() {
return {
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: '',
approvalText: '0',
approval: false,
siteType: null,
siteId: null,
},
typeOptions: [
{
text: '全部类型',
value: null,
}
],
siteOptions: [
{
text: '全部场地',
value: null,
}
],
formData: {},
showApprovalForm: false,
auditLoading: false,
revokeLoading: false,
}
},
methods: {
historyBack,
async onReady() {
await this.querySiteType()
await this.querySites()
this.doSearch()
},
onApprovalTabChange(val) {
this.pageForm.approval = val === '1'
this.doSearch()
},
canRevoke(row) {
return Number(row.instanceState) === 20
},
onView(row) {
this.showApprovalForm = false
this.$refs.infoRef.onOpen(row)
},
onAudit(row) {
this.showApprovalForm = true
this.formData = {
processTaskId: row.taskId,
applyId: row.id,
taskName: row.curTaskName,
tf_opinion: '',
yearlyBatch: row.yearlyBatch,
batchAuditCount: row.batchAuditCount,
}
this.$refs.infoRef.onOpen(row)
},
async handleTaskAction(submitType) {
if (this.auditLoading) {
return
}
try {
await this.$refs.formRef.validate()
this.$dialog.confirm({
title: '提示',
message: this.formData.yearlyBatch
? ('该记录属于“预约本年”批量预约,提交后将一并审核同批次的 ' + (this.formData.batchAuditCount || 0) + ' 条预约记录,是否继续?')
: '您确定要提交吗?',
}).then(() => {
this.auditLoading = true
this.$axios.post('/platform/siteCug/schoolUnionAudit/executeTask', {
data: JSON.stringify({
...this.formData,
submitType: submitType,
})
}).then((res) => {
if (res.code === 0) {
this.$refs.infoRef.onClose()
this.$toast.success(res.msg || '提交成功')
this.doSearch()
} else {
this.$dialog.alert({
title: '提示',
message: res.msg || '提交失败',
}).catch(() => {})
}
}).catch((err) => {
this.$dialog.alert({
title: '提示',
message: (err && err.msg) || '提交失败',
}).catch(() => {})
}).finally(() => {
this.auditLoading = false
})
}).catch(() => {})
} catch (e) {
}
},
onRevoke(row) {
if (!this.canRevoke(row) || this.revokeLoading) {
return
}
this.$dialog.confirm({
title: '提示',
message: row.yearlyBatch
? ('该记录属于“预约本年”批量预约,撤回后将一并撤回同批次的 ' + (row.batchAuditCount || 0) + ' 条审核任务,是否继续?')
: '您确定要撤回吗?',
}).then(() => {
this.revokeLoading = true
this.$axios.post('/platform/siteCug/schoolUnionAudit/revokeTask', { taskId: row.taskId, applyId: row.id }).then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg || '撤回成功')
this.doSearch()
} else {
this.$dialog.alert({
title: '提示',
message: res.msg || '撤回失败',
}).catch(() => {})
}
}).catch((err) => {
this.$dialog.alert({
title: '提示',
message: (err && err.msg) || '撤回失败',
}).catch(() => {})
}).finally(() => {
this.revokeLoading = false
})
}).catch(() => {})
},
querySiteType() {
return this.$axios.post('/platform/siteCug/function/type/queryFunctionType').then((res) => {
const list = Array.isArray(res.data) ? res.data : []
this.typeOptions = [
{
text: '全部类型',
value: null,
}
].concat(list.map((item) => ({
text: item.name,
value: item.id,
})))
})
},
querySites() {
return this.$axios.post('/platform/siteCug/manage/querySites').then((res) => {
const list = Array.isArray(res.data) ? res.data : []
this.siteOptions = [
{
text: '全部场地',
value: null,
}
].concat(list.map((item) => ({
text: item.name,
value: item.id,
})))
})
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
},
},
})
</script>
<!--#
}
#-->