记得上下班打卡 | git大法好,push需谨慎

Commit 1ff59c56 authored by wangyifan's avatar wangyifan

Merge branch 'dev-wyf-1.5' into 'master'

Dev wyf 1.5

See merge request !410
parents ab2b517e 8bb4314f
-- 需求文档:https://zzvwci6syl.feishu.cn/wiki/ZrY1wpQCBi2QLukvyEVct8PynIe
-- 艺人管理表
CREATE TABLE `kylin_artist` (
`mid` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`artist_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '艺人ID',
`artist_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '艺人名称',
`artist_type` tinyint(4) NOT NULL COMMENT '艺人类型 1音乐人 2艺术家 3厂牌 4品牌方',
`avatar_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '艺人头像',
`introduction` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '艺人简介',
`status` tinyint(4) DEFAULT '1' COMMENT '状态 1启用 0禁用',
`sort` int(11) DEFAULT '0' COMMENT '排序权重,越大越靠前',
`created_at` datetime DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`mid`),
UNIQUE KEY `uk_artist_id` (`artist_id`),
UNIQUE KEY `uk_artist_name` (`artist_name`),
KEY `idx_artist_type` (`artist_type`),
KEY `idx_status` (`status`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='艺人管理表';
-- 艺人相册表
CREATE TABLE `kylin_artist_album` (
`mid` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`album_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '相册ID',
`artist_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '艺人ID',
`image_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '图片地址',
`sort` int(11) DEFAULT '0' COMMENT '排序权重,越大越靠前',
`status` tinyint(4) DEFAULT '1' COMMENT '状态 1启用 0禁用',
`created_at` datetime DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`mid`),
UNIQUE KEY `uk_album_id` (`album_id`),
KEY `idx_artist_id` (`artist_id`),
KEY `idx_status` (`status`),
KEY `idx_sort` (`sort`)
) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='艺人相册表';
-- 艺人-演出关联表
CREATE TABLE `kylin_artist_performance` (
`mid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键ID',
`artist_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '关联 kylin_artist.artist_id',
`performances_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '关联演出ID kylin_performances.performances_id',
`times_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '关联场次 kylin_ticket_times.ticket_times_id',
`sort` int(11) DEFAULT '0' COMMENT '该艺人在本演出中的排序权重,越大越靠前',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`mid`),
UNIQUE KEY `uk_artist_performance` (`artist_id`,`performances_id`,`times_id`) USING BTREE COMMENT '艺人、演出、场次唯一索引',
KEY `idx_performances_id` (`performances_id`),
KEY `idx_artist_id` (`artist_id`)
) ENGINE=InnoDB AUTO_INCREMENT=83 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='艺人-演出关联表';
-- 艺人操作记录表
CREATE TABLE `kylin_artist_operation_log` (
`mid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键ID',
`log_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '日志ID',
`artist_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '关联艺人ID kylin_artist.artist_id',
`operation_type` tinyint(4) NOT NULL COMMENT '操作类型 1新增 2编辑 3删除',
`operation_content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '修改内容描述',
`operator_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '操作人ID',
`operator_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '操作人名称',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
PRIMARY KEY (`mid`),
KEY `idx_artist_id` (`artist_id`),
KEY `idx_log_id` (`log_id`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
\ No newline at end of file
......@@ -5,6 +5,7 @@ public class KylinRedisConst {
public static final String PERFORMANCES = "kylin:performances:id:";
public static final String PERFORMANCES_INVOICE_REMINDER = "kylin:performances:invoice_reminder:id:";
public static final String PERFORMANCES_NOTICE_REMIND_STATUS = "kylin:performances:noticeRemindStatus:id:";
public static final String PERFORMANCES_ARTISTS = "kylin:performances:artists:id:";
public static final String PERFORMANCES_TRUE_NAME = "kylin:performances_true_name:id:";
public static final String PERFORMANCES_LIST_CITY = "kylin:performances:city:";
public static final String PERFORMANCES_LIST_SYSTEM_RECOMMEND = "kylin:performances:systemRecommend";
......
package com.liquidnet.service.kylin.dto.param;
import com.liquidnet.service.kylin.entity.KylinArtist;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.beans.BeanUtils;
import java.time.LocalDateTime;
import java.util.List;
/**
* 艺人管理参数
*
* @author system
* @since 2026-02-04
*/
@Data
@ApiModel("艺人管理参数")
public class ArtistParam {
@ApiModelProperty(value = "艺人ID")
private String artistId;
@ApiModelProperty(value = "艺人名称")
private String artistName;
@ApiModelProperty(value = "艺人类型 1音乐人 2艺术家 3厂牌 4品牌方")
private Integer artistType;
@ApiModelProperty(value = "艺人头像")
private String avatarUrl;
@ApiModelProperty(value = "艺人简介")
private String introduction;
@ApiModelProperty(value = "艺人相册图片列表")
private List<String> albumImages;
@ApiModelProperty(value = "排序权重")
private Integer sort;
@ApiModelProperty(value = "状态 0禁用 1启用")
private Integer status;
@ApiModelProperty(value = "批量操作的ID列表")
private List<String> ids;
public KylinArtist getFields(String artistId, LocalDateTime createdAt) {
KylinArtist kylinArtist = new KylinArtist();
BeanUtils.copyProperties(this, kylinArtist);
if (artistId != null) {
kylinArtist.setArtistId(artistId);
}
if (createdAt != null) {
kylinArtist.setCreatedAt(createdAt);
}
kylinArtist.setUpdatedAt(LocalDateTime.now());
// 设置默认值
if (kylinArtist.getStatus() == null) {
kylinArtist.setStatus(1);
}
if (kylinArtist.getSort() == null) {
kylinArtist.setSort(0);
}
return kylinArtist;
}
}
package com.liquidnet.service.kylin.dto.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 艺人搜索参数
*
* @author system
* @since 2026-02-04
*/
@Data
@ApiModel("艺人搜索参数")
public class ArtistSearchParam {
@ApiModelProperty(value = "艺人名称")
private String artistName;
@ApiModelProperty(value = "艺人ID")
private String artistId;
@ApiModelProperty(value = "艺人类型 1音乐人 2艺术家 3厂牌 4品牌方")
private Integer artistType;
@ApiModelProperty(value = "开始日期")
private String beginDate;
@ApiModelProperty(value = "结束日期")
private String endDate;
@ApiModelProperty(value = "当前页码")
private Integer pageNum = 1;
@ApiModelProperty(value = "每页数量")
private Integer pageSize = 10;
}
package com.liquidnet.service.kylin.dto.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* 艺人详情VO
*
* @author system
* @since 2026-02-04
*/
@Data
@ApiModel("艺人详情VO")
public class ArtistVo {
@ApiModelProperty(value = "艺人ID")
private String artistId;
@ApiModelProperty(value = "艺人名称")
private String artistName;
@ApiModelProperty(value = "艺人类型 1音乐人 2艺术家 3厂牌 4品牌方")
private Integer artistType;
@ApiModelProperty(value = "艺人类型名称")
private String artistTypeName;
@ApiModelProperty(value = "艺人头像")
private String avatarUrl;
@ApiModelProperty(value = "艺人简介")
private String introduction;
@ApiModelProperty(value = "艺人相册图片列表")
private List<String> albumImages;
@ApiModelProperty(value = "关联演出")
private List<PerformanceVo> performanceVoList;
@ApiModelProperty(value = "关联商品")
private List<ProductVo> productVoList;
@ApiModelProperty(value = "排序权重")
private Integer sort;
@ApiModelProperty(value = "状态")
private Integer status;
@ApiModelProperty(value = "创建时间")
private String createdAt;
@ApiModelProperty(value = "更新时间")
private String updatedAt;
@Data
@ApiModel("演出VO")
public static class PerformanceVo{
@ApiModelProperty("演出ID")
private String performanceId;
@ApiModelProperty("演出名称")
private String title;
@ApiModelProperty("演出开始时间")
private String timeStart;
@ApiModelProperty("场次ID")
private String timesId;
@ApiModelProperty("场次名称")
private String timeTitle;
}
@Data
@ApiModel("商品VO")
private static class ProductVo {
}
}
package com.liquidnet.service.kylin.dto.vo.returns;
import com.liquidnet.service.kylin.dao.KylinArtistPerformanceDao;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
@ApiModel("演出场次艺人阵容Vo")
public class KylinPerformanceArtistLineupVo implements Serializable {
@ApiModelProperty(value = "场次ID")
private String ticketTimesId;
@ApiModelProperty(value = "场次标题")
private String timeTitle;
@ApiModelProperty(value = "艺人列表")
private List<KylinPerformanceTimeArtist> artists;
@Data
@ApiModel("演出场次艺人")
public static class KylinPerformanceTimeArtist {
@ApiModelProperty(value = "艺人ID")
private String artistId;
@ApiModelProperty(value = "艺人名称")
private String artistName;
@ApiModelProperty(value = "艺人头像")
private String avatarUrl;
@ApiModelProperty(value = "演出ID")
private String performanceId;
@ApiModelProperty(value = "演出标题")
private String title;
@ApiModelProperty(value = "艺人排序 越大越靠前")
private Integer sort;
// 👇 静态转换方法:从 Dao 转为 Vo
public static KylinPerformanceTimeArtist from(KylinArtistPerformanceDao dao) {
if (dao == null) {
return null;
}
KylinPerformanceTimeArtist artist = new KylinPerformanceTimeArtist();
artist.setArtistId(dao.getArtistId());
artist.setArtistName(dao.getArtistName());
artist.setAvatarUrl(dao.getAvatarUrl());
artist.setPerformanceId(dao.getPerformanceId());
artist.setTitle(dao.getTitle());
artist.setSort(dao.getSort());
return artist;
}
}
}
......@@ -4,6 +4,7 @@ import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.kylin.dto.param.KylinCandyItemParam;
import com.liquidnet.service.kylin.dto.param.KylinPerformanceSubscribeParam;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinCandyVo;
import com.liquidnet.service.kylin.dto.vo.returns.KylinPerformanceArtistLineupVo;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
......@@ -32,33 +33,42 @@ public interface IKylinPerformancesService {
ResponseDto<Integer> isSubscribe(String performancesId);
ResponseDto<List<KylinCandyVo>> kylinCandy(List<KylinCandyItemParam> data, String roadShowId);
/**
* 演出预约
*
* @param param
* @author zjp
* @param param
* @return: com.liquidnet.service.base.ResponseDto<java.lang.String>
* @date 2024/3/12 14:39
*/
ResponseDto<String> performanceSubscribe(HttpServletRequest request,KylinPerformanceSubscribeParam param);
*/
ResponseDto<String> performanceSubscribe(HttpServletRequest request, KylinPerformanceSubscribeParam param);
/**
* 是否演出预约
* @author zjp
* @param performancesId
*
* @param performancesId
* @param ticketTimesId
* @author zjp
* @return: com.liquidnet.service.base.ResponseDto<java.lang.Integer>
* @date 2024/3/6 14:45
*/
ResponseDto<Integer> performanceIsSubscribe(String performancesId,String ticketTimesId,String ticketsId);
*/
ResponseDto<Integer> performanceIsSubscribe(String performancesId, String ticketTimesId, String ticketsId);
/*
* @description:删除预约演出票种
* @author: zjp
* @date: 2024/5/15 14:10
* @param:
* @return:
* @param:
* @return:
**/
void deleteIsSubscribe(String performancesId,String ticketTimesId,String ticketsId);
void deleteIsSubscribe(String performancesId, String ticketTimesId, String ticketsId);
/**
* 根据演出ID获取演出阵容
* @param performancesId
* @return
*/
List<KylinPerformanceArtistLineupVo> performanceArtists(String performancesId);
}
package com.liquidnet.service.kylin.service.admin;
import com.baomidou.mybatisplus.extension.service.IService;
import com.github.pagehelper.PageInfo;
import com.liquidnet.service.kylin.dao.KylinArtistOperationLogDao;
import com.liquidnet.service.kylin.entity.KylinArtistOperationLog;
/**
* <p>
* 艺人操作日志 服务类
* </p>
*
* @author liquidnet
* @since 2026-03-04
*/
public interface IKylinArtistOperationLogService extends IService<KylinArtistOperationLog> {
/**
* 记录操作日志(自动获取当前用户)
*
* @param artistId 艺人ID
* @param operationType 操作类型 1新增 2编辑 3删除
* @param operationContent 操作内容描述
*/
void recordLog(String artistId, Integer operationType, String operationContent);
/**
* 记录操作日志(指定操作人)
*
* @param artistId 艺人ID
* @param operationType 操作类型 1新增 2编辑 3删除
* @param operationContent 操作内容描述
* @param operatorId 操作人ID
* @param operatorName 操作人名称
*/
void recordLog(String artistId, Integer operationType, String operationContent, String operatorId, String operatorName);
/**
* 获取艺人操作记录列表
*
* @param artistId 艺人ID
* @param pageNum 页码
* @param pageSize 每页数量
* @return 操作记录分页列表
*/
PageInfo<KylinArtistOperationLogDao> getOperationLogs(String artistId, Integer pageNum, Integer pageSize);
}
package com.liquidnet.service.kylin.service.admin;
import com.baomidou.mybatisplus.extension.service.IService;
import com.liquidnet.service.kylin.dao.KylinArtistAssociationStatusDto;
import com.liquidnet.service.kylin.dao.KylinArtistPerformanceDao;
import com.liquidnet.service.kylin.entity.KylinArtistPerformance;
import java.util.List;
import java.util.Map;
public interface IKylinArtistPerformanceService extends IService<KylinArtistPerformance> {
/**
* 获取指定场次的艺人阵容
* @param performancesId
* @param timesId
* @return
*/
List<KylinArtistPerformanceDao> getSessionArtists(String performancesId, String timesId);
/**
* 更新演出艺人排序
*
* @param performanceId
* @param orderData
* @return
*/
boolean updateArtistOrder(String performanceId, List<Map<String, Object>> orderData);
/**
* 删除演出艺人关联
*
* @param mid
* @param performanceId
* @return
*/
int deletePerformanceArtist(Long mid, String performanceId);
/**
* 获取所有艺人
* @param performancesId
* @param timesId
* @param keyword
* @return
*/
List<KylinArtistAssociationStatusDto> getAllArtists(String performancesId, String timesId, String keyword);
/**
* 修改艺人关联
* @param performancesId
* @param timesId
* @param artistOrders
* @return
*/
boolean updateArtistAssociations(String performancesId, String timesId, List<Map<String, Object>> artistOrders);
}
package com.liquidnet.service.kylin.service.admin;
import com.baomidou.mybatisplus.extension.service.IService;
import com.github.pagehelper.PageInfo;
import com.liquidnet.service.kylin.dao.KylinArtistDao;
import com.liquidnet.service.kylin.dto.param.ArtistParam;
import com.liquidnet.service.kylin.dto.param.ArtistSearchParam;
import com.liquidnet.service.kylin.dto.vo.ArtistVo;
import com.liquidnet.service.kylin.entity.KylinArtist;
import java.util.List;
/**
* <p>
* 艺人管理 服务类
* </p>
*
* @author liquidnet
* @since 2026-02-04
*/
public interface IKylinArtistService extends IService<KylinArtist> {
Boolean create(ArtistParam param);
Boolean update(ArtistParam param);
ArtistVo detail(String artistId);
PageInfo<KylinArtistDao> artistList(ArtistSearchParam param);
Boolean delete(List<String> artistIds);
Boolean checkArtistNameExists(String artistName, String artistId);
}
package com.liquidnet.client.admin.web.controller.zhengzai.kylin;
import com.github.pagehelper.PageInfo;
import com.liquidnet.client.admin.common.annotation.Log;
import com.liquidnet.client.admin.common.core.controller.BaseController;
import com.liquidnet.client.admin.common.core.domain.AjaxResult;
import com.liquidnet.client.admin.common.core.page.TableDataInfo;
import com.liquidnet.client.admin.common.enums.BusinessType;
import com.liquidnet.service.kylin.dao.KylinArtistAssociationStatusDto;
import com.liquidnet.service.kylin.dao.KylinArtistDao;
import com.liquidnet.service.kylin.dao.KylinArtistOperationLogDao;
import com.liquidnet.service.kylin.dao.KylinArtistPerformanceDao;
import com.liquidnet.service.kylin.dto.param.ArtistParam;
import com.liquidnet.service.kylin.dto.param.ArtistSearchParam;
import com.liquidnet.service.kylin.dto.vo.ArtistVo;
import com.liquidnet.service.kylin.service.admin.IKylinArtistOperationLogService;
import com.liquidnet.service.kylin.service.admin.IKylinArtistPerformanceService;
import com.liquidnet.service.kylin.service.admin.IKylinArtistService;
import io.swagger.annotations.Api;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* <p>
* 艺人管理 后端管理控制器
* </p>
*
* @author liquidnet
* @since 2026-02-04
*/
@Api(tags = "艺人管理")
@Controller
@RequestMapping("/kylin/artist")
public class KylinArtistController extends BaseController {
private final String prefix = "zhengzai/kylin/artist";
@Value("${liquidnet.client.admin.platformUrl}")
private String platformUrl;
@Autowired
private IKylinArtistService kylinArtistService;
@Autowired
private IKylinArtistOperationLogService operationLogService;
@Autowired
private IKylinArtistPerformanceService artistPerformanceService;
@GetMapping("/create")
@RequiresPermissions("kylin:artist:create")
public String create(ModelMap mmap) {
mmap.put("platformUrl", platformUrl);
return prefix + "/create";
}
@GetMapping("/update/{artistId}")
@RequiresPermissions("kylin:artist:update")
public String update(@PathVariable("artistId") String artistId, ModelMap mmap) {
ArtistVo result = kylinArtistService.detail(artistId);
mmap.put("ArtistVo", result);
mmap.put("platformUrl", platformUrl);
return prefix + "/update";
}
@RequiresPermissions("kylin:artist:list")
@GetMapping()
public String artists() {
return prefix + "/artists";
}
@RequiresPermissions("kylin:artist:detail")
@GetMapping("/detail/{artistId}")
public String detail(@PathVariable("artistId") String artistId, ModelMap mmap) {
ArtistVo result = kylinArtistService.detail(artistId);
mmap.put("ArtistVo", result);
return prefix + "/detail";
}
@RequiresPermissions("kylin:artist:list")
@PostMapping("list")
@ResponseBody
public TableDataInfo artistList(ArtistSearchParam param) {
PageInfo<KylinArtistDao> result = kylinArtistService.artistList(param);
return getDataTable(result.getList());
}
@Log(title = "创建艺人", businessType = BusinessType.INSERT)
@RequiresPermissions("kylin:artist:create")
@PostMapping("create")
@ResponseBody
public AjaxResult createSave(ArtistParam param, String albumImagesJson) {
// 校验艺人名称是否已存在
Boolean exists = kylinArtistService.checkArtistNameExists(param.getArtistName(), null);
if (exists) {
return error("艺人名称已存在,不可重复创建");
}
// 解析相册图片JSON数组
if (albumImagesJson != null && !albumImagesJson.isEmpty() && !albumImagesJson.equals("[]")) {
try {
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
List<String> albumImages = mapper.readValue(albumImagesJson,
mapper.getTypeFactory().constructCollectionType(List.class, String.class));
param.setAlbumImages(albumImages);
} catch (Exception e) {
return error("相册图片数据格式错误");
}
}
Boolean res = kylinArtistService.create(param);
if (res) {
return success();
} else {
return error("添加艺人失败");
}
}
@Log(title = "修改艺人", businessType = BusinessType.UPDATE)
@RequiresPermissions("kylin:artist:update")
@PostMapping("update")
@ResponseBody
public AjaxResult updateSave(ArtistParam param, String albumImagesJson) {
// 校验艺人名称是否已被其他艺人使用
Boolean exists = kylinArtistService.checkArtistNameExists(param.getArtistName(), param.getArtistId());
if (exists) {
return error("艺人名称已被其他艺人使用");
}
// 解析相册图片JSON数组
if (albumImagesJson != null && !albumImagesJson.isEmpty() && !albumImagesJson.equals("[]")) {
try {
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
List<String> albumImages = mapper.readValue(albumImagesJson,
mapper.getTypeFactory().constructCollectionType(List.class, String.class));
param.setAlbumImages(albumImages);
} catch (Exception e) {
return error("相册图片数据格式错误");
}
}
Boolean res = kylinArtistService.update(param);
if (res) {
return success();
} else {
return error("修改艺人失败");
}
}
@Log(title = "删除艺人", businessType = BusinessType.DELETE)
@RequiresPermissions("kylin:artist:delete")
@PostMapping("delete")
@ResponseBody
public AjaxResult delete(ArtistParam param) {
List<String> artistIds = param.getIds();
Boolean result = kylinArtistService.delete(artistIds);
if (result) {
return success("删除成功");
} else {
return error("删除失败");
}
}
/**
* 检查艺人名称是否存在
*/
@PostMapping("checkName")
@ResponseBody
public AjaxResult checkArtistName(String artistName, String artistId) {
Boolean exists = kylinArtistService.checkArtistNameExists(artistName, artistId);
if (exists) {
return error("艺人名称已存在");
}
return success();
}
/**
* 艺人操作记录页面
*/
@RequiresPermissions("kylin:artist:detail")
@GetMapping("/operationLog/{artistId}")
public String operationLog(@PathVariable("artistId") String artistId, ModelMap mmap) {
// 获取艺人基本信息
ArtistVo artistVo = kylinArtistService.detail(artistId);
mmap.put("artistId", artistId);
mmap.put("artistName", artistVo != null ? artistVo.getArtistName() : "");
return prefix + "/operationLog";
}
/**
* 获取艺人操作记录列表
*/
@RequiresPermissions("kylin:artist:detail")
@PostMapping("/operationLogList")
@ResponseBody
public TableDataInfo operationLogList(String artistId, Integer pageNum, Integer pageSize) {
PageInfo<KylinArtistOperationLogDao> result =
operationLogService.getOperationLogs(artistId, pageNum, pageSize);
return getDataTable(result.getList());
}
/**
* 获取指定场次的艺人阵容
*/
@GetMapping("/getSessionArtists")
@ResponseBody
public AjaxResult getSessionArtists(String performancesId, String timesId) {
try {
if (performancesId == null || timesId == null) {
return error("参数不能为空");
}
List<KylinArtistPerformanceDao> artists = artistPerformanceService.getSessionArtists(performancesId, timesId);
return AjaxResult.success(artists);
} catch (Exception e) {
return error("获取艺人阵容失败: " + e.getMessage());
}
}
/**
* 更新艺人排序
*/
@Log(title = "更新演出艺人排序", businessType = BusinessType.UPDATE)
@PostMapping("/updateArtistOrder")
@ResponseBody
public AjaxResult updateArtistOrder(@RequestBody Map<String, Object> params) {
try {
String performancesId = (String) params.get("performancesId");
String timesId = (String) params.get("timesId");
List<Map<String, Object>> orderData = (List<Map<String, Object>>) params.get("orderData");
if (orderData == null || orderData.isEmpty()) {
return error("排序数据不能为空");
}
boolean updateResult = artistPerformanceService.updateArtistOrder(performancesId, orderData);
return success("排序更新成功");
} catch (Exception e) {
return error("更新排序失败: " + e.getMessage());
}
}
/**
* 删除演出艺人关联
*/
@Log(title = "删除演出艺人", businessType = BusinessType.DELETE)
@PostMapping("/deletePerformanceArtist")
@ResponseBody
public AjaxResult deletePerformanceArtist(Long mid, String performancesId, String timesId) {
try {
if (mid == null) {
return error("参数错误");
}
int result = artistPerformanceService.deletePerformanceArtist(mid, performancesId);
if (result > 0) {
return success("删除成功");
} else {
return error("删除失败");
}
} catch (Exception e) {
return error("删除失败: " + e.getMessage());
}
}
/**
* 获取所有艺人
*/
@GetMapping("/getAllArtists")
@ResponseBody
public AjaxResult getAllArtists(String performancesId, String timesId, String keyword) {
try {
List<KylinArtistAssociationStatusDto> resultList = artistPerformanceService.getAllArtists(performancesId, timesId, keyword);
return AjaxResult.success(resultList);
} catch (Exception e) {
return error("获取艺人列表失败: " + e.getMessage());
}
}
/**
* 修改艺人关联
*
* @param payload
* @return
*/
@PostMapping("/updateArtistAssociations")
@ResponseBody
public AjaxResult updateArtistAssociations(@RequestBody Map<String, Object> payload) {
try {
String performancesId = (String) payload.get("performancesId");
String timesId = (String) payload.get("timesId");
List<Map<String, Object>> artistOrders = (List<Map<String, Object>>) payload.get("artistOrders");
boolean result = artistPerformanceService.updateArtistAssociations(performancesId, timesId, artistOrders);
return success("关联更新成功");
} catch (Exception e) {
logger.error("error", e);
return error("更新关联失败: " + e.getMessage());
}
}
}
......@@ -26,6 +26,7 @@ import com.liquidnet.service.kylin.dto.param.SysDamaiParam;
import com.liquidnet.service.kylin.dto.vo.admin.*;
import com.liquidnet.service.kylin.dto.vo.partner.KylinPerformanceMisVo;
import com.liquidnet.service.kylin.dto.vo.partner.KylinPerformancesVo;
import com.liquidnet.service.kylin.dto.vo.partner.TicketTimesTicketCreatePartnerVo;
import com.liquidnet.service.kylin.entity.KylinOrderImport;
import com.liquidnet.service.kylin.service.admin.IKylinPerformancesAdminService;
import com.liquidnet.service.kylin.service.other.DamaiService;
......@@ -45,6 +46,7 @@ import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
......@@ -258,6 +260,41 @@ public class KylinPerformancesController extends BaseController {
return prefix + "/subscribe";
}
/**
* 演出阵容页面
*/
@GetMapping(value = "/artistLineup/{performancesId}")
public String artistLineup(@PathVariable("performancesId") String performancesId, ModelMap mmap) {
try {
// 获取演出信息
KylinPerformanceMisVo performance = kylinPerformancesService.performanceDetails(performancesId);
if (performance == null) {
mmap.put("errorMsg", "演出不存在");
return prefix + "/artistLineup";
}
// 将场次数据转换为JSON
List<TicketTimesTicketCreatePartnerVo> ticketTimes = performance.getTicketTimes();
String ticketTimesJson = "[]";
if (ticketTimes != null && !ticketTimes.isEmpty()) {
List<TicketTimesTicketCreatePartnerVo> collect = ticketTimes.stream()
.filter(f -> f.getType().equals(1))
.collect(Collectors.toList());
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
ticketTimesJson = mapper.writeValueAsString(collect);
}
mmap.put("performancesId", performancesId);
mmap.put("performanceTitle", performance.getTitle());
mmap.put("ticketTimesJson", ticketTimesJson);
return prefix + "/artistLineup";
} catch (Exception e) {
mmap.put("errorMsg", "加载演出阵容失败: " + e.getMessage());
return prefix + "/artistLineup";
}
}
@Log(title = "预约统计:导出列表")
@PostMapping("/subscribe/export")
@ResponseBody
......
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<th:block th:include="include :: header('艺人列表')"/>
</head>
<body class="gray-bg">
<div class="container-div">
<div class="row">
<div class="col-sm-12 search-collapse">
<form id="formId">
<div class="select-list">
<ul>
<li>
<label>艺人ID:</label>
<input type="text" name="artistId" placeholder="请输入艺人ID"/>
</li>
<li>
<label>艺人名称:</label>
<input type="text" name="artistName" placeholder="请输入艺人名称"/>
</li>
<li>
<label>艺人类型:</label>
<select name="artistType">
<option value="">全部</option>
<option value="1">音乐人</option>
<option value="2">艺术家</option>
<option value="3">厂牌</option>
<option value="4">品牌方</option>
</select>
</li>
<li class="select-time">
<label>入驻时间:</label>
<input type="text" class="time-input" id="beginDate" name="beginDate" placeholder="开始日期"/>
<span>-</span>
<input type="text" class="time-input" id="endDate" name="endDate" placeholder="结束日期"/>
</li>
<li>
<a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i
class="fa fa-search"></i>&nbsp;搜索</a>
<a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i
class="fa fa-refresh"></i>&nbsp;重置</a>
</li>
</ul>
</div>
</form>
</div>
<div class="btn-group-sm" id="toolbar" role="group">
<a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="kylin:artist:create">
<i class="fa fa-plus"></i> 添加
</a>
</div>
<div class="col-sm-12 select-table table-bordered">
<table id="bootstrap-table"></table>
</div>
</div>
</div>
<th:block th:include="include :: footer"/>
<script th:inline="javascript">
var updateFlag = [[${@permission.hasPermi('kylin:artist:update')}]];
var removeFlag = [[${@permission.hasPermi('kylin:artist:delete')}]];
var detailFlag = [[${@permission.hasPermi('kylin:artist:detail')}]];
var prefix = ctx + "kylin/artist";
$(function () {
var options = {
url: prefix + "/list",
updateUrl: prefix + "/update/{id}",
createUrl: prefix + "/create",
removeUrl: prefix + "/delete",
detailUrl: prefix + "/detail/{id}",
modalName: "艺人",
columns: [
{
field: 'artistId',
title: '艺人ID'
},
{
field: 'avatarUrl',
title: '艺人头像',
formatter: function(value, row, index) {
return $.table.imageView(value, "80", "80");
}
},
{
field: 'artistName',
title: '艺人名称'
},
{
field: 'artistTypeName',
title: '艺人类型',
},
{
field: 'createdAt',
title: '艺人入驻时间',
formatter: function(value, row, index) {
if (value) {
var date = new Date(value);
return date.getFullYear() + '-' +
String(date.getMonth() + 1).padStart(2, '0') + '-' +
String(date.getDate()).padStart(2, '0') + ' ' +
String(date.getHours()).padStart(2, '0') + ':' +
String(date.getMinutes()).padStart(2, '0') + ':' +
String(date.getSeconds()).padStart(2, '0');
}
return '';
}
},
{
field: 'performanceCount',
title: '关联演出',
formatter: function(value, row, index) {
return value || 0;
}
},
{
field: 'productCount',
title: '关联商品',
formatter: function(value, row, index) {
return value || 0;
}
},
{
title: '操作',
align: 'center',
formatter: function (value, row, index) {
var actions = [];
actions.push('<a class="btn btn-info btn-xs ' + detailFlag + '" href="javascript:void(0)" onclick="$.operate.detail(\'' + row.artistId + '\')"><i class="fa fa-eye"></i>详情</a> ');
actions.push('<a class="btn btn-success btn-xs ' + updateFlag + '" href="javascript:void(0)" onclick="$.operate.edit(\'' + row.artistId + '\')"><i class="fa fa-edit"></i>编辑</a> ');
actions.push('<a class="btn btn-warning btn-xs ' + detailFlag + '" href="javascript:void(0)" onclick="showOperationLog(\'' + row.artistId + '\')"><i class="fa fa-history"></i>操作日志</a> ');
return actions.join('');
}
}]
};
$.table.init(options);
// 初始化日期选择器
layui.use('laydate', function(){
var laydate = layui.laydate;
// 开始日期
laydate.render({
elem: '#beginDate',
format: 'yyyy-MM-dd',
type: 'date',
done: function(value, date){
// 可以在这里添加日期选择后的逻辑
}
});
// 结束日期
laydate.render({
elem: '#endDate',
format: 'yyyy-MM-dd',
type: 'date',
done: function(value, date){
// 可以在这里添加日期选择后的逻辑
}
});
});
});
// 单条删除
function removeArtist(id) {
$.operate.remove(id, "删除后该艺人信息将无法被引用,也无法找回,确认要将该艺人删除吗?");
}
// 显示操作日志
function showOperationLog(artistId) {
var url = prefix + '/operationLog/' + artistId;
$.modal.openTab("艺人操作记录", url);
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="include :: header('添加艺人')"/>
<th:block th:include="include :: bootstrap-fileinput-css"/>
<th:block th:include="include :: summernote-css"/>
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<form class="form-horizontal m" id="form-artist-add">
<div class="form-group">
<label class="col-sm-2 control-label is-required">艺人名称:</label>
<div class="col-sm-10">
<input class="form-control" type="text" name="artistName" id="artistName"
maxlength="50" placeholder="最多输入50个字" required>
<span class="help-block m-b-none text-muted">
<span id="nameCount">0</span>/50 字
</span>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label is-required">艺人类型:</label>
<div class="col-sm-10">
<select class="form-control" name="artistType" id="artistType" required>
<option value="">请选择艺人类型</option>
<option value="1">音乐人</option>
<option value="2">艺术家</option>
<option value="3">厂牌</option>
<option value="4">品牌方</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label is-required">艺人头像:</label>
<div class="col-sm-10">
<div class="file-loading">
<input id="fileinput-avatar" type="file" name="file" data-browse-on-zone-click="true">
</div>
<span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 支持上传jpg、png格式图片,最大支持5M,上传后按照1:1格式进行裁剪</span>
</div>
</div>
<input hidden id="avatarUrl" name="avatarUrl">
<div class="form-group">
<label class="col-sm-2 control-label is-required">艺人简介:</label>
<div class="col-sm-10">
<div id="introduction" name="introduction"></div>
<span class="help-block m-b-none text-muted">
<span id="introCount">0</span>/2000 字
</span>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">艺人相册:</label>
<div class="col-sm-10">
<div class="file-loading">
<input id="fileinput-album" type="file" name="file" multiple data-browse-on-zone-click="true">
</div>
<span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 支持上传jpg、png格式图片,单次最多上传20张,每张最大支持5M</span>
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer"/>
<th:block th:include="include :: bootstrap-fileinput-js"/>
<th:block th:include="include :: summernote-js"/>
<script type="text/javascript">
var prefix = ctx + "kylin/artist";
var platformUrl = "[[${platformUrl}]]";
var albumImagesList = [];
function submitHandler() {
var artistName = $('#artistName').val().trim();
if (!artistName) {
$.modal.alertWarning("请填写艺人名称");
$('#artistName').focus();
return false;
}
if (artistName.length > 50) {
$.modal.alertWarning("艺人名称最多输入50个字");
$('#artistName').focus();
return false;
}
// 检查艺人名称是否已存在
var nameExists = false;
$.ajax({
url: prefix + "/checkName",
type: "post",
data: { artistName: artistName },
async: false,
success: function(result) {
if (result.code != 0) {
$.modal.alertWarning("艺人名称已存在,不可重复创建");
$('#artistName').focus();
nameExists = true;
}
}
});
if (nameExists) {
return false;
}
var artistType = $('#artistType').val();
if (!artistType) {
$.modal.alertWarning("请选择艺人类型");
$('#artistType').focus();
return false;
}
var avatarUrl = $('#avatarUrl').val();
if (!avatarUrl) {
$.modal.alertWarning("请上传艺人头像");
return false;
}
var introduction = $('#introduction').summernote('code');
if (!introduction || introduction.trim() === '' || introduction === '<p><br></p>') {
$.modal.alertWarning("请填写艺人简介");
return false;
}
// 计算富文本内容的文字长度
var introText = $('<div>').html(introduction).text();
if (introText.length > 2000) {
$.modal.alertWarning("艺人简介最多输入2000个字");
return false;
}
// 将相册图片列表转换为JSON数组
var albumImagesJson = JSON.stringify(albumImagesList);
if ($.validate.form()) {
var data = $('#form-artist-add').serializeArray();
// 添加富文本内容
data.push({name: 'introduction', value: introduction});
// 添加相册图片(作为JSON数组字符串)
data.push({name: 'albumImagesJson', value: albumImagesJson});
$.operate.save(prefix + "/create", data);
}
}
$(function () {
// 艺人名称字数统计和实时校验
$('#artistName').on('input', function() {
var length = $(this).val().length;
$('#nameCount').text(length);
if (length > 50) {
$(this).addClass('is-invalid');
$(this).parent().find('.error-msg').remove();
$(this).parent().append('<span class="error-msg text-danger">艺人名称不能超过50个字</span>');
} else {
$(this).removeClass('is-invalid');
$(this).parent().find('.error-msg').remove();
}
});
// 艺人名称失去焦点校验
$('#artistName').on('blur', function() {
var value = $(this).val().trim();
$(this).parent().find('.error-msg').remove();
if (!value) {
$(this).addClass('is-invalid');
$(this).parent().append('<span class="error-msg text-danger">请填写艺人名称</span>');
} else if (value.length > 50) {
$(this).addClass('is-invalid');
$(this).parent().append('<span class="error-msg text-danger">艺人名称不能超过50个字</span>');
} else {
$(this).removeClass('is-invalid');
}
});
// 初始化富文本编辑器
$('#introduction').summernote({
height: 200,
lang: 'zh-CN',
placeholder: '请输入艺人简介,最多输入2000个字',
toolbar: [
['style', ['style']],
['font', ['bold', 'italic', 'underline', 'clear']],
['fontname', ['fontname']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['insert', ['link', 'picture']],
['view', ['fullscreen', 'codeview']]
],
callbacks: {
onChange: function(contents, $editable) {
var text = $('<div>').html(contents).text();
var length = text.length;
$('#introCount').text(length);
if (length > 2000) {
$('#introCount').addClass('text-danger');
} else {
$('#introCount').removeClass('text-danger');
}
},
onImageUpload: function(files) {
var data = new FormData();
data.append("file", files[0]);
data.append("pathName", "artist/introduction");
data.append("buckType", 1);
$.ajax({
url: platformUrl + "/platform/basicServices/alOss/upload/unsm",
type: "post",
data: data,
cache: false,
contentType: false,
processData: false,
success: function(result) {
if (result.code == 0 || result.code == 200) {
var imgPath = result.data.ossPath;
var fullUrl = "https://img.zhengzai.tv/" + imgPath;
$('#introduction').summernote('insertImage', fullUrl);
} else {
$.modal.alertWarning(result.msg || "图片上传失败");
}
},
error: function() {
$.modal.alertWarning("图片上传服务异常");
}
});
}
}
});
// 初始化头像上传
$("#fileinput-avatar").fileinput({
'theme': 'explorer-fas',
'uploadUrl': platformUrl + "/platform/basicServices/alOss/upload/unsm",
"uploadExtraData": {
"pathName": "artist/avatar",
"buckType": 1
},
autoReplace: true,
dropZoneTitle: "请上传艺人头像(1:1比例,仅限1张)",
maxFileCount: 1,
maxFileSize: 5120,
allowedFileExtensions: ['jpg', 'png', 'jpeg'],
msgSizeTooLarge: '文件 "{name}" ({size} KB) 超过了允许大小 {maxSize} KB,最大支持上传5M文件',
msgFilesTooMany: '只能上传1张头像图片!'
});
$("#fileinput-avatar").on("fileuploaded", function (event, data, previewId, index) {
var imgPath = data.response.data.ossPath;
var fullUrl = "https://img.zhengzai.tv/" + imgPath;
$("#avatarUrl").val(fullUrl);
});
// 初始化相册上传
$("#fileinput-album").fileinput({
'theme': 'explorer-fas',
'uploadUrl': platformUrl + "/platform/basicServices/alOss/upload/unsm",
"uploadExtraData": {
"pathName": "artist/album",
"buckType": 1
},
uploadAsync: true, // 异步上传,每次上传一个文件
dropZoneTitle: "请上传艺人相册图片(最多20张)",
maxFileCount: 20, // 最多20张
maxFileSize: 5120,
allowedFileExtensions: ['jpg', 'png', 'jpeg'],
msgSizeTooLarge: '文件 "{name}" ({size} KB) 超过了允许大小 {maxSize} KB,最大支持上传5M文件',
msgFilesTooMany: '选择的文件数量({n})超过了允许的最大数量{m},相册最多只能上传20张图片!',
showRemove: true,
showUpload: false,
validateInitialCount: true,
maxTotalFileCount: 20
}).on('filebatchselected', function(event, files) {
// 检查选择的文件数量
var currentCount = $("#fileinput-album").fileinput('getFilesCount');
console.log("当前文件总数:", currentCount);
if (currentCount > 20) {
$.modal.alertWarning("相册最多只能上传20张图片!");
return false;
}
});
$("#fileinput-album").on("fileuploaded", function (event, data, previewId, index) {
var imgPath = data.response.data.ossPath;
var fullUrl = "https://img.zhengzai.tv/" + imgPath;
albumImagesList.push(fullUrl);
});
$("#fileinput-album").on("fileremoved", function(event, id, index) {
// 从列表中移除对应的图片
if (index < albumImagesList.length) {
albumImagesList.splice(index, 1);
}
});
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="include :: header('艺人详情')"/>
<style>
.artist-detail-container {
padding: 20px;
}
.artist-basic-info {
margin-bottom: 30px;
}
.artist-basic-info h3 {
margin-bottom: 15px;
color: #333;
font-size: 18px;
}
.info-row {
display: flex;
margin-bottom: 12px;
}
.info-label {
width: 120px;
font-weight: bold;
color: #666;
}
.info-value {
flex: 1;
color: #333;
}
.artist-intro {
margin-top: 20px;
padding: 15px;
background-color: #f8f9fa;
border-radius: 4px;
}
.section-title {
font-size: 18px;
font-weight: bold;
color: #333;
margin: 30px 0 15px 0;
padding-bottom: 10px;
border-bottom: 2px solid #e9ecef;
}
.album-gallery {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 10px;
}
.album-item {
width: 100px;
height: 100px;
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
background-color: #f8f9fa;
}
.album-item img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.table-container {
margin-top: 15px;
border: 1px solid #e9ecef;
border-radius: 4px;
overflow: hidden;
}
.custom-table {
width: 100%;
border-collapse: collapse;
}
.custom-table thead {
background-color: #f8f9fa;
}
.custom-table th,
.custom-table td {
padding: 12px 15px;
border-bottom: 1px solid #e9ecef;
text-align: left;
}
.custom-table th {
font-weight: bold;
color: #333;
}
.custom-table tbody tr:hover {
background-color: #f8f9fa;
}
.product-image {
width: 60px;
height: 60px;
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.product-image img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.status-badge {
display: inline-block;
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
}
.status-on {
background-color: #d4edda;
color: #155724;
}
.status-off {
background-color: #f8d7da;
color: #721c24;
}
.empty-data {
text-align: center;
padding: 40px;
color: #999;
font-style: italic;
}
/* Lightbox */
.img-zoomable {
cursor: zoom-in;
}
#artist-lightbox {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.85);
z-index: 99999;
align-items: center;
justify-content: center;
}
#artist-lightbox.active {
display: flex;
}
#artist-lightbox img {
max-width: 90vw;
max-height: 90vh;
object-fit: contain;
border-radius: 6px;
box-shadow: 0 8px 40px rgba(0,0,0,0.6);
transition: transform 0.2s;
}
#artist-lightbox-close {
position: fixed;
top: 20px;
right: 28px;
font-size: 36px;
color: #fff;
cursor: pointer;
line-height: 1;
user-select: none;
opacity: 0.85;
}
#artist-lightbox-close:hover {
opacity: 1;
}
</style>
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight">
<div class="ibox">
<div class="ibox-content" th:object="${ArtistVo}">
<div class="artist-detail-container">
<!-- 基本信息 -->
<div class="artist-basic-info">
<h3>基本信息</h3>
<div class="info-row">
<div class="info-label">艺人ID:</div>
<div class="info-value" th:text="*{artistId}">10001</div>
</div>
<div class="info-row">
<div class="info-label">艺人名称:</div>
<div class="info-value" th:text="*{artistName}">摩登天空</div>
</div>
<div class="info-row">
<div class="info-label">艺人类型:</div>
<div class="info-value" th:text="*{artistTypeName}">品牌方</div>
</div>
<!-- 艺人头像 -->
<div class="info-row" th:if="*{avatarUrl}">
<div class="info-label">艺人头像:</div>
<div class="info-value">
<div style="width: 100px; height: 100px; border: 1px solid #ddd; border-radius: 4px; overflow: hidden; display: flex; align-items: center; justify-content: center;">
<img th:src="*{avatarUrl}" alt="艺人头像" style="max-width: 100%; max-height: 100%;" class="img-zoomable" onclick="artistLightboxOpen(this.src, this.alt)">
</div>
</div>
</div>
<!-- 艺人简介 -->
<div class="artist-intro">
<div class="info-label" style="margin-bottom: 8px;">艺人简介:</div>
<div class="info-value" th:utext="*{introduction} ?: '暂无介绍'">
摩登天空是中国知名音乐厂牌,致力于推广独立音乐。
</div>
</div>
</div>
<!-- 艺人相册 -->
<div th:if="*{albumImages != null and !albumImages.isEmpty()}">
<div class="section-title">艺人相册</div>
<div class="album-gallery">
<th:block th:each="img, iterStat : ${ArtistVo.albumImages}">
<div class="album-item">
<img th:src="${img}" th:alt="'相册图片' + ${iterStat.index + 1}" class="img-zoomable" onclick="artistLightboxOpen(this.src, this.alt)">
</div>
</th:block>
</div>
</div>
<!-- 演出信息 -->
<div>
<div class="section-title">
艺人演出信息
<span th:if="*{performanceVoList != null}" style="font-size: 14px; color: #666; margin-left: 10px;">
(共<span th:text="*{performanceVoList.size()}">2</span>条)
</span>
</div>
<div class="table-container" th:if="*{performanceVoList != null and !performanceVoList.isEmpty()}">
<table class="custom-table">
<thead>
<tr>
<th width="40%">演出名称</th>
<th width="30%">演出ID</th>
<th width="30%">演出时间</th>
</tr>
</thead>
<tbody>
<tr th:each="performance : ${ArtistVo.performanceVoList}">
<td th:text="${performance.title}">摩登天空音乐节</td>
<td th:text="${performance.performanceId}">show001</td>
<td th:text="${performance.timeStart}">2023-05-01</td>
</tr>
</tbody>
</table>
</div>
<div th:if="*{performanceVoList == null or performanceVoList.isEmpty()}" class="empty-data">
暂无演出信息
</div>
</div>
<!-- 关联商品信息 -->
<div>
<div class="section-title">
艺人关联商品信息
<span th:if="*{productVoList != null}" style="font-size: 14px; color: #666; margin-left: 10px;">
(共<span th:text="*{productVoList.size()}">2</span>条)
</span>
</div>
<div class="table-container" th:if="*{productVoList != null and !productVoList.isEmpty()}">
<table class="custom-table">
<thead>
<tr>
<th width="15%">商品编码</th>
<th width="20%">商品名称</th>
<th width="15%">商品头图</th>
<th width="15%">商品分类</th>
<th width="15%">商品售价</th>
<th width="15%">上架状态</th>
</tr>
</thead>
<tbody>
<tr th:each="product : ${ArtistVo.productVoList}">
<td th:text="${product.productCode} ?: '--'">goods001</td>
<td th:text="${product.productName} ?: '--'">摩登天空T恤</td>
<td>
<div class="product-image" th:if="${product.imageUrl}">
<img th:src="${product.imageUrl}" alt="商品图片">
</div>
<span th:unless="${product.imageUrl}">--</span>
</td>
<td th:text="${product.category} ?: '--'">服饰</td>
<td th:text="${product.price} ?: '--'">99.00元</td>
<td>
<span th:if="${product.status == 1}" class="status-badge status-on">已上架</span>
<span th:if="${product.status == 0}" class="status-badge status-off">未上架</span>
<span th:if="${product.status != 1 and product.status != 0}" th:text="${product.status}">--</span>
</td>
</tr>
</tbody>
</table>
</div>
<div th:if="*{productVoList == null or productVoList.isEmpty()}" class="empty-data">
暂无关联商品信息
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 图片放大灯箱 -->
<div id="artist-lightbox" onclick="artistLightboxClose()">
<span id="artist-lightbox-close" onclick="artistLightboxClose()">&times;</span>
<img id="artist-lightbox-img" src="" alt="" onclick="event.stopPropagation()">
</div>
<script>
function artistLightboxOpen(src, alt) {
var lb = document.getElementById('artist-lightbox');
var img = document.getElementById('artist-lightbox-img');
img.src = src;
img.alt = alt || '';
lb.classList.add('active');
document.body.style.overflow = 'hidden';
}
function artistLightboxClose() {
var lb = document.getElementById('artist-lightbox');
lb.classList.remove('active');
document.getElementById('artist-lightbox-img').src = '';
document.body.style.overflow = '';
}
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') { artistLightboxClose(); }
});
</script>
<th:block th:include="include :: footer"/>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="include :: header('艺人操作记录')"/>
</head>
<body class="gray-bg">
<div class="container-div">
<div class="row">
<!-- 头部信息 -->
<div class="col-sm-12" style="margin-bottom: 15px;">
<div class="alert alert-info" style="margin-bottom: 0;">
<strong>艺人ID:</strong><span th:text="${artistId}">10001</span>
<strong style="margin-left: 30px;">艺人名称:</strong><span th:text="${artistName}">摩登天空</span>
</div>
</div>
<div class="col-sm-12 select-table table-bordered">
<table id="bootstrap-table"></table>
</div>
</div>
</div>
<th:block th:include="include :: footer"/>
<script th:inline="javascript">
var artistId = [[${artistId}]];
var prefix = ctx + "kylin/artist";
$(function () {
var options = {
url: prefix + "/operationLogList",
queryParams: function(params) {
return {
pageNum: params.offset / params.limit + 1,
pageSize: params.limit,
artistId: artistId
};
},
columns: [
{
field: 'operationType',
title: '操作类型',
width: '10%',
formatter: function(value, row, index) {
var typeClass = '';
var typeName = row.operationTypeName || '未知';
if (value == 1) {
typeClass = 'label-success';
} else if (value == 2) {
typeClass = 'label-warning';
} else if (value == 3) {
typeClass = 'label-danger';
}
return '<span class="label ' + typeClass + '">' + typeName + '</span>';
}
},
{
field: 'createdAt',
title: '操作时间',
width: '20%'
},
{
field: 'operationContent',
title: '修改内容',
width: '50%'
},
{
field: 'operatorName',
title: '操作人',
width: '20%',
formatter: function(value, row, index) {
return value || '系统';
}
}
]
};
$.table.init(options);
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="include :: header('编辑艺人')"/>
<th:block th:include="include :: bootstrap-fileinput-css"/>
<th:block th:include="include :: summernote-css"/>
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<form class="form-horizontal m" id="form-artist-edit" th:object="${ArtistVo}">
<input type="hidden" name="artistId" th:field="*{artistId}">
<div class="form-group">
<label class="col-sm-2 control-label is-required">艺人名称:</label>
<div class="col-sm-10">
<input class="form-control" type="text" name="artistName" id="artistName"
th:field="*{artistName}" maxlength="50" placeholder="最多输入50个字" required>
<span class="help-block m-b-none text-muted">
<span id="nameCount">0</span>/50 字
</span>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label is-required">艺人类型:</label>
<div class="col-sm-10">
<select class="form-control" name="artistType" id="artistType" th:field="*{artistType}" required>
<option value="">请选择艺人类型</option>
<option value="1">音乐人</option>
<option value="2">艺术家</option>
<option value="3">厂牌</option>
<option value="4">品牌方</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label is-required">艺人头像:</label>
<div class="col-sm-10">
<div class="file-loading">
<input id="fileinput-avatar" type="file" name="file" data-browse-on-zone-click="true">
</div>
<span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 支持上传jpg、png格式图片,最大支持5M,上传后按照1:1格式进行裁剪</span>
</div>
</div>
<input hidden id="avatarUrl" name="avatarUrl" th:field="*{avatarUrl}">
<div class="form-group">
<label class="col-sm-2 control-label is-required">艺人简介:</label>
<div class="col-sm-10">
<div id="introduction" name="introduction"></div>
<span class="help-block m-b-none text-muted">
<span id="introCount">0</span>/2000 字
</span>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">艺人相册:</label>
<div class="col-sm-10">
<div class="file-loading">
<input id="fileinput-album" type="file" name="file" multiple data-browse-on-zone-click="true">
</div>
<span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 支持上传jpg、png格式图片,单次最多上传20张,每张最大支持5M</span>
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer"/>
<th:block th:include="include :: bootstrap-fileinput-js"/>
<th:block th:include="include :: summernote-js"/>
<script th:inline="javascript">
var prefix = ctx + "kylin/artist";
var platformUrl = /*[[${platformUrl}]]*/ "";
// 使用安全的方式获取数据,避免JavaScript语法错误
var artistData = {
artistId: /*[[${ArtistVo?.artistId}]]*/ "",
artistName: /*[[${ArtistVo?.artistName}]]*/ "",
artistType: /*[[${ArtistVo?.artistType}]]*/ 0,
avatarUrl: /*[[${ArtistVo?.avatarUrl}]]*/ "",
introduction: /*[[${ArtistVo?.introduction}]]*/ "",
albumImages: /*[[${ArtistVo?.albumImages}]]*/ []
};
var albumImagesList = [];
var originalArtistName = "";
console.log("原始艺人数据:", artistData);
// 安全地获取艺人数据
if (artistData) {
originalArtistName = artistData.artistName || "";
// 初始化相册图片列表
if (artistData.albumImages && Array.isArray(artistData.albumImages) && artistData.albumImages.length > 0) {
albumImagesList = artistData.albumImages.slice(); // 复制数组
console.log("初始化相册图片列表:", albumImagesList);
} else {
console.log("没有相册图片数据");
}
} else {
console.error("艺人数据为空");
}
function submitHandler() {
var artistName = $('#artistName').val().trim();
if (!artistName) {
$.modal.alertWarning("请填写艺人名称");
$('#artistName').focus();
return false;
}
if (artistName.length > 50) {
$.modal.alertWarning("艺人名称最多输入50个字");
$('#artistName').focus();
return false;
}
// 如果艺人名称有修改,检查是否已存在
if (artistName !== originalArtistName) {
var nameExists = false;
$.ajax({
url: prefix + "/checkName",
type: "post",
data: {
artistName: artistName,
artistId: artistData.artistId
},
async: false,
success: function(result) {
if (result.code != 0) {
$.modal.alertWarning("艺人名称已被其他艺人使用");
$('#artistName').focus();
nameExists = true;
}
}
});
if (nameExists) {
return false;
}
}
var artistType = $('#artistType').val();
if (!artistType) {
$.modal.alertWarning("请选择艺人类型");
$('#artistType').focus();
return false;
}
var avatarUrl = $('#avatarUrl').val();
if (!avatarUrl) {
$.modal.alertWarning("请上传艺人头像");
return false;
}
var introduction = $('#introduction').summernote('code');
if (!introduction || introduction.trim() === '' || introduction === '<p><br></p>') {
$.modal.alertWarning("请填写艺人简介");
return false;
}
var introText = $('<div>').html(introduction).text();
if (introText.length > 2000) {
$.modal.alertWarning("艺人简介最多输入2000个字");
return false;
}
// 重新构建最终的相册列表
// 方案:遍历当前显示的所有预览图,提取图片URL
var finalAlbumList = [];
// 获取所有预览框
var previews = $("#fileinput-album").parent().find('.file-preview-frame');
previews.each(function() {
var $preview = $(this);
// 检查是否是初始预览(有data-fileindex属性)
if ($preview.attr('data-fileindex') !== undefined) {
var fileIndex = parseInt($preview.attr('data-fileindex'));
if (fileIndex >= 0 && fileIndex < albumImagesList.length) {
finalAlbumList.push(albumImagesList[fileIndex]);
}
} else {
// 新上传的图片,从img标签获取src
var imgSrc = $preview.find('img').attr('src');
if (imgSrc && imgSrc.indexOf('https://img.zhengzai.tv/') === 0) {
finalAlbumList.push(imgSrc);
}
}
});
// 如果上面的方法没有获取到,使用albumImagesList作为备份
if (finalAlbumList.length === 0 && albumImagesList.length > 0) {
finalAlbumList = albumImagesList.slice();
}
console.log("最终相册列表:", finalAlbumList);
var albumImagesJson = JSON.stringify(finalAlbumList);
if ($.validate.form()) {
var data = $('#form-artist-edit').serializeArray();
data.push({name: 'introduction', value: introduction});
data.push({name: 'albumImagesJson', value: albumImagesJson});
$.operate.save(prefix + "/update", data);
}
}
$(function () {
console.log("页面加载完成");
console.log("艺人数据:", artistData);
console.log("相册图片列表:", albumImagesList);
// 初始化字数统计
var nameLength = $('#artistName').val().length;
$('#nameCount').text(nameLength);
// 艺人名称字数统计和实时校验
$('#artistName').on('input', function() {
var length = $(this).val().length;
$('#nameCount').text(length);
if (length > 50) {
$(this).addClass('is-invalid');
$(this).parent().find('.error-msg').remove();
$(this).parent().append('<span class="error-msg text-danger">艺人名称不能超过50个字</span>');
} else {
$(this).removeClass('is-invalid');
$(this).parent().find('.error-msg').remove();
}
});
// 初始化富文本编辑器
$('#introduction').summernote({
height: 200,
lang: 'zh-CN',
placeholder: '请输入艺人简介,最多输入2000个字',
toolbar: [
['style', ['style']],
['font', ['bold', 'italic', 'underline', 'clear']],
['fontname', ['fontname']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['insert', ['link', 'picture']],
['view', ['fullscreen', 'codeview']]
],
callbacks: {
onChange: function(contents, $editable) {
var text = $('<div>').html(contents).text();
var length = text.length;
$('#introCount').text(length);
if (length > 2000) {
$('#introCount').addClass('text-danger');
} else {
$('#introCount').removeClass('text-danger');
}
},
onImageUpload: function(files) {
var data = new FormData();
data.append("file", files[0]);
data.append("pathName", "artist/introduction");
data.append("buckType", 1);
$.ajax({
url: platformUrl + "/platform/basicServices/alOss/upload/unsm",
type: "post",
data: data,
cache: false,
contentType: false,
processData: false,
success: function(result) {
if (result.code == 0 || result.code == 200) {
var imgPath = result.data.ossPath;
var fullUrl = "https://img.zhengzai.tv/" + imgPath;
$('#introduction').summernote('insertImage', fullUrl);
} else {
$.modal.alertWarning(result.msg || "图片上传失败");
}
},
error: function() {
$.modal.alertWarning("图片上传服务异常");
}
});
}
}
});
// 设置富文本内容
if (artistData && artistData.introduction) {
$('#introduction').summernote('code', artistData.introduction);
}
// 初始化头像上传
console.log("头像URL:", artistData ? artistData.avatarUrl : "无数据");
$("#fileinput-avatar").fileinput({
'theme': 'explorer-fas',
'uploadUrl': platformUrl + "/platform/basicServices/alOss/upload/unsm",
"uploadExtraData": {
"pathName": "artist/avatar",
"buckType": 1
},
autoReplace: true,
overwriteInitial: false,
initialPreviewAsData: true,
initialPreview: (artistData && artistData.avatarUrl) ? [artistData.avatarUrl] : [],
initialPreviewConfig: (artistData && artistData.avatarUrl) ? [{
caption: "当前头像",
size: 0,
width: "120px",
key: 1,
extra: {url: artistData.avatarUrl}
}] : [],
dropZoneTitle: "请上传艺人头像(1:1比例,仅限1张)",
maxFileCount: 1,
maxFileSize: 5120,
allowedFileExtensions: ['jpg', 'png', 'jpeg'],
msgSizeTooLarge: '文件 "{name}" ({size} KB) 超过了允许大小 {maxSize} KB,最大支持上传5M文件',
msgFilesTooMany: '只能上传1张头像图片!',
deleteUrl: "#", // 设置一个假的删除URL,启用删除按钮
previewSettings: {
image: {width: "auto", height: "160px"}
}
}).on('fileloaded', function(event, file, previewId, index, reader) {
console.log("头像加载成功:", previewId);
}).on('fileerror', function(event, data, msg) {
console.error("头像加载错误:", msg);
});
$("#fileinput-avatar").on("fileuploaded", function (event, data, previewId, index) {
var imgPath = data.response.data.ossPath;
var fullUrl = "https://img.zhengzai.tv/" + imgPath;
$("#avatarUrl").val(fullUrl);
console.log("头像上传成功:", fullUrl);
});
// 监听头像删除事件
$("#fileinput-avatar").on("filedeleted", function(event, key) {
console.log("删除头像");
$("#avatarUrl").val("");
});
// 监听头像清空事件
$("#fileinput-avatar").on("filecleared", function(event) {
console.log("清空头像");
$("#avatarUrl").val("");
});
// 初始化相册上传
console.log("相册图片列表:", albumImagesList);
// 计算还可以上传多少张图片
var remainingCount = 20 - (albumImagesList ? albumImagesList.length : 0);
console.log("还可以上传:", remainingCount, "张图片");
$("#fileinput-album").fileinput({
'theme': 'explorer-fas',
'uploadUrl': platformUrl + "/platform/basicServices/alOss/upload/unsm",
"uploadExtraData": {
"pathName": "artist/album",
"buckType": 1
},
uploadAsync: true, // 异步上传,每次上传一个文件
overwriteInitial: false,
initialPreviewAsData: true,
initialPreview: (albumImagesList && albumImagesList.length > 0) ? albumImagesList : [],
initialPreviewConfig: (albumImagesList && albumImagesList.length > 0) ?
albumImagesList.map(function(url, index) {
return {
caption: "相册图片" + (index + 1),
size: 0,
width: "120px",
key: index,
extra: {url: url} // 保存URL用于后续处理
};
}) : [],
dropZoneTitle: "请上传艺人相册图片(最多20张)",
maxFileCount: 20, // 总共最多20张
maxFileSize: 5120,
allowedFileExtensions: ['jpg', 'png', 'jpeg'],
msgSizeTooLarge: '文件 "{name}" ({size} KB) 超过了允许大小 {maxSize} KB,最大支持上传5M文件',
msgFilesTooMany: '选择的文件数量({n})超过了允许的最大数量{m},相册最多只能上传20张图片!',
showRemove: true,
showUpload: false,
deleteUrl: "#", // 设置一个假的删除URL,启用删除按钮
previewSettings: {
image: {width: "auto", height: "160px"}
},
validateInitialCount: true, // 验证初始文件数量
maxTotalFileCount: 20 // 包括初始文件在内的最大文件总数
}).on('fileloaded', function(event, file, previewId, index, reader) {
console.log("文件加载成功:", previewId);
}).on('fileerror', function(event, data, msg) {
console.error("文件加载错误:", msg);
}).on('filebatchselected', function(event, files) {
// 检查选择的文件数量
var currentCount = $("#fileinput-album").fileinput('getFilesCount');
console.log("当前文件总数:", currentCount);
if (currentCount > 20) {
$.modal.alertWarning("相册最多只能上传20张图片!");
return false;
}
});
$("#fileinput-album").on("fileuploaded", function (event, data, previewId, index) {
var imgPath = data.response.data.ossPath;
var fullUrl = "https://img.zhengzai.tv/" + imgPath;
albumImagesList.push(fullUrl);
console.log("上传后相册列表:", albumImagesList);
});
// 监听初始预览图片的删除事件
$("#fileinput-album").on("filedeleted", function(event, key) {
console.log("删除图片,key:", key);
// key 对应 initialPreviewConfig 中的 key
if (key >= 0 && key < albumImagesList.length) {
albumImagesList.splice(key, 1);
console.log("删除后相册列表:", albumImagesList);
}
});
// 监听文件清除事件
$("#fileinput-album").on("filecleared", function(event) {
console.log("清空所有文件");
albumImagesList = [];
console.log("清空后相册列表:", albumImagesList);
});
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="include :: header('演出阵容')" />
<style>
/* ===== 演出阵容主体样式 ===== */
.lineup-session {
margin-bottom: 30px;
border: 1px solid #e7eaec;
padding: 20px;
background: #fff;
border-radius: 4px;
}
.lineup-session-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
color: #676a6c;
padding-bottom: 10px;
border-bottom: 2px solid #1ab394;
}
.artist-list {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.artist-item {
position: relative;
text-align: center;
cursor: default;
width: 100px;
transition: all 0.3s;
}
.edit-mode .artist-item {
cursor: move;
}
.artist-item:hover {
transform: translateY(-5px);
}
.artist-item.dragging {
opacity: 0.5;
}
.artist-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
border: 2px solid #e7eaec;
transition: border-color 0.3s;
}
.artist-item:hover .artist-avatar {
border-color: #1ab394;
}
.artist-name {
margin-top: 8px;
font-size: 14px;
color: #676a6c;
word-break: break-all;
}
.delete-icon {
display: none;
position: absolute;
top: -5px;
right: 10px;
width: 20px;
height: 20px;
background: #ed5565;
color: white;
border-radius: 50%;
cursor: pointer;
align-items: center;
justify-content: center;
font-size: 14px;
line-height: 1;
transition: background-color 0.3s;
}
.edit-mode .delete-icon {
display: flex;
}
.delete-icon:hover {
background: #da4453;
}
.no-artists-tip {
text-align: center;
padding: 40px;
color: #999;
}
/* 编辑模式下的拖拽提示(标题旁小字) */
.drag-hint {
display: none;
margin-left: 10px;
color: #aaa;
font-size: 11px;
font-weight: normal;
}
.edit-mode .drag-hint {
display: inline;
}
/* ===== 关联艺人弹窗样式 ===== */
/* 搜索框容器:内嵌 × 按钮 */
.artist-search-wrap {
position: relative;
margin-bottom: 12px;
}
.artist-search-wrap .form-control {
padding-right: 34px;
border-radius: 4px;
}
.artist-search-clear {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: #aaa;
font-size: 16px;
line-height: 1;
display: none;
background: none;
border: none;
padding: 0;
}
.artist-search-clear:hover {
color: #555;
}
/* 搜索结果列表 */
.modal-artist-list {
max-height: 300px;
overflow-y: auto;
border: 1px solid #eee;
border-radius: 4px;
}
.modal-artist-item {
display: flex;
align-items: center;
padding: 10px 14px;
cursor: pointer;
border-bottom: 1px solid #f0f0f0;
transition: background 0.15s;
}
.modal-artist-item:last-child {
border-bottom: none;
}
.modal-artist-item:hover {
background: #f7f9fc;
}
.modal-artist-avatar {
width: 44px;
height: 44px;
border-radius: 50%;
object-fit: cover;
margin-right: 12px;
flex-shrink: 0;
border: 2px solid #e0e0e0;
}
.modal-artist-name {
flex: 1;
font-size: 14px;
color: #333;
}
/* 单选按钮样式 */
.artist-radio-btn {
width: 22px;
height: 22px;
border-radius: 50%;
border: 2px solid #ccc;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
transition: border-color 0.2s, background 0.2s;
}
.artist-radio-btn.selected {
border-color: #1ab394;
background: #1ab394;
}
.artist-radio-btn.selected::after {
content: '';
display: block;
width: 8px;
height: 5px;
border-left: 2px solid #fff;
border-bottom: 2px solid #fff;
transform: rotate(-45deg) translateY(-1px);
}
/* 加载动画 */
.modal-loading {
text-align: center;
padding: 30px;
color: #999;
}
/* 空状态 */
.modal-empty {
text-align: center;
padding: 30px;
color: #aaa;
font-size: 13px;
}
/* 已选艺人区域 */
.selected-artists-section {
margin-top: 14px;
}
.selected-artists-section h5 {
font-size: 14px;
font-weight: 600;
color: #555;
margin-bottom: 10px;
}
.selected-artists-container {
display: flex;
flex-wrap: nowrap;
/* 不换行,支持横向滚动 */
overflow-x: auto;
gap: 12px;
padding: 10px;
min-height: 90px;
border: 1px solid #eee;
border-radius: 4px;
background: #fafafa;
align-items: flex-start;
}
.selected-artists-container::-webkit-scrollbar {
height: 4px;
}
.selected-artists-container::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 2px;
}
.selected-artist-card {
position: relative;
text-align: center;
flex-shrink: 0;
width: 72px;
cursor: grab;
transition: opacity 0.2s, transform 0.15s;
}
.selected-artist-card:active {
cursor: grabbing;
}
.selected-artist-card.dragging {
opacity: 0.4;
transform: scale(0.95);
}
.selected-artist-card.drag-over {
outline: 2px dashed #1ab394;
outline-offset: 2px;
border-radius: 6px;
}
.selected-artist-avatar {
width: 56px;
height: 56px;
border-radius: 50%;
object-fit: cover;
border: 2px solid #1ab394;
}
.selected-artist-name {
margin-top: 5px;
font-size: 11px;
color: #555;
word-break: break-all;
line-height: 1.3;
}
.remove-selected-artist {
position: absolute;
top: -4px;
right: 4px;
width: 18px;
height: 18px;
background: #ed5565;
color: white;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
line-height: 1;
transition: background-color 0.2s;
}
.remove-selected-artist:hover {
background: #da4453;
}
.selected-empty-tip {
width: 100%;
text-align: center;
color: #bbb;
font-size: 13px;
padding: 20px 0;
align-self: center;
}
/* 弹窗底部按钮 */
.modal-footer .btn-cancel {
border: 1px solid #ccc;
color: #555;
background: #fff;
}
.modal-footer .btn-cancel:hover {
background: #f5f5f5;
}
</style>
</head>
<body class="gray-bg">
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox">
<!-- <div class="ibox-title">-->
<!-- <h5>演出阵容管理</h5>-->
<!-- <div class="ibox-tools">-->
<!-- <span th:text="${performanceTitle}"-->
<!-- style="font-size:15px;font-weight:600;color:#333;vertical-align:middle;"></span>-->
<!-- </div>-->
<!-- </div>-->
<div class="ibox-content">
<div style="margin-bottom:15px;">
<button class="btn btn-primary btn-sm" id="edit-lineup-btn">编辑演出阵容</button>
</div>
<div id="artist-lineup-container">
<div class="text-center">
<i class="fa fa-spinner fa-spin fa-3x"></i>
<p>加载中...</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 关联艺人弹窗 -->
<div class="modal fade" id="associateArtistModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">关联艺人</h4>
</div>
<div class="modal-body">
<!-- 搜索框 -->
<div class="artist-search-wrap">
<input type="text" class="form-control" id="artist-search-input" placeholder="搜索艺人姓名"
autocomplete="off">
<button class="artist-search-clear" id="clear-search-btn" title="清空">&times;</button>
</div>
<!-- 搜索结果列表 -->
<div class="modal-artist-list" id="modal-artist-list">
<!-- 动态渲染 -->
</div>
<!-- 已选艺人区 -->
<div class="selected-artists-section">
<h5>已选艺人 <small style="font-weight:normal;color:#aaa;font-size:11px;">(可拖拽调整顺序)</small></h5>
<div class="selected-artists-container" id="selected-artists-container">
<!-- 动态渲染 -->
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-cancel" id="cancel-associate-btn">取消</button>
<button type="button" class="btn btn-primary" onclick="saveAssociatedArtists()">保存</button>
</div>
</div>
</div>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
const performancesId = [[${ performancesId }]];
let ticketTimes = [];
try {
const rawTicketTimes = /*[[${ticketTimesJson}]]*/ '[]';
ticketTimes = JSON.parse(rawTicketTimes);
} catch (e) {
console.error('Invalid ticketTimesJson:', e);
ticketTimes = [];
}
$(function () {
loadArtistLineup();
// 编辑模式切换
$('#edit-lineup-btn').on('click', function () {
const container = $('#artist-lineup-container');
const isEditMode = container.hasClass('edit-mode');
if (isEditMode) {
container.removeClass('edit-mode');
$(this).text('编辑演出阵容');
$('.associate-artist-btn').hide();
destroyDragAndDrop();
} else {
container.addClass('edit-mode');
$(this).text('完成编辑');
$('.associate-artist-btn').show();
initDragAndDrop();
}
});
// 搜索框输入事件(防抖)
$(document).on('input', '#artist-search-input', function () {
$('#clear-search-btn').toggle($(this).val().length > 0);
debouncedSearch();
});
// 清空搜索框
$(document).on('click', '#clear-search-btn', function () {
$('#artist-search-input').val('').trigger('input');
});
// 取消按钮:关闭弹窗,不保存
$(document).on('click', '#cancel-associate-btn', function () {
$('#associateArtistModal').modal('hide');
});
});
// ===== 演出阵容加载与渲染 =====
function loadArtistLineup() {
const times = Array.isArray(ticketTimes) ? ticketTimes : [];
if (!times || times.length === 0) {
$('#artist-lineup-container').html(
'<div class="alert alert-info">该演出暂无场次信息</div>'
);
return;
}
let loadedCount = 0;
const allSessionData = [];
times.forEach(function (ticketTime, index) {
$.ajax({
url: ctx + "kylin/artist/getSessionArtists",
type: "GET",
data: {
performancesId: performancesId,
timesId: ticketTime.ticketTimesId
},
success: function (response) {
if (response.code === 0) {
allSessionData[index] = {
timesId: ticketTime.ticketTimesId,
sessionDate: ticketTime.title.replace(/ 00:00/g, ''),
artists: response.data || []
};
}
loadedCount++;
if (loadedCount === times.length) {
renderArtistLineup(allSessionData);
}
},
error: function () {
loadedCount++;
allSessionData[index] = {
timesId: ticketTime.ticketTimesId,
sessionDate: ticketTime.title.replace(/ 00:00/g, ''),
artists: []
};
if (loadedCount === times.length) {
renderArtistLineup(allSessionData);
}
}
});
});
}
function renderArtistLineup(data) {
const container = $('#artist-lineup-container');
if (!data || data.length === 0) {
container.html('<div class="alert alert-info">暂无演出阵容数据</div>');
return;
}
let html = '';
data.forEach(function (session) {
html += '<div class="lineup-session">';
html += '<div class="lineup-session-title">' + session.sessionDate;
html += '<span class="drag-hint">注:可拖拽调整出场顺序,松开后实时更新</span>';
html += '<button class="btn btn-xs btn-primary associate-artist-btn" style="display:none; float: right;" onclick="openAssociateArtistModal(\'' + session.timesId + '\')">关联艺人</button>';
html += '</div>';
html += '<div class="artist-list" data-times-id="' + session.timesId + '">';
if (session.artists && session.artists.length > 0) {
session.artists.forEach(function (artist) {
html += '<div class="artist-item" draggable="true" data-artist-id="' + artist.artistId + '" data-mid="' + artist.mid + '">';
html += '<div class="delete-icon" onclick="deleteArtist(\'' + artist.mid + '\', \'' + session.timesId + '\')">×</div>';
html += '<img class="artist-avatar" src="' + artist.avatarUrl + '" alt="' + artist.artistName + '" onerror="this.src=\'/img/profile.jpg\'">';
html += '<div class="artist-name">' + artist.artistName + '</div>';
html += '</div>';
});
} else {
html += '<div class="no-artists-tip">该场次暂无艺人</div>';
}
html += '</div>';
html += '</div>';
});
container.html(html);
// 渲染完成后,若当前仍处于编辑模式,恢复按钮显示和拖拽绑定
if (container.hasClass('edit-mode')) {
$('.associate-artist-btn').show();
initDragAndDrop();
}
}
// ===== 拖拽排序 =====
function initDragAndDrop() {
if (!$('#artist-lineup-container').hasClass('edit-mode')) return;
const artistItems = document.querySelectorAll('.artist-item');
artistItems.forEach(item => {
item.setAttribute('draggable', 'true');
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragend', handleDragEnd);
item.addEventListener('dragover', handleDragOver);
item.addEventListener('drop', handleDrop);
});
}
function destroyDragAndDrop() {
const artistItems = document.querySelectorAll('.artist-item');
artistItems.forEach(item => {
item.setAttribute('draggable', 'false');
item.removeEventListener('dragstart', handleDragStart);
item.removeEventListener('dragend', handleDragEnd);
item.removeEventListener('dragover', handleDragOver);
item.removeEventListener('drop', handleDrop);
});
}
let draggedElement = null;
function handleDragStart(e) {
draggedElement = this;
this.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
}
function handleDragEnd(e) {
this.classList.remove('dragging');
draggedElement = null;
}
function handleDragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
if (draggedElement && draggedElement !== this) {
const parent = this.parentNode;
const draggedParent = draggedElement.parentNode;
if (parent === draggedParent) {
const allItems = Array.from(parent.querySelectorAll('.artist-item'));
const draggedIndex = allItems.indexOf(draggedElement);
const targetIndex = allItems.indexOf(this);
if (draggedIndex < targetIndex) {
parent.insertBefore(draggedElement, this.nextSibling);
} else {
parent.insertBefore(draggedElement, this);
}
}
}
}
function handleDrop(e) {
e.preventDefault();
e.stopPropagation();
saveArtistOrder(this.parentNode);
}
function saveArtistOrder(artistListElement) {
const timesId = artistListElement.getAttribute('data-times-id');
const artistItems = artistListElement.querySelectorAll('.artist-item');
const orderData = [];
artistItems.forEach(function (item, index) {
orderData.push({
mid: item.getAttribute('data-mid'),
sort: artistItems.length - index
});
});
$.ajax({
url: ctx + "kylin/artist/updateArtistOrder",
type: "POST",
contentType: "application/json",
data: JSON.stringify({
performancesId: performancesId,
timesId: timesId,
orderData: orderData
}),
success: function (response) {
if (response.code === 0) {
$.modal.msgSuccess("排序已更新");
} else {
$.modal.msgError(response.msg || "更新排序失败");
loadArtistLineup();
}
},
error: function () {
$.modal.msgError("更新排序失败");
loadArtistLineup();
}
});
}
// ===== 删除艺人 =====
function deleteArtist(mid, timesId) {
$.modal.confirm("确定要删除该艺人吗?", function () {
$.ajax({
url: ctx + "kylin/artist/deletePerformanceArtist",
type: "POST",
data: {
mid: mid,
performancesId: performancesId,
timesId: timesId
},
success: function (response) {
if (response.code === 0) {
$.modal.msgSuccess("删除成功");
loadArtistLineup();
} else {
$.modal.msgError(response.msg || "删除失败");
}
},
error: function () {
$.modal.msgError("删除失败");
}
});
});
}
// ===== 关联艺人弹窗逻辑 =====
let selectedArtists = []; // 当前已选艺人列表
let currentModalArtists = []; // 当前搜索结果中的艺人列表
function openAssociateArtistModal(timesId) {
$('#associateArtistModal').data('timesId', timesId);
// 重置状态
selectedArtists = [];
currentModalArtists = [];
$('#artist-search-input').val('');
$('#clear-search-btn').hide();
$('#modal-artist-list').html('');
$('#selected-artists-container').html('');
// 打开弹窗
$('#associateArtistModal').modal('show');
// 加载初始艺人列表(含已关联状态)
loadAllArtistsForModal(timesId, true);
}
// 防抖
function debounce(func, wait) {
let timeout;
return function (...args) {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
const debouncedSearch = debounce(function () {
const timesId = $('#associateArtistModal').data('timesId');
loadAllArtistsForModal(timesId, false);
}, 300);
/**
* 从后端加载艺人列表
* @param {string} timesId
* @param {boolean} isInitialLoad 是否首次加载(用于初始化已选艺人)
*/
function loadAllArtistsForModal(timesId, isInitialLoad) {
const keyword = $('#artist-search-input').val().trim();
// 显示加载中
$('#modal-artist-list').html(
'<div class="modal-loading"><i class="fa fa-spinner fa-spin"></i> 搜索中...</div>'
);
$.ajax({
url: ctx + "kylin/artist/getAllArtists",
type: "GET",
data: {
performancesId: performancesId,
timesId: timesId,
keyword: keyword
},
success: function (response) {
if (response.code === 0) {
currentModalArtists = response.data || [];
renderArtistsInModal(currentModalArtists, isInitialLoad);
} else {
$('#modal-artist-list').html(
'<div class="modal-empty">加载艺人列表失败,请重试</div>'
);
}
},
error: function () {
$('#modal-artist-list').html(
'<div class="modal-empty">加载艺人列表失败,请重试</div>'
);
}
});
}
/**
* 渲染搜索结果列表
* @param {Array} artists
* @param {boolean} isInitialLoad
*/
function renderArtistsInModal(artists, isInitialLoad) {
const listContainer = $('#modal-artist-list');
if (!artists || artists.length === 0) {
listContainer.html('<div class="modal-empty">暂无该艺人信息,请联系运营人员添加</div>');
updateSelectedArtistsView();
return;
}
// 首次加载时,从后端的 associated 字段初始化已选列表,并按 sort 降序排列(sort 越大越靠前)
if (isInitialLoad) {
selectedArtists = artists
.filter(a => a.associated)
.map(a => ({ artistId: a.artistId, artistName: a.artistName, avatarUrl: a.avatarUrl, sort: a.sort || 0 }))
.sort((a, b) => b.sort - a.sort);
}
let html = '';
artists.forEach(function (artist) {
const isSelected = selectedArtists.some(a => a.artistId === artist.artistId);
html += '<div class="modal-artist-item" data-artist-id="' + artist.artistId + '">';
html += '<img src="' + (artist.avatarUrl || '/img/profile.jpg') + '" class="modal-artist-avatar" onerror="this.src=\'/img/profile.jpg\'">';
html += '<div class="modal-artist-name">' + escapeHtml(artist.artistName) + '</div>';
html += '<div class="artist-radio-btn' + (isSelected ? ' selected' : '') + '"></div>';
html += '</div>';
});
listContainer.html(html);
updateSelectedArtistsView();
// 点击整行切换选中状态
listContainer.find('.modal-artist-item').on('click', function () {
const artistId = $(this).data('artist-id');
const artist = currentModalArtists.find(a => a.artistId === artistId);
if (!artist) return;
const radioBtn = $(this).find('.artist-radio-btn');
const isSelected = selectedArtists.some(a => a.artistId === artistId);
if (isSelected) {
// 取消选中
selectedArtists = selectedArtists.filter(a => a.artistId !== artistId);
radioBtn.removeClass('selected');
} else {
// 选中,防止重复
if (!selectedArtists.some(a => a.artistId === artistId)) {
selectedArtists.push({
artistId: artist.artistId,
artistName: artist.artistName,
avatarUrl: artist.avatarUrl
});
}
radioBtn.addClass('selected');
}
updateSelectedArtistsView();
});
}
/**
* 更新"已选艺人"区域视图,并初始化拖拽排序
*/
function updateSelectedArtistsView() {
const container = $('#selected-artists-container');
if (selectedArtists.length === 0) {
container.html('<div class="selected-empty-tip">尚未选择艺人</div>');
return;
}
let html = '';
selectedArtists.forEach(function (artist) {
html += '<div class="selected-artist-card" draggable="true" data-artist-id="' + artist.artistId + '">';
html += '<img src="' + (artist.avatarUrl || '/img/profile.jpg') + '" class="selected-artist-avatar" onerror="this.src=\'/img/profile.jpg\'">';
html += '<div class="selected-artist-name">' + escapeHtml(artist.artistName) + '</div>';
html += '<div class="remove-selected-artist" title="移除">&times;</div>';
html += '</div>';
});
container.html(html);
// 初始化已选区域拖拽排序
initSelectedArtistsDrag();
}
// 点击"已选艺人"区域的 × 移除艺人
$(document).on('click', '.remove-selected-artist', function () {
const artistId = $(this).closest('.selected-artist-card').data('artist-id');
selectedArtists = selectedArtists.filter(a => a.artistId !== artistId);
updateSelectedArtistsView();
// 同步取消搜索列表中对应的选中状态
const item = $('#modal-artist-list .modal-artist-item[data-artist-id="' + artistId + '"]');
item.find('.artist-radio-btn').removeClass('selected');
});
/**
* 初始化已选艺人卡片的拖拽排序
*/
let modalDraggedCard = null;
function initSelectedArtistsDrag() {
const cards = document.querySelectorAll('#selected-artists-container .selected-artist-card');
cards.forEach(card => {
card.addEventListener('dragstart', onModalDragStart);
card.addEventListener('dragend', onModalDragEnd);
card.addEventListener('dragover', onModalDragOver);
card.addEventListener('dragleave', onModalDragLeave);
card.addEventListener('drop', onModalDrop);
});
}
function onModalDragStart(e) {
// 若点击的是 × 按钮则不触发拖拽
if (e.target.classList.contains('remove-selected-artist')) {
e.preventDefault();
return;
}
modalDraggedCard = this;
this.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
}
function onModalDragEnd() {
if (modalDraggedCard) {
modalDraggedCard.classList.remove('dragging');
}
document.querySelectorAll('#selected-artists-container .selected-artist-card')
.forEach(c => c.classList.remove('drag-over'));
modalDraggedCard = null;
}
function onModalDragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
if (modalDraggedCard && modalDraggedCard !== this) {
this.classList.add('drag-over');
}
}
function onModalDragLeave() {
this.classList.remove('drag-over');
}
function onModalDrop(e) {
e.preventDefault();
e.stopPropagation();
this.classList.remove('drag-over');
if (!modalDraggedCard || modalDraggedCard === this) return;
const container = document.getElementById('selected-artists-container');
const cards = Array.from(container.querySelectorAll('.selected-artist-card'));
const fromIndex = cards.indexOf(modalDraggedCard);
const toIndex = cards.indexOf(this);
if (fromIndex === -1 || toIndex === -1) return;
// 更新 selectedArtists 数组顺序
const moved = selectedArtists.splice(fromIndex, 1)[0];
selectedArtists.splice(toIndex, 0, moved);
// 重新渲染(保持拖拽事件绑定)
updateSelectedArtistsView();
}
/**
* 保存关联艺人
*/
function saveAssociatedArtists() {
const timesId = $('#associateArtistModal').data('timesId');
if (selectedArtists.length === 0) {
$.modal.msgWarning("请至少选择一位艺人");
return;
}
// 根据当前显示顺序计算 sort:越靠前的 sort 值越大
const total = selectedArtists.length;
const artistOrders = selectedArtists.map((a, index) => ({
artistId: a.artistId,
sort: total - index // 第 0 位 → sort 最大
}));
$.ajax({
url: ctx + "kylin/artist/updateArtistAssociations",
type: "POST",
contentType: "application/json",
data: JSON.stringify({
performancesId: performancesId,
timesId: timesId,
artistOrders: artistOrders
}),
success: function (response) {
if (response.code === 0) {
$.modal.msgSuccess("关联成功");
$('#associateArtistModal').modal('hide');
loadArtistLineup();
} else {
$.modal.msgError(response.msg || "关联失败");
}
},
error: function () {
$.modal.msgError("关联失败");
}
});
}
/**
* HTML 转义,防止 XSS
*/
function escapeHtml(str) {
if (!str) return '';
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
</script>
</body>
</html>
\ No newline at end of file
......@@ -43,6 +43,8 @@
</li>
<li id="li-tab-11"><a data-toggle="tab" href="#tab-11" aria-expanded="false" onclick="subscribeInfo()">预约统计</a>
</li>
<li id="li-tab-12"><a data-toggle="tab" href="#tab-12" aria-expanded="false" onclick="artistLineupInfo()">演出阵容</a>
</li>
</ul>
<div class="tab-content">
<div id="tab-1" class="tab-pane">
......@@ -364,6 +366,13 @@
height=800px frameborder=0></iframe>
</div>
</div>
<div id="tab-12" class="tab-pane">
<div class="panel-body">
<iframe id="artist_lineup_iframe" name="artist_lineup_iframe" marginwidth=0 marginheight=0
width=100%
height=800px frameborder=0></iframe>
</div>
</div>
</div>
</div>
</div>
......@@ -564,6 +573,11 @@
document.getElementById("subscribe_iframe").src = "../subscribe/" + '[[${kylinPerformanceMisVo.performancesId}]]'.replaceAll("\"", "");
}
//演出阵容
function artistLineupInfo() {
document.getElementById("artist_lineup_iframe").src = "../artistLineup/" + '[[${kylinPerformanceMisVo.performancesId}]]'.replaceAll("\"", "");
}
$("#tab-nav-1").bind("click", function () {
$("#tab_iframe_1").attr("src", prefix + "/performanceStatic/" + '[[${kylinPerformanceMisVo.performancesId}]]'.replaceAll("\"", ""));
});
......
package com.liquidnet.client.admin.zhengzai.kylin.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.liquidnet.client.admin.common.core.domain.entity.SysUser;
import com.liquidnet.client.admin.common.utils.ShiroUtils;
import com.liquidnet.commons.lang.util.IDGenerator;
import com.liquidnet.service.kylin.dao.KylinArtistOperationLogDao;
import com.liquidnet.service.kylin.entity.KylinArtistOperationLog;
import com.liquidnet.service.kylin.mapper.KylinArtistOperationLogMapper;
import com.liquidnet.service.kylin.service.admin.IKylinArtistOperationLogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
/**
* <p>
* 艺人操作日志 服务实现类
* </p>
*
* @author liquidnet
* @since 2026-03-04
*/
@Slf4j
@Service
public class KylinArtistOperationLogServiceImpl extends ServiceImpl<KylinArtistOperationLogMapper, KylinArtistOperationLog>
implements IKylinArtistOperationLogService {
@Override
public void recordLog(String artistId, Integer operationType, String operationContent) {
// 获取当前用户
String operatorId = "SYSTEM";
String operatorName = "系统";
try {
SysUser currentUser = ShiroUtils.getSysUser();
if (currentUser != null) {
operatorId = currentUser.getUserId().toString();
operatorName = currentUser.getUserName();
}
} catch (Exception e) {
log.warn("获取当前用户失败,使用系统默认值", e);
}
// 调用重载方法
recordLog(artistId, operationType, operationContent, operatorId, operatorName);
}
@Override
public void recordLog(String artistId, Integer operationType, String operationContent, String operatorId, String operatorName) {
try {
// 保存操作日志
KylinArtistOperationLog operLog = new KylinArtistOperationLog();
operLog.setLogId(IDGenerator.nextSnowId());
operLog.setArtistId(artistId);
operLog.setOperationType(operationType);
operLog.setOperationContent(operationContent);
operLog.setOperatorId(operatorId);
operLog.setOperatorName(operatorName);
operLog.setCreatedAt(LocalDateTime.now());
baseMapper.insert(operLog);
log.info("艺人操作日志记录成功: artistId={}, operationType={}, operator={}", artistId, operationType, operatorName);
} catch (Exception e) {
// 日志记录失败不影响主业务
log.error("保存艺人操作日志失败", e);
}
}
@Override
public PageInfo<KylinArtistOperationLogDao> getOperationLogs(String artistId, Integer pageNum, Integer pageSize) {
try {
PageHelper.startPage(pageNum != null ? pageNum : 1, pageSize != null ? pageSize : 10);
List<KylinArtistOperationLogDao> list = baseMapper.selectLogsByArtistId(artistId);
return new PageInfo<>(list);
} catch (Exception e) {
log.error("获取艺人操作记录失败, artistId: {}", artistId, e);
return new PageInfo<>();
}
}
}
package com.liquidnet.client.admin.zhengzai.kylin.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.liquidnet.client.admin.zhengzai.kylin.utils.DataUtils;
import com.liquidnet.service.kylin.dao.KylinArtistAssociationStatusDto;
import com.liquidnet.service.kylin.dao.KylinArtistPerformanceDao;
import com.liquidnet.service.kylin.entity.KylinArtist;
import com.liquidnet.service.kylin.entity.KylinArtistPerformance;
import com.liquidnet.service.kylin.mapper.KylinArtistMapper;
import com.liquidnet.service.kylin.mapper.KylinArtistPerformanceMapper;
import com.liquidnet.service.kylin.service.admin.IKylinArtistPerformanceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.HashMap;
import java.util.stream.Collectors;
import com.liquidnet.service.kylin.service.admin.IKylinArtistOperationLogService;
@Slf4j
@Service
public class KylinArtistPerformanceServiceImpl extends ServiceImpl<KylinArtistPerformanceMapper, KylinArtistPerformance> implements IKylinArtistPerformanceService {
@Autowired
private KylinArtistPerformanceMapper artistPerformanceMapper;
@Autowired
private KylinArtistMapper artistMapper;
@Autowired
private DataUtils dataUtils;
@Autowired
private IKylinArtistOperationLogService operationLogService;
@Override
public List<KylinArtistPerformanceDao> getSessionArtists(String performancesId, String timesId) {
// 查询该场次的艺人
return artistPerformanceMapper.selectArtistsByPerformanceAndTimes(
performancesId, timesId
);
}
@Override
public boolean updateArtistOrder(String performanceId, List<Map<String, Object>> orderData) {
// 更新每个艺人的排序
for (Map<String, Object> item : orderData) {
Long mid = Long.valueOf(item.get("mid").toString());
Integer sort = Integer.valueOf(item.get("sort").toString());
log.info("[updateArtistOrder] mid: {}, sort: {}", mid, sort);
KylinArtistPerformance entity = artistPerformanceMapper.selectById(mid);
if (entity != null) {
entity.setSort(sort);
artistPerformanceMapper.updateById(entity);
}
}
// 删除缓存演出阵容
dataUtils.delPerformanceArtists(performanceId);
return true;
}
@Override
public int deletePerformanceArtist(Long mid, String performanceId) {
KylinArtistPerformance association = artistPerformanceMapper.selectById(mid);
if (association == null) {
return 0;
}
String artistId = association.getArtistId();
// 记录删除前的关联数量
Integer beforeCount = artistPerformanceMapper.selectCount(new QueryWrapper<KylinArtistPerformance>().eq("artist_id", artistId));
beforeCount = beforeCount == null ? 0 : beforeCount;
// 删除缓存演出阵容
dataUtils.delPerformanceArtists(performanceId);
int result = artistPerformanceMapper.deleteById(mid);
// 记录删除后的关联数量
Integer afterCount = artistPerformanceMapper.selectCount(new QueryWrapper<KylinArtistPerformance>().eq("artist_id", artistId));
afterCount = afterCount == null ? 0 : afterCount;
if (!beforeCount.equals(afterCount)) {
String logContent = String.format("更新关联演出数量:从[%d]场改为[%d]场,", beforeCount, afterCount);
operationLogService.recordLog(artistId, 2, logContent);
}
return result;
}
@Override
public List<KylinArtistAssociationStatusDto> getAllArtists(String performancesId, String timesId, String keyword) {
// 1. 获取所有艺人
List<KylinArtist> allArtists = artistMapper.selectList(new QueryWrapper<KylinArtist>()
.like(keyword != null && !keyword.isEmpty(), "artist_name", keyword)
.orderByDesc("created_at"));
// 2. 获取当前场次已关联的艺人,用 Map 保留各自的 sort 值
List<KylinArtistPerformanceDao> associatedArtists =
artistPerformanceMapper.selectArtistsByPerformanceAndTimes(performancesId, timesId);
// key: artistId value: 演出关联表中的 sort(越大越靠前)
Map<String, Integer> associatedSortMap = associatedArtists.stream()
.collect(Collectors.toMap(
KylinArtistPerformanceDao::getArtistId,
KylinArtistPerformanceDao::getSort,
(existing, replacement) -> existing // 重复 key 保留已有值
));
// 3. 组装返回结果
List<KylinArtistAssociationStatusDto> resultList = allArtists.stream().map(artist -> {
KylinArtistAssociationStatusDto dto = new KylinArtistAssociationStatusDto();
dto.setArtistId(artist.getArtistId());
dto.setArtistName(artist.getArtistName());
dto.setAvatarUrl(artist.getAvatarUrl());
boolean associated = associatedSortMap.containsKey(artist.getArtistId());
dto.setAssociated(associated);
// sort 取演出关联表的值;未关联的艺人 sort 置 0
dto.setSort(associated ? associatedSortMap.get(artist.getArtistId()) : 0);
return dto;
}).collect(Collectors.toList());
return resultList;
}
@Override
public boolean updateArtistAssociations(String performancesId, String timesId, List<Map<String, Object>> artistOrders) {
// 先查询该演出、场次下关联的旧艺人
List<KylinArtistPerformance> oldAssociations = artistPerformanceMapper.selectList(new QueryWrapper<KylinArtistPerformance>()
.eq("performances_id", performancesId)
.eq("times_id", timesId));
Set<String> affectedArtistIds = new HashSet<>(); // 旧关联的艺人ID set
if (oldAssociations != null) {
for (KylinArtistPerformance a : oldAssociations) {
affectedArtistIds.add(a.getArtistId());
}
}
if (artistOrders != null && !artistOrders.isEmpty()) {
for (Map<String, Object> item : artistOrders) {
String artistId = (String) item.get("artistId");
if (artistId != null) {
affectedArtistIds.add(artistId);
}
}
}
// Record before counts
Map<String, Integer> beforeCounts = new HashMap<>();
for (String artistId : affectedArtistIds) {
Integer count = artistPerformanceMapper.selectCount(new QueryWrapper<KylinArtistPerformance>().eq("artist_id", artistId));
beforeCounts.put(artistId, count == null ? 0 : count);
}
// 1. 删除当前场次所有已关联的艺人
artistPerformanceMapper.delete(new QueryWrapper<KylinArtistPerformance>()
.eq("performances_id", performancesId)
.eq("times_id", timesId));
// 2. 按前端传入的顺序重新关联,sort 越大越靠前
if (artistOrders != null && !artistOrders.isEmpty()) {
for (Map<String, Object> item : artistOrders) {
String artistId = (String) item.get("artistId");
Integer sort = item.get("sort") != null
? Integer.parseInt(item.get("sort").toString()) : 0;
KylinArtistPerformance newAssociation = new KylinArtistPerformance();
newAssociation.setPerformancesId(performancesId);
newAssociation.setTimesId(timesId);
newAssociation.setArtistId(artistId);
newAssociation.setSort(sort);
artistPerformanceMapper.insert(newAssociation);
}
}
// 3. Compare and log
for (String artistId : affectedArtistIds) {
Integer afterCount = artistPerformanceMapper.selectCount(new QueryWrapper<KylinArtistPerformance>().eq("artist_id", artistId));
afterCount = afterCount == null ? 0 : afterCount;
Integer beforeCount = beforeCounts.get(artistId);
if (!beforeCount.equals(afterCount)) {
String logContent = String.format("更新关联演出数量:从[%d]场改为[%d]场,", beforeCount, afterCount);
operationLogService.recordLog(artistId, 2, logContent);
}
}
// 删除缓存演出阵容
dataUtils.delPerformanceArtists(performancesId);
return true;
}
}
package com.liquidnet.client.admin.zhengzai.kylin.service.impl;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.liquidnet.client.admin.zhengzai.kylin.utils.DataUtils;
import com.liquidnet.commons.lang.util.BeanUtil;
import com.liquidnet.commons.lang.util.DateUtil;
import com.liquidnet.commons.lang.util.IDGenerator;
import com.liquidnet.service.kylin.dao.KylinArtistDao;
import com.liquidnet.service.kylin.dao.KylinArtistPerformanceDao;
import com.liquidnet.service.kylin.dto.param.ArtistParam;
import com.liquidnet.service.kylin.dto.param.ArtistSearchParam;
import com.liquidnet.service.kylin.dto.vo.ArtistVo;
import com.liquidnet.service.kylin.entity.KylinArtist;
import com.liquidnet.service.kylin.entity.KylinArtistAlbum;
import com.liquidnet.service.kylin.mapper.KylinArtistAlbumMapper;
import com.liquidnet.service.kylin.mapper.KylinArtistMapper;
import com.liquidnet.service.kylin.mapper.KylinArtistPerformanceMapper;
import com.liquidnet.service.kylin.service.admin.IKylinArtistOperationLogService;
import com.liquidnet.service.kylin.service.admin.IKylinArtistService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* <p>
* 艺人管理 服务实现类
* </p>
*
* @author liquidnet
* @since 2026-02-04
*/
@Slf4j
@Service
public class KylinArtistServiceImpl extends ServiceImpl<KylinArtistMapper, KylinArtist> implements IKylinArtistService {
@Autowired
private KylinArtistMapper artistMapper;
@Autowired
private KylinArtistAlbumMapper artistAlbumMapper;
@Autowired
private KylinArtistPerformanceMapper artistPerformanceMapper;
@Autowired
private IKylinArtistOperationLogService operationLogService;
@Autowired
private DataUtils dataUtils;
/**
* 创建艺人
*/
@Transactional(rollbackFor = Exception.class)
public Boolean create(ArtistParam param) {
try {
// 校验艺人名称是否已存在
KylinArtist existArtist = artistMapper.selectByArtistName(param.getArtistName());
if (existArtist != null) {
log.warn("艺人名称已存在: {}", param.getArtistName());
return false;
}
String artistId = IDGenerator.nextSnowId();
LocalDateTime createdAt = LocalDateTime.now();
// 保存艺人基本信息
KylinArtist kylinArtist = param.getFields(artistId, createdAt);
artistMapper.insert(kylinArtist);
// 保存相册图片
if (param.getAlbumImages() != null && !param.getAlbumImages().isEmpty()) {
saveAlbumImages(artistId, param.getAlbumImages());
}
// 记录操作日志
operationLogService.recordLog(artistId, 1, buildCreateLogContent(param));
return true;
} catch (Exception e) {
log.error("创建艺人失败", e);
throw new RuntimeException("创建艺人失败", e);
}
}
/**
* 修改艺人
*/
@Transactional(rollbackFor = Exception.class)
public Boolean update(ArtistParam param) {
try {
String artistId = param.getArtistId();
// 获取原始数据用于对比
KylinArtist oldArtist = artistMapper.selectOne(
new UpdateWrapper<KylinArtist>().eq("artist_id", artistId).eq("status", 1)
);
if (oldArtist == null) {
log.warn("艺人不存在: {}", artistId);
return false;
}
// 校验艺人名称是否已被其他艺人使用
KylinArtist existArtist = artistMapper.selectByArtistName(param.getArtistName());
if (existArtist != null && !existArtist.getArtistId().equals(artistId)) {
log.warn("艺人名称已被其他艺人使用: {}", param.getArtistName());
return false;
}
LocalDateTime updatedAt = LocalDateTime.now();
// 更新艺人基本信息
KylinArtist params = param.getFields(null, null);
params.setUpdatedAt(updatedAt);
artistMapper.update(params, new UpdateWrapper<KylinArtist>().eq("artist_id", artistId));
// 删除旧的相册图片(逻辑删除)
artistAlbumMapper.deleteByArtistId(artistId);
// 保存新的相册图片
if (param.getAlbumImages() != null && !param.getAlbumImages().isEmpty()) {
saveAlbumImages(artistId, param.getAlbumImages());
}
// 记录操作日志(对比变更)
String logContent = buildUpdateLogContent(oldArtist, param);
if (logContent != null && !logContent.isEmpty()) {
operationLogService.recordLog(artistId, 2, logContent);
}
// 根据艺人ID查询所有关联的演出,然后删除缓存中对应的演出阵容
List<String> performanceIdList = artistPerformanceMapper.selectPerformanceIdsByArtistIds(Collections.singletonList(artistId));
if (!performanceIdList.isEmpty()) {
for (String p : performanceIdList) {
dataUtils.delPerformanceArtists(p);
}
}
return true;
} catch (Exception e) {
log.error("修改艺人失败", e);
throw new RuntimeException("修改艺人失败", e);
}
}
/**
* 艺人详情
*/
public ArtistVo detail(String artistId) {
KylinArtist data = artistMapper.selectOne(
new UpdateWrapper<KylinArtist>().eq("artist_id", artistId).eq("status", 1)
);
ArtistVo artistVo = new ArtistVo();
if (data != null) {
BeanUtils.copyProperties(data, artistVo);
// 设置艺人类型名称
if (data.getArtistType() != null) {
String typeName = "";
switch (data.getArtistType()) {
case 1:
typeName = "音乐人";
break;
case 2:
typeName = "艺术家";
break;
case 3:
typeName = "厂牌";
break;
case 4:
typeName = "品牌方";
break;
default:
typeName = "未知";
break;
}
artistVo.setArtistTypeName(typeName);
}
// 查询相册图片
List<KylinArtistAlbum> albumList = artistAlbumMapper.selectByArtistId(artistId);
if (albumList != null && !albumList.isEmpty()) {
List<String> imageUrls = albumList.stream()
.map(KylinArtistAlbum::getImageUrl)
.collect(Collectors.toList());
artistVo.setAlbumImages(imageUrls);
}
// 格式化时间显示
if (data.getCreatedAt() != null) {
artistVo.setCreatedAt(DateUtil.Formatter.yyyyMMddHHmmss.format(data.getCreatedAt()));
}
if (data.getUpdatedAt() != null) {
artistVo.setUpdatedAt(DateUtil.Formatter.yyyyMMddHHmmss.format(data.getUpdatedAt()));
}
// 设置关联演出
List<KylinArtistPerformanceDao> performanceList = artistPerformanceMapper.selectPerformanceDaoByArtistId(artistId);
if (performanceList != null && !performanceList.isEmpty()) {
List<ArtistVo.PerformanceVo> performanceVoList = performanceList.stream()
.map(performance -> {
ArtistVo.PerformanceVo vo = new ArtistVo.PerformanceVo();
vo.setPerformanceId(performance.getPerformanceId());
vo.setTitle(performance.getTitle());
vo.setTimeStart(performance.getTimeStart());
vo.setTimesId(performance.getTimesId());
vo.setTimeTitle(performance.getTimeTitle());
return vo;
})
.collect(Collectors.toList());
artistVo.setPerformanceVoList(performanceVoList);
} else {
artistVo.setPerformanceVoList(Collections.emptyList());
}
} else {
log.error("[detail] query artist is null, artistId: {}", artistId);
return null;
}
return artistVo;
}
/**
* 艺人列表
*/
public PageInfo<KylinArtistDao> artistList(ArtistSearchParam param) {
PageInfo<KylinArtistDao> pageInfoTmp = null;
try {
PageHelper.startPage(param.getPageNum(), param.getPageSize());
Map<String, Object> paramMap = BeanUtil.convertBeanToMap(param);
log.info("艺人列表查询参数: {}", paramMap);
List<KylinArtistDao> list = artistMapper.searchArtistList(paramMap);
pageInfoTmp = new PageInfo<>(list);
} catch (Exception e) {
log.error("获取艺人列表失败", e);
return new PageInfo<>();
}
return pageInfoTmp;
}
/**
* 删除艺人(逻辑删除)
*/
@Transactional(rollbackFor = Exception.class)
public Boolean delete(List<String> artistIds) {
try {
LocalDateTime updatedAt = LocalDateTime.now();
// 删除艺人基本信息
KylinArtist kylinArtist = new KylinArtist();
kylinArtist.setUpdatedAt(updatedAt);
kylinArtist.setStatus(0);
artistMapper.update(
kylinArtist,
new UpdateWrapper<KylinArtist>().in("artist_id", artistIds)
);
// 删除相册图片并记录日志
for (String artistId : artistIds) {
// 获取艺人信息用于日志
KylinArtist artist = artistMapper.selectOne(
new UpdateWrapper<KylinArtist>().eq("artist_id", artistId)
);
artistAlbumMapper.deleteByArtistId(artistId);
// 记录删除日志
String logContent = "删除艺人:" + (artist != null ? artist.getArtistName() + "(ID:" + artistId + ")" : artistId);
operationLogService.recordLog(artistId, 3, logContent);
}
// 根据艺人ID查询所有关联的演出
List<String> performanceIdList = artistPerformanceMapper.selectPerformanceIdsByArtistIds(artistIds);
if (!performanceIdList.isEmpty()) {
for (String p : performanceIdList) {
dataUtils.delPerformanceArtists(p);
}
}
return true;
} catch (Exception e) {
log.error("删除艺人失败", e);
throw new RuntimeException("删除艺人失败", e);
}
}
/**
* 检查艺人名称是否存在
*/
public Boolean checkArtistNameExists(String artistName, String artistId) {
KylinArtist existArtist = artistMapper.selectByArtistName(artistName);
if (existArtist == null) {
return false;
}
// 如果是编辑操作,排除自己
if (artistId != null && existArtist.getArtistId().equals(artistId)) {
return false;
}
return true;
}
/**
* 保存相册图片
*/
private void saveAlbumImages(String artistId, List<String> imageUrls) {
LocalDateTime now = LocalDateTime.now();
int sort = imageUrls.size();
for (String imageUrl : imageUrls) {
KylinArtistAlbum album = new KylinArtistAlbum();
album.setAlbumId(IDGenerator.nextSnowId());
album.setArtistId(artistId);
album.setImageUrl(imageUrl);
album.setSort(sort--);
album.setStatus(1);
album.setCreatedAt(now);
album.setUpdatedAt(now);
artistAlbumMapper.insert(album);
}
}
/**
* 构建新增操作的日志内容
*/
private String buildCreateLogContent(ArtistParam param) {
StringBuilder content = new StringBuilder();
content.append("新增艺人: [").append(param.getArtistName()).append("];");
content.append("类型: [").append(getArtistTypeName(param.getArtistType())).append("];");
// if (param.getIntroduction() != null && !param.getIntroduction().isEmpty()) {
// String intro = param.getIntroduction();
// content.append("介绍: ").append(intro.length() > 50 ? intro.substring(0, 50) + "..." : intro);
// }
return content.toString();
}
/**
* 构建修改操作的日志内容(逐字段对比)
*/
private String buildUpdateLogContent(KylinArtist oldArtist, ArtistParam newParam) {
StringBuilder content = new StringBuilder();
boolean hasChange = false;
// 对比艺人名称
if (!oldArtist.getArtistName().equals(newParam.getArtistName())) {
content.append("名称从[").append(oldArtist.getArtistName()).append("]")
.append("改为[").append(newParam.getArtistName()).append("];");
hasChange = true;
}
// 对比艺人类型
if (!oldArtist.getArtistType().equals(newParam.getArtistType())) {
content.append("类型从[").append(getArtistTypeName(oldArtist.getArtistType()))
.append("]改为[").append(getArtistTypeName(newParam.getArtistType())).append("];");
hasChange = true;
}
// 对比艺人头像
String oldAvatar = oldArtist.getAvatarUrl() != null ? oldArtist.getAvatarUrl() : "";
String newAvatar = newParam.getAvatarUrl() != null ? newParam.getAvatarUrl() : "";
if (!oldAvatar.equals(newAvatar)) {
content.append("更新了艺人头像;");
hasChange = true;
}
// 对比艺人简介
String oldIntro = oldArtist.getIntroduction() != null ? oldArtist.getIntroduction() : "";
String newIntro = newParam.getIntroduction() != null ? newParam.getIntroduction() : "";
if (!oldIntro.equals(newIntro)) {
content.append("更新了艺人简介;");
hasChange = true;
}
// 对比排序权重
Integer oldSort = oldArtist.getSort() != null ? oldArtist.getSort() : 0;
Integer newSort = newParam.getSort() != null ? newParam.getSort() : 0;
if (!oldSort.equals(newSort)) {
content.append("排序权重从").append(oldSort).append("改为").append(newSort).append(";");
hasChange = true;
}
// 注意:相册图片每次都会删除重建,所以不对比相册
// 如果其他字段都没变化,说明只是更新了相册
if (!hasChange && newParam.getAlbumImages() != null && !newParam.getAlbumImages().isEmpty()) {
content.append("更新了艺人相册图片");
hasChange = true;
}
return hasChange ? content.toString() : null;
}
/**
* 获取艺人类型名称
*/
private String getArtistTypeName(Integer artistType) {
if (artistType == null) return "未知";
switch (artistType) {
case 1:
return "音乐人";
case 2:
return "艺术家";
case 3:
return "厂牌";
case 4:
return "品牌方";
default:
return "未知";
}
}
}
......@@ -621,4 +621,14 @@ public class DataUtils {
public static ArrayList<KylinOrderCoupons> getKylinOrderCouponsArrayList() {
return (ArrayList<KylinOrderCoupons>) kylinOrderCouponsArrayList.clone();
}
/**
* 删除演出阵容 缓存
* @param performancesId
*/
public void delPerformanceArtists(String performancesId) {
final String redisKey = KylinRedisConst.PERFORMANCES_ARTISTS + performancesId;
redisDataSourceUtil.getRedisKylinUtil().del(redisKey);
}
}
......@@ -53,4 +53,7 @@ public class RedisKeyExpireConst {
public static final long GOBLIN_BRACELET_USER_EXPIRE = 30 * 24 * 60 * 60;
// GOBLIN_BRACELET_RELATED_ORDER 过期30天
public static final long GOBLIN_BRACELET_RELATED_ORDER_EXPIRE = 30 * 24 * 60 * 60;
// 演出关联阵容缓存过期时间
public static final long PERFORMANCES_ARTISTS_EXPIRE = 30 * 24 * 60 * 60;
}
......@@ -58,8 +58,8 @@ info:
artifactId: '@project.artifactId@'
version: '@project.version@'
# -----------------------------------------------------------
#mybatis-plus:
# mapper-locations: classpath*:com.liquidnet.service.*.mapper/*Mapper.xml
mybatis-plus:
mapper-locations: classpath*:com.liquidnet.service.*.mapper/*Mapper.xml
# -----------------------------------------------------------
spring:
application:
......@@ -159,6 +159,8 @@ global-auth:
- ${liquidnet.info.context}/luckyBag/code/**
# 健康检查
- ${liquidnet.info.context}/health
# 演出阵容
- ${liquidnet.info.context}/performance/artists/**
oncheck-url-pattern:
- ${liquidnet.info.context}/order/details
- ${liquidnet.info.context}/order/transfer*
......
package com.liquidnet.service.kylin.dao;
import lombok.Data;
@Data
public class KylinArtistAssociationStatusDto {
/**
* 艺人ID
*/
private String artistId;
/**
* 艺人名称
*/
private String artistName;
/**
* 艺人头像地址
*/
private String avatarUrl;
/**
* 是否关联
*/
private boolean isAssociated;
/**
* 排序 越大越靠前
*/
private int sort;
}
package com.liquidnet.service.kylin.dao;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 艺人管理DAO - 用于列表展示
* </p>
*
* @author liquidnet
* @since 2026-02-04
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class KylinArtistDao implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 艺人ID
*/
private String artistId;
/**
* 艺人名称
*/
private String artistName;
/**
* 艺人类型 1音乐人 2艺术家 3厂牌 4品牌方
*/
private Integer artistType;
/**
* 艺人类型名称
*/
private String artistTypeName;
/**
* 艺人头像
*/
private String avatarUrl;
/**
* 艺人简介
*/
private String introduction;
/**
* 关联演出数量
*/
private Integer performanceCount;
/**
* 关联商品数量
*/
private Integer productCount;
/**
* 创建时间(艺人入驻时间)
*/
private LocalDateTime createdAt;
}
package com.liquidnet.service.kylin.dao;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
/**
* <p>
* 艺人操作记录DAO
* </p>
*
* @author liquidnet
* @since 2026-03-04
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class KylinArtistOperationLogDao implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 操作类型 1新增 2编辑 3删除
*/
private Integer operationType;
/**
* 操作类型名称
*/
private String operationTypeName;
/**
* 操作时间
*/
private String createdAt;
/**
* 修改内容
*/
private String operationContent;
/**
* 操作人
*/
private String operatorName;
}
package com.liquidnet.service.kylin.dao;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@Data
@EqualsAndHashCode(callSuper = false)
public class KylinArtistPerformanceDao implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 关联记录ID
*/
private Long mid;
/**
* 艺人ID
*/
private String artistId;
/**
* 艺人名称
*/
private String artistName;
/**
* 艺人头像
*/
private String avatarUrl;
/**
* 演出ID
*/
private String performanceId;
/**
* 演出标题
*/
private String title;
/**
* 演出开始时间
*/
private String timeStart;
/**
* 演出场次ID
*/
private String timesId;
/**
* 演出场次标题
*/
private String timeTitle;
/**
* 排序权重
*/
private Integer sort;
}
package com.liquidnet.service.kylin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 艺人管理表
* </p>
*
* @author liquidnet
* @since 2026-02-04
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class KylinArtist implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
@TableId(value = "mid", type = IdType.AUTO)
private Integer mid;
/**
* 艺人ID
*/
private String artistId;
/**
* 艺人名称
*/
private String artistName;
/**
* 艺人类型 1音乐人 2艺术家 3厂牌 4品牌方
*/
private Integer artistType;
/**
* 艺人头像
*/
private String avatarUrl;
/**
* 艺人简介
*/
private String introduction;
/**
* 状态 1启用 0禁用
*/
private Integer status;
/**
* 排序权重
*/
private Integer sort;
/**
* 创建时间
*/
private LocalDateTime createdAt;
/**
* 修改时间
*/
private LocalDateTime updatedAt;
private static final KylinArtist obj = new KylinArtist();
public static KylinArtist getNew() {
try {
return (KylinArtist) obj.clone();
} catch (CloneNotSupportedException e) {
return new KylinArtist();
}
}
}
package com.liquidnet.service.kylin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 艺人相册表
* </p>
*
* @author liquidnet
* @since 2026-02-04
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class KylinArtistAlbum implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
@TableId(value = "mid", type = IdType.AUTO)
private Integer mid;
/**
* 相册ID
*/
private String albumId;
/**
* 艺人ID
*/
private String artistId;
/**
* 图片地址
*/
private String imageUrl;
/**
* 排序权重
*/
private Integer sort;
/**
* 状态 1启用 0禁用
*/
private Integer status;
/**
* 创建时间
*/
private LocalDateTime createdAt;
/**
* 修改时间
*/
private LocalDateTime updatedAt;
private static final KylinArtistAlbum obj = new KylinArtistAlbum();
public static KylinArtistAlbum getNew() {
try {
return (KylinArtistAlbum) obj.clone();
} catch (CloneNotSupportedException e) {
return new KylinArtistAlbum();
}
}
}
package com.liquidnet.service.kylin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 艺人操作记录表
* </p>
*
* @author liquidnet
* @since 2026-03-04
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class KylinArtistOperationLog implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "mid", type = IdType.AUTO)
private Long mid;
/**
* 日志ID
*/
private String logId;
/**
* 关联艺人ID
*/
private String artistId;
/**
* 操作类型 1新增 2编辑 3删除
*/
private Integer operationType;
/**
* 修改内容描述
*/
private String operationContent;
/**
* 操作人ID
*/
private String operatorId;
/**
* 操作人名称
*/
private String operatorName;
/**
* 操作时间
*/
private LocalDateTime createdAt;
}
package com.liquidnet.service.kylin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = false)
public class KylinArtistPerformance implements Serializable{
private static final long serialVersionUID = 1L;
@TableId(value = "mid", type = IdType.AUTO)
private Long mid;
/**
* 关联 kylin_artist.artist_id
*/
private String artistId;
/**
* 关联 kylin_performances.performances_id
*/
private String performancesId;
/**
* 场次ID
*/
private String timesId;
/**
* 该艺人在本演出中的排序权重,越大越靠前
*/
private Integer sort;
/**
*创建时间
*/
private LocalDateTime createdAt;
/**
*更新时间
*/
private LocalDateTime updatedAt;
}
package com.liquidnet.service.kylin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liquidnet.service.kylin.entity.KylinArtistAlbum;
import java.util.List;
public interface KylinArtistAlbumMapper extends BaseMapper<KylinArtistAlbum> {
/**
* 根据艺人ID查询相册列表
*/
List<KylinArtistAlbum> selectByArtistId(String artistId);
/**
* 根据艺人ID删除相册(逻辑删除)
*/
int deleteByArtistId(String artistId);
}
package com.liquidnet.service.kylin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liquidnet.service.kylin.dao.KylinArtistDao;
import com.liquidnet.service.kylin.entity.KylinArtist;
import java.util.List;
import java.util.Map;
public interface KylinArtistMapper extends BaseMapper<KylinArtist> {
/**
* 搜索艺人列表
*/
List<KylinArtistDao> searchArtistList(Map<String, Object> paramMap);
/**
* 根据艺人名称查询是否存在
*/
KylinArtist selectByArtistName(String artistName);
}
package com.liquidnet.service.kylin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liquidnet.service.kylin.dao.KylinArtistOperationLogDao;
import com.liquidnet.service.kylin.entity.KylinArtistOperationLog;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* <p>
* 艺人操作记录 Mapper 接口
* </p>
*
* @author liquidnet
* @since 2026-03-04
*/
public interface KylinArtistOperationLogMapper extends BaseMapper<KylinArtistOperationLog> {
/**
* 根据艺人ID查询操作记录列表
*
* @param artistId 艺人ID
* @return 操作记录列表
*/
List<KylinArtistOperationLogDao> selectLogsByArtistId(@Param("artistId") String artistId);
}
package com.liquidnet.service.kylin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liquidnet.service.kylin.dao.KylinArtistPerformanceDao;
import com.liquidnet.service.kylin.entity.KylinArtistPerformance;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface KylinArtistPerformanceMapper extends BaseMapper<KylinArtistPerformance> {
/**
* 根据艺人ID查询关联的演出信息
*
* @param artistId 艺人ID
* @return 演出信息列表
*/
List<KylinArtistPerformanceDao> selectPerformanceDaoByArtistId(@Param("artistId") String artistId);
/**
* 根据演出ID和场次ID查询艺人阵容
*
* @param performancesId 演出ID
* @param timesId 场次ID
* @return 艺人阵容列表
*/
List<KylinArtistPerformanceDao> selectArtistsByPerformanceAndTimes(@Param("performancesId") String performancesId, @Param("timesId") String timesId);
/**
* 根据艺人ID列表查询关联的演出ID列表
*
* @param artistIds 艺人ID集合
* @return 演出ID列表
*/
List<String> selectPerformanceIdsByArtistIds(@Param("artistIds") List<String> artistIds);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liquidnet.service.kylin.mapper.KylinArtistAlbumMapper">
<!-- 根据艺人ID查询相册列表 -->
<select id="selectByArtistId" parameterType="java.lang.String" resultType="com.liquidnet.service.kylin.entity.KylinArtistAlbum">
SELECT
mid,
album_id,
artist_id,
image_url,
sort,
status,
created_at,
updated_at
FROM kylin_artist_album
WHERE artist_id = #{artistId} AND status = 1
ORDER BY sort DESC, mid DESC
</select>
<!-- 根据艺人ID删除相册(逻辑删除) -->
<update id="deleteByArtistId" parameterType="java.lang.String">
UPDATE kylin_artist_album
SET status = 0, updated_at = NOW()
WHERE artist_id = #{artistId}
</update>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liquidnet.service.kylin.mapper.KylinArtistMapper">
<!-- 搜索艺人列表 -->
<select id="searchArtistList" parameterType="java.util.Map" resultType="com.liquidnet.service.kylin.dao.KylinArtistDao">
SELECT
a.artist_id,
a.artist_name,
a.artist_type,
CASE a.artist_type
WHEN 1 THEN '音乐人'
WHEN 2 THEN '艺术家'
WHEN 3 THEN '厂牌'
WHEN 4 THEN '品牌方'
ELSE '未知'
END as artist_type_name,
a.avatar_url,
a.introduction,
a.created_at,
(SELECT COUNT(1) FROM kylin_artist_performance ap WHERE ap.artist_id = a.artist_id) as performance_count,
0 as product_count
FROM kylin_artist a
<where>
a.status = 1
<if test="artistId != null and artistId != ''">
AND a.artist_id = #{artistId}
</if>
<if test="artistName != null and artistName != ''">
AND a.artist_name LIKE CONCAT('%', #{artistName}, '%')
</if>
<if test="artistType != null and artistType != ''">
AND a.artist_type = #{artistType}
</if>
<if test="beginDate != null and beginDate != ''">
AND DATE(a.created_at) &gt;= #{beginDate}
</if>
<if test="endDate != null and endDate != ''">
AND DATE(a.created_at) &lt;= #{endDate}
</if>
</where>
ORDER BY a.sort DESC, a.mid DESC
</select>
<!-- 根据艺人名称查询是否存在 -->
<select id="selectByArtistName" parameterType="java.lang.String" resultType="com.liquidnet.service.kylin.entity.KylinArtist">
SELECT * FROM kylin_artist
WHERE artist_name = #{artistName} AND status = 1
LIMIT 1
</select>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liquidnet.service.kylin.mapper.KylinArtistOperationLogMapper">
<!-- 根据艺人ID查询操作记录列表 -->
<select id="selectLogsByArtistId" parameterType="java.lang.String" resultType="com.liquidnet.service.kylin.dao.KylinArtistOperationLogDao">
SELECT
operation_type as operationType,
CASE operation_type
WHEN 1 THEN '新增'
WHEN 2 THEN '编辑'
WHEN 3 THEN '删除'
ELSE '未知'
END as operationTypeName,
DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s') as createdAt,
operation_content as operationContent,
operator_name as operatorName
FROM kylin_artist_operation_log
WHERE artist_id = #{artistId}
ORDER BY created_at DESC
</select>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liquidnet.service.kylin.mapper.KylinArtistPerformanceMapper">
<select id="selectPerformanceDaoByArtistId" parameterType="java.lang.String" resultType="com.liquidnet.service.kylin.dao.KylinArtistPerformanceDao">
SELECT
ap.mid,
ap.artist_id as artistId,
a.artist_name as artistName,
a.avatar_url as avatarUrl,
p.performances_id as performanceId,
p.title,
DATE_FORMAT(p.time_start, '%Y-%m-%d %H:%i:%s') as timeStart,
t.ticket_times_id as timesId,
t.title as timeTitle,
ap.sort
FROM kylin_artist_performance ap
INNER JOIN kylin_artist a ON ap.artist_id = a.artist_id
INNER JOIN kylin_performances p ON ap.performances_id = p.performances_id
INNER JOIN kylin_ticket_times t ON ap.times_id = t.ticket_times_id
WHERE ap.artist_id = #{artistId}
ORDER BY ap.sort DESC, p.time_start DESC
</select>
<!-- 根据艺人ID获取关联演出数量 -->
<!-- <select id="countPerformancesByArtistId" parameterType="java.lang.String" resultType="java.lang.Integer">-->
<!-- SELECT COUNT(1)-->
<!-- FROM kylin_artist_performance-->
<!-- WHERE artist_id = #{artistId}-->
<!-- AND status = 1-->
<!-- </select>-->
<!-- 根据演出ID和场次ID查询艺人阵容 -->
<select id="selectArtistsByPerformanceAndTimes" resultType="com.liquidnet.service.kylin.dao.KylinArtistPerformanceDao">
SELECT
ap.mid,
ap.artist_id as artistId,
ap.times_id as timesId,
ap.sort,
a.artist_name as artistName,
a.avatar_url as avatarUrl,
p.performances_id as performanceId,
p.title,
DATE_FORMAT(p.time_start, '%Y-%m-%d %H:%i:%s') as timeStart,
t.title as timeTitle
FROM kylin_artist_performance ap
INNER JOIN kylin_artist a ON ap.artist_id = a.artist_id
INNER JOIN kylin_performances p ON ap.performances_id = p.performances_id
INNER JOIN kylin_ticket_times t ON ap.times_id = t.ticket_times_id
WHERE ap.performances_id = #{performancesId}
<if test="timesId != null and timesId != ''">
AND ap.times_id = #{timesId}
</if>
ORDER BY ap.sort DESC, ap.created_at ASC
</select>
<!-- 根据艺人ID列表查询关联的演出ID列表 -->
<select id="selectPerformanceIdsByArtistIds" resultType="java.lang.String">
SELECT DISTINCT performances_id
FROM kylin_artist_performance
WHERE artist_id IN
<foreach collection="artistIds" item="artistId" open="(" separator="," close=")">
#{artistId}
</foreach>
</select>
</mapper>
......@@ -7,11 +7,11 @@ import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.kylin.dto.param.KylinCandyParam;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinCandyVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinPerformanceVo;
import com.liquidnet.service.kylin.dto.vo.returns.KylinPerformanceArtistLineupVo;
import com.liquidnet.service.kylin.dto.vo.returns.PayDetailVo;
import com.liquidnet.service.kylin.service.IKylinLackRegistersService;
import com.liquidnet.service.kylin.service.IKylinPerformancesService;
import com.liquidnet.service.kylin.service.impl.KylinPerformancesServiceImpl;
import com.liquidnet.service.kylin.utils.DataUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
......@@ -228,6 +228,20 @@ public class KylinPerformancesController {
}
}
@GetMapping("artists/{performancesId}")
@ApiOperation("演出艺人阵容及场次详情")
@ApiImplicitParams({
@ApiImplicitParam(type = "path", dataType = "String", name = "performancesId", value = "演出id", required = true)
})
public ResponseDto<List<KylinPerformanceArtistLineupVo>> performanceArtists(@PathVariable("performancesId") String performancesId) {
List<KylinPerformanceArtistLineupVo> result = kylinPerformancesService.performanceArtists(performancesId);
if (result != null) {
return ResponseDto.success(result);
} else {
return ResponseDto.failure(ErrorMapping.get("20700"));
}
}
@GetMapping("payDetail")
@ApiOperation("支付前详情")
@ApiImplicitParams({
......
......@@ -23,6 +23,7 @@ import com.liquidnet.service.kylin.dto.vo.mongo.KylinPerformanceVo;
import com.liquidnet.service.kylin.dto.vo.partner.KylinTicketExpressModuleVo;
import com.liquidnet.service.kylin.dto.vo.partner.KylinTicketPartnerVo;
import com.liquidnet.service.kylin.dto.vo.partner.KylinTicketTimesPartnerVo;
import com.liquidnet.service.kylin.dto.vo.returns.KylinPerformanceArtistLineupVo;
import com.liquidnet.service.kylin.dto.vo.returns.PayDetailVo;
import com.liquidnet.service.kylin.service.IKylinPerformancesService;
import com.liquidnet.service.kylin.utils.DataUtils;
......@@ -318,7 +319,7 @@ public class KylinPerformancesServiceImpl implements IKylinPerformancesService {
performancesInfo.setIsInvoiceReminder(dataUtils.getPerformanceInvoiceReminder(performancesId));
performancesInfo.setFieldAddress(dataUtils.getFieldAddressByFieldId(performancesInfo.getFieldId()));
performancesInfo.setNoticeRemindStatus(dataUtils.getPerformanceNoticeRemindStatus(performancesId));
List<OrderRefundPoundage> result = dataUtils.getRefundPoundage(1, performancesId);
List<OrderRefundPoundage> result = dataUtils.getRefundPoundage(1, performancesId);
performancesInfo.setRefundPoundages(result);
return performancesInfo;
}
......@@ -379,10 +380,10 @@ public class KylinPerformancesServiceImpl implements IKylinPerformancesService {
info.put("field_name", performancesInfo.getFieldName());
info.put("title", performancesInfo.getTitle());
info.put("appStatus", performancesInfo.getAppStatus());
info.put("imgPoster",performancesInfo.getImgPoster());
info.put("fieldName",performancesInfo.getFieldName());
info.put("longitude",performancesInfo.getLongitude());
info.put("latitude",performancesInfo.getLatitude());
info.put("imgPoster", performancesInfo.getImgPoster());
info.put("fieldName", performancesInfo.getFieldName());
info.put("longitude", performancesInfo.getLongitude());
info.put("latitude", performancesInfo.getLatitude());
HashMap<String, Object> result = CollectionUtil.mapStringObject();
result.put("performancesInfo", info);
result.put("ticketTimesList", ticketTimesList);
......@@ -555,6 +556,7 @@ public class KylinPerformancesServiceImpl implements IKylinPerformancesService {
/**
* 设置观演人数量
*
* @param ticketsId
*/
private Integer getViewersNumberByTicketsId(String ticketsId) {
......@@ -697,7 +699,7 @@ public class KylinPerformancesServiceImpl implements IKylinPerformancesService {
//老判断 不根据用户会员做判断
//int isMemberStatus = getPerformanceIsMemberStatus(info);
//新判断 根据用户会员做判断
int isMemberStatus = getPerformanceIsMemberStatusByIsMember(info,CurrentUtil.getCurrentUid());
int isMemberStatus = getPerformanceIsMemberStatusByIsMember(info, CurrentUtil.getCurrentUid());
if (1 == isMemberStatus) {
info.setAppStatus(6);
}
......@@ -802,18 +804,19 @@ public class KylinPerformancesServiceImpl implements IKylinPerformancesService {
}
return isMemberStatus;
}
// 会员状态
public Integer getPerformanceIsMemberStatusByIsMember(KylinPerformanceVo info,String uid) {
public Integer getPerformanceIsMemberStatusByIsMember(KylinPerformanceVo info, String uid) {
Integer isMemberStatus = 0;
if (null != info) {
if (1 == info.getIsMember()) { // 有会员
//普通用户开售时间
String memberTimeStart = info.getSellTime();
if(StringUtils.isNotEmpty(uid)){
if (StringUtils.isNotEmpty(uid)) {
Integer memberByUser = dataUtils.isMemberByUser(uid);
if(memberByUser==1){
if (memberByUser == 1) {
//会员开售开售时间
memberTimeStart=info.getSellMemberTime();
memberTimeStart = info.getSellMemberTime();
}
}
String nowTime = DateUtil.getNowTime();
......@@ -828,6 +831,7 @@ public class KylinPerformancesServiceImpl implements IKylinPerformancesService {
}
return isMemberStatus;
}
/**
* 获取我的已购票演出列表
*
......@@ -915,110 +919,120 @@ public class KylinPerformancesServiceImpl implements IKylinPerformancesService {
}
@Override
public ResponseDto<String> performanceSubscribe(HttpServletRequest request,KylinPerformanceSubscribeParam param) {
public ResponseDto<String> performanceSubscribe(HttpServletRequest request, KylinPerformanceSubscribeParam param) {
try {
//获取设备类型
String source = request.getHeader("source");
String uid = CurrentUtil.getCurrentUid();
// 避免重复预约
Integer performanceSubscribe = dataUtils.getPerformanceTicketsSubscribe(uid, param.getPerformancesId(), param.getTicketTimesId(),param.getTicketsId());
Integer performanceSubscribe = dataUtils.getPerformanceTicketsSubscribe(uid, param.getPerformancesId(), param.getTicketTimesId(), param.getTicketsId());
if (performanceSubscribe != 1) {
//先删除其他票种预约信息
List<String> ticketsIds = param.getTicketsIds();
if(!CollectionUtils.isEmpty(ticketsIds)){
ticketsIds.forEach(iter->{
dataUtils.deleteIsSubscribe(uid,param.getPerformancesId(), param.getTicketTimesId(),iter);
});
}
//删除预约记录
queueUtils.sendMsgByRedis(
MQConst.KylinQueue.SQL_PERFORMANCE_SUBSCRIBE.getKey(),
SqlMapping.get(
"kylin_performance_subscribe.delete",
new Object[]{
param.getPerformancesId(), param.getTicketTimesId(),uid
}
));
// 计算过期时间 演出结束时间-当前时间
long expirationTime = DateUtil.intervalSeconds(DateUtil.parse(DateUtil.format(param.getTimeEnd(), DateUtil.Formatter.yyyyMMddHHmmss), DateUtil.DATE_FULL_STR), DateUtil.now());
// 计算通知时间 演出开始时间-10分钟
LocalDateTime timeStart = param.getTimeStart();
LocalDateTime localDateTime = timeStart.minusMinutes(10);
String pushTime = DateUtil.format(localDateTime, DateUtil.Formatter.yyyyMMddHHmmss);
//票种预约
dataUtils.setPerformanceTicketsSubscribe(uid, param.getPerformancesId(), param.getTicketTimesId(),param.getTicketsId(),expirationTime);
//本场演出预约
dataUtils.setPerformanceSubscribe(uid, param.getPerformancesId(),expirationTime);
//push推送内容
String title=param.getPerformancesTitle()+DateUtil.format(timeStart, DateUtil.Formatter.yyyyMMddHHmmss)+"即将开票,登登登提前"+param.getAdvanceMinuteMember()+"分钟开抢";
// 记录预约信息
queueUtils.sendMsgByRedis(
MQConst.KylinQueue.SQL_PERFORMANCE_SUBSCRIBE.getKey(),
SqlMapping.get(
"kylin_performance_subscribe.insert",
new Object[]{
param.getPerformancesId(), param.getTicketTimesId(), param.getTicketsId(), uid,KylinTableStatusConst.SubscribeTypeEnum.TYPE1.getKey(),param.getDeviceTokens(),
source,pushTime,title,0
}
));
//redis 用户开票提醒列表
KylinPerformanceSubscribeUpushVo kylinPerformanceSubscribeUpushVo = new KylinPerformanceSubscribeUpushVo();
kylinPerformanceSubscribeUpushVo.setPushTitle(param.getPerformancesTitle());
kylinPerformanceSubscribeUpushVo.setPushContent(title);
kylinPerformanceSubscribeUpushVo.setImg(param.getImgPoster());
kylinPerformanceSubscribeUpushVo.setJumpType(6);
kylinPerformanceSubscribeUpushVo.setJumpValue(param.getPerformancesId());
kylinPerformanceSubscribeUpushVo.setPushTime(pushTime);
kylinPerformanceSubscribeUpushVo.setTimeStart(DateUtil.format(param.getTimeStart(), DateUtil.Formatter.yyyyMMddHHmmss));
kylinPerformanceSubscribeUpushVo.setType(KylinTableStatusConst.SubscribeTypeEnum.TYPE1.getKey());
kylinPerformanceSubscribeUpushVo.setPerformancesId(param.getPerformancesId());
kylinPerformanceSubscribeUpushVo.setTicketTimesId(param.getTicketTimesId());
kylinPerformanceSubscribeUpushVo.setTicketsId(param.getTicketsId());
//开票提醒最多存20条数据
LinkedList<KylinPerformanceSubscribeUpushVo> performanceSubscribeList = dataUtils.getPerformanceSubscribeList(uid);
//删除其他票种的消息提醒
for (Iterator<KylinPerformanceSubscribeUpushVo> it = performanceSubscribeList.iterator(); it.hasNext(); ) {
KylinPerformanceSubscribeUpushVo info = it.next();
if(info.getPerformancesId().equals(param.getPerformancesId()) && info.getTicketTimesId().equals(param.getTicketTimesId())){
//移除
it.remove();
//先删除其他票种预约信息
List<String> ticketsIds = param.getTicketsIds();
if (!CollectionUtils.isEmpty(ticketsIds)) {
ticketsIds.forEach(iter -> {
dataUtils.deleteIsSubscribe(uid, param.getPerformancesId(), param.getTicketTimesId(), iter);
});
}
}
if(performanceSubscribeList.size()>=20){
KylinPerformanceSubscribeUpushVo last = performanceSubscribeList.getLast();
//删除redis中已读信息
dataUtils.deletePerformanceSubscribeRead(uid,last.getPerformancesId(), last.getTicketTimesId());
//删除最后一条数据
performanceSubscribeList.removeLast();
//添加到第一条
performanceSubscribeList.addFirst(kylinPerformanceSubscribeUpushVo);
}else {
//添加到第一条
performanceSubscribeList.addFirst(kylinPerformanceSubscribeUpushVo);
}
//开票提醒列表存入redis中
dataUtils.setPerformanceSubscribeList(uid,performanceSubscribeList);
//删除预约记录
queueUtils.sendMsgByRedis(
MQConst.KylinQueue.SQL_PERFORMANCE_SUBSCRIBE.getKey(),
SqlMapping.get(
"kylin_performance_subscribe.delete",
new Object[]{
param.getPerformancesId(), param.getTicketTimesId(), uid
}
));
// 计算过期时间 演出结束时间-当前时间
long expirationTime = DateUtil.intervalSeconds(DateUtil.parse(DateUtil.format(param.getTimeEnd(), DateUtil.Formatter.yyyyMMddHHmmss), DateUtil.DATE_FULL_STR), DateUtil.now());
// 计算通知时间 演出开始时间-10分钟
LocalDateTime timeStart = param.getTimeStart();
LocalDateTime localDateTime = timeStart.minusMinutes(10);
String pushTime = DateUtil.format(localDateTime, DateUtil.Formatter.yyyyMMddHHmmss);
//票种预约
dataUtils.setPerformanceTicketsSubscribe(uid, param.getPerformancesId(), param.getTicketTimesId(), param.getTicketsId(), expirationTime);
//本场演出预约
dataUtils.setPerformanceSubscribe(uid, param.getPerformancesId(), expirationTime);
//push推送内容
String title = param.getPerformancesTitle() + DateUtil.format(timeStart, DateUtil.Formatter.yyyyMMddHHmmss) + "即将开票,登登登提前" + param.getAdvanceMinuteMember() + "分钟开抢";
// 记录预约信息
queueUtils.sendMsgByRedis(
MQConst.KylinQueue.SQL_PERFORMANCE_SUBSCRIBE.getKey(),
SqlMapping.get(
"kylin_performance_subscribe.insert",
new Object[]{
param.getPerformancesId(), param.getTicketTimesId(), param.getTicketsId(), uid, KylinTableStatusConst.SubscribeTypeEnum.TYPE1.getKey(), param.getDeviceTokens(),
source, pushTime, title, 0
}
));
//redis 用户开票提醒列表
KylinPerformanceSubscribeUpushVo kylinPerformanceSubscribeUpushVo = new KylinPerformanceSubscribeUpushVo();
kylinPerformanceSubscribeUpushVo.setPushTitle(param.getPerformancesTitle());
kylinPerformanceSubscribeUpushVo.setPushContent(title);
kylinPerformanceSubscribeUpushVo.setImg(param.getImgPoster());
kylinPerformanceSubscribeUpushVo.setJumpType(6);
kylinPerformanceSubscribeUpushVo.setJumpValue(param.getPerformancesId());
kylinPerformanceSubscribeUpushVo.setPushTime(pushTime);
kylinPerformanceSubscribeUpushVo.setTimeStart(DateUtil.format(param.getTimeStart(), DateUtil.Formatter.yyyyMMddHHmmss));
kylinPerformanceSubscribeUpushVo.setType(KylinTableStatusConst.SubscribeTypeEnum.TYPE1.getKey());
kylinPerformanceSubscribeUpushVo.setPerformancesId(param.getPerformancesId());
kylinPerformanceSubscribeUpushVo.setTicketTimesId(param.getTicketTimesId());
kylinPerformanceSubscribeUpushVo.setTicketsId(param.getTicketsId());
//开票提醒最多存20条数据
LinkedList<KylinPerformanceSubscribeUpushVo> performanceSubscribeList = dataUtils.getPerformanceSubscribeList(uid);
//删除其他票种的消息提醒
for (Iterator<KylinPerformanceSubscribeUpushVo> it = performanceSubscribeList.iterator(); it.hasNext(); ) {
KylinPerformanceSubscribeUpushVo info = it.next();
if (info.getPerformancesId().equals(param.getPerformancesId()) && info.getTicketTimesId().equals(param.getTicketTimesId())) {
//移除
it.remove();
}
}
if (performanceSubscribeList.size() >= 20) {
KylinPerformanceSubscribeUpushVo last = performanceSubscribeList.getLast();
//删除redis中已读信息
dataUtils.deletePerformanceSubscribeRead(uid, last.getPerformancesId(), last.getTicketTimesId());
//删除最后一条数据
performanceSubscribeList.removeLast();
//添加到第一条
performanceSubscribeList.addFirst(kylinPerformanceSubscribeUpushVo);
} else {
//添加到第一条
performanceSubscribeList.addFirst(kylinPerformanceSubscribeUpushVo);
}
//开票提醒列表存入redis中
dataUtils.setPerformanceSubscribeList(uid, performanceSubscribeList);
}
return ResponseDto.success("预约成功");
} catch (Exception e) {
log.error("演出预约 [performancesId:{},ticketTimesId:{},ticketsId:{},uid:{}, e:{}]",param.getPerformancesId(), param.getTicketTimesId(),param.getTicketsId(), CurrentUtil.getCurrentUid(), e);
log.error("演出预约 [performancesId:{},ticketTimesId:{},ticketsId:{},uid:{}, e:{}]", param.getPerformancesId(), param.getTicketTimesId(), param.getTicketsId(), CurrentUtil.getCurrentUid(), e);
return ResponseDto.success("预约失败");
}
}
@Override
public ResponseDto<Integer> performanceIsSubscribe(String performancesId, String ticketTimesId,String ticketsId ) {
public ResponseDto<Integer> performanceIsSubscribe(String performancesId, String ticketTimesId, String ticketsId) {
String uid = CurrentUtil.getCurrentUid();
Integer performanceSubscribe = dataUtils.getPerformanceTicketsSubscribe(uid, performancesId, ticketTimesId,ticketsId);
Integer performanceSubscribe = dataUtils.getPerformanceTicketsSubscribe(uid, performancesId, ticketTimesId, ticketsId);
return ResponseDto.success(performanceSubscribe);
}
@Override
public void deleteIsSubscribe(String performancesId, String ticketTimesId,String ticketsId) {
public void deleteIsSubscribe(String performancesId, String ticketTimesId, String ticketsId) {
String uid = CurrentUtil.getCurrentUid();
//删除预约提醒
dataUtils.deleteIsSubscribe(uid,performancesId,ticketTimesId,ticketsId);
dataUtils.deleteIsSubscribe(uid, performancesId, ticketTimesId, ticketsId);
//删除已读
dataUtils.deleteIsSubscribeRead(uid,performancesId,ticketTimesId);
dataUtils.deleteIsSubscribeRead(uid, performancesId, ticketTimesId);
}
@Override
public List<KylinPerformanceArtistLineupVo> performanceArtists(String performancesId) {
List<KylinPerformanceArtistLineupVo> artistLineupVos = dataUtils.getPerformanceArtists(performancesId);
if (artistLineupVos.isEmpty()) {
log.info("演出阵容为空, performanceId: {}", performancesId);
return Collections.emptyList();
}
return artistLineupVos;
}
}
......@@ -14,6 +14,7 @@ import com.liquidnet.service.goblin.dto.vo.*;
import com.liquidnet.service.kylin.constant.KylinRedisConst;
import com.liquidnet.service.kylin.constant.KylinTableStatusConst;
import com.liquidnet.service.kylin.constant.LuckyBagStatusEnum;
import com.liquidnet.service.kylin.dao.KylinArtistPerformanceDao;
import com.liquidnet.service.kylin.dao.KylinTicketActive;
import com.liquidnet.service.kylin.dto.vo.KylinApiCameraDevicesVo;
import com.liquidnet.service.kylin.dto.vo.KylinPerformanceSubscribeUpushVo;
......@@ -23,12 +24,15 @@ import com.liquidnet.service.kylin.dto.vo.admin.OrderRefundPoundage;
import com.liquidnet.service.kylin.dto.vo.admin.OrderRefundPoundageAll;
import com.liquidnet.service.kylin.dto.vo.express.KylinOrderExpressRouteVo;
import com.liquidnet.service.kylin.dto.vo.express.KylinOrderExpressVo;
import com.liquidnet.service.kylin.dto.vo.middle.KylinTicketTimesVo;
import com.liquidnet.service.kylin.dto.vo.mongo.*;
import com.liquidnet.service.kylin.dto.vo.partner.KylinTicketExpressModuleVo;
import com.liquidnet.service.kylin.dto.vo.partner.KylinTicketPartnerVo;
import com.liquidnet.service.kylin.dto.vo.returns.KylinOrderListVo;
import com.liquidnet.service.kylin.dto.vo.returns.KylinOrderRefundsVo;
import com.liquidnet.service.kylin.dto.vo.returns.KylinPerformanceArtistLineupVo;
import com.liquidnet.service.kylin.entity.*;
import com.liquidnet.service.kylin.mapper.KylinArtistPerformanceMapper;
import com.liquidnet.service.kylin.mapper.KylinLuckyBagActivityMapper;
import com.liquidnet.service.kylin.mapper.KylinLuckyBagMapper;
import com.liquidnet.service.kylin.mapper.KylinRewardUserMapper;
......@@ -67,6 +71,8 @@ public class DataUtils {
private KylinLuckyBagMapper kylinLuckyBagMapper;
@Autowired
private KylinRewardUserMapper kylinRewardUserMapper;
@Autowired
private KylinArtistPerformanceMapper artistPerformanceMapper;
/**
......@@ -1723,4 +1729,57 @@ public class DataUtils {
return (int) obj;
}
}
/**
* 根据演出ID获取演出阵容
* @param performancesId
* @return
*/
public List<KylinPerformanceArtistLineupVo> getPerformanceArtists(String performancesId) {
final String redisKey = KylinRedisConst.PERFORMANCES_ARTISTS + performancesId;
try {
Object obj = redisUtil.get(redisKey);
if (null == obj) {
KylinPerformanceVo performancesInfo = getPerformanceVo(performancesId);
if (null == performancesInfo) {
return Collections.emptyList();
}
List<KylinPerformanceArtistLineupVo> resultList = new ArrayList<>();
List<KylinTicketTimesVo> ticketTimeList = performancesInfo.getTicketTimeList();
if (!CollectionUtils.isEmpty(ticketTimeList)) {
for (KylinTicketTimesVo timesVo : ticketTimeList) {
if (timesVo.getType().equals(2)) {
continue;
}
// 获取当前场次关联的艺人,按sort降序
List<KylinArtistPerformanceDao> artists = artistPerformanceMapper.selectArtistsByPerformanceAndTimes(performancesId, timesVo.getTicketTimesId());
KylinPerformanceArtistLineupVo lineupVo = new KylinPerformanceArtistLineupVo();
lineupVo.setTicketTimesId(timesVo.getTicketTimesId());
lineupVo.setTimeTitle(timesVo.getTitle());
lineupVo.setArtists(artists == null ? new ArrayList<>() :
artists.stream()
.map(KylinPerformanceArtistLineupVo.KylinPerformanceTimeArtist::from)
.filter(Objects::nonNull)
.collect(Collectors.toList()));
resultList.add(lineupVo);
}
}
setPerformanceArtists(performancesId, resultList);
return resultList;
}else {
return (List<KylinPerformanceArtistLineupVo>)obj;
}
}catch (Exception e){
log.error("error:", e);
}
return Collections.emptyList();
}
public void setPerformanceArtists(String performancesId, List<KylinPerformanceArtistLineupVo> artistLineupVos){
final String redisKey = KylinRedisConst.PERFORMANCES_ARTISTS + performancesId;
redisUtil.set(redisKey, artistLineupVos, RedisKeyExpireConst.PERFORMANCES_ARTISTS_EXPIRE);
}
}
......@@ -146,6 +146,7 @@ public class KylinTicketTimesPartnerServiceImpl implements IKylinTicketTimesPart
@Override
public ResponseDto<String> deleteTimes(String ticketTimesId, String performanceId) {
try {
redisSlimeUtils.delPerformanceArtists(performanceId);
LocalDateTime updatedAt = LocalDateTime.now();
KylinTicketTimesPartnerVo data = mongoSlimeUtils.getTicketTimesPartnerVo(ticketTimesId);
if (data.getStatus() == 0) {
......@@ -193,6 +194,7 @@ public class KylinTicketTimesPartnerServiceImpl implements IKylinTicketTimesPart
@Override
public ResponseDto<KylinTicketTimesPartnerVo> changeTimes(CreateTicketTimesParam createTicketTimesParam) {
try {
redisSlimeUtils.delPerformanceArtists(createTicketTimesParam.getPerformancesId());
LocalDateTime updatedAt = LocalDateTime.now();
String title = "";
if (createTicketTimesParam.getType() == 1) {
......
......@@ -332,4 +332,13 @@ public class RedisSlimeUtils {
String redisKey = KylinRedisConst.PERFORMANCES_NOTICE_REMIND_STATUS + performanceId;
redisUtil.set(redisKey, noticeRemindStatus);
}
/**
* 删除演出-场次阵容 缓存
* @param performancesId
*/
public void delPerformanceArtists(String performancesId) {
final String redisKey = KylinRedisConst.PERFORMANCES_ARTISTS + performancesId;
redisUtil.del(redisKey);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment