This commit is contained in:
2026-06-17 11:27:54 +08:00
parent 33859d3be0
commit 7f643dd4d4
11 changed files with 464 additions and 92 deletions
@@ -15,6 +15,7 @@ import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.validation.Valid;
@@ -49,6 +50,7 @@ public class CoffeeTicketActivityController {
if (StrUtil.isNotBlank(title)) {
cnd.and(Cnd.likeEX("title", title));
}
cnd.desc("createdAt");
Pagination pagination = coffeeTicketActivityService.listPage(pageForm.getPageNumber(), pageForm.getPageSize(), cnd);
return Result.success(pagination);
}
@@ -67,4 +69,23 @@ public class CoffeeTicketActivityController {
coffeeTicketActivityService.deleteActivity(id);
return Result.success();
}
/**
* 开启或关闭咖啡抢票活动。
*
* @param id 活动ID,传 coffee_ticket_activity 表主键
* @param enabled true 表示开启报名并推送首页,false 表示关闭报名并移除首页入口
* @return Result,成功后前端刷新列表即可看到最新状态
*/
@At
@SaCheckPermission("coffeeTicket.activity")
@Aop(TransAop.READ_COMMITTED)
public Result updateEnabled(@Param("id") @Valid String id, @Param("enabled") Boolean enabled) {
try {
coffeeTicketActivityService.updateEnabled(id, enabled);
return Result.success(Boolean.TRUE.equals(enabled) ? "开启成功" : "关闭成功");
} catch (IllegalStateException e) {
return Result.error(e.getMessage());
}
}
}
@@ -35,7 +35,7 @@ public class CoffeeTicketNewController {
/**
* 保存咖啡抢票活动。
*
* @param activity 页面提交的活动配置,包含标题、封面、描述、活动开始/结束日期、可预约星期、票数、每日预约时间段、预约方式、活动地点和参与须知
* @param activity 页面提交的活动配置,包含标题、封面、描述、活动开始/结束日期、可预约星期、票数、每日预约时间段、可报名人员范围、预约方式、活动地点和参与须知
* @return Result,成功时 data 为 CoffeeTicketActivity,可通过 data.id 获取活动ID
*/
@At
@@ -46,8 +46,12 @@ public class CoffeeTicketNewController {
if (validateResult != null) {
return validateResult;
}
CoffeeTicketActivity savedActivity = coffeeTicketActivityService.saveActivity(activity);
return Result.success(savedActivity);
try {
CoffeeTicketActivity savedActivity = coffeeTicketActivityService.saveActivity(activity);
return Result.success(savedActivity);
} catch (IllegalStateException e) {
return Result.error(e.getMessage());
}
}
/**
@@ -87,6 +91,9 @@ public class CoffeeTicketNewController {
if (StrUtil.isBlank(activity.getReserveMode())) {
return Result.error("请选择预约方式");
}
if (activity.getActivityGroupId() == null) {
return Result.error("请选择可报名人员范围");
}
return null;
}
}
@@ -45,6 +45,13 @@ public class H5CoffeeTicketDetailController {
if (activity == null) {
return Result.error("活动不存在");
}
if (!Boolean.TRUE.equals(activity.getEnabled())) {
return Result.error("活动未开启");
}
String scopeMsg = coffeeTicketActivityService.checkH5ActivityUserScope(activity, SecurityUtil.getUserId());
if (scopeMsg != null) {
return Result.error(scopeMsg);
}
CoffeeTicketActivityH5DetailVO detailVO = BeanUtil.copyProperties(activity, CoffeeTicketActivityH5DetailVO.class);
CoffeeTicketRecord userRecord = coffeeTicketRecordService.fetchLatestUserRecord(id, SecurityUtil.getUserId());
detailVO.setHasGrabbed(userRecord != null);
@@ -1,12 +1,15 @@
package com.budwk.app.zhgh.dayofficework.coffeeTicket.models;
import com.budwk.app.base.model.BaseModel;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.services.SysHomeConvert;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Default;
import org.nutz.dao.entity.annotation.Name;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.entity.annotation.TableMeta;
@@ -21,7 +24,7 @@ import java.util.List;
@Table("coffee_ticket_activity")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("咖啡抢票活动表")
public class CoffeeTicketActivity extends BaseModel implements Serializable {
public class CoffeeTicketActivity extends BaseModel implements Serializable, SysHomeConvert {
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@@ -78,6 +81,17 @@ public class CoffeeTicketActivity extends BaseModel implements Serializable {
@ColDefine(type = ColType.VARCHAR, width = 20)
private String reserveMode;
@Column
@Comment("可报名人员范围分组ID,对应 activity_user_scope.groupId")
@ColDefine(type = ColType.INT, width = 32)
private Integer activityGroupId;
@Column
@Comment("是否开启,开启后手机端可报名并推送首页")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean enabled;
@Column
@Comment("活动地点")
@ColDefine(type = ColType.VARCHAR, width = 100)
@@ -87,4 +101,21 @@ public class CoffeeTicketActivity extends BaseModel implements Serializable {
@Comment("参与须知")
@ColDefine(type = ColType.TEXT)
private String notice;
@Override
public Sys_home_activity covertToSysHomeActivity() {
Sys_home_activity sysHomeActivity = new Sys_home_activity();
sysHomeActivity.setId(this.getId());
sysHomeActivity.setName(this.getTitle());
sysHomeActivity.setCover(this.getCover());
sysHomeActivity.setContent(this.getDescription());
sysHomeActivity.setUrl("/platform/coffeeTicket/activity");
sysHomeActivity.setH5Url("/platform/h5/coffeeTicket/detail?id=" + this.getId());
sysHomeActivity.setStartDate(this.getActivityStartDate());
sysHomeActivity.setEndDate(this.getActivityEndDate());
sysHomeActivity.setAllowUserGroupId(this.getActivityGroupId());
sysHomeActivity.setEnable(Boolean.TRUE.equals(this.getEnabled()));
sysHomeActivity.setClassPath(this.getClass().getPackageName() + this.getClass().getName());
return sysHomeActivity;
}
}
@@ -25,6 +25,14 @@ public interface CoffeeTicketActivityService extends BaseService<CoffeeTicketAct
*/
void deleteActivity(String id);
/**
* 开启或关闭咖啡抢票活动,并同步移动端首页入口。
*
* @param id 活动ID
* @param enabled true 表示开启报名并推送首页,false 表示关闭报名并移除首页入口
*/
void updateEnabled(String id, Boolean enabled);
/**
* 手机端分页查询可展示的咖啡抢票活动。
*
@@ -51,6 +59,15 @@ public interface CoffeeTicketActivityService extends BaseService<CoffeeTicketAct
*/
String checkH5GrabTicket(String id, Integer ticketNum);
/**
* 校验当前用户是否在活动可报名人员范围内。
*
* @param activity 活动实体,必须包含 activityGroupId
* @param userId 当前登录用户ID
* @return String,返回 null 表示在范围内;返回非空字符串表示不可参与原因
*/
String checkH5ActivityUserScope(CoffeeTicketActivity activity, String userId);
/**
* 手机端立即抢票并保存抢票记录。
*
@@ -6,7 +6,9 @@ import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import com.budwk.app.zhgh.dayofficework.coffeeTicket.models.CoffeeTicketActivity;
import com.budwk.app.zhgh.dayofficework.coffeeTicket.models.CoffeeTicketRecord;
import com.budwk.app.zhgh.dayofficework.coffeeTicket.service.CoffeeTicketActivityService;
@@ -30,6 +32,9 @@ public class CoffeeTicketActivityServiceImpl extends BaseServiceImpl<CoffeeTicke
@Inject
private CoffeeTicketRecordService coffeeTicketRecordService;
@Inject
private ActivityBasicScopeService activityBasicScopeService;
public CoffeeTicketActivityServiceImpl(Dao dao) {
super(dao);
}
@@ -37,7 +42,14 @@ public class CoffeeTicketActivityServiceImpl extends BaseServiceImpl<CoffeeTicke
@Override
@Aop(TransAop.READ_COMMITTED)
public CoffeeTicketActivity saveActivity(CoffeeTicketActivity activity) {
if (activity.getActivityGroupId() == null || activityBasicScopeService.getGroupInfo(activity.getActivityGroupId()) == null) {
throw new IllegalStateException("请选择有效的可报名人员范围");
}
if (activity.getEnabled() == null) {
activity.setEnabled(false);
}
dao().insertOrUpdate(activity);
syncHomeActivity(activity);
return activity;
}
@@ -45,11 +57,25 @@ public class CoffeeTicketActivityServiceImpl extends BaseServiceImpl<CoffeeTicke
@Aop(TransAop.READ_COMMITTED)
public void deleteActivity(String id) {
dao().delete(CoffeeTicketActivity.class, id);
dao().delete(Sys_home_activity.class, id);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void updateEnabled(String id, Boolean enabled) {
CoffeeTicketActivity activity = fetch(id);
if (activity == null) {
throw new IllegalStateException("活动不存在");
}
activity.setEnabled(Boolean.TRUE.equals(enabled));
dao().updateIgnoreNull(activity);
syncHomeActivity(activity);
}
@Override
public Pagination h5PageData(PageForm pageForm, String status) {
Cnd cnd = Cnd.NEW();
cnd.and("enabled", "=", true);
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
String keyword = "%" + pageForm.getSearchKeyword() + "%";
cnd.and(Cnd.exps("title", "like", keyword).or("address", "like", keyword));
@@ -64,6 +90,12 @@ public class CoffeeTicketActivityServiceImpl extends BaseServiceImpl<CoffeeTicke
} else if ("ended".equals(status)) {
cnd.and("activityEndDate", "<", todayStart);
}
List<Integer> visibleGroupIds = buildCurrentUserVisibleGroupIds(cnd);
if (visibleGroupIds.isEmpty()) {
cnd.and("id", "=", "__none__");
} else {
cnd.and("activityGroupId", "in", visibleGroupIds);
}
cnd.desc("activityStartDate");
Pagination pagination = listPage(pageForm.getPageNumber(), pageForm.getPageSize(), cnd);
List<CoffeeTicketActivity> activityList = pagination.getList(CoffeeTicketActivity.class);
@@ -93,6 +125,13 @@ public class CoffeeTicketActivityServiceImpl extends BaseServiceImpl<CoffeeTicke
if (activity == null) {
return "活动不存在";
}
if (!Boolean.TRUE.equals(activity.getEnabled())) {
return "活动未开启";
}
String scopeMsg = checkH5ActivityUserScope(activity, SecurityUtil.getUserId());
if (scopeMsg != null) {
return scopeMsg;
}
Date now = new Date();
if (activity.getActivityStartDate() == null || activity.getActivityEndDate() == null) {
return "活动周期未配置";
@@ -126,6 +165,20 @@ public class CoffeeTicketActivityServiceImpl extends BaseServiceImpl<CoffeeTicke
return null;
}
@Override
public String checkH5ActivityUserScope(CoffeeTicketActivity activity, String userId) {
if (activity == null) {
return "活动不存在";
}
if (activity.getActivityGroupId() == null) {
return "活动未配置可报名人员范围";
}
if (!activityBasicScopeService.isUserInGroup(activity.getActivityGroupId(), userId)) {
return "您不在本活动可报名人员范围内";
}
return null;
}
@Override
@Aop(TransAop.READ_COMMITTED)
public CoffeeTicketRecord grabH5Ticket(String id, Integer ticketNum) {
@@ -169,6 +222,35 @@ public class CoffeeTicketActivityServiceImpl extends BaseServiceImpl<CoffeeTicke
return vo;
}
/**
* 按当前查询条件先收集活动分组,再复用分组服务过滤出当前用户可参与的分组ID。
*
* @param cnd H5列表搜索条件,包含关键字和活动状态筛选
* @return List<Integer>,当前登录用户在人员范围内的活动分组ID列表
*/
private List<Integer> buildCurrentUserVisibleGroupIds(Cnd cnd) {
List<Integer> groupIds = dao().query(CoffeeTicketActivity.class, cnd)
.stream()
.map(CoffeeTicketActivity::getActivityGroupId)
.filter(groupId -> groupId != null)
.distinct()
.toList();
return activityBasicScopeService.filterGroupIdsByUser(groupIds, SecurityUtil.getUserId());
}
/**
* 同步移动端首页入口。活动开启时写入首页,关闭时删除首页入口。
*
* @param activity 咖啡抢票活动,包含首页展示和跳转所需字段
*/
private void syncHomeActivity(CoffeeTicketActivity activity) {
if (Boolean.TRUE.equals(activity.getEnabled())) {
dao().insertOrUpdate(activity.covertToSysHomeActivity());
return;
}
dao().delete(Sys_home_activity.class, activity.getId());
}
/**
* 将 Java 星期值转换为页面保存的 1-7 格式,其中 1 表示周一、7 表示周日。
*
@@ -32,6 +32,11 @@ layout("/layouts/platform.html"){
<el-table :data="tableData" ref="tableRef" @sort-change="pageOrder">
<el-table-column label="序号" width="60" type="index" :index="indexMethod"></el-table-column>
<el-table-column label="活动标题" prop="title" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="创建时间" prop="createdAt" width="170" sortable>
<template scope="{row}">
<span>{{formatDateTime(row.createdAt)}}</span>
</template>
</el-table-column>
<el-table-column label="活动周期" width="220">
<template scope="{row}">
<span>{{formatDate(row.activityStartDate)}} - {{formatDate(row.activityEndDate)}}</span>
@@ -48,14 +53,22 @@ layout("/layouts/platform.html"){
<span>{{row.reserveStartTime || '--'}} - {{row.reserveEndTime || '--'}}</span>
</template>
</el-table-column>
<el-table-column label="创建时间" prop="createdAt" width="170" sortable>
<el-table-column label="可报名人员范围" width="180" show-overflow-tooltip>
<template scope="{row}">
<span>{{formatDateTime(row.createdAt)}}</span>
<span>{{formatActivityGroup(row.activityGroupId)}}</span>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="150">
<el-table-column label="状态" width="100" align="center">
<template scope="{row}">
<el-tag v-if="row.enabled" type="success" size="small">已开启</el-tag>
<el-tag v-else type="info" size="small">已关闭</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="220">
<template scope="{row}">
<el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
<el-button v-if="row.enabled" size="mini" type="warning" @click="updateEnabled(row, false)">关闭</el-button>
<el-button v-else size="mini" type="success" @click="updateEnabled(row, true)">开启</el-button>
<el-button size="mini" type="danger" @click="onDelete(row.id)">删除</el-button>
</template>
</el-table-column>
@@ -79,7 +92,8 @@ layout("/layouts/platform.html"){
"5": "周五",
"6": "周六",
"7": "周日"
}
},
groupList: []
}
},
methods: {
@@ -101,6 +115,21 @@ layout("/layouts/platform.html"){
}
return weekDays.map(item => this.weekMap[item] || item).join("、")
},
formatActivityGroup(activityGroupId) {
if (!activityGroupId) {
return "--"
}
const group = this.groupList.find(item => String(item.groupId) === String(activityGroupId))
return group ? group.groupName : "--"
},
loadGroupList() {
this.$axios.post("/platform/activity/basic/scope/getActivityUserScopeGroup", {})
.then((res) => {
if (res.code === 0) {
this.groupList = res.data || []
}
})
},
pageData() {
this.$axios.post(loc() + "/pageData", this.pageForm)
.then((res) => {
@@ -110,6 +139,27 @@ layout("/layouts/platform.html"){
}
})
},
updateEnabled(row, enabled) {
const actionText = enabled ? "开启" : "关闭"
this.$confirm("确定" + actionText + "该活动吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post("/platform/coffeeTicket/activity/updateEnabled", {
id: row.id,
enabled: enabled
})
.then((res) => {
if (res.code === 0) {
this.$message.success(res.msg || actionText + "成功")
this.pageData()
} else {
this.$message.warning(res.msg)
}
})
}).catch(() => {})
},
onDelete(id) {
this.$confirm("删除后该活动配置不可恢复,确定删除吗?", "提示", {
confirmButtonText: "确定",
@@ -129,6 +179,7 @@ layout("/layouts/platform.html"){
}
},
created() {
this.loadGroupList()
this.pageData()
}
})
@@ -110,7 +110,8 @@ layout("/layouts/platform.html"){
<custom-card>
<h2 slot="header">创建咖啡抢票活动</h2>
<template>
<el-form class="coffee-ticket-form" :model="formData" ref="formRef" :rules="formRules" label-width="150px" v-loading="formLoading">
<el-form class="coffee-ticket-form" :model="formData" ref="formRef" :rules="formRules" label-width="150px"
v-loading="formLoading">
<section class="coffee-section">
<div class="coffee-section-index">1</div>
<h3 class="coffee-section-title">基础信息</h3>
@@ -178,21 +179,27 @@ layout("/layouts/platform.html"){
<section class="coffee-section">
<div class="coffee-section-index">3</div>
<h3 class="coffee-section-title">预约规则</h3>
<el-form-item label="每次可预约票数" prop="ticketCount">
<el-form-item label="每次可抢票票数" prop="ticketCount">
<div class="coffee-ticket-number">
<el-input-number v-model="formData.ticketCount" :min="1" :max="999" :precision="0"></el-input-number>
<el-input-number v-model="formData.ticketCount" :min="1" :max="999"
:precision="0"></el-input-number>
<span>张</span>
<span class="coffee-inline-note">每次可预约的票数上限</span>
<span class="coffee-inline-note">每次可抢票的票数上限</span>
</div>
</el-form-item>
<el-form-item label="每日可预约时间段" required>
<el-form-item label="可报名人员范围" prop="activityGroupId">
<permission-group :value.sync="formData.activityGroupId"></permission-group>
<div class="coffee-form-item-tip">只有该人员范围内的用户可以在手机端看到并参与本活动</div>
</el-form-item>
<el-form-item label="每日可抢票时间段" required>
<div class="coffee-time-range">
<el-form-item prop="reserveStartTime">
<el-time-select
v-model="formData.reserveStartTime"
placeholder="开始时间"
:picker-options="{ start: '00:00', step: '00:30', end: '23:30' }">
:picker-options="{ start: '07:00', step: '00:10', end: '23:30' }">
</el-time-select>
</el-form-item>
<span>-</span>
@@ -268,34 +275,37 @@ layout("/layouts/platform.html"){
reserveStartTime: "",
reserveEndTime: "",
reserveMode: "WEEKLY",
activityGroupId: null,
enabled: false,
address: "",
notice: ""
},
weekOptions: [
{ value: "1", label: "周一" },
{ value: "2", label: "周二" },
{ value: "3", label: "周三" },
{ value: "4", label: "周四" },
{ value: "5", label: "周五" },
{ value: "6", label: "周六" },
{ value: "7", label: "周日" }
{value: "1", label: "周一"},
{value: "2", label: "周二"},
{value: "3", label: "周三"},
{value: "4", label: "周四"},
{value: "5", label: "周五"},
{value: "6", label: "周六"},
{value: "7", label: "周日"}
],
formRules: {
title: [{ required: true, message: "请输入活动标题", trigger: ["blur", "change"] }],
cover: [{ required: true, message: "请上传活动封面", trigger: ["blur", "change"] }],
description: [{ required: true, message: "请输入活动描述", trigger: ["blur", "change"] }],
activityDateRange: [{ required: true, type: "array", message: "请选择活动周期", trigger: "change" }],
weekDays: [{ required: true, type: "array", message: "请选择可预约星期", trigger: "change" }],
ticketCount: [{ required: true, message: "请输入每次可预约票数", trigger: "change" }],
title: [{required: true, message: "请输入活动标题", trigger: ["blur", "change"]}],
cover: [{required: true, message: "请上传活动封面", trigger: ["blur", "change"]}],
description: [{required: true, message: "请输入活动描述", trigger: ["blur", "change"]}],
activityDateRange: [{required: true, type: "array", message: "请选择活动周期", trigger: "change"}],
weekDays: [{required: true, type: "array", message: "请选择可预约星期", trigger: "change"}],
ticketCount: [{required: true, message: "请输入每次可预约票数", trigger: "change"}],
reserveStartTime: [
{ required: true, message: "请选择开始时间", trigger: "change" },
{ validator: validateTimeRange, trigger: "change" }
{required: true, message: "请选择开始时间", trigger: "change"},
{validator: validateTimeRange, trigger: "change"}
],
reserveEndTime: [
{ required: true, message: "请选择结束时间", trigger: "change" },
{ validator: validateTimeRange, trigger: "change" }
{required: true, message: "请选择结束时间", trigger: "change"},
{validator: validateTimeRange, trigger: "change"}
],
reserveMode: [{ required: true, message: "请选择预约方式", trigger: "change" }]
reserveMode: [{required: true, message: "请选择预约方式", trigger: "change"}],
activityGroupId: [{required: true, message: "请选择可报名人员范围", trigger: ["blur", "change"]}]
}
}
},
@@ -319,6 +329,8 @@ layout("/layouts/platform.html"){
reserveStartTime: this.formData.reserveStartTime,
reserveEndTime: this.formData.reserveEndTime,
reserveMode: this.formData.reserveMode,
activityGroupId: this.formData.activityGroupId,
enabled: this.formData.enabled,
address: this.formData.address,
notice: this.formData.notice
}
@@ -330,13 +342,11 @@ layout("/layouts/platform.html"){
}
const submitData = this.buildSubmitData()
this.formLoading = true
this.$axios.post("/platform/coffeeTicket/new/save", { data: JSON.stringify(submitData) })
this.$axios.post("/platform/coffeeTicket/new/save", {data: JSON.stringify(submitData)})
.then((res) => {
if (res.code === 0) {
this.$message.success(res.msg || "保存成功")
if (res.data && res.data.id) {
this.$set(this.formData, "id", res.data.id)
}
pjaxReplace("/platform/coffeeTicket/activity")
} else {
this.$message.warning(res.msg)
}
@@ -355,7 +365,7 @@ layout("/layouts/platform.html"){
return
}
this.formLoading = true
this.$axios.post("/platform/coffeeTicket/activity/findOne", { id: id })
this.$axios.post("/platform/coffeeTicket/activity/findOne", {id: id})
.then((res) => {
if (res.code === 0 && res.data) {
this.formData = Object.assign({}, this.formData, res.data, {
@@ -7,18 +7,35 @@ layout("/layouts/platform_h5.html"){
}
.detail-page {
min-height: 100vh;
padding-bottom: 74px;
height: 100vh;
overflow: hidden;
background: #f5f7fb;
}
.detail-cover {
position: relative;
position: fixed;
left: 12px;
right: 12px;
top: 58px;
z-index: 8;
height: 170px;
overflow: hidden;
border-radius: 14px;
background: linear-gradient(135deg, #dfeeff, #f8fbff);
}
.detail-cover::after {
content: "";
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 1;
background: linear-gradient(to top, rgba(0, 0, 0, .58), rgba(0, 0, 0, .08) 58%, rgba(0, 0, 0, 0));
pointer-events: none;
}
.detail-cover img {
width: 100%;
height: 100%;
@@ -35,8 +52,48 @@ layout("/layouts/platform_h5.html"){
font-size: 13px;
}
.detail-cover-text {
position: absolute;
left: 16px;
right: 16px;
bottom: 16px;
z-index: 2;
color: #ffffff;
}
.detail-cover-title {
margin-bottom: 4px;
color: #ffffff;
font-size: 21px;
line-height: 28px;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.detail-cover-subtitle {
color: rgba(255, 255, 255, .9);
font-size: 14px;
line-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.detail-scroll {
position: fixed;
left: 0;
right: 0;
top: 240px;
bottom: 66px;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
}
.detail-card {
margin: -10px 12px 12px;
margin: 0 12px 12px;
position: relative;
z-index: 1;
border-radius: 10px;
@@ -179,7 +236,7 @@ layout("/layouts/platform_h5.html"){
</style>
<div id="app" v-cloak class="detail-page">
<van-nav-bar title="活动详情" left-arrow placeholder fixed @click-left="historyBack">
<van-nav-bar title="活动详情" left-text="返回" left-arrow placeholder fixed @click-left="historyBack">
</van-nav-bar>
<div class="detail-cover">
@@ -187,58 +244,76 @@ layout("/layouts/platform_h5.html"){
<div v-else class="detail-cover-empty">暂无封面</div>
</div>
<div class="detail-card">
<div class="detail-main">
<div class="detail-title-row">
<div class="detail-title">{{formData.title || '--'}}</div>
<div class="detail-status" :class="statusInfo.className">{{statusInfo.text}}</div>
</div>
<div class="detail-desc">{{formData.description || '暂无活动描述'}}</div>
<div class="detail-info">
<div class="detail-info-row">
<i class="van-icon van-icon-calendar-o"></i>
<div class="detail-info-label">活动周期</div>
<div class="detail-info-value">{{formatDate(formData.activityStartDate)}} ~ {{formatDate(formData.activityEndDate)}}</div>
<div class="detail-scroll">
<div class="detail-card">
<div class="detail-main">
<div class="detail-title-row">
<div class="detail-title">{{formData.title || '--'}}</div>
<div class="detail-status" :class="statusInfo.className">{{statusInfo.text}}</div>
</div>
<div class="detail-info-row">
<i class="van-icon van-icon-todo-list-o"></i>
<div class="detail-info-label">可抢票星期</div>
<div class="detail-info-value">{{formatWeekDays(formData.weekDays)}}</div>
</div>
<div class="detail-info-row">
<i class="van-icon van-icon-coupon-o"></i>
<div class="detail-info-label">可抢票票数</div>
<div class="detail-info-value">{{formData.ticketCount || 0}} 张</div>
</div>
<div class="detail-info-row">
<i class="van-icon van-icon-clock-o"></i>
<div class="detail-info-label">抢票时间段</div>
<div class="detail-info-value">{{formData.reserveStartTime || '--'}} - {{formData.reserveEndTime || '--'}}</div>
</div>
<div class="detail-info-row">
<i class="van-icon van-icon-location-o"></i>
<div class="detail-info-label">活动地点</div>
<div class="detail-info-value">{{formData.address || '--'}}</div>
</div>
<div class="detail-info-row" v-if="formData.hasGrabbed">
<i class="van-icon van-icon-passed"></i>
<div class="detail-info-label">我的抢票</div>
<div class="detail-info-value">已抢 {{formData.myTicketNum || 0}} 张</div>
<div class="detail-info">
<div class="detail-info-row">
<i class="van-icon van-icon-calendar-o"></i>
<div class="detail-info-label">活动周期</div>
<div class="detail-info-value">{{formatDate(formData.activityStartDate)}} ~ {{formatDate(formData.activityEndDate)}}</div>
</div>
<div class="detail-info-row">
<i class="van-icon van-icon-todo-list-o"></i>
<div class="detail-info-label">可抢票星期</div>
<div class="detail-info-value">{{formatWeekDays(formData.weekDays)}}</div>
</div>
<div class="detail-info-row">
<i class="van-icon van-icon-coupon-o"></i>
<div class="detail-info-label">可抢票票数</div>
<div class="detail-info-value">{{formData.ticketCount || 0}} 张</div>
</div>
<div class="detail-info-row">
<i class="van-icon van-icon-clock-o"></i>
<div class="detail-info-label">抢票时间段</div>
<div class="detail-info-value">{{formData.reserveStartTime || '--'}} - {{formData.reserveEndTime || '--'}}</div>
</div>
<div class="detail-info-row">
<i class="van-icon van-icon-location-o"></i>
<div class="detail-info-label">活动地点</div>
<div class="detail-info-value">{{formData.address || '--'}}</div>
</div>
<div class="detail-info-row" v-if="formData.hasGrabbed">
<i class="van-icon van-icon-passed"></i>
<div class="detail-info-label">我的抢票</div>
<div class="detail-info-value">已抢 {{formData.myTicketNum || 0}} 张</div>
</div>
</div>
</div>
</div>
</div>
<div class="notice-card">
<div class="notice-title">
<i class="van-icon van-icon-bookmark-o"></i>
<span>参与须知</span>
<div class="notice-card">
<div class="notice-title">
<i class="van-icon van-icon-bookmark-o"></i>
<span>活动描述</span>
</div>
<div class="notice-content">{{formData.description || '暂无活动描述'}}</div>
</div>
<div class="notice-card">
<div class="notice-title">
<i class="van-icon van-icon-bookmark-o"></i>
<span>参与须知</span>
</div>
<div class="notice-content">{{noticeText}}</div>
</div>
<div class="notice-content">{{noticeText}}</div>
</div>
<div class="detail-footer">
<van-button class="detail-action" type="info" block :disabled="formData.hasGrabbed || ticketLoading" :loading="ticketLoading" loading-text="校验中..." @click="onTicket">{{formData.hasGrabbed ? '已抢票' : '立即抢票'}}</van-button>
<van-button
v-if="ticketActionInfo.visible"
class="detail-action"
type="info"
block
:disabled="ticketActionInfo.disabled || ticketLoading"
:loading="ticketLoading"
loading-text="校验中..."
@click="onTicket">
{{formData.hasGrabbed ? '已抢票' : ticketActionInfo.text}}
</van-button>
</div>
</div>
@@ -249,6 +324,8 @@ layout("/layouts/platform_h5.html"){
return {
formLoading: false,
ticketLoading: false,
nowTimestamp: Date.now(),
ticketTimer: null,
formData: {},
weekMap: {
"1": "周一",
@@ -259,7 +336,7 @@ layout("/layouts/platform_h5.html"){
"6": "周六",
"7": "周日"
},
defaultNotice: "1. 请提前到达活动地点,活动开始后15分钟停止入场。\n2. 每人每次最多可预约40张,请合理安排名额。\n3. 预约成功后如需取消,请提前2小时操作。\n4. 如有疑问,请联系客服或关注活动公告。"
defaultNotice: "如有疑问,请联系校工会或关注活动公告。"
}
},
computed: {
@@ -275,6 +352,44 @@ layout("/layouts/platform_h5.html"){
},
noticeText() {
return this.formData.notice || this.defaultNotice
},
ticketActionInfo() {
const now = this.$moment(this.nowTimestamp)
const today = now.format("YYYY-MM-DD")
if (this.formData.hasGrabbed) {
return { visible: true, disabled: true, text: "已抢票" }
}
if (!this.formData.activityStartDate || !this.formData.activityEndDate) {
return { visible: false, disabled: true, text: "" }
}
if (now.isBefore(this.$moment(this.formData.activityStartDate).startOf("day"))) {
return { visible: false, disabled: true, text: "" }
}
if (now.isAfter(this.$moment(this.formData.activityEndDate).endOf("day"))) {
return { visible: true, disabled: true, text: "活动已结束" }
}
if (!this.formData.weekDays || this.formData.weekDays.indexOf(this.getTodayWeekValue()) === -1) {
return { visible: true, disabled: true, text: "今日不可抢票" }
}
if (!this.formData.reserveStartTime || !this.formData.reserveEndTime) {
return { visible: true, disabled: true, text: "未配置抢票时间" }
}
const startTime = this.$moment(today + " " + this.formData.reserveStartTime, "YYYY-MM-DD HH:mm")
const endTime = this.$moment(today + " " + this.formData.reserveEndTime, "YYYY-MM-DD HH:mm")
const showTime = startTime.clone().subtract(10, "minutes")
if (now.isBefore(showTime)) {
return { visible: false, disabled: true, text: "" }
}
if (now.isBefore(startTime)) {
const seconds = Math.max(startTime.diff(now, "seconds"), 0)
const minutes = Math.floor(seconds / 60)
const leftSeconds = seconds % 60
return { visible: true, disabled: true, text: "距抢票开始 " + minutes + "分" + leftSeconds + "秒" }
}
if (now.isAfter(endTime)) {
return { visible: true, disabled: true, text: "今日抢票已结束" }
}
return { visible: true, disabled: false, text: "立即抢票" }
}
},
methods: {
@@ -293,7 +408,13 @@ layout("/layouts/platform_h5.html"){
if (!weekDays || weekDays.length === 0) {
return "--"
}
return weekDays.map(item => this.weekMap[item] || item).join("、")
return weekDays.slice().sort((a, b) => {
return Number(a) - Number(b)
}).map(item => this.weekMap[item] || item).join("、")
},
getTodayWeekValue() {
const day = this.$moment(this.nowTimestamp).day()
return day === 0 ? "7" : String(day)
},
loadData() {
const id = this.getQueryString("id")
@@ -318,6 +439,9 @@ layout("/layouts/platform_h5.html"){
if (this.ticketLoading) {
return
}
if (this.ticketActionInfo.disabled) {
return
}
if (!this.formData.id) {
this.$toast.fail("活动ID不能为空")
return
@@ -365,6 +489,15 @@ layout("/layouts/platform_h5.html"){
},
mounted() {
this.loadData()
this.ticketTimer = setInterval(() => {
this.nowTimestamp = Date.now()
}, 1000)
},
beforeDestroy() {
if (this.ticketTimer) {
clearInterval(this.ticketTimer)
this.ticketTimer = null
}
}
})
</script>
@@ -4,12 +4,16 @@ layout("/layouts/platform_h5.html"){
<style scoped>
body {
background: #f5f7fb;
overflow: hidden;
}
.ticket-page {
min-height: 100vh;
height: 100vh;
overflow-y: auto;
box-sizing: border-box;
padding-bottom: 62px;
background: #f5f7fb;
-webkit-overflow-scrolling: touch;
}
.ticket-sticky {
@@ -216,7 +220,7 @@ layout("/layouts/platform_h5.html"){
</style>
<div id="app" v-cloak class="ticket-page">
<van-nav-bar title="抢票活动" left-text="返回" left-arrow placeholder fixed @click-left="historyBack"></van-nav-bar>
<van-nav-bar title="抢票活动" left-text="返回" left-arrow placeholder fixed @click-left="()=>this.$pjaxReplace('/platform/h5/home')"></van-nav-bar>
<van-sticky offset-top="46px" class="ticket-sticky">
<div class="ticket-search">
@@ -261,7 +265,7 @@ layout("/layouts/platform_h5.html"){
<i class="van-icon van-icon-clock-o"></i>
<span class="ticket-info-label">可抢票</span>
<span class="ticket-info-value ticket-week-tags" v-if="item.weekDays && item.weekDays.length > 0">
<span class="ticket-week-tag" v-for="week in item.weekDays" :key="week">{{weekMap[week] || week}}</span>
<span class="ticket-week-tag" v-for="week in sortWeekDays(item.weekDays)" :key="week">{{weekMap[week] || week}}</span>
</span>
<span class="ticket-info-value" v-else>--</span>
</div>
@@ -331,6 +335,11 @@ layout("/layouts/platform_h5.html"){
}
return weekDays.map(item => this.weekMap[item] || item).join(" / ")
},
sortWeekDays(weekDays) {
return weekDays.slice().sort((a, b) => {
return Number(a) - Number(b)
})
},
getStatusInfo(row) {
const now = this.$moment()
if (row.activityEndDate && now.isAfter(this.$moment(row.activityEndDate).endOf("day"))) {
@@ -4,12 +4,16 @@ layout("/layouts/platform_h5.html"){
<style scoped>
body {
background: #f5f7fb;
overflow: hidden;
}
.mine-page {
min-height: 100vh;
height: 100vh;
overflow-y: auto;
box-sizing: border-box;
padding-bottom: 62px;
background: #f5f7fb;
-webkit-overflow-scrolling: touch;
}
.mine-filter-wrap {
@@ -181,7 +185,7 @@ layout("/layouts/platform_h5.html"){
</style>
<div id="app" v-cloak class="mine-page">
<van-nav-bar title="我的抢票" left-arrow placeholder fixed @click-left="historyBack"></van-nav-bar>
<van-nav-bar title="我的抢票" left-arrow left-text="返回" placeholder fixed @click-left="()=>this.$pjaxReplace('/platform/h5/home')"></van-nav-bar>
<van-sticky offset-top="46px" class="mine-filter-wrap">
<div class="mine-filter">