commit
This commit is contained in:
+105
@@ -0,0 +1,105 @@
|
||||
package io.v.nutz.zhgh.coffeeTicket.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.v.nutz.base.page.Pagination;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.base.result.Result;
|
||||
import io.v.nutz.zhgh.coffeeTicket.models.CoffeeTicketActivity;
|
||||
import io.v.nutz.zhgh.coffeeTicket.service.CoffeeTicketActivityService;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
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 org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/coffeeTicket/activity")
|
||||
@Ok("json:full")
|
||||
public class CoffeeTicketActivityController {
|
||||
|
||||
@Inject
|
||||
private CoffeeTicketActivityService coffeeTicketActivityService;
|
||||
|
||||
/**
|
||||
* 打开咖啡抢票活动管理页面。
|
||||
*/
|
||||
@At("")
|
||||
@Ok("beetl:/platform/coffeeTicket/activity/index.html")
|
||||
@RequiresPermissions("coffeeTicket.activity")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询咖啡抢票活动。
|
||||
*
|
||||
* @param pageForm 分页参数,包含 pageNumber、pageSize、排序字段等公共分页信息
|
||||
* @param year 活动开始日期所属年度,传 yyyy 年份;为空时不按年度筛选
|
||||
* @param title 活动标题关键字;为空时不按标题筛选
|
||||
* @return Result,data 为 Pagination,list 为活动列表,totalCount 为总数
|
||||
*/
|
||||
@At
|
||||
@RequiresPermissions("coffeeTicket.activity")
|
||||
public Result pageData(@Valid PageForm pageForm, Integer year, String title) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.andEX("YEAR(activityStartDate)", "=", year);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询一条活动配置,用于管理列表和编辑页面回显。
|
||||
*
|
||||
* @param id 咖啡票活动ID
|
||||
* @return Result,data 为 CoffeeTicketActivity;活动不存在时 data 为空
|
||||
*/
|
||||
@At
|
||||
@RequiresPermissions(value = {"coffeeTicket.activity", "coffeeTicket.new"}, logical = Logical.OR)
|
||||
public Result findOne(@Valid String id) {
|
||||
CoffeeTicketActivity activity = coffeeTicketActivityService.fetch(id);
|
||||
return Result.success(activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除活动配置并同步移除首页活动入口。
|
||||
*
|
||||
* @param id 咖啡票活动ID
|
||||
* @return Result,code 为 0 表示删除成功
|
||||
*/
|
||||
@At
|
||||
@RequiresPermissions("coffeeTicket.activity")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result delete(@Valid String id) {
|
||||
coffeeTicketActivityService.deleteActivity(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启或关闭咖啡抢票活动。
|
||||
*
|
||||
* @param id 活动ID,传 coffee_ticket_activity 表主键
|
||||
* @param enabled true 表示开启报名并推送首页,false 表示关闭报名并移除首页入口
|
||||
* @return Result,成功后前端刷新列表即可看到最新状态
|
||||
*/
|
||||
@At
|
||||
@RequiresPermissions("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().addMsg(Boolean.TRUE.equals(enabled) ? "开启成功" : "关闭成功");
|
||||
} catch (IllegalStateException e) {
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package io.v.nutz.zhgh.coffeeTicket.controller;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.v.nutz.base.result.Result;
|
||||
import io.v.nutz.zhgh.coffeeTicket.models.CoffeeTicketActivity;
|
||||
import io.v.nutz.zhgh.coffeeTicket.service.CoffeeTicketActivityService;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
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 org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/coffeeTicket/new")
|
||||
@Ok("json:full")
|
||||
public class CoffeeTicketNewController {
|
||||
|
||||
@Inject
|
||||
private CoffeeTicketActivityService coffeeTicketActivityService;
|
||||
|
||||
/**
|
||||
* 打开咖啡抢票活动新增或编辑页面。
|
||||
*/
|
||||
@At("")
|
||||
@Ok("beetl:/platform/coffeeTicket/new/index.html")
|
||||
@RequiresPermissions("coffeeTicket.new")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存咖啡抢票活动。
|
||||
*
|
||||
* @param activity 页面提交的活动配置,包含标题、封面、描述、活动开始/结束日期、可预约星期、活动总票数、每人每次可抢票数、每日预约时间段、可报名人员范围、预约方式、活动地点和参与须知
|
||||
* @return Result,成功时 data 为 CoffeeTicketActivity,可通过 data.id 获取活动ID
|
||||
*/
|
||||
@At
|
||||
@RequiresPermissions("coffeeTicket.new")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result save(@Param("data") @Valid CoffeeTicketActivity activity) {
|
||||
Result validateResult = validateActivity(activity);
|
||||
if (validateResult != null) {
|
||||
return validateResult;
|
||||
}
|
||||
try {
|
||||
CoffeeTicketActivity savedActivity = coffeeTicketActivityService.saveActivity(activity);
|
||||
return Result.success(savedActivity);
|
||||
} catch (IllegalStateException e) {
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 只做入参完整性和范围校验,业务保存交给 service 层处理。
|
||||
*/
|
||||
private Result validateActivity(CoffeeTicketActivity activity) {
|
||||
if (activity == null) {
|
||||
return Result.error("活动信息不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(activity.getTitle())) {
|
||||
return Result.error("请输入活动标题");
|
||||
}
|
||||
if (StrUtil.isBlank(activity.getCover())) {
|
||||
return Result.error("请上传活动封面");
|
||||
}
|
||||
if (StrUtil.isBlank(activity.getDescription())) {
|
||||
return Result.error("请输入活动描述");
|
||||
}
|
||||
if (activity.getActivityStartDate() == null || activity.getActivityEndDate() == null) {
|
||||
return Result.error("请选择活动周期");
|
||||
}
|
||||
if (activity.getActivityStartDate().after(activity.getActivityEndDate())) {
|
||||
return Result.error("活动结束日期必须晚于开始日期");
|
||||
}
|
||||
if (CollUtil.isEmpty(activity.getWeekDays())) {
|
||||
return Result.error("请选择可预约星期");
|
||||
}
|
||||
if (activity.getTicketCount() == null || activity.getTicketCount() <= 0) {
|
||||
return Result.error("活动总票数必须大于0");
|
||||
}
|
||||
if (activity.getPerUserTicketNum() == null || activity.getPerUserTicketNum() <= 0) {
|
||||
return Result.error("每人每次可抢票数必须大于0");
|
||||
}
|
||||
if (activity.getPerUserTicketNum() > activity.getTicketCount()) {
|
||||
return Result.error("每人每次可抢票数不能大于活动总票数");
|
||||
}
|
||||
if (StrUtil.isBlank(activity.getReserveStartTime()) || StrUtil.isBlank(activity.getReserveEndTime())) {
|
||||
return Result.error("请选择每日可预约时间段");
|
||||
}
|
||||
if (activity.getReserveStartTime().compareTo(activity.getReserveEndTime()) >= 0) {
|
||||
return Result.error("每日可预约结束时间必须晚于开始时间");
|
||||
}
|
||||
if (StrUtil.isBlank(activity.getReserveMode())) {
|
||||
return Result.error("请选择预约方式");
|
||||
}
|
||||
if (activity.getActivityGroupId() == null) {
|
||||
return Result.error("请选择可报名人员范围");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
package io.v.nutz.zhgh.coffeeTicket.controller;
|
||||
|
||||
import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||
import cn.afterturn.easypoi.excel.entity.ExportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
|
||||
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import io.v.nutz.base.page.Pagination;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.base.result.Result;
|
||||
import io.v.nutz.base.utils.CommonDownloadUtil;
|
||||
import io.v.nutz.zhgh.coffeeTicket.service.CoffeeTicketActivityService;
|
||||
import io.v.nutz.zhgh.coffeeTicket.service.CoffeeTicketRecordService;
|
||||
import io.v.nutz.zhgh.coffeeTicket.vo.CoffeeTicketGrabStatisticsVO;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/coffeeTicket/statistics")
|
||||
@Ok("json:full")
|
||||
public class CoffeeTicketStatisticsController {
|
||||
|
||||
@Inject
|
||||
private CoffeeTicketActivityService coffeeTicketActivityService;
|
||||
|
||||
@Inject
|
||||
private CoffeeTicketRecordService coffeeTicketRecordService;
|
||||
|
||||
/**
|
||||
* 打开咖啡抢票统计页面。
|
||||
*/
|
||||
@At("")
|
||||
@Ok("beetl:/platform/coffeeTicket/statistics/index.html")
|
||||
@RequiresPermissions("coffeeTicket.statistics")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询统计页活动下拉列表。
|
||||
*
|
||||
* @param year 活动开始日期所属年度,传 yyyy 年份;为空时返回全部活动
|
||||
* @return Result,data 为 List<CoffeeTicketActivity>,前端使用 id 作为活动值、title 作为活动名称
|
||||
*/
|
||||
@At
|
||||
@RequiresPermissions("coffeeTicket.statistics")
|
||||
public Result activityList(@Param("year") Integer year) {
|
||||
return Result.success(coffeeTicketActivityService.statisticsActivityList(year));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询咖啡抢票统计明细。
|
||||
*
|
||||
* @param pageForm 分页参数,包含 pageNumber、pageSize、排序字段等公共分页信息
|
||||
* @param year 活动开始日期所属年度,传 yyyy 年份;为空且 activityId 为空时查询全部记录
|
||||
* @param activityId 活动ID,传 coffee_ticket_activity 表主键;为空时按年度查询多个活动
|
||||
* @param unionId 抢票用户所属工会ID,传 user.unionid;为空时不按工会筛选
|
||||
* @param unitId 抢票用户所属单位ID,传 user.unitid;为空时不按单位筛选
|
||||
* @param grabDate 抢票日期,传 yyyy-MM-dd;为空时不按实际抢票日期筛选
|
||||
* @return Result,data 为 Pagination,list 为 CoffeeTicketGrabStatisticsVO 抢票明细列表
|
||||
*/
|
||||
@At
|
||||
@RequiresPermissions("coffeeTicket.statistics")
|
||||
public Result pageData(@Valid PageForm pageForm, @Param("year") Integer year, @Param("activityId") String activityId,
|
||||
@Param("unionId") String unionId, @Param("unitId") String unitId, @Param("grabDate") String grabDate) {
|
||||
Pagination pagination = coffeeTicketRecordService.statisticsPageData(pageForm, year, activityId, unionId, unitId, grabDate);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前统计条件下有抢票记录的日期。
|
||||
*
|
||||
* @param year 活动开始日期所属年度,传 yyyy 年份;为空时不按年度筛选
|
||||
* @param activityId 活动ID,传 coffee_ticket_activity 表主键;为空时按年度查询多个活动
|
||||
* @param unionId 抢票用户所属工会ID,传 user.unionid;为空时不按工会筛选
|
||||
* @param unitId 抢票用户所属单位ID,传 user.unitid;为空时不按单位筛选
|
||||
* @return Result,data 为 yyyy-MM-dd 字符串列表,用于限制抢票日期选择器可选日期
|
||||
*/
|
||||
@At
|
||||
@RequiresPermissions("coffeeTicket.statistics")
|
||||
public Result grabDateList(@Param("year") Integer year, @Param("activityId") String activityId,
|
||||
@Param("unionId") String unionId, @Param("unitId") String unitId) {
|
||||
return Result.success(coffeeTicketRecordService.statisticsGrabDateList(year, activityId, unionId, unitId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出当前筛选条件下的咖啡抢票统计明细。
|
||||
*
|
||||
* @param year 活动开始日期所属年度,传 yyyy 年份;为空时不按年度筛选
|
||||
* @param activityId 活动ID,传 coffee_ticket_activity 表主键;为空时按年度查询多个活动
|
||||
* @param unionId 抢票用户所属工会ID,传 user.unionid;为空时不按工会筛选
|
||||
* @param unitId 抢票用户所属单位ID,传 user.unitid;为空时不按单位筛选
|
||||
* @param grabDate 抢票日期,传 yyyy-MM-dd;为空时不按实际抢票日期筛选
|
||||
* @param response HttpServletResponse,用于输出 xlsx 文件流
|
||||
* @return void,浏览器直接下载 Excel 文件
|
||||
*/
|
||||
@At
|
||||
@Ok("void")
|
||||
@RequiresPermissions("coffeeTicket.statistics")
|
||||
public void export(@Param("year") Integer year, @Param("activityId") String activityId,
|
||||
@Param("unionId") String unionId, @Param("unitId") String unitId,
|
||||
@Param("grabDate") String grabDate, HttpServletResponse response) {
|
||||
List<CoffeeTicketGrabStatisticsVO> list = coffeeTicketRecordService.statisticsExportList(year, activityId, unionId, unitId, grabDate);
|
||||
List<NutMap> exportList = list.stream().map(this::buildExportRow).collect(Collectors.toList());
|
||||
List<ExcelExportEntity> entities = new ArrayList<>();
|
||||
entities.add(new ExcelExportEntity("姓名", "userName", 20));
|
||||
entities.add(new ExcelExportEntity("工号", "loginName", 20));
|
||||
entities.add(new ExcelExportEntity("单位", "unitName", 25));
|
||||
entities.add(new ExcelExportEntity("分工会", "unionName", 25));
|
||||
entities.add(new ExcelExportEntity("活动名称", "activityTitle", 30));
|
||||
entities.add(new ExcelExportEntity("抢票张数", "ticketNum", 15));
|
||||
entities.add(new ExcelExportEntity("实际抢票时间", "grabTime", 25));
|
||||
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, exportList);
|
||||
CommonDownloadUtil.download("咖啡抢票统计.xlsx", workbook, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除统计页面中的一条抢票记录。
|
||||
*
|
||||
* @param recordId 抢票记录ID,传 coffee_ticket_record 表主键;删除后该用户可重新参与该活动抢票
|
||||
* @return Result,code 为 0 表示删除成功;非 0 时 msg 为失败原因
|
||||
*/
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@RequiresPermissions("coffeeTicket.statistics.delete")
|
||||
public Result delete(@Param("recordId") @Valid String recordId) {
|
||||
try {
|
||||
coffeeTicketRecordService.deleteStatisticsRecord(recordId);
|
||||
return Result.success();
|
||||
} catch (IllegalStateException e) {
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装导出行数据,字段与页面当前统计列表保持一致,不包含操作列。
|
||||
*
|
||||
* @param item 一条抢票统计明细
|
||||
* @return NutMap,key 与 ExcelExportEntity 中的字段名对应
|
||||
*/
|
||||
private NutMap buildExportRow(CoffeeTicketGrabStatisticsVO item) {
|
||||
return NutMap.NEW()
|
||||
.setv("userName", item.getUserName())
|
||||
.setv("loginName", item.getLoginName())
|
||||
.setv("unitName", item.getUnitName())
|
||||
.setv("unionName", item.getUnionName())
|
||||
.setv("activityTitle", item.getActivityTitle())
|
||||
.setv("ticketNum", item.getTicketNum())
|
||||
.setv("grabTime", item.getGrabTime() == null ? "" : DateUtil.format(item.getGrabTime(), "yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package io.v.nutz.zhgh.coffeeTicket.h5controller;
|
||||
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.base.result.Result;
|
||||
import io.v.nutz.zhgh.coffeeTicket.service.CoffeeTicketActivityService;
|
||||
import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
||||
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.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/h5/coffeeTicket/list")
|
||||
@Ok("json:full")
|
||||
public class H5CoffeeTicketActivityController {
|
||||
|
||||
@Inject
|
||||
private CoffeeTicketActivityService coffeeTicketActivityService;
|
||||
|
||||
/**
|
||||
* 打开移动端咖啡抢票活动列表页面。
|
||||
*/
|
||||
@At("")
|
||||
@Ok("beetl:/mobile/coffeeTicket/list/index.html")
|
||||
@RequiresAuthentication
|
||||
public void index() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机端抢票活动列表。
|
||||
*
|
||||
* @param pageForm 分页参数,searchKeyword 可传活动标题或活动地点关键字
|
||||
* @param status 状态筛选,all全部、active报名中、upcoming即将开始、ended已结束
|
||||
* @return Result,data 为 Pagination,list 为 CoffeeTicketActivity 活动列表
|
||||
*/
|
||||
@At
|
||||
@RequiresAuthentication
|
||||
public Result pageData(@Valid PageForm pageForm, String status) {
|
||||
return Result.success(coffeeTicketActivityService.h5PageData(pageForm, status));
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package io.v.nutz.zhgh.coffeeTicket.h5controller;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import io.v.nutz.base.result.Result;
|
||||
import io.v.nutz.web.commons.utils.ShiroUtil;
|
||||
import io.v.nutz.zhgh.coffeeTicket.models.CoffeeTicketActivity;
|
||||
import io.v.nutz.zhgh.coffeeTicket.models.CoffeeTicketRecord;
|
||||
import io.v.nutz.zhgh.coffeeTicket.service.CoffeeTicketActivityService;
|
||||
import io.v.nutz.zhgh.coffeeTicket.service.CoffeeTicketRecordService;
|
||||
import io.v.nutz.zhgh.coffeeTicket.vo.CoffeeTicketActivityH5DetailVO;
|
||||
import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
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 org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/h5/coffeeTicket/detail")
|
||||
@Ok("json:full")
|
||||
public class H5CoffeeTicketDetailController {
|
||||
|
||||
@Inject
|
||||
private CoffeeTicketActivityService coffeeTicketActivityService;
|
||||
|
||||
@Inject
|
||||
private CoffeeTicketRecordService coffeeTicketRecordService;
|
||||
|
||||
/**
|
||||
* 打开移动端咖啡抢票活动详情页面。
|
||||
*/
|
||||
@At("")
|
||||
@Ok("beetl:/mobile/coffeeTicket/detail/index.html")
|
||||
@RequiresAuthentication
|
||||
public void index() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询移动端活动详情,并补充当前用户抢票状态。
|
||||
*
|
||||
* @param id 咖啡票活动ID
|
||||
* @return Result,data 为 CoffeeTicketActivityH5DetailVO,包含活动详情和当前用户抢票信息
|
||||
*/
|
||||
@At
|
||||
@RequiresAuthentication
|
||||
public Result findOne(@Valid String id) {
|
||||
CoffeeTicketActivity activity = coffeeTicketActivityService.fetch(id);
|
||||
if (activity == null) {
|
||||
return Result.error("活动不存在");
|
||||
}
|
||||
if (!Boolean.TRUE.equals(activity.getEnabled())) {
|
||||
return Result.error("活动未开启");
|
||||
}
|
||||
String scopeMsg = coffeeTicketActivityService.checkH5ActivityUserScope(activity, ShiroUtil.getUserId());
|
||||
if (scopeMsg != null) {
|
||||
return Result.error(scopeMsg);
|
||||
}
|
||||
CoffeeTicketActivityH5DetailVO detailVO = BeanUtil.copyProperties(activity, CoffeeTicketActivityH5DetailVO.class);
|
||||
CoffeeTicketRecord userRecord = coffeeTicketRecordService.fetchLatestUserRecord(id, ShiroUtil.getUserId());
|
||||
detailVO.setHasGrabbed(userRecord != null);
|
||||
detailVO.setMyTicketNum(userRecord == null ? 0 : coffeeTicketRecordService.countUserTicketNum(id, ShiroUtil.getUserId()));
|
||||
detailVO.setMyGrabTime(userRecord == null ? null : userRecord.getGrabTime());
|
||||
return Result.success(detailVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机端立即抢票并保存记录。
|
||||
*
|
||||
* @param id 活动ID,前端从详情页地址参数或活动详情数据中传入
|
||||
* @param ticketNum 本次抢票张数,页面传步进器所选数量;数值必须大于 0 且不超过活动配置的每人每次上限
|
||||
* @return Result,code 为 0 表示抢票记录保存成功,data 为 CoffeeTicketRecord;非 0 时 msg 为不可抢票原因
|
||||
*/
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@RequiresAuthentication
|
||||
public Result grab(@Param("id") @Valid String id, @Param("ticketNum") Integer ticketNum) {
|
||||
try {
|
||||
CoffeeTicketRecord record = coffeeTicketActivityService.grabH5Ticket(id, ticketNum);
|
||||
return Result.success().addMsg("抢票成功").addData(record);
|
||||
} catch (IllegalStateException e) {
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package io.v.nutz.zhgh.coffeeTicket.h5controller;
|
||||
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.base.result.Result;
|
||||
import io.v.nutz.zhgh.coffeeTicket.service.CoffeeTicketRecordService;
|
||||
import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
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 org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/h5/coffeeTicket/mine")
|
||||
@Ok("json:full")
|
||||
public class H5CoffeeTicketMineController {
|
||||
|
||||
@Inject
|
||||
private CoffeeTicketRecordService coffeeTicketRecordService;
|
||||
|
||||
/**
|
||||
* 打开移动端“我的咖啡票”页面。
|
||||
*/
|
||||
@At("")
|
||||
@Ok("beetl:/mobile/coffeeTicket/mine/index.html")
|
||||
@RequiresAuthentication
|
||||
public void index() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机端我的抢票列表。
|
||||
*
|
||||
* @param pageForm 分页参数,searchKeyword 可传活动标题关键字
|
||||
* @return Result,data 为 Pagination,list 为 CoffeeTicketMineListVO,按抢票时间倒序排列
|
||||
*/
|
||||
@At
|
||||
@RequiresAuthentication
|
||||
public Result pageData(@Valid PageForm pageForm) {
|
||||
return Result.success(coffeeTicketRecordService.h5MinePageData(pageForm));
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机端取消抢票。
|
||||
*
|
||||
* @param recordId 抢票记录ID
|
||||
* @return Result,code 为 0 表示取消成功
|
||||
*/
|
||||
@At("/cancel")
|
||||
@RequiresAuthentication
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result cancel(@Param("recordId") @Valid String recordId) {
|
||||
try {
|
||||
coffeeTicketRecordService.cancelH5Ticket(recordId);
|
||||
return Result.success().addMsg("取消成功");
|
||||
} catch (IllegalStateException e) {
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package io.v.nutz.zhgh.coffeeTicket.models;
|
||||
|
||||
import io.v.nutz.base.model.BaseModel;
|
||||
import io.v.nutz.sys.models.Sys_home_activity;
|
||||
import io.v.nutz.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;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Table("coffee_ticket_activity")
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@Comment("咖啡抢票活动表")
|
||||
public class CoffeeTicketActivity extends BaseModel implements Serializable, SysHomeConvert {
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("活动标题")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String title;
|
||||
|
||||
@Column
|
||||
@Comment("活动封面")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 255)
|
||||
private String cover;
|
||||
|
||||
@Column
|
||||
@Comment("活动描述")
|
||||
@ColDefine(type = ColType.TEXT)
|
||||
private String description;
|
||||
|
||||
@Column
|
||||
@Comment("活动开始日期")
|
||||
@ColDefine(type = ColType.DATE)
|
||||
private Date activityStartDate;
|
||||
|
||||
@Column
|
||||
@Comment("活动结束日期")
|
||||
@ColDefine(type = ColType.DATE)
|
||||
private Date activityEndDate;
|
||||
|
||||
@Column
|
||||
@Comment("可预约星期,保存周一至周日对应的1-7")
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
private List<String> weekDays;
|
||||
|
||||
@Column
|
||||
@Comment("活动总票数")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer ticketCount;
|
||||
|
||||
@Column
|
||||
@Comment("每人每次可抢票数")
|
||||
@Default("1")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer perUserTicketNum;
|
||||
|
||||
@Column
|
||||
@Comment("每日可预约开始时间")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 5)
|
||||
private String reserveStartTime;
|
||||
|
||||
@Column
|
||||
@Comment("每日可预约结束时间")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 5)
|
||||
private String reserveEndTime;
|
||||
|
||||
@Column
|
||||
@Comment("预约方式:WEEKLY每周固定,SPECIAL特殊日期")
|
||||
@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)
|
||||
private String address;
|
||||
|
||||
@Column
|
||||
@Comment("参与须知")
|
||||
@ColDefine(type = ColType.TEXT)
|
||||
private String notice;
|
||||
|
||||
/**
|
||||
* 将咖啡票活动转换为目标项目首页活动数据,供开启活动时同步移动端入口。
|
||||
*
|
||||
* @return Sys_home_activity,包含标题、封面、PC/H5 跳转地址、活动周期和人员范围
|
||||
*/
|
||||
@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.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().getName());
|
||||
return sysHomeActivity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package io.v.nutz.zhgh.coffeeTicket.models;
|
||||
|
||||
import io.v.nutz.base.model.BaseModel;
|
||||
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.Index;
|
||||
import org.nutz.dao.entity.annotation.Name;
|
||||
import org.nutz.dao.entity.annotation.Table;
|
||||
import org.nutz.dao.entity.annotation.TableIndexes;
|
||||
import org.nutz.dao.entity.annotation.TableMeta;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Table("coffee_ticket_record")
|
||||
@TableIndexes({
|
||||
@Index(name = "UK_COFFEE_TICKET_RECORD_ACTIVITY_USER", fields = {"activityId", "userId"}, unique = true)
|
||||
})
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@Comment("咖啡抢票记录表")
|
||||
public class CoffeeTicketRecord extends BaseModel implements Serializable {
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("活动ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String activityId;
|
||||
|
||||
@Column
|
||||
@Comment("活动标题")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String activityTitle;
|
||||
|
||||
@Column
|
||||
@Comment("抢票张数")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer ticketNum;
|
||||
|
||||
@Column
|
||||
@Comment("抢票时间")
|
||||
@ColDefine(type = ColType.DATETIME)
|
||||
private Date grabTime;
|
||||
|
||||
@Column
|
||||
@Comment("用户ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String userId;
|
||||
|
||||
@Column
|
||||
@Comment("姓名")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String userName;
|
||||
|
||||
@Column
|
||||
@Comment("工号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 120)
|
||||
private String loginName;
|
||||
|
||||
@Column
|
||||
@Comment("状态:SUCCESS已抢票")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package io.v.nutz.zhgh.coffeeTicket.service;
|
||||
|
||||
import io.v.nutz.base.service.BaseService;
|
||||
import io.v.nutz.base.page.Pagination;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.zhgh.coffeeTicket.models.CoffeeTicketActivity;
|
||||
import io.v.nutz.zhgh.coffeeTicket.models.CoffeeTicketRecord;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface CoffeeTicketActivityService extends BaseService<CoffeeTicketActivity> {
|
||||
|
||||
/**
|
||||
* 保存咖啡抢票活动基础配置。
|
||||
*
|
||||
* @param activity 页面提交的活动配置,包含标题、封面、活动日期、预约星期、活动总票数、每人每次可抢票数、每日预约时间段、地点和须知等内容
|
||||
* @return CoffeeTicketActivity 保存后的活动实体,新增时会带有生成后的活动ID
|
||||
*/
|
||||
CoffeeTicketActivity saveActivity(CoffeeTicketActivity activity);
|
||||
|
||||
/**
|
||||
* 删除咖啡抢票活动及其基础配置。
|
||||
*
|
||||
* @param id 活动ID
|
||||
*/
|
||||
void deleteActivity(String id);
|
||||
|
||||
/**
|
||||
* 开启或关闭咖啡抢票活动,并同步移动端首页入口。
|
||||
*
|
||||
* @param id 活动ID
|
||||
* @param enabled true 表示开启报名并推送首页,false 表示关闭报名并移除首页入口
|
||||
*/
|
||||
void updateEnabled(String id, Boolean enabled);
|
||||
|
||||
/**
|
||||
* 手机端分页查询可展示的咖啡抢票活动。
|
||||
*
|
||||
* @param pageForm 分页参数,searchKeyword 可传活动标题或活动地点关键字
|
||||
* @param status 活动状态,all全部、active报名中、upcoming即将开始、ended已结束
|
||||
* @return Pagination,list 为 CoffeeTicketActivity 活动实体列表
|
||||
*/
|
||||
Pagination h5PageData(PageForm pageForm, String status);
|
||||
|
||||
/**
|
||||
* 查询统计页面年度活动下拉列表。
|
||||
*
|
||||
* @param year 活动开始日期所属年度,传 yyyy 年份;为空时返回全部咖啡抢票活动
|
||||
* @return List<CoffeeTicketActivity>,按活动开始日期倒序排列
|
||||
*/
|
||||
List<CoffeeTicketActivity> statisticsActivityList(Integer year);
|
||||
|
||||
/**
|
||||
* 校验手机端抢票请求是否符合活动开放规则。
|
||||
*
|
||||
* @param id 活动ID,必须传 coffee_ticket_activity 表中的活动主键
|
||||
* @param ticketNum 本次抢票张数,手机端传步进器所选数量,用于校验不超过活动配置的每人每次上限且活动余票充足
|
||||
* @return String,返回 null 表示校验通过;返回非空字符串表示不可抢票的具体原因
|
||||
*/
|
||||
String checkH5GrabTicket(String id, Integer ticketNum);
|
||||
|
||||
/**
|
||||
* 校验当前用户是否在活动可报名人员范围内。
|
||||
*
|
||||
* @param activity 活动实体,必须包含 activityGroupId
|
||||
* @param userId 当前登录用户ID
|
||||
* @return String,返回 null 表示在范围内;返回非空字符串表示不可参与原因
|
||||
*/
|
||||
String checkH5ActivityUserScope(CoffeeTicketActivity activity, String userId);
|
||||
|
||||
/**
|
||||
* 手机端立即抢票并保存抢票记录。
|
||||
*
|
||||
* @param id 活动ID,必须传 coffee_ticket_activity 表中的活动主键
|
||||
* @param ticketNum 本次抢票张数,手机端传步进器所选数量,必须大于 0 且不超过每人每次上限
|
||||
* @return CoffeeTicketRecord,返回保存后的抢票记录;同一活动会先锁定活动数据行,再校验余票并保存,失败时抛出 IllegalStateException
|
||||
*/
|
||||
CoffeeTicketRecord grabH5Ticket(String id, Integer ticketNum);
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package io.v.nutz.zhgh.coffeeTicket.service;
|
||||
|
||||
import io.v.nutz.base.page.Pagination;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.base.service.BaseService;
|
||||
import io.v.nutz.zhgh.coffeeTicket.models.CoffeeTicketActivity;
|
||||
import io.v.nutz.zhgh.coffeeTicket.models.CoffeeTicketRecord;
|
||||
import io.v.nutz.zhgh.coffeeTicket.vo.CoffeeTicketGrabStatisticsVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface CoffeeTicketRecordService extends BaseService<CoffeeTicketRecord> {
|
||||
|
||||
/**
|
||||
* 统计指定活动已经占用的票数。
|
||||
*
|
||||
* @param activityId 活动ID,必须传 coffee_ticket_activity 表中的活动主键
|
||||
* @return int,返回 coffee_ticket_record 中该活动 ticketNum 的合计值,无记录时返回 0
|
||||
*/
|
||||
int countGrabbedTicketNum(String activityId);
|
||||
|
||||
/**
|
||||
* 判断用户是否已经抢过指定活动的票。
|
||||
*
|
||||
* @param activityId 活动ID
|
||||
* @param userId 当前登录用户ID
|
||||
* @return boolean,true 表示该用户已有该活动抢票记录
|
||||
*/
|
||||
boolean hasUserGrabbed(String activityId, String userId);
|
||||
|
||||
/**
|
||||
* 查询用户在指定活动下最近一次抢票记录。
|
||||
*
|
||||
* @param activityId 活动ID
|
||||
* @param userId 当前登录用户ID
|
||||
* @return CoffeeTicketRecord,返回最近一次抢票记录;没有记录时返回 null
|
||||
*/
|
||||
CoffeeTicketRecord fetchLatestUserRecord(String activityId, String userId);
|
||||
|
||||
/**
|
||||
* 统计用户在指定活动下已抢票数。
|
||||
*
|
||||
* @param activityId 活动ID
|
||||
* @param userId 当前登录用户ID
|
||||
* @return int,返回该用户在当前活动下 ticketNum 的合计值,无记录时返回 0
|
||||
*/
|
||||
int countUserTicketNum(String activityId, String userId);
|
||||
|
||||
/**
|
||||
* 保存手机端抢票记录。
|
||||
*
|
||||
* @param activity 已通过规则校验的活动实体,用于回填活动ID和标题
|
||||
* @param ticketNum 本次抢票张数,当前 H5 详情页默认传 1
|
||||
* @return CoffeeTicketRecord,返回已经写入数据库的抢票记录实体
|
||||
*/
|
||||
CoffeeTicketRecord saveGrabRecord(CoffeeTicketActivity activity, Integer ticketNum);
|
||||
|
||||
/**
|
||||
* 手机端分页查询当前用户的抢票记录。
|
||||
*
|
||||
* @param pageForm 分页参数,searchKeyword 可传活动标题关键字
|
||||
* @return Pagination,list 为 CoffeeTicketMineListVO,按抢票时间倒序排列
|
||||
*/
|
||||
Pagination h5MinePageData(PageForm pageForm);
|
||||
|
||||
/**
|
||||
* PC端分页查询咖啡抢票统计明细。
|
||||
*
|
||||
* @param pageForm 分页参数,包含 pageNumber、pageSize、排序字段等公共分页信息
|
||||
* @param year 活动开始日期所属年度,传 yyyy 年份;为空且 activityId 为空时查询全部记录
|
||||
* @param activityId 活动ID,传 coffee_ticket_activity 表主键;为空时按年度查询多个活动
|
||||
* @param unionId 抢票用户所属工会ID,传 user.unionid;为空时不按工会筛选
|
||||
* @param unitId 抢票用户所属单位ID,传 user.unitid;为空时不按单位筛选
|
||||
* @param grabDate 抢票日期,传 yyyy-MM-dd;为空时不按实际抢票日期筛选
|
||||
* @return Pagination,list 为 CoffeeTicketGrabStatisticsVO,展示用户、活动、票数和抢票时间
|
||||
*/
|
||||
Pagination statisticsPageData(PageForm pageForm, Integer year, String activityId, String unionId, String unitId, String grabDate);
|
||||
|
||||
/**
|
||||
* 查询当前统计条件下的全部抢票明细,用于按页面筛选条件导出。
|
||||
*
|
||||
* @param year 活动开始日期所属年度,传 yyyy 年份;为空时不按年度筛选
|
||||
* @param activityId 活动ID,传 coffee_ticket_activity 表主键;为空时按年度查询多个活动
|
||||
* @param unionId 抢票用户所属工会ID,传 user.unionid;为空时不按工会筛选
|
||||
* @param unitId 抢票用户所属单位ID,传 user.unitid;为空时不按单位筛选
|
||||
* @param grabDate 抢票日期,传 yyyy-MM-dd;为空时不按实际抢票日期筛选
|
||||
* @return List<CoffeeTicketGrabStatisticsVO>,返回与统计列表字段一致的全部匹配数据
|
||||
*/
|
||||
List<CoffeeTicketGrabStatisticsVO> statisticsExportList(Integer year, String activityId, String unionId, String unitId, String grabDate);
|
||||
|
||||
/**
|
||||
* 查询当前统计条件下存在抢票记录的日期。
|
||||
*
|
||||
* @param year 活动开始日期所属年度,传 yyyy 年份;为空时不按年度筛选
|
||||
* @param activityId 活动ID,传 coffee_ticket_activity 表主键;为空时按年度查询多个活动
|
||||
* @param unionId 抢票用户所属工会ID,传 user.unionid;为空时不按工会筛选
|
||||
* @param unitId 抢票用户所属单位ID,传 user.unitid;为空时不按单位筛选
|
||||
* @return List<String>,返回 yyyy-MM-dd 格式的实际抢票日期列表,前端据此控制日期选择器可选范围
|
||||
*/
|
||||
List<String> statisticsGrabDateList(Integer year, String activityId, String unionId, String unitId);
|
||||
|
||||
/**
|
||||
* PC端统计页面删除一条抢票记录。
|
||||
*
|
||||
* @param recordId 抢票记录ID,必须传 coffee_ticket_record 表中的记录主键
|
||||
* @return void,成功则删除该条抢票记录
|
||||
*/
|
||||
void deleteStatisticsRecord(String recordId);
|
||||
|
||||
/**
|
||||
* 手机端取消一条抢票记录。
|
||||
*
|
||||
* @param recordId 要取消的抢票记录ID
|
||||
* @return void,成功则直接删除该条抢票记录
|
||||
*/
|
||||
void cancelH5Ticket(String recordId);
|
||||
}
|
||||
+348
@@ -0,0 +1,348 @@
|
||||
package io.v.nutz.zhgh.coffeeTicket.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.v.nutz.base.page.Pagination;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.base.service.impl.BaseServiceImpl;
|
||||
import io.v.nutz.sys.models.Sys_home_activity;
|
||||
import io.v.nutz.web.commons.utils.ShiroUtil;
|
||||
import io.v.nutz.zhgh.activity.models.ActivityUserScope;
|
||||
import io.v.nutz.zhgh.coffeeTicket.models.CoffeeTicketActivity;
|
||||
import io.v.nutz.zhgh.coffeeTicket.models.CoffeeTicketRecord;
|
||||
import io.v.nutz.zhgh.coffeeTicket.service.CoffeeTicketActivityService;
|
||||
import io.v.nutz.zhgh.coffeeTicket.service.CoffeeTicketRecordService;
|
||||
import io.v.nutz.zhgh.coffeeTicket.vo.CoffeeTicketActivityH5ListVO;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.DaoException;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class CoffeeTicketActivityServiceImpl extends BaseServiceImpl<CoffeeTicketActivity> implements CoffeeTicketActivityService {
|
||||
|
||||
@Inject
|
||||
private CoffeeTicketRecordService coffeeTicketRecordService;
|
||||
|
||||
public CoffeeTicketActivityServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public CoffeeTicketActivity saveActivity(CoffeeTicketActivity activity) {
|
||||
if (activity.getActivityGroupId() == null
|
||||
|| dao().count(ActivityUserScope.class, Cnd.where("groupId", "=", activity.getActivityGroupId())) == 0) {
|
||||
throw new IllegalStateException("请选择有效的可报名人员范围");
|
||||
}
|
||||
if (activity.getEnabled() == null) {
|
||||
activity.setEnabled(false);
|
||||
}
|
||||
if (activity.getPerUserTicketNum() == null) {
|
||||
activity.setPerUserTicketNum(1);
|
||||
}
|
||||
dao().insertOrUpdate(activity);
|
||||
syncHomeActivity(activity);
|
||||
return activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
@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));
|
||||
}
|
||||
Date todayStart = DateUtil.beginOfDay(new Date());
|
||||
Date todayEnd = DateUtil.endOfDay(new Date());
|
||||
if ("active".equals(status)) {
|
||||
cnd.and("activityStartDate", "<=", todayEnd);
|
||||
cnd.and("activityEndDate", ">=", todayStart);
|
||||
} else if ("upcoming".equals(status)) {
|
||||
cnd.and("activityStartDate", ">", todayEnd);
|
||||
} 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);
|
||||
List<CoffeeTicketActivityH5ListVO> voList = activityList.stream().map(this::buildH5ListVO).collect(Collectors.toList());
|
||||
pagination.setList(voList);
|
||||
return pagination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CoffeeTicketActivity> statisticsActivityList(Integer year) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.andEX("YEAR(activityStartDate)", "=", year);
|
||||
cnd.desc("activityStartDate");
|
||||
return dao().query(CoffeeTicketActivity.class, cnd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String checkH5GrabTicket(String id, Integer ticketNum) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return "活动ID不能为空";
|
||||
}
|
||||
CoffeeTicketActivity activity = fetch(id);
|
||||
if (activity == null) {
|
||||
return "活动不存在";
|
||||
}
|
||||
int currentTicketNum = ticketNum == null ? getPerUserTicketNum(activity) : ticketNum;
|
||||
if (currentTicketNum <= 0) {
|
||||
return "抢票张数必须大于0";
|
||||
}
|
||||
if (!Boolean.TRUE.equals(activity.getEnabled())) {
|
||||
return "活动未开启";
|
||||
}
|
||||
String scopeMsg = checkH5ActivityUserScope(activity, ShiroUtil.getUserId());
|
||||
if (scopeMsg != null) {
|
||||
return scopeMsg;
|
||||
}
|
||||
Date now = new Date();
|
||||
if (activity.getActivityStartDate() == null || activity.getActivityEndDate() == null) {
|
||||
return "活动周期未配置";
|
||||
}
|
||||
if (now.before(DateUtil.beginOfDay(activity.getActivityStartDate()))) {
|
||||
return "活动尚未开始";
|
||||
}
|
||||
if (now.after(DateUtil.endOfDay(activity.getActivityEndDate()))) {
|
||||
return "活动已结束";
|
||||
}
|
||||
List<String> weekDays = activity.getWeekDays();
|
||||
if (weekDays == null || weekDays.isEmpty()) {
|
||||
return "活动未配置可预约星期";
|
||||
}
|
||||
if (!weekDays.contains(getTodayWeekValue(now))) {
|
||||
return "今天不在可预约星期范围内";
|
||||
}
|
||||
if (StrUtil.isBlank(activity.getReserveStartTime()) || StrUtil.isBlank(activity.getReserveEndTime())) {
|
||||
return "活动未配置每日可预约时间段";
|
||||
}
|
||||
String currentTime = DateUtil.format(now, "HH:mm");
|
||||
if (currentTime.compareTo(activity.getReserveStartTime()) < 0 || currentTime.compareTo(activity.getReserveEndTime()) > 0) {
|
||||
return "当前不在时间段内";
|
||||
}
|
||||
if (activity.getTicketCount() == null || activity.getTicketCount() <= 0) {
|
||||
return "活动票数不足";
|
||||
}
|
||||
if (currentTicketNum > getPerUserTicketNum(activity)) {
|
||||
return "本次抢票张数超过每人每次可抢票数";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String checkH5ActivityUserScope(CoffeeTicketActivity activity, String userId) {
|
||||
if (activity == null) {
|
||||
return "活动不存在";
|
||||
}
|
||||
if (activity.getActivityGroupId() == null) {
|
||||
return "活动未配置可报名人员范围";
|
||||
}
|
||||
int scopeUserCount = dao().count(ActivityUserScope.class,
|
||||
Cnd.where("groupId", "=", activity.getActivityGroupId()).and("userId", "=", userId));
|
||||
if (scopeUserCount == 0) {
|
||||
return "您不在本活动可报名人员范围内";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public CoffeeTicketRecord grabH5Ticket(String id, Integer ticketNum) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
throw new IllegalStateException("活动ID不能为空");
|
||||
}
|
||||
try {
|
||||
lockActivity(id);
|
||||
String errorMsg = checkH5GrabTicket(id, ticketNum);
|
||||
if (errorMsg != null) {
|
||||
throw new IllegalStateException(errorMsg);
|
||||
}
|
||||
CoffeeTicketActivity activity = fetch(id);
|
||||
int currentTicketNum = ticketNum == null ? getPerUserTicketNum(activity) : ticketNum;
|
||||
if (coffeeTicketRecordService.hasUserGrabbed(activity.getId(), ShiroUtil.getUserId())) {
|
||||
throw new IllegalStateException("您已抢过该活动票");
|
||||
}
|
||||
int grabbedTicketNum = coffeeTicketRecordService.countGrabbedTicketNum(activity.getId());
|
||||
int remainingTicketNum = activity.getTicketCount() - grabbedTicketNum;
|
||||
if (remainingTicketNum < currentTicketNum) {
|
||||
throw new IllegalStateException("票数不足");
|
||||
}
|
||||
return coffeeTicketRecordService.saveGrabRecord(activity, currentTicketNum);
|
||||
} catch (DaoException e) {
|
||||
if (isDuplicateGrabException(e)) {
|
||||
throw new IllegalStateException("您已抢过该活动票");
|
||||
}
|
||||
if (isGrabLockException(e)) {
|
||||
throw new IllegalStateException("当前抢票人数较多,请稍后重试");
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在当前抢票事务中锁定活动主键对应的数据行,保证同一活动的余票统计和记录保存串行执行。
|
||||
*
|
||||
* @param id 活动ID,必须传 coffee_ticket_activity 表主键
|
||||
*/
|
||||
private void lockActivity(String id) {
|
||||
Sql lockSql = Sqls.fetchRecord("SELECT id FROM coffee_ticket_activity WHERE id = @id FOR UPDATE")
|
||||
.setParam("id", id);
|
||||
dao().execute(lockSql);
|
||||
if (lockSql.getResult() == null) {
|
||||
throw new IllegalStateException("活动不存在");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装手机端列表展示对象,补齐活动总票数、已抢票数和剩余票数字段。
|
||||
*
|
||||
* @param activity 当前页活动实体
|
||||
* @return CoffeeTicketActivityH5ListVO,包含活动基础字段和票数统计字段
|
||||
*/
|
||||
private CoffeeTicketActivityH5ListVO buildH5ListVO(CoffeeTicketActivity activity) {
|
||||
CoffeeTicketActivityH5ListVO vo = BeanUtil.copyProperties(activity, CoffeeTicketActivityH5ListVO.class);
|
||||
int totalTicketNum = activity.getTicketCount() == null ? 0 : activity.getTicketCount();
|
||||
int grabbedTicketNum = coffeeTicketRecordService.countGrabbedTicketNum(activity.getId());
|
||||
vo.setTotalTicketNum(totalTicketNum);
|
||||
vo.setGrabbedTicketNum(grabbedTicketNum);
|
||||
vo.setRemainingTicketNum(Math.max(totalTicketNum - grabbedTicketNum, 0));
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取活动的单人单次抢票数量;历史活动尚未配置该字段时按 1 张兼容。
|
||||
*
|
||||
* @param activity 当前抢票活动
|
||||
* @return int 每人每次可抢票数,最小为 1
|
||||
*/
|
||||
private int getPerUserTicketNum(CoffeeTicketActivity activity) {
|
||||
if (activity == null || activity.getPerUserTicketNum() == null || activity.getPerUserTicketNum() <= 0) {
|
||||
return 1;
|
||||
}
|
||||
return activity.getPerUserTicketNum();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按当前查询条件先收集活动分组,再使用目标项目已落库的活动人员范围过滤当前用户可参与的分组。
|
||||
*
|
||||
* @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()
|
||||
.collect(Collectors.toList());
|
||||
if (groupIds.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
return dao().query(ActivityUserScope.class,
|
||||
Cnd.where("groupId", "in", groupIds).and("userId", "=", ShiroUtil.getUserId()))
|
||||
.stream()
|
||||
.map(ActivityUserScope::getGroupId)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步移动端首页入口。活动开启时写入首页,关闭时删除首页入口。
|
||||
*
|
||||
* @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 表示周日。
|
||||
*
|
||||
* @param date 需要判断星期的日期
|
||||
* @return String,返回当前日期对应的星期配置值
|
||||
*/
|
||||
private String getTodayWeekValue(Date date) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(date);
|
||||
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
|
||||
if (dayOfWeek == Calendar.SUNDAY) {
|
||||
return "7";
|
||||
}
|
||||
return String.valueOf(dayOfWeek - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库唯一索引用于兜底防止同一用户并发重复抢同一活动。
|
||||
*
|
||||
* @param e 抢票记录插入时抛出的数据库异常
|
||||
* @return boolean,true 表示命中 activityId + userId 唯一约束
|
||||
*/
|
||||
private boolean isDuplicateGrabException(DaoException e) {
|
||||
String message = e.getMessage();
|
||||
return StrUtil.containsIgnoreCase(message, "Duplicate")
|
||||
|| StrUtil.containsIgnoreCase(message, "UK_COFFEE_TICKET_RECORD_ACTIVITY_USER");
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断数据库异常是否由行锁等待超时或死锁引起,用于向手机端返回可理解的重试提示。
|
||||
*
|
||||
* @param e 抢票事务执行期间产生的数据库异常
|
||||
* @return boolean,true 表示当前请求应提示用户稍后重试
|
||||
*/
|
||||
private boolean isGrabLockException(DaoException e) {
|
||||
Throwable cause = e;
|
||||
while (cause != null) {
|
||||
String message = cause.getMessage();
|
||||
if (StrUtil.containsIgnoreCase(message, "Lock wait timeout")
|
||||
|| StrUtil.containsIgnoreCase(message, "Deadlock found")) {
|
||||
return true;
|
||||
}
|
||||
cause = cause.getCause();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+296
@@ -0,0 +1,296 @@
|
||||
package io.v.nutz.zhgh.coffeeTicket.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import io.v.nutz.base.page.Pagination;
|
||||
import io.v.nutz.base.query.PageForm;
|
||||
import io.v.nutz.base.service.impl.BaseServiceImpl;
|
||||
import io.v.nutz.web.commons.utils.ShiroUtil;
|
||||
import io.v.nutz.zhgh.coffeeTicket.models.CoffeeTicketActivity;
|
||||
import io.v.nutz.zhgh.coffeeTicket.models.CoffeeTicketRecord;
|
||||
import io.v.nutz.zhgh.coffeeTicket.service.CoffeeTicketRecordService;
|
||||
import io.v.nutz.zhgh.coffeeTicket.vo.CoffeeTicketGrabStatisticsVO;
|
||||
import io.v.nutz.zhgh.coffeeTicket.vo.CoffeeTicketMineListVO;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class CoffeeTicketRecordServiceImpl extends BaseServiceImpl<CoffeeTicketRecord> implements CoffeeTicketRecordService {
|
||||
|
||||
public CoffeeTicketRecordServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countGrabbedTicketNum(String activityId) {
|
||||
return dao().func(CoffeeTicketRecord.class, "sum", "ticketNum", Cnd.where("activityId", "=", activityId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasUserGrabbed(String activityId, String userId) {
|
||||
return dao().count(CoffeeTicketRecord.class, Cnd.where("activityId", "=", activityId).and("userId", "=", userId)) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoffeeTicketRecord fetchLatestUserRecord(String activityId, String userId) {
|
||||
Cnd cnd = Cnd.where("activityId", "=", activityId).and("userId", "=", userId);
|
||||
cnd.desc("grabTime");
|
||||
return dao().fetch(CoffeeTicketRecord.class, cnd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countUserTicketNum(String activityId, String userId) {
|
||||
return dao().func(CoffeeTicketRecord.class, "sum", "ticketNum", Cnd.where("activityId", "=", activityId).and("userId", "=", userId));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public CoffeeTicketRecord saveGrabRecord(CoffeeTicketActivity activity, Integer ticketNum) {
|
||||
CoffeeTicketRecord record = new CoffeeTicketRecord();
|
||||
record.setActivityId(activity.getId());
|
||||
record.setActivityTitle(activity.getTitle());
|
||||
record.setTicketNum(ticketNum);
|
||||
record.setGrabTime(new Date());
|
||||
record.setUserId(ShiroUtil.getUserId());
|
||||
record.setUserName(ShiroUtil.getPlatformUsername());
|
||||
record.setLoginName(ShiroUtil.getPlatformLoginname());
|
||||
record.setStatus("SUCCESS");
|
||||
return dao().insert(record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pagination h5MinePageData(PageForm pageForm) {
|
||||
Cnd cnd = Cnd.where("userId", "=", ShiroUtil.getUserId());
|
||||
cnd.desc("grabTime");
|
||||
List<CoffeeTicketRecord> recordList = dao().query(CoffeeTicketRecord.class, cnd);
|
||||
List<CoffeeTicketMineListVO> voList = recordList.stream().map(this::buildMineListVO).filter(item -> matchKeyword(item, pageForm.getSearchKeyword())).collect(Collectors.toList());
|
||||
int totalCount = voList.size();
|
||||
int fromIndex = Math.min((pageForm.getPageNumber() - 1) * pageForm.getPageSize(), totalCount);
|
||||
int toIndex = Math.min(fromIndex + pageForm.getPageSize(), totalCount);
|
||||
return new Pagination(pageForm.getPageNumber(), pageForm.getPageSize(), totalCount, new ArrayList<>(voList.subList(fromIndex, toIndex)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pagination statisticsPageData(PageForm pageForm, Integer year, String activityId, String unionId, String unitId, String grabDate) {
|
||||
Sql sql = buildStatisticsSql(year, activityId, unionId, unitId, grabDate);
|
||||
Pagination pagination = listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
List<NutMap> recordList = pagination.getList();
|
||||
List<CoffeeTicketGrabStatisticsVO> voList = recordList.stream().map(this::buildGrabStatisticsVO).collect(Collectors.toList());
|
||||
pagination.setList(voList);
|
||||
return pagination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CoffeeTicketGrabStatisticsVO> statisticsExportList(Integer year, String activityId, String unionId, String unitId, String grabDate) {
|
||||
Sql sql = buildStatisticsSql(year, activityId, unionId, unitId, grabDate);
|
||||
return listMap(sql).stream().map(this::buildGrabStatisticsVO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> statisticsGrabDateList(Integer year, String activityId, String unionId, String unitId) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
DATE_FORMAT(r.grabTime, '%Y-%m-%d') AS grabDate
|
||||
FROM
|
||||
coffee_ticket_record r
|
||||
LEFT JOIN `user` u ON u.id = r.userId
|
||||
LEFT JOIN coffee_ticket_activity a ON a.id = r.activityId
|
||||
$condition
|
||||
GROUP BY DATE_FORMAT(r.grabTime, '%Y-%m-%d')
|
||||
ORDER BY grabDate DESC
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.andEX("r.activityId", "=", activityId);
|
||||
cnd.andEX("YEAR(a.activityStartDate)", "=", year);
|
||||
cnd.andEX("u.unionid", "=", unionId);
|
||||
cnd.andEX("u.unitid", "=", unitId);
|
||||
sql.setCondition(cnd);
|
||||
List<NutMap> list = listMap(sql);
|
||||
return list.stream().map(item -> item.getString("grabDate")).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void deleteStatisticsRecord(String recordId) {
|
||||
if (StrUtil.isBlank(recordId)) {
|
||||
throw new IllegalStateException("抢票记录ID不能为空");
|
||||
}
|
||||
CoffeeTicketRecord record = fetch(recordId);
|
||||
if (record == null) {
|
||||
throw new IllegalStateException("抢票记录不存在");
|
||||
}
|
||||
dao().delete(record);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void cancelH5Ticket(String recordId) {
|
||||
if (StrUtil.isBlank(recordId)) {
|
||||
throw new IllegalStateException("抢票记录ID不能为空");
|
||||
}
|
||||
CoffeeTicketRecord record = fetch(recordId);
|
||||
if (record == null) {
|
||||
throw new IllegalStateException("抢票记录不存在");
|
||||
}
|
||||
if (!ShiroUtil.getUserId().equals(record.getUserId())) {
|
||||
throw new IllegalStateException("无权取消该抢票记录");
|
||||
}
|
||||
CoffeeTicketActivity activity = dao().fetch(CoffeeTicketActivity.class, record.getActivityId());
|
||||
if (activity == null) {
|
||||
throw new IllegalStateException("活动不存在");
|
||||
}
|
||||
if (!isWithinGrabWindow(activity)) {
|
||||
throw new IllegalStateException("当前不在可取消时间内");
|
||||
}
|
||||
dao().delete(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将抢票记录和活动信息组合成手机端“我的抢票”卡片数据。
|
||||
*
|
||||
* @param record 当前用户的一条抢票记录
|
||||
* @return CoffeeTicketMineListVO,包含记录字段和活动字段
|
||||
*/
|
||||
private CoffeeTicketMineListVO buildMineListVO(CoffeeTicketRecord record) {
|
||||
CoffeeTicketActivity activity = dao().fetch(CoffeeTicketActivity.class, record.getActivityId());
|
||||
CoffeeTicketMineListVO vo = new CoffeeTicketMineListVO();
|
||||
vo.setRecordId(record.getId());
|
||||
vo.setActivityId(record.getActivityId());
|
||||
vo.setTitle(activity == null ? record.getActivityTitle() : activity.getTitle());
|
||||
vo.setCover(activity == null ? null : activity.getCover());
|
||||
vo.setDescription(activity == null ? null : activity.getDescription());
|
||||
vo.setActivityStartDate(activity == null ? null : activity.getActivityStartDate());
|
||||
vo.setActivityEndDate(activity == null ? null : activity.getActivityEndDate());
|
||||
vo.setReserveStartTime(activity == null ? null : activity.getReserveStartTime());
|
||||
vo.setReserveEndTime(activity == null ? null : activity.getReserveEndTime());
|
||||
vo.setAddress(activity == null ? null : activity.getAddress());
|
||||
vo.setTicketNum(record.getTicketNum());
|
||||
vo.setGrabTime(record.getGrabTime());
|
||||
vo.setCanCancel(activity != null && isWithinGrabWindow(activity));
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装PC端抢票统计明细,保留记录表中的用户、活动、票数和实际抢票时间。
|
||||
*
|
||||
* @param record 当前页的一条抢票记录
|
||||
* @return CoffeeTicketGrabStatisticsVO,返回给统计页面展示的明细数据
|
||||
*/
|
||||
private CoffeeTicketGrabStatisticsVO buildGrabStatisticsVO(NutMap record) {
|
||||
CoffeeTicketGrabStatisticsVO vo = new CoffeeTicketGrabStatisticsVO();
|
||||
vo.setRecordId(record.getString("recordId"));
|
||||
vo.setActivityId(record.getString("activityId"));
|
||||
vo.setActivityTitle(record.getString("activityTitle"));
|
||||
vo.setUserId(record.getString("userId"));
|
||||
vo.setUserName(record.getString("userName"));
|
||||
vo.setLoginName(record.getString("loginName"));
|
||||
vo.setUnitId(record.getString("unitId"));
|
||||
vo.setUnitName(record.getString("unitName"));
|
||||
vo.setUnionId(record.getString("unionId"));
|
||||
vo.setUnionName(record.getString("unionName"));
|
||||
vo.setTicketNum(record.getInt("ticketNum"));
|
||||
vo.setGrabTime(record.getAs("grabTime", Date.class));
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建PC端抢票统计查询SQL,分页列表和导出共用同一套筛选条件,避免页面展示与导出结果不一致。
|
||||
*
|
||||
* @param year 活动开始日期所属年度
|
||||
* @param activityId 活动ID
|
||||
* @param unionId 抢票用户所属工会ID
|
||||
* @param unitId 抢票用户所属单位ID
|
||||
* @param grabDate 实际抢票日期,格式 yyyy-MM-dd
|
||||
* @return Sql,已带查询条件和按抢票时间倒序排序
|
||||
*/
|
||||
private Sql buildStatisticsSql(Integer year, String activityId, String unionId, String unitId, String grabDate) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
r.id AS recordId,
|
||||
r.activityId,
|
||||
r.activityTitle,
|
||||
r.userId,
|
||||
r.userName,
|
||||
r.loginName,
|
||||
u.unitid AS unitId,
|
||||
u.unitname AS unitName,
|
||||
u.unionid AS unionId,
|
||||
u.unionname AS unionName,
|
||||
r.ticketNum,
|
||||
r.grabTime
|
||||
FROM
|
||||
coffee_ticket_record r
|
||||
LEFT JOIN `user` u ON u.id = r.userId
|
||||
LEFT JOIN coffee_ticket_activity a ON a.id = r.activityId
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.andEX("r.activityId", "=", activityId);
|
||||
cnd.andEX("YEAR(a.activityStartDate)", "=", year);
|
||||
cnd.andEX("u.unionid", "=", unionId);
|
||||
cnd.andEX("u.unitid", "=", unitId);
|
||||
if (StrUtil.isNotBlank(grabDate)) {
|
||||
Date date = DateUtil.parse(grabDate, DatePattern.NORM_DATE_PATTERN);
|
||||
cnd.and("r.grabTime", ">=", DateUtil.beginOfDay(date));
|
||||
cnd.and("r.grabTime", "<=", DateUtil.endOfDay(date));
|
||||
}
|
||||
cnd.desc("r.grabTime");
|
||||
sql.setCondition(cnd);
|
||||
return sql;
|
||||
}
|
||||
|
||||
private boolean isWithinGrabWindow(CoffeeTicketActivity activity) {
|
||||
Date now = new Date();
|
||||
if (activity.getActivityStartDate() == null || activity.getActivityEndDate() == null) {
|
||||
return false;
|
||||
}
|
||||
if (now.before(DateUtil.beginOfDay(activity.getActivityStartDate()))) {
|
||||
return false;
|
||||
}
|
||||
if (now.after(DateUtil.endOfDay(activity.getActivityEndDate()))) {
|
||||
return false;
|
||||
}
|
||||
List<String> weekDays = activity.getWeekDays();
|
||||
if (weekDays == null || weekDays.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (!weekDays.contains(getTodayWeekValue(now))) {
|
||||
return false;
|
||||
}
|
||||
if (StrUtil.isBlank(activity.getReserveStartTime()) || StrUtil.isBlank(activity.getReserveEndTime())) {
|
||||
return false;
|
||||
}
|
||||
String currentTime = DateUtil.format(now, "HH:mm");
|
||||
if (currentTime.compareTo(activity.getReserveStartTime()) < 0 || currentTime.compareTo(activity.getReserveEndTime()) > 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getTodayWeekValue(Date date) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(date);
|
||||
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
|
||||
if (dayOfWeek == Calendar.SUNDAY) {
|
||||
return "7";
|
||||
}
|
||||
return String.valueOf(dayOfWeek - 1);
|
||||
}
|
||||
|
||||
private boolean matchKeyword(CoffeeTicketMineListVO item, String keyword) {
|
||||
return StrUtil.isBlank(keyword) || StrUtil.containsIgnoreCase(item.getTitle(), keyword);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.v.nutz.zhgh.coffeeTicket.vo;
|
||||
|
||||
import io.v.nutz.zhgh.coffeeTicket.models.CoffeeTicketActivity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class CoffeeTicketActivityH5DetailVO extends CoffeeTicketActivity implements Serializable {
|
||||
|
||||
/**
|
||||
* 当前登录用户是否已经抢过该活动票。
|
||||
*/
|
||||
private Boolean hasGrabbed;
|
||||
|
||||
/**
|
||||
* 当前登录用户在该活动下已抢票张数合计。
|
||||
*/
|
||||
private Integer myTicketNum;
|
||||
|
||||
/**
|
||||
* 当前登录用户最近一次抢票时间。
|
||||
*/
|
||||
private Date myGrabTime;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.v.nutz.zhgh.coffeeTicket.vo;
|
||||
|
||||
import io.v.nutz.zhgh.coffeeTicket.models.CoffeeTicketActivity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class CoffeeTicketActivityH5ListVO extends CoffeeTicketActivity implements Serializable {
|
||||
|
||||
/**
|
||||
* 活动总票数,当前业务按活动表 ticketCount 字段计算。
|
||||
*/
|
||||
private Integer totalTicketNum;
|
||||
|
||||
/**
|
||||
* 已抢票数,统计抢票记录表中当前活动 ticketNum 合计。
|
||||
*/
|
||||
private Integer grabbedTicketNum;
|
||||
|
||||
/**
|
||||
* 剩余票数,按 totalTicketNum - grabbedTicketNum 计算,小于 0 时按 0 返回。
|
||||
*/
|
||||
private Integer remainingTicketNum;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package io.v.nutz.zhgh.coffeeTicket.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class CoffeeTicketGrabStatisticsVO implements Serializable {
|
||||
|
||||
/**
|
||||
* 抢票记录ID,用于定位一条用户抢票明细。
|
||||
*/
|
||||
private String recordId;
|
||||
|
||||
/**
|
||||
* 活动ID,对应 coffee_ticket_activity 表主键。
|
||||
*/
|
||||
private String activityId;
|
||||
|
||||
/**
|
||||
* 活动标题,优先使用当前活动表标题,活动被删除时使用记录表冗余标题。
|
||||
*/
|
||||
private String activityTitle;
|
||||
|
||||
/**
|
||||
* 抢票用户ID,对应当前登录用户体系中的用户主键。
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 抢票用户姓名,来源于抢票时保存的用户信息。
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 抢票用户工号,来源于抢票时保存的登录名。
|
||||
*/
|
||||
private String loginName;
|
||||
|
||||
/**
|
||||
* 抢票用户所属单位ID,来源于目标项目 user 视图。
|
||||
*/
|
||||
private String unitId;
|
||||
|
||||
/**
|
||||
* 抢票用户所属单位名称,来源于目标项目 user 视图。
|
||||
*/
|
||||
private String unitName;
|
||||
|
||||
/**
|
||||
* 抢票用户所属工会ID,来源于目标项目 user 视图。
|
||||
*/
|
||||
private String unionId;
|
||||
|
||||
/**
|
||||
* 抢票用户所属工会名称,来源于目标项目 user 视图。
|
||||
*/
|
||||
private String unionName;
|
||||
|
||||
/**
|
||||
* 本次抢票张数。
|
||||
*/
|
||||
private Integer ticketNum;
|
||||
|
||||
/**
|
||||
* 用户实际点击抢票并生成记录的时间。
|
||||
*/
|
||||
private Date grabTime;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.v.nutz.zhgh.coffeeTicket.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class CoffeeTicketMineListVO implements Serializable {
|
||||
|
||||
private String recordId;
|
||||
private String activityId;
|
||||
private String title;
|
||||
private String cover;
|
||||
private String description;
|
||||
private Date activityStartDate;
|
||||
private Date activityEndDate;
|
||||
private String reserveStartTime;
|
||||
private String reserveEndTime;
|
||||
private String address;
|
||||
private Integer ticketNum;
|
||||
private Date grabTime;
|
||||
private Boolean canCancel;
|
||||
}
|
||||
@@ -0,0 +1,618 @@
|
||||
<!--#
|
||||
layout("/mobile/platform.html"){
|
||||
#-->
|
||||
<style scoped>
|
||||
body {
|
||||
background: #f5f7fb;
|
||||
}
|
||||
|
||||
.detail-page {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: #f5f7fb;
|
||||
}
|
||||
|
||||
.detail-cover {
|
||||
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%;
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.detail-cover-empty {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #9aa8ba;
|
||||
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: 118px;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
margin: 0 12px 12px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
border-radius: 10px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 6px 18px rgba(25, 54, 94, .08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.detail-main {
|
||||
padding: 16px 14px 8px;
|
||||
}
|
||||
|
||||
.detail-title-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
color: #1f2d3d;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.detail-status {
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 12px;
|
||||
background: #e7f8ee;
|
||||
color: #21b45b;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.detail-status.upcoming {
|
||||
background: #fff5e5;
|
||||
color: #f29b22;
|
||||
}
|
||||
|
||||
.detail-status.ended {
|
||||
background: #eef0f5;
|
||||
color: #8d98a8;
|
||||
}
|
||||
|
||||
.detail-desc {
|
||||
color: #4b5565;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.detail-info {
|
||||
margin-top: 12px;
|
||||
border-top: 1px solid #eef1f6;
|
||||
}
|
||||
|
||||
.detail-info-row {
|
||||
display: grid;
|
||||
grid-template-columns: 22px 96px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
min-height: 44px;
|
||||
border-bottom: 1px solid #eef1f6;
|
||||
color: #334155;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.detail-info-row i {
|
||||
color: #71829b;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.detail-info-label {
|
||||
color: #4b5565;
|
||||
}
|
||||
|
||||
.detail-info-value {
|
||||
text-align: right;
|
||||
color: #243044;
|
||||
font-weight: normal;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.notice-card {
|
||||
margin: 0 12px 12px;
|
||||
padding: 14px;
|
||||
border-radius: 10px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 6px 18px rgba(25, 54, 94, .08);
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.notice-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
color: #1f2d3d;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.notice-title i {
|
||||
color: #1f73ff;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.notice-content {
|
||||
max-width: 100%;
|
||||
color: #5f6f86;
|
||||
font-size: 12px;
|
||||
line-height: 22px;
|
||||
white-space: pre-line;
|
||||
word-break: break-all;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.notice-content-collapsed {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 10;
|
||||
}
|
||||
|
||||
.notice-expand-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #1f73ff;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.notice-expand-button i {
|
||||
margin-left: 3px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.detail-footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
|
||||
border-top: 1px solid #eef1f6;
|
||||
background: #ffffff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.detail-ticket-number {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 40px;
|
||||
margin-bottom: 8px;
|
||||
color: #334155;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.detail-ticket-number-title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.detail-ticket-number-tip {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.detail-ticket-number .van-stepper--round .van-stepper__plus {
|
||||
background-color: #1f73ff;
|
||||
}
|
||||
|
||||
.detail-ticket-number .van-stepper--round .van-stepper__minus {
|
||||
color: #1f73ff;
|
||||
border-color: #1f73ff;
|
||||
}
|
||||
|
||||
.detail-action {
|
||||
height: 42px;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak class="detail-page">
|
||||
<van-nav-bar title="活动详情" left-text="返回" left-arrow placeholder fixed @click-left="historyBack">
|
||||
</van-nav-bar>
|
||||
|
||||
<div class="detail-cover">
|
||||
<img v-if="formData.cover" :src="CREATE_PREVIEW_URL(formData.cover)">
|
||||
<div v-else class="detail-cover-empty">暂无封面</div>
|
||||
</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">
|
||||
<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-user-o"></i>
|
||||
<div class="detail-info-label">每人每次可抢</div>
|
||||
<div class="detail-info-value">{{formData.perUserTicketNum || 1}} 张</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 class="notice-card">
|
||||
<div class="notice-title">
|
||||
<i class="van-icon van-icon-bookmark-o"></i>
|
||||
<span>活动描述</span>
|
||||
</div>
|
||||
<div class="notice-content" :class="{'notice-content-collapsed': !descriptionExpanded}">
|
||||
{{formData.description || '暂无活动描述'}}
|
||||
</div>
|
||||
<button v-if="formData.description" type="button" class="notice-expand-button" @click="toggleDescription">
|
||||
{{descriptionExpanded ? '收起' : '展开'}}
|
||||
<i class="van-icon" :class="descriptionExpanded ? 'van-icon-arrow-up' : 'van-icon-arrow-down'"></i>
|
||||
</button>
|
||||
</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>
|
||||
|
||||
<div class="detail-footer">
|
||||
<!-- 本次抢票数量只能在活动配置的单次上限内选择。 -->
|
||||
<div v-if="ticketActionInfo.visible" class="detail-ticket-number">
|
||||
<div class="detail-ticket-number-title">
|
||||
<span>本次抢票</span>
|
||||
<span class="detail-ticket-number-tip">每人可抢 {{formData.perUserTicketNum || 1}} 张</span>
|
||||
</div>
|
||||
<van-stepper
|
||||
v-model="grabTicketNum"
|
||||
:min="1"
|
||||
:max="maxGrabTicketNum"
|
||||
integer
|
||||
disable-input
|
||||
button-size="26"
|
||||
theme="round"
|
||||
:disabled="ticketActionInfo.disabled || ticketLoading">
|
||||
</van-stepper>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
formLoading: false,
|
||||
ticketLoading: false,
|
||||
grabTicketNum: 1,
|
||||
descriptionExpanded: false,
|
||||
nowTimestamp: Date.now(),
|
||||
ticketTimer: null,
|
||||
formData: {},
|
||||
weekMap: {
|
||||
"1": "周一",
|
||||
"2": "周二",
|
||||
"3": "周三",
|
||||
"4": "周四",
|
||||
"5": "周五",
|
||||
"6": "周六",
|
||||
"7": "周日"
|
||||
},
|
||||
defaultNotice: "如有疑问,请联系校工会或关注活动公告。"
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
statusInfo() {
|
||||
const now = this.$moment()
|
||||
if (this.formData.activityEndDate && now.isAfter(this.$moment(this.formData.activityEndDate).endOf("day"))) {
|
||||
return { text: "已结束", className: "ended" }
|
||||
}
|
||||
if (this.formData.activityStartDate && now.isBefore(this.$moment(this.formData.activityStartDate).startOf("day"))) {
|
||||
return { text: "即将开始", className: "upcoming" }
|
||||
}
|
||||
return { text: "报名中", className: "" }
|
||||
},
|
||||
noticeText() {
|
||||
return this.formData.notice || this.defaultNotice
|
||||
},
|
||||
maxGrabTicketNum() {
|
||||
return Number(this.formData.perUserTicketNum) > 0 ? Number(this.formData.perUserTicketNum) : 1
|
||||
},
|
||||
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: {
|
||||
historyBack() {
|
||||
pjaxReplace("/platform/h5/coffeeTicket/list")
|
||||
},
|
||||
getQueryString(name) {
|
||||
const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)")
|
||||
const result = window.location.search.substr(1).match(reg)
|
||||
return result ? decodeURIComponent(result[2]) : ""
|
||||
},
|
||||
formatDate(value) {
|
||||
return value ? this.$moment(value).format("YYYY-MM-DD") : "--"
|
||||
},
|
||||
formatDateTime(value) {
|
||||
return value ? this.$moment(value).format("YYYY-MM-DD HH:mm") : "--"
|
||||
},
|
||||
formatWeekDays(weekDays) {
|
||||
if (!weekDays || weekDays.length === 0) {
|
||||
return "--"
|
||||
}
|
||||
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")
|
||||
if (!id) {
|
||||
this.$toast.fail("活动ID不能为空")
|
||||
return
|
||||
}
|
||||
this.formLoading = true
|
||||
$.post("/platform/h5/coffeeTicket/detail/findOne", {id: id})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$set(this, "formData", res.data || {})
|
||||
this.$set(this, "grabTicketNum", 1)
|
||||
this.$set(this, "descriptionExpanded", false)
|
||||
} else {
|
||||
this.$toast.fail(res.msg)
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
this.formLoading = false
|
||||
})
|
||||
},
|
||||
onTicket() {
|
||||
if (this.ticketLoading) {
|
||||
return
|
||||
}
|
||||
if (this.ticketActionInfo.disabled) {
|
||||
return
|
||||
}
|
||||
if (!this.formData.id) {
|
||||
this.$toast.fail("活动ID不能为空")
|
||||
return
|
||||
}
|
||||
if (this.formData.hasGrabbed) {
|
||||
this.$toast("您已抢过该活动票")
|
||||
return
|
||||
}
|
||||
this.$dialog.confirm({
|
||||
title: "确认抢票",
|
||||
message: "确定要抢 " + this.grabTicketNum + " 张活动票吗?",
|
||||
confirmButtonText: "确认抢票",
|
||||
cancelButtonText: "再看看"
|
||||
})
|
||||
.then(() => {
|
||||
this.doGrabTicket()
|
||||
})
|
||||
.catch(() => {
|
||||
})
|
||||
},
|
||||
// 活动描述默认折叠为 10 行,点击后切换完整内容显示状态。
|
||||
toggleDescription() {
|
||||
this.$set(this, "descriptionExpanded", !this.descriptionExpanded)
|
||||
},
|
||||
doGrabTicket() {
|
||||
if (this.ticketLoading) {
|
||||
return
|
||||
}
|
||||
this.$set(this, "ticketLoading", true)
|
||||
const grabLoading = this.$toast.loading({
|
||||
message: "抢票中,请耐心等待...",
|
||||
forbidClick: true,
|
||||
overlay: true,
|
||||
duration: 0
|
||||
})
|
||||
let grabResponseHandled = false
|
||||
$.post("/platform/h5/coffeeTicket/detail/grab", {
|
||||
id: this.formData.id,
|
||||
ticketNum: this.grabTicketNum
|
||||
})
|
||||
.then((res) => {
|
||||
// Vant Toast 默认复用同一实例,先关闭加载提示,再显示抢票结果。
|
||||
grabResponseHandled = true
|
||||
grabLoading.clear()
|
||||
if (res.code === 0) {
|
||||
const record = res.data || {}
|
||||
this.$set(this.formData, "hasGrabbed", true)
|
||||
this.$set(this.formData, "myTicketNum", record.ticketNum || this.grabTicketNum)
|
||||
this.$set(this.formData, "myGrabTime", record.grabTime || "")
|
||||
this.$toast.success(res.msg || "抢票成功")
|
||||
} else {
|
||||
this.$toast.fail(res.msg)
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
if (!grabResponseHandled) {
|
||||
grabLoading.clear()
|
||||
}
|
||||
this.$set(this, "ticketLoading", false)
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
this.ticketTimer = setInterval(() => {
|
||||
this.nowTimestamp = Date.now()
|
||||
}, 1000)
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.ticketTimer) {
|
||||
clearInterval(this.ticketTimer)
|
||||
this.ticketTimer = null
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,402 @@
|
||||
<!--#
|
||||
layout("/mobile/platform.html"){
|
||||
#-->
|
||||
<style scoped>
|
||||
body {
|
||||
background: #f5f7fb;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ticket-page {
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
padding-bottom: 62px;
|
||||
background: #f5f7fb;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.ticket-sticky {
|
||||
background: #f5f7fb;
|
||||
box-shadow: 0 4px 12px rgba(30, 70, 130, .05);
|
||||
}
|
||||
|
||||
.ticket-search {
|
||||
padding: 8px 14px 4px;
|
||||
background: #f5f7fb;
|
||||
}
|
||||
|
||||
.ticket-tabs {
|
||||
padding: 0 12px 8px;
|
||||
background: #f5f7fb;
|
||||
}
|
||||
|
||||
.ticket-tabs /deep/ .van-tabs__nav {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.ticket-tabs /deep/ .van-tab {
|
||||
flex: none;
|
||||
margin-right: 8px;
|
||||
padding: 0 14px;
|
||||
border-radius: 15px;
|
||||
background: #eef2f8;
|
||||
color: #7a8799;
|
||||
font-size: 12px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.ticket-tabs /deep/ .van-tab--active {
|
||||
background: #1f73ff;
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ticket-tabs /deep/ .van-tabs__line {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ticket-list {
|
||||
padding: 14px 14px 0;
|
||||
}
|
||||
|
||||
.ticket-card {
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
border-radius: 14px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, .08);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ticket-cover {
|
||||
position: relative;
|
||||
height: 150px;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, #dfeeff, #f8fbff);
|
||||
}
|
||||
|
||||
.ticket-cover img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ticket-cover-empty {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #9aa8ba;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.ticket-status {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 1;
|
||||
padding: 3px 8px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #82d9a1;
|
||||
background: #eefbf3;
|
||||
color: #19a957;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ticket-status.upcoming {
|
||||
background: #fff5e5;
|
||||
color: #f29b22;
|
||||
}
|
||||
|
||||
.ticket-status.ended {
|
||||
background: #eef0f5;
|
||||
color: #8d98a8;
|
||||
}
|
||||
|
||||
.ticket-content {
|
||||
padding: 14px 0 0;
|
||||
}
|
||||
|
||||
.ticket-title {
|
||||
margin-bottom: 12px;
|
||||
color: #1f2d3d;
|
||||
font-size: 17px;
|
||||
line-height: 24px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.ticket-desc {
|
||||
margin-bottom: 10px;
|
||||
color: #7a8799;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.ticket-info {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.ticket-info-row {
|
||||
display: grid;
|
||||
grid-template-columns: 20px 78px minmax(0, 1fr);
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ticket-info-row i {
|
||||
color: #8ca0bc;
|
||||
font-size: 15px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ticket-info-label {
|
||||
color: #8b98aa;
|
||||
}
|
||||
|
||||
.ticket-info-value {
|
||||
color: #243044;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ticket-week-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.ticket-week-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 20px;
|
||||
padding: 0 7px;
|
||||
border: 1px solid #8ec8ff;
|
||||
border-radius: 4px;
|
||||
background: #eef8ff;
|
||||
color: #1684e8;
|
||||
font-size: 11px;
|
||||
line-height: 18px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ticket-quota {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 14px;
|
||||
color: #7a8799;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ticket-quota strong {
|
||||
color: #1f73ff;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.ticket-action {
|
||||
height: 42px;
|
||||
border-radius: 8px;
|
||||
border: 0;
|
||||
background: #1890ff;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 8px 18px rgba(24, 144, 255, .26);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak class="ticket-page">
|
||||
<van-nav-bar title="抢票活动" left-text="返回" left-arrow placeholder fixed @click-left="goHome"></van-nav-bar>
|
||||
|
||||
<van-sticky offset-top="46px" class="ticket-sticky">
|
||||
<div class="ticket-search">
|
||||
<van-search
|
||||
v-model="pageForm.searchKeyword"
|
||||
placeholder="搜索活动名称或地点"
|
||||
shape="round"
|
||||
@search="doSearch">
|
||||
</van-search>
|
||||
</div>
|
||||
<div class="ticket-tabs">
|
||||
<van-tabs v-model="status" @change="doSearch">
|
||||
<van-tab title="全部" name="all"></van-tab>
|
||||
<van-tab title="报名中" name="active"></van-tab>
|
||||
<van-tab title="即将开始" name="upcoming"></van-tab>
|
||||
<van-tab title="已结束" name="ended"></van-tab>
|
||||
</van-tabs>
|
||||
</div>
|
||||
</van-sticky>
|
||||
|
||||
<van-list
|
||||
v-model="loading"
|
||||
:finished="finished"
|
||||
finished-text="没有更多了"
|
||||
@load="pageData">
|
||||
<div class="ticket-list">
|
||||
<div class="ticket-card" v-for="(item,index) in tableData" :key="item.id">
|
||||
<div class="ticket-cover">
|
||||
<img v-if="item.cover" :src="CREATE_PREVIEW_URL(item.cover)">
|
||||
<div v-else class="ticket-cover-empty">暂无封面</div>
|
||||
<div class="ticket-status" :class="getStatusInfo(item).className">{{getStatusInfo(item).text}}</div>
|
||||
</div>
|
||||
<div class="ticket-content">
|
||||
<div class="ticket-title">{{item.title}}</div>
|
||||
<div class="ticket-info">
|
||||
<div class="ticket-info-row">
|
||||
<i class="van-icon van-icon-calendar-o"></i>
|
||||
<span class="ticket-info-label">活动时间</span>
|
||||
<span class="ticket-info-value">{{formatDate(item.activityStartDate)}} 至 {{formatDate(item.activityEndDate)}}</span>
|
||||
</div>
|
||||
<div class="ticket-info-row">
|
||||
<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 sortWeekDays(item.weekDays)" :key="week">{{weekMap[week] || week}}</span>
|
||||
</span>
|
||||
<span class="ticket-info-value" v-else>--</span>
|
||||
</div>
|
||||
<div class="ticket-info-row">
|
||||
<i class="van-icon van-icon-underway-o"></i>
|
||||
<span class="ticket-info-label">抢票时间</span>
|
||||
<span class="ticket-info-value">{{item.reserveStartTime || '--'}} - {{item.reserveEndTime || '--'}}</span>
|
||||
</div>
|
||||
<div class="ticket-info-row">
|
||||
<i class="van-icon van-icon-location-o"></i>
|
||||
<span class="ticket-info-label">活动地点</span>
|
||||
<span class="ticket-info-value">{{item.address || '--'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ticket-quota">
|
||||
<span>活动总票数 <strong>{{item.totalTicketNum || 0}}</strong> 张</span>
|
||||
<span>剩余票数 <strong>{{item.remainingTicketNum || 0}}</strong> 张</span>
|
||||
</div>
|
||||
<van-button class="ticket-action" type="info" block @click="onTicket(item)">立即抢票</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
|
||||
<van-empty v-if="!loading && tableData.length === 0" description="暂无抢票活动"></van-empty>
|
||||
|
||||
<van-tabbar v-model="activeTab" @change="onTabChange" safe-area-inset-bottom>
|
||||
<van-tabbar-item name="list" icon="wap-home-o">抢票活动</van-tabbar-item>
|
||||
<van-tabbar-item name="mine" icon="coupon-o">我的抢票</van-tabbar-item>
|
||||
</van-tabbar>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
activeTab: "list",
|
||||
status: "all",
|
||||
loading: false,
|
||||
finished: false,
|
||||
tableData: [],
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
searchKeyword: ""
|
||||
},
|
||||
weekMap: {
|
||||
"1": "周一",
|
||||
"2": "周二",
|
||||
"3": "周三",
|
||||
"4": "周四",
|
||||
"5": "周五",
|
||||
"6": "周六",
|
||||
"7": "周日"
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatDate(value) {
|
||||
return value ? this.$moment(value).format("YYYY-MM-DD") : "--"
|
||||
},
|
||||
formatWeekDays(weekDays) {
|
||||
if (!weekDays || weekDays.length === 0) {
|
||||
return "--"
|
||||
}
|
||||
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"))) {
|
||||
return { text: "已结束", className: "ended" }
|
||||
}
|
||||
if (row.activityStartDate && now.isBefore(this.$moment(row.activityStartDate).startOf("day"))) {
|
||||
return { text: "即将开始", className: "upcoming" }
|
||||
}
|
||||
return { text: "报名中", className: "" }
|
||||
},
|
||||
pageData() {
|
||||
const params = Object.assign({}, this.pageForm, {
|
||||
status: this.status
|
||||
})
|
||||
$.post("/platform/h5/coffeeTicket/list/pageData", params)
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
const list = res.data && res.data.list ? res.data.list : []
|
||||
this.tableData = this.tableData.concat(list)
|
||||
this.$set(this.pageForm, "totalCount", res.data.totalCount || 0)
|
||||
if (this.tableData.length >= this.pageForm.totalCount) {
|
||||
this.finished = true
|
||||
} else {
|
||||
this.$set(this.pageForm, "pageNumber", this.pageForm.pageNumber + 1)
|
||||
}
|
||||
} else {
|
||||
this.$toast.fail(res.msg)
|
||||
this.finished = true
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
doSearch() {
|
||||
this.$set(this.pageForm, "pageNumber", 1)
|
||||
this.$set(this.pageForm, "totalCount", 0)
|
||||
this.tableData = []
|
||||
this.finished = false
|
||||
this.loading = true
|
||||
this.pageData()
|
||||
},
|
||||
goHome() {
|
||||
pjaxReplace("/mobile/index")
|
||||
},
|
||||
onTicket(row) {
|
||||
pjaxReplace("/platform/h5/coffeeTicket/detail?id=" + row.id)
|
||||
},
|
||||
onTabChange(name) {
|
||||
if (name === "mine") {
|
||||
pjaxReplace("/platform/h5/coffeeTicket/mine")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,354 @@
|
||||
<!--#
|
||||
layout("/mobile/platform.html"){
|
||||
#-->
|
||||
<style scoped>
|
||||
body {
|
||||
background: #f5f7fb;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mine-page {
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
padding-bottom: 62px;
|
||||
background: #f5f7fb;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.mine-filter-wrap {
|
||||
background: #ffffff;
|
||||
box-shadow: 0 4px 12px rgba(30, 70, 130, .05);
|
||||
}
|
||||
|
||||
.mine-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 10px 14px 8px;
|
||||
color: #8b98aa;
|
||||
font-size: 12px;
|
||||
background: #f5f7fb;
|
||||
}
|
||||
|
||||
.mine-total strong {
|
||||
color: #1f73ff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.mine-list {
|
||||
padding: 10px 12px 0;
|
||||
}
|
||||
|
||||
.mine-card {
|
||||
display: grid;
|
||||
grid-template-columns: 94px minmax(0, 1fr);
|
||||
gap: 10px;
|
||||
position: relative;
|
||||
margin-bottom: 12px;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 6px 18px rgba(25, 54, 94, .08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mine-cover {
|
||||
width: 94px;
|
||||
height: 94px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, #dfeeff, #f8fbff);
|
||||
}
|
||||
|
||||
.mine-cover img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.mine-cover-empty {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #9aa8ba;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.mine-content {
|
||||
min-width: 0;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.mine-title-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.mine-title {
|
||||
min-width: 0;
|
||||
color: #1f2d3d;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mine-desc {
|
||||
margin-bottom: 8px;
|
||||
color: #7a8799;
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mine-ticket-count {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px dashed #e7edf5;
|
||||
color: #5f6f86;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.mine-ticket-count i,
|
||||
.mine-info-row i {
|
||||
color: #1f73ff;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.mine-ticket-count strong {
|
||||
color: #1f73ff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.mine-info {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
color: #64748b;
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.mine-info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mine-info-row i {
|
||||
width: 16px;
|
||||
color: #8ca0bc;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mine-info-row span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mine-card-footer {
|
||||
grid-column: 2;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
.mine-action {
|
||||
min-width: 76px;
|
||||
height: 28px;
|
||||
padding: 0 12px;
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
box-shadow: 0 5px 12px rgba(24, 144, 255, .18);
|
||||
}
|
||||
|
||||
.mine-action-danger {
|
||||
color: #ffffff;
|
||||
border-color: #e3402a;
|
||||
background: #e3402a;
|
||||
box-shadow: 0 5px 12px rgba(227, 64, 42, .24);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak class="mine-page">
|
||||
<van-nav-bar title="我的抢票" left-arrow left-text="返回" placeholder fixed @click-left="goHome"></van-nav-bar>
|
||||
|
||||
<van-sticky offset-top="46px" class="mine-filter-wrap">
|
||||
<div class="mine-filter">
|
||||
<div class="mine-total">共 <strong>{{pageForm.totalCount || 0}}</strong> 张票</div>
|
||||
</div>
|
||||
</van-sticky>
|
||||
|
||||
<van-list
|
||||
v-model="loading"
|
||||
:finished="finished"
|
||||
finished-text="没有更多了"
|
||||
@load="pageData">
|
||||
<div class="mine-list">
|
||||
<div class="mine-card" v-for="item in tableData" :key="item.recordId">
|
||||
<div class="mine-cover">
|
||||
<img v-if="item.cover" :src="CREATE_PREVIEW_URL(item.cover)">
|
||||
<div v-else class="mine-cover-empty">暂无封面</div>
|
||||
</div>
|
||||
<div class="mine-content">
|
||||
<div class="mine-title-row">
|
||||
<div class="mine-title">{{item.title || '--'}}</div>
|
||||
</div>
|
||||
<div class="mine-ticket-count">
|
||||
<i class="van-icon van-icon-coupon-o"></i>
|
||||
<span>已抢 <strong>{{item.ticketNum || 0}}</strong> 张</span>
|
||||
</div>
|
||||
<div class="mine-info">
|
||||
<div class="mine-info-row">
|
||||
<i class="van-icon van-icon-calendar-o"></i>
|
||||
<span>活动时间 {{formatDate(item.activityStartDate)}} 至 {{formatDate(item.activityEndDate)}}</span>
|
||||
</div>
|
||||
<div class="mine-info-row">
|
||||
<i class="van-icon van-icon-clock-o"></i>
|
||||
<span>抢票时间 {{formatDateTime(item.grabTime)}}</span>
|
||||
</div>
|
||||
<div class="mine-info-row">
|
||||
<i class="van-icon van-icon-location-o"></i>
|
||||
<span>活动地点 {{item.address || '--'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mine-card-footer">
|
||||
<van-button v-if="item.canCancel" class="mine-action mine-action-danger" hairline type="danger" :loading="cancelLoading && cancelingRecordId === item.recordId" @click="onCancel(item)">取消抢票</van-button>
|
||||
<van-button class="mine-action" hairline type="info" @click="onDetail(item)">查看详情</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
|
||||
<van-empty v-if="!loading && tableData.length === 0" description="暂无抢票记录"></van-empty>
|
||||
|
||||
<van-tabbar v-model="activeTab" @change="onTabChange" safe-area-inset-bottom>
|
||||
<van-tabbar-item name="list" icon="wap-home-o">抢票活动</van-tabbar-item>
|
||||
<van-tabbar-item name="mine" icon="coupon-o">我的抢票</van-tabbar-item>
|
||||
</van-tabbar>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
activeTab: "mine",
|
||||
loading: false,
|
||||
finished: false,
|
||||
cancelLoading: false,
|
||||
cancelingRecordId: "",
|
||||
tableData: [],
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
searchKeyword: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatDate(value) {
|
||||
return value ? this.$moment(value).format("YYYY-MM-DD") : "--"
|
||||
},
|
||||
formatDateTime(value) {
|
||||
return value ? this.$moment(value).format("YYYY-MM-DD HH:mm") : "--"
|
||||
},
|
||||
pageData() {
|
||||
$.post("/platform/h5/coffeeTicket/mine/pageData", this.pageForm)
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
const list = res.data && res.data.list ? res.data.list : []
|
||||
this.tableData = this.tableData.concat(list)
|
||||
this.$set(this.pageForm, "totalCount", res.data.totalCount || 0)
|
||||
if (this.tableData.length >= this.pageForm.totalCount) {
|
||||
this.finished = true
|
||||
} else {
|
||||
this.$set(this.pageForm, "pageNumber", this.pageForm.pageNumber + 1)
|
||||
}
|
||||
} else {
|
||||
this.$toast.fail(res.msg)
|
||||
this.finished = true
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
doSearch() {
|
||||
this.$set(this.pageForm, "pageNumber", 1)
|
||||
this.$set(this.pageForm, "totalCount", 0)
|
||||
this.tableData = []
|
||||
this.finished = false
|
||||
this.loading = true
|
||||
this.pageData()
|
||||
},
|
||||
goHome() {
|
||||
pjaxReplace("/mobile/index")
|
||||
},
|
||||
onDetail(item) {
|
||||
pjaxReplace("/platform/h5/coffeeTicket/detail?id=" + item.activityId)
|
||||
},
|
||||
onCancel(item) {
|
||||
if (!item.recordId) {
|
||||
this.$toast.fail("抢票记录ID缺失")
|
||||
return
|
||||
}
|
||||
if (!item.canCancel) {
|
||||
this.$toast.fail("当前不在可取消时间内")
|
||||
return
|
||||
}
|
||||
this.$dialog.confirm({
|
||||
title: "取消确认",
|
||||
message: "确定要取消本次抢票吗?",
|
||||
confirmButtonText: "确认取消",
|
||||
cancelButtonText: "再想想"
|
||||
})
|
||||
.then(() => {
|
||||
this.cancelLoading = true
|
||||
this.cancelingRecordId = item.recordId
|
||||
$.post("/platform/h5/coffeeTicket/mine/cancel", {recordId: item.recordId})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$toast.success("取消成功")
|
||||
this.tableData = this.tableData.filter((row) => row.recordId !== item.recordId)
|
||||
this.$set(this.pageForm, "totalCount", Math.max((this.pageForm.totalCount || 0) - 1, 0))
|
||||
} else {
|
||||
this.$toast.fail(res.msg)
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
this.cancelLoading = false
|
||||
this.cancelingRecordId = ""
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
})
|
||||
},
|
||||
onTabChange(name) {
|
||||
if (name === "list") {
|
||||
pjaxReplace("/platform/h5/coffeeTicket/list")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,203 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<div class="search">
|
||||
<div class="search-item">
|
||||
<div class="search-item-label">年度:</div>
|
||||
<div class="search-item-option">
|
||||
<el-date-picker
|
||||
placeholder="请选择年度"
|
||||
style="width: 100%"
|
||||
type="year"
|
||||
v-model="pageForm.year"
|
||||
value-format="yyyy">
|
||||
</el-date-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-item">
|
||||
<div class="search-item-label">活动标题:</div>
|
||||
<div class="search-item-option">
|
||||
<el-input
|
||||
placeholder="请输入活动标题"
|
||||
v-model="pageForm.title"
|
||||
clearable
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-query">
|
||||
<el-button type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mt10">
|
||||
<table-tool :app="this" label="活动列表">
|
||||
<el-button size="small" type="primary" @click="openAdd">
|
||||
<i class="ti-plus"></i>
|
||||
新增活动
|
||||
</el-button>
|
||||
</table-tool>
|
||||
|
||||
<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>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="可预约星期" width="220">
|
||||
<template scope="{row}">
|
||||
<span>{{formatWeekDays(row.weekDays)}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="活动总票数" prop="ticketCount" width="110" sortable></el-table-column>
|
||||
<el-table-column label="每人每次可抢" prop="perUserTicketNum" width="130" sortable>
|
||||
<template scope="{row}">
|
||||
<span>{{row.perUserTicketNum || 1}} 张</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="每日抢票时间段" width="180">
|
||||
<template scope="{row}">
|
||||
<span>{{row.reserveStartTime || '--'}} - {{row.reserveEndTime || '--'}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="可报名人员范围" width="180" show-overflow-tooltip>
|
||||
<template scope="{row}">
|
||||
<span>{{formatActivityGroup(row.activityGroupId)}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<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>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
weekMap: {
|
||||
"1": "周一",
|
||||
"2": "周二",
|
||||
"3": "周三",
|
||||
"4": "周四",
|
||||
"5": "周五",
|
||||
"6": "周六",
|
||||
"7": "周日"
|
||||
},
|
||||
groupList: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openAdd() {
|
||||
location.href = "/platform/coffeeTicket/new"
|
||||
},
|
||||
openEdit(row) {
|
||||
location.href = "/platform/coffeeTicket/new?id=" + row.id
|
||||
},
|
||||
formatDate(value) {
|
||||
return value ? moment(value).format("YYYY-MM-DD") : "--"
|
||||
},
|
||||
formatDateTime(value) {
|
||||
return value ? moment(value).format("YYYY-MM-DD HH:mm:ss") : "--"
|
||||
},
|
||||
formatWeekDays(weekDays) {
|
||||
if (!weekDays || weekDays.length === 0) {
|
||||
return "--"
|
||||
}
|
||||
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() {
|
||||
$.post("/platform/activity/basic/scope/getActivityUserScopeGroup", {})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.groupList = res.data || []
|
||||
}
|
||||
})
|
||||
.always(() => {})
|
||||
},
|
||||
updateEnabled(row, enabled) {
|
||||
const actionText = enabled ? "开启" : "关闭"
|
||||
this.$confirm("确定" + actionText + "该活动吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
$.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)
|
||||
}
|
||||
})
|
||||
.always(() => {})
|
||||
}).catch(() => {})
|
||||
},
|
||||
onDelete(id) {
|
||||
this.$confirm("删除后该活动配置不可恢复,确定删除吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
$.post("/platform/coffeeTicket/activity/delete", {id: id})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("删除成功")
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.warning(res.msg)
|
||||
}
|
||||
})
|
||||
.always(() => {})
|
||||
}).catch(() => {})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadGroupList()
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,477 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.coffee-ticket-page {
|
||||
min-height: calc(100vh - 120px);
|
||||
padding-bottom: 18px;
|
||||
background: #f5f7fb;
|
||||
}
|
||||
|
||||
.coffee-ticket-form {
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.coffee-section {
|
||||
position: relative;
|
||||
margin-bottom: 14px;
|
||||
padding: 24px 30px 20px 54px;
|
||||
background: #ffffff;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 4px rgba(31, 45, 61, .04);
|
||||
}
|
||||
|
||||
.coffee-section-index {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
left: 18px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
border-radius: 50%;
|
||||
background: #1f73ff;
|
||||
color: #ffffff;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.coffee-section-title {
|
||||
margin: 0 0 24px;
|
||||
color: #1f2d3d;
|
||||
font-size: 22px;
|
||||
line-height: 28px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.coffee-form-item-tip {
|
||||
margin-top: 8px;
|
||||
color: #7b8794;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.coffee-cover-tip {
|
||||
margin-top: 8px;
|
||||
color: #7b8794;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.coffee-week-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.coffee-week-group .el-checkbox {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.coffee-ticket-number {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.coffee-time-range {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.coffee-time-range .el-date-editor.el-input,
|
||||
.coffee-time-range .el-date-editor.el-input__inner {
|
||||
width: 190px;
|
||||
}
|
||||
|
||||
.coffee-inline-note {
|
||||
margin-left: 24px;
|
||||
color: #7b8794;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.coffee-radio-help {
|
||||
margin-left: 6px;
|
||||
color: #8c98a8;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.coffee-action-note {
|
||||
margin-right: 12px;
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.coffee-cover-uploader .el-upload {
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.coffee-cover-uploader .el-upload:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.coffee-cover-preview,
|
||||
.coffee-cover-uploader-icon {
|
||||
width: 260px;
|
||||
height: 146px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.coffee-cover-uploader-icon {
|
||||
line-height: 146px;
|
||||
color: #8c939d;
|
||||
font-size: 28px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.coffee-form-actions {
|
||||
padding: 8px 0 20px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak class="coffee-ticket-page">
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<div slot="header">{{formData.id ? "编辑咖啡抢票活动" : "创建咖啡抢票活动"}}</div>
|
||||
<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>
|
||||
<el-form-item label="活动标题" prop="title">
|
||||
<el-input
|
||||
v-model="formData.title"
|
||||
placeholder="请输入活动标题"
|
||||
maxlength="50"
|
||||
show-word-limit
|
||||
clearable>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="活动封面" prop="cover">
|
||||
<el-upload
|
||||
class="coffee-cover-uploader"
|
||||
action="/file_server/uploadFile"
|
||||
accept="image/jpeg,image/png"
|
||||
:show-file-list="false"
|
||||
:on-success="handleCoverSuccess"
|
||||
:before-upload="beforeCoverUpload">
|
||||
<img v-if="formData.cover" :src="CREATE_PREVIEW_URL(formData.cover)" class="coffee-cover-preview">
|
||||
<i v-else class="el-icon-plus coffee-cover-uploader-icon"></i>
|
||||
</el-upload>
|
||||
<el-button v-if="formData.cover" type="text" @click="clearCover">移除封面</el-button>
|
||||
<div class="coffee-cover-tip">建议尺寸:750*422px,支持 jpg、png 格式,大小不超过 2MB</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="活动描述" prop="description">
|
||||
<el-input
|
||||
v-model="formData.description"
|
||||
placeholder="请输入活动描述,介绍活动内容、亮点等"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
maxlength="200"
|
||||
show-word-limit>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</section>
|
||||
|
||||
<section class="coffee-section">
|
||||
<div class="coffee-section-index">2</div>
|
||||
<h3 class="coffee-section-title">活动时间与频次</h3>
|
||||
<el-form-item label="活动周期" prop="activityDateRange">
|
||||
<el-date-picker
|
||||
v-model="formData.activityDateRange"
|
||||
type="daterange"
|
||||
start-placeholder="选择开始日期"
|
||||
end-placeholder="选择结束日期"
|
||||
range-separator="-"
|
||||
value-format="yyyy-MM-dd"
|
||||
style="width: 460px">
|
||||
</el-date-picker>
|
||||
<div class="coffee-form-item-tip">设置活动整体的开始和结束时间</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="可预约星期" prop="weekDays">
|
||||
<el-checkbox-group v-model="formData.weekDays" class="coffee-week-group">
|
||||
<el-checkbox v-for="item in weekOptions" :key="item.value" :label="item.value" border>
|
||||
{{item.label}}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
<div class="coffee-form-item-tip">设置每周哪些天可以预约(可多选)</div>
|
||||
</el-form-item>
|
||||
</section>
|
||||
|
||||
<section class="coffee-section">
|
||||
<div class="coffee-section-index">3</div>
|
||||
<h3 class="coffee-section-title">抢票规则</h3>
|
||||
<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>
|
||||
<span>张</span>
|
||||
<span class="coffee-inline-note">活动可供抢购的总票数</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="每人每次可抢票数" prop="perUserTicketNum">
|
||||
<div class="coffee-ticket-number">
|
||||
<el-input-number v-model="formData.perUserTicketNum" :min="1" :max="999"
|
||||
:precision="0"></el-input-number>
|
||||
<span>张</span>
|
||||
<span class="coffee-inline-note">默认 1 张,用户每次抢票将按此数量领取</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="可报名人员范围" prop="activityGroupId">
|
||||
<el-select v-model="formData.activityGroupId" clearable filterable placeholder="请选择人员范围" style="width: 460px">
|
||||
<el-option v-for="item in groupList" :key="item.groupId" :label="item.groupName" :value="item.groupId"></el-option>
|
||||
</el-select>
|
||||
<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: '07:00', step: '00:10', end: '23:30' }">
|
||||
</el-time-select>
|
||||
</el-form-item>
|
||||
<span>-</span>
|
||||
<el-form-item prop="reserveEndTime">
|
||||
<el-time-select
|
||||
v-model="formData.reserveEndTime"
|
||||
placeholder="结束时间"
|
||||
:picker-options="{ start: '00:30', step: '00:30', end: '24:00' }">
|
||||
</el-time-select>
|
||||
</el-form-item>
|
||||
<span class="coffee-inline-note">在该时间段内可预约,可自行调整</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</section>
|
||||
|
||||
<section class="coffee-section">
|
||||
<div class="coffee-section-index">4</div>
|
||||
<h3 class="coffee-section-title">其他设置</h3>
|
||||
<el-form-item label="活动地点" prop="address">
|
||||
<el-input
|
||||
v-model="formData.address"
|
||||
placeholder="请输入活动地点"
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
clearable>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="参与须知" prop="notice">
|
||||
<el-input
|
||||
v-model="formData.notice"
|
||||
placeholder="请输入参与须知,如:请提前 10 分钟到场,遵守现场秩序等"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
maxlength="200"
|
||||
show-word-limit>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</section>
|
||||
</el-form>
|
||||
<div class="coffee-form-actions">
|
||||
<el-button @click="goBack">取消</el-button>
|
||||
<el-button type="primary" :loading="formLoading" @click="submitForm">保存</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</guava>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
const validateTimeRange = (rule, value, callback) => {
|
||||
if (!this.formData.reserveStartTime || !this.formData.reserveEndTime) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
if (this.formData.reserveStartTime >= this.formData.reserveEndTime) {
|
||||
callback(new Error("结束时间必须晚于开始时间"))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
return {
|
||||
formLoading: false,
|
||||
groupList: [],
|
||||
formData: {
|
||||
title: "",
|
||||
cover: "",
|
||||
description: "",
|
||||
activityDateRange: [],
|
||||
weekDays: ["2", "4"],
|
||||
ticketCount: 40,
|
||||
perUserTicketNum: 1,
|
||||
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: "周日"}
|
||||
],
|
||||
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"}],
|
||||
perUserTicketNum: [{required: true, message: "请输入每人每次可抢票数", trigger: "change"}],
|
||||
reserveStartTime: [
|
||||
{required: true, message: "请选择开始时间", trigger: "change"},
|
||||
{validator: validateTimeRange, trigger: "change"}
|
||||
],
|
||||
reserveEndTime: [
|
||||
{required: true, message: "请选择结束时间", trigger: "change"},
|
||||
{validator: validateTimeRange, trigger: "change"}
|
||||
],
|
||||
reserveMode: [{required: true, message: "请选择预约方式", trigger: "change"}],
|
||||
activityGroupId: [{required: true, message: "请选择可报名人员范围", trigger: ["blur", "change"]}]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getQueryString(name) {
|
||||
const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)")
|
||||
const result = window.location.search.substr(1).match(reg)
|
||||
return result ? decodeURIComponent(result[2]) : ""
|
||||
},
|
||||
// 保存接口参数:活动基础信息、可预约星期、票数、每日预约时间段、地点和须知;成功后返回活动实体。
|
||||
buildSubmitData() {
|
||||
return {
|
||||
id: this.formData.id,
|
||||
title: this.formData.title,
|
||||
cover: this.formData.cover,
|
||||
description: this.formData.description,
|
||||
activityStartDate: this.formData.activityDateRange && this.formData.activityDateRange.length > 0 ? this.formData.activityDateRange[0] : "",
|
||||
activityEndDate: this.formData.activityDateRange && this.formData.activityDateRange.length > 1 ? this.formData.activityDateRange[1] : "",
|
||||
weekDays: this.formData.weekDays,
|
||||
ticketCount: this.formData.ticketCount,
|
||||
perUserTicketNum: this.formData.perUserTicketNum,
|
||||
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
|
||||
}
|
||||
},
|
||||
// 上传接口返回 Sys_file 信息,页面只保存 filepath 文件ID,展示时统一通过预览地址转换。
|
||||
handleCoverSuccess(response) {
|
||||
if (response.code === 0 && response.data && response.data.filepath) {
|
||||
this.$set(this.formData, "cover", response.data.filepath)
|
||||
this.$message.success("封面上传成功")
|
||||
this.$refs.formRef.validateField("cover")
|
||||
} else {
|
||||
this.$message.warning(response.msg || "封面上传失败")
|
||||
}
|
||||
},
|
||||
beforeCoverUpload(file) {
|
||||
const imageType = file.type === "image/jpeg" || file.type === "image/png"
|
||||
const sizeValid = file.size / 1024 / 1024 <= 2
|
||||
if (!imageType) {
|
||||
this.$message.warning("封面仅支持 jpg、jpeg、png 格式")
|
||||
}
|
||||
if (!sizeValid) {
|
||||
this.$message.warning("封面大小不能超过 2MB")
|
||||
}
|
||||
return imageType && sizeValid
|
||||
},
|
||||
clearCover() {
|
||||
this.$set(this.formData, "cover", "")
|
||||
},
|
||||
loadGroupList() {
|
||||
$.post("/platform/activity/basic/scope/getActivityUserScopeGroup", {})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.groupList = res.data || []
|
||||
} else {
|
||||
this.$message.warning(res.msg)
|
||||
}
|
||||
})
|
||||
.always(() => {})
|
||||
},
|
||||
submitForm() {
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
const submitData = this.buildSubmitData()
|
||||
this.formLoading = true
|
||||
$.post("/platform/coffeeTicket/new/save", {data: JSON.stringify(submitData)})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg || "保存成功")
|
||||
location.href = "/platform/coffeeTicket/activity"
|
||||
} else {
|
||||
this.$message.warning(res.msg)
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
this.formLoading = false
|
||||
})
|
||||
})
|
||||
},
|
||||
goBack() {
|
||||
window.history.back()
|
||||
},
|
||||
loadActivity() {
|
||||
const id = this.getQueryString("id")
|
||||
if (!id) {
|
||||
return
|
||||
}
|
||||
this.formLoading = true
|
||||
$.post("/platform/coffeeTicket/activity/findOne", {id: id})
|
||||
.then((res) => {
|
||||
if (res.code === 0 && res.data) {
|
||||
this.$set(this, "formData", Object.assign({}, this.formData, res.data, {
|
||||
activityDateRange: [
|
||||
res.data.activityStartDate ? moment(res.data.activityStartDate).format("YYYY-MM-DD") : "",
|
||||
res.data.activityEndDate ? moment(res.data.activityEndDate).format("YYYY-MM-DD") : ""
|
||||
].filter(v => v),
|
||||
weekDays: res.data.weekDays || [],
|
||||
perUserTicketNum: res.data.perUserTicketNum || 1
|
||||
}))
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
this.formLoading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadGroupList()
|
||||
this.loadActivity()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,274 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<div class="search">
|
||||
<div class="search-item">
|
||||
<div class="search-item-label">年度:</div>
|
||||
<div class="search-item-option">
|
||||
<el-date-picker
|
||||
placeholder="请选择年度"
|
||||
style="width: 100%"
|
||||
type="year"
|
||||
v-model="pageForm.year"
|
||||
value-format="yyyy"
|
||||
@change="yearChange">
|
||||
</el-date-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-item">
|
||||
<div class="search-item-label">活动名称:</div>
|
||||
<div class="search-item-option">
|
||||
<el-select
|
||||
placeholder="请选择活动"
|
||||
style="width: 100%"
|
||||
v-model="pageForm.activityId"
|
||||
clearable
|
||||
@change="activityChange">
|
||||
<el-option label="全部活动" value=""></el-option>
|
||||
<el-option
|
||||
:label="item.title"
|
||||
:value="item.id"
|
||||
v-for="item in activityList"
|
||||
:key="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-item">
|
||||
<div class="search-item-label">抢票日期:</div>
|
||||
<div class="search-item-option">
|
||||
<el-date-picker
|
||||
clearable
|
||||
placeholder="请选择抢票日期"
|
||||
style="width: 100%"
|
||||
type="date"
|
||||
:picker-options="grabDatePickerOptions"
|
||||
v-model="pageForm.grabDate"
|
||||
value-format="yyyy-MM-dd"
|
||||
@change="doSearch">
|
||||
</el-date-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-item">
|
||||
<div class="search-item-label">所属工会:</div>
|
||||
<div class="search-item-option">
|
||||
<el-select
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择所属工会"
|
||||
style="width: 100%"
|
||||
v-model="pageForm.unionId"
|
||||
@change="unionChange">
|
||||
<el-option
|
||||
:label="item.unionname"
|
||||
:value="item.id"
|
||||
v-for="item in unionList"
|
||||
:key="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-item">
|
||||
<div class="search-item-label">所属单位:</div>
|
||||
<div class="search-item-option">
|
||||
<el-select
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择所属单位"
|
||||
style="width: 100%"
|
||||
v-model="pageForm.unitId"
|
||||
@change="unitChange">
|
||||
<el-option
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
v-for="item in unitList"
|
||||
:key="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-query">
|
||||
<el-button type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mt10">
|
||||
<table-tool :app="this" label="抢票统计">
|
||||
<template #func>
|
||||
<el-button size="small" type="primary" @click="exportExcel">导出</el-button>
|
||||
</template>
|
||||
</table-tool>
|
||||
|
||||
<el-table :data="tableData" ref="tableRef" @sort-change="pageOrder">
|
||||
<el-table-column label="序号" width="70" type="index" :index="indexMethod"></el-table-column>
|
||||
<el-table-column label="姓名" prop="userName" width="120" sortable
|
||||
show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="工号" prop="loginName" width="140" sortable
|
||||
show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="单位" prop="unitName" min-width="160" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="分工会" prop="unionName" min-width="160"
|
||||
show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="活动名称" prop="activityTitle" min-width="180"
|
||||
show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="抢票张数" prop="ticketNum" width="110" sortable align="center"
|
||||
header-align="center"></el-table-column>
|
||||
<el-table-column label="实际抢票时间" prop="grabTime" width="180" sortable>
|
||||
<template scope="{row}">
|
||||
<span>{{formatDateTime(row.grabTime)}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="90"
|
||||
v-if="$auth.hasPermission('coffeeTicket.statistics.delete')">
|
||||
<template scope="{row}">
|
||||
<el-button size="mini" type="danger" @click="onDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
activityList: [],
|
||||
unionList: [],
|
||||
unitList: [],
|
||||
grabDateList: [],
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
year: new Date().getFullYear().toString(),
|
||||
activityId: "",
|
||||
grabDate: "",
|
||||
unionId: "",
|
||||
unitId: ""
|
||||
},
|
||||
grabDatePickerOptions: {
|
||||
disabledDate: (date) => {
|
||||
const value = moment(date).format("YYYY-MM-DD")
|
||||
return this.grabDateList.indexOf(value) === -1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatDateTime(value) {
|
||||
return value ? moment(value).format("YYYY-MM-DD HH:mm:ss") : "--"
|
||||
},
|
||||
yearChange() {
|
||||
this.getActivityList(true)
|
||||
},
|
||||
activityChange() {
|
||||
this.getGrabDateList(true)
|
||||
},
|
||||
unionChange() {
|
||||
this.$set(this.pageForm, "unitId", "")
|
||||
this.getUnitList()
|
||||
this.getGrabDateList(false)
|
||||
},
|
||||
unitChange() {
|
||||
this.getGrabDateList(false)
|
||||
},
|
||||
getActivityList(searchAfterLoad) {
|
||||
$.post(loc() + "/activityList", {year: this.pageForm.year})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.activityList = res.data || []
|
||||
this.$set(this.pageForm, "activityId", this.activityList.length > 0 ? this.activityList[0].id : "")
|
||||
if (searchAfterLoad) {
|
||||
this.getGrabDateList(true)
|
||||
}
|
||||
} else {
|
||||
this.$message.warning(res.msg)
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
})
|
||||
},
|
||||
getUnionList() {
|
||||
$.post("/platform/vi/common/unionList", {})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.unionList = res.data || []
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
})
|
||||
},
|
||||
getUnitList() {
|
||||
$.post("/platform/vi/common/units", {unionId: this.pageForm.unionId})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.unitList = res.data || []
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
})
|
||||
},
|
||||
getGrabDateList(clearInvalidDate) {
|
||||
$.post(loc() + "/grabDateList", {
|
||||
year: this.pageForm.year,
|
||||
activityId: this.pageForm.activityId,
|
||||
unionId: this.pageForm.unionId,
|
||||
unitId: this.pageForm.unitId
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.grabDateList = res.data || []
|
||||
if (clearInvalidDate && this.pageForm.grabDate && this.grabDateList.indexOf(this.pageForm.grabDate) === -1) {
|
||||
this.$set(this.pageForm, "grabDate", "")
|
||||
}
|
||||
this.doSearch()
|
||||
} else {
|
||||
this.$message.warning(res.msg)
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
})
|
||||
},
|
||||
exportExcel() {
|
||||
this.$downLoad(loc() + "/export", this.pageForm)
|
||||
},
|
||||
onDelete(row) {
|
||||
this.$confirm("删除后该用户可重新参与该活动抢票,确定删除吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
$.post(loc() + "/delete", {recordId: row.recordId})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("删除成功")
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.warning(res.msg)
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
})
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getActivityList(true)
|
||||
this.getUnionList()
|
||||
this.getUnitList()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
Reference in New Issue
Block a user