Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 05ea918ee1 | |||
| 50f1bdba38 | |||
| 74fa304cd9 | |||
| 9d352f40a0 | |||
| 674188f471 | |||
| 62ca1730b9 | |||
| 0d27d1a072 | |||
| d74d8b22c6 | |||
| 489131a04b | |||
| 43ac037f5b |
@@ -149,6 +149,7 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { useAppStore } from '../../stores/app.js'
|
||||
import { toggleFavoriteScript } from '../../services/epicScript.js'
|
||||
|
||||
const store = useAppStore()
|
||||
const activeType = ref('long')
|
||||
@@ -316,15 +317,34 @@ const toggleScriptMenu = (script) => {
|
||||
activeMenuId.value = activeMenuId.value === id ? '' : id
|
||||
}
|
||||
|
||||
const toggleFavorite = (script) => {
|
||||
const favorite = isFavorite(script)
|
||||
const toggleFavorite = async (script) => {
|
||||
const wasFav = isFavorite(script)
|
||||
// 乐观更新:先改本地缓存
|
||||
const next = { ...localFavorites.value }
|
||||
if (favorite) delete next[String(script.id)]
|
||||
else next[String(script.id)] = true
|
||||
if (wasFav) {
|
||||
delete next[String(script.id)]
|
||||
} else {
|
||||
next[String(script.id)] = true
|
||||
}
|
||||
localFavorites.value = next
|
||||
uni.setStorageSync('script_favorites', next)
|
||||
closeScriptMenu()
|
||||
uni.showToast({ title: favorite ? '已取消收藏' : '已收藏', icon: 'success' })
|
||||
|
||||
try {
|
||||
const result = await toggleFavoriteScript(script.id)
|
||||
uni.showToast({ title: result.isFavorited ? '已收藏' : '已取消收藏', icon: 'success' })
|
||||
} catch (error) {
|
||||
// 回滚乐观更新
|
||||
const restored = { ...localFavorites.value }
|
||||
if (wasFav) {
|
||||
restored[String(script.id)] = true
|
||||
} else {
|
||||
delete restored[String(script.id)]
|
||||
}
|
||||
localFavorites.value = restored
|
||||
uni.setStorageSync('script_favorites', restored)
|
||||
uni.showToast({ title: error?.message || '操作失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
const continueScript = (script) => {
|
||||
|
||||
@@ -198,6 +198,39 @@ export const transformListToFrontend = (backendList) => {
|
||||
return backendList.map(transformToFrontendFormat)
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换剧本收藏状态(收藏/取消收藏)
|
||||
* @param {string} scriptId 剧本ID
|
||||
* @returns {Promise<{success: boolean, isFavorited: boolean}>}
|
||||
*/
|
||||
export const toggleFavoriteScript = async (scriptId) => {
|
||||
const res = await post('/epicScript/favorite/toggle', { scriptId })
|
||||
return {
|
||||
success: true,
|
||||
isFavorited: res.data?.isFavorited ?? false,
|
||||
favoriteTime: res.data?.favoriteTime ?? null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取收藏剧本列表
|
||||
* @param {number} current 当前页
|
||||
* @param {number} size 每页大小
|
||||
*/
|
||||
export const getFavoriteScriptPage = async (current = 1, size = 10) => {
|
||||
return get('/epicScript/favorite/page', { current, size })
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查剧本是否已收藏
|
||||
* @param {string} scriptId 剧本ID
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
export const checkFavoriteScript = async (scriptId) => {
|
||||
const res = await get('/epicScript/favorite/check', { scriptId })
|
||||
return res.data?.isFavorited ?? false
|
||||
}
|
||||
|
||||
export default {
|
||||
getScriptList,
|
||||
getScriptPage,
|
||||
@@ -212,5 +245,8 @@ export default {
|
||||
buildCharacterInfo,
|
||||
buildLifeEventsSummary,
|
||||
transformToFrontendFormat,
|
||||
transformListToFrontend
|
||||
transformListToFrontend,
|
||||
toggleFavoriteScript,
|
||||
getFavoriteScriptPage,
|
||||
checkFavoriteScript,
|
||||
}
|
||||
|
||||
@@ -8,13 +8,16 @@ import com.emotion.dto.request.EpicScriptInspirationRequest;
|
||||
import com.emotion.dto.request.EpicScriptPageRequest;
|
||||
import com.emotion.dto.request.EpicScriptUpdateRequest;
|
||||
import com.emotion.dto.request.SwitchVersionRequest;
|
||||
import com.emotion.dto.request.ScriptFavoriteToggleRequest;
|
||||
import com.emotion.dto.response.EpicScriptCreateWithDialogueResponse;
|
||||
import com.emotion.dto.response.EpicScriptInspirationResponse;
|
||||
import com.emotion.dto.response.EpicScriptResponse;
|
||||
import com.emotion.dto.response.InspirationSuggestionResponse;
|
||||
import com.emotion.service.EpicScriptDialogueService;
|
||||
import com.emotion.dto.response.ScriptFavoriteResponse;
|
||||
import com.emotion.service.EpicScriptService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import com.emotion.service.ScriptFavoriteService;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -41,6 +44,9 @@ public class EpicScriptController {
|
||||
@Autowired
|
||||
private EpicScriptDialogueService epicScriptDialogueService;
|
||||
|
||||
@Autowired
|
||||
private ScriptFavoriteService scriptFavoriteService;
|
||||
|
||||
/**
|
||||
* 分页查询当前用户的爽文剧本
|
||||
*/
|
||||
@@ -172,4 +178,31 @@ public class EpicScriptController {
|
||||
}
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
// ==================== 剧本收藏 ====================
|
||||
|
||||
@Operation(summary = "切换收藏", description = "收藏或取消收藏指定剧本。")
|
||||
@PostMapping(value = "/favorite/toggle")
|
||||
public Result<ScriptFavoriteResponse> toggleFavorite(
|
||||
@Valid @RequestBody ScriptFavoriteToggleRequest request) {
|
||||
return Result.success(scriptFavoriteService.toggleFavorite(request.getScriptId()));
|
||||
}
|
||||
|
||||
@Operation(summary = "分页查询收藏", description = "分页查询当前用户收藏的剧本列表。")
|
||||
@GetMapping(value = "/favorite/page")
|
||||
public Result<PageResult<EpicScriptResponse>> getFavoritePage(
|
||||
@RequestParam(defaultValue = "1") long current,
|
||||
@RequestParam(defaultValue = "10") long size) {
|
||||
return Result.success(scriptFavoriteService.getFavoritePage(current, size));
|
||||
}
|
||||
|
||||
@Operation(summary = "检查收藏状态", description = "检查指定剧本是否已被当前用户收藏。")
|
||||
@GetMapping(value = "/favorite/check")
|
||||
public Result<ScriptFavoriteResponse> checkFavorite(@RequestParam String scriptId) {
|
||||
boolean favorited = scriptFavoriteService.isFavorited(scriptId);
|
||||
ScriptFavoriteResponse r = new ScriptFavoriteResponse();
|
||||
r.setScriptId(scriptId);
|
||||
r.setIsFavorited(favorited);
|
||||
return Result.success(r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.emotion.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 剧本收藏切换请求
|
||||
*
|
||||
* @author huazhongmin
|
||||
* @date 2026-07-19
|
||||
*/
|
||||
@Data
|
||||
public class ScriptFavoriteToggleRequest {
|
||||
|
||||
@NotBlank(message = "剧本ID不能为空")
|
||||
private String scriptId;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.emotion.dto.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 剧本收藏状态响应
|
||||
*
|
||||
* @author huazhongmin
|
||||
* @date 2026-07-19
|
||||
*/
|
||||
@Data
|
||||
public class ScriptFavoriteResponse {
|
||||
|
||||
private String scriptId;
|
||||
|
||||
private Boolean isFavorited;
|
||||
|
||||
private String favoriteTime;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.emotion.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.emotion.common.BaseEntity;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
/**
|
||||
* 剧本收藏关联实体
|
||||
*
|
||||
* @author huazhongmin
|
||||
* @date 2026-07-19
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@SuperBuilder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TableName("t_script_favorite")
|
||||
public class ScriptFavorite extends BaseEntity {
|
||||
|
||||
@TableField("user_id")
|
||||
private String userId;
|
||||
|
||||
@TableField("script_id")
|
||||
private String scriptId;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.emotion.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.emotion.entity.ScriptFavorite;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 剧本收藏Mapper接口
|
||||
*
|
||||
* @author huazhongmin
|
||||
* @date 2026-07-19
|
||||
*/
|
||||
@Mapper
|
||||
public interface ScriptFavoriteMapper extends BaseMapper<ScriptFavorite> {
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.emotion.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.emotion.common.PageResult;
|
||||
import com.emotion.dto.response.EpicScriptResponse;
|
||||
import com.emotion.dto.response.ScriptFavoriteResponse;
|
||||
import com.emotion.entity.ScriptFavorite;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 剧本收藏服务接口
|
||||
*
|
||||
* @author huazhongmin
|
||||
* @date 2026-07-19
|
||||
*/
|
||||
public interface ScriptFavoriteService extends IService<ScriptFavorite> {
|
||||
|
||||
ScriptFavoriteResponse toggleFavorite(String scriptId);
|
||||
|
||||
PageResult<EpicScriptResponse> getFavoritePage(long current, long size);
|
||||
|
||||
Set<String> getFavoritedScriptIds(Set<String> scriptIds);
|
||||
|
||||
boolean isFavorited(String scriptId);
|
||||
|
||||
long getFavoriteCount();
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.emotion.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.emotion.common.PageResult;
|
||||
import com.emotion.dto.response.EpicScriptResponse;
|
||||
import com.emotion.dto.response.ScriptFavoriteResponse;
|
||||
import com.emotion.entity.ScriptFavorite;
|
||||
import com.emotion.mapper.ScriptFavoriteMapper;
|
||||
import com.emotion.service.EpicScriptService;
|
||||
import com.emotion.service.ScriptFavoriteService;
|
||||
import com.emotion.util.UserContextHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 剧本收藏服务实现
|
||||
*
|
||||
* @author huazhongmin
|
||||
* @date 2026-07-19
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ScriptFavoriteServiceImpl extends ServiceImpl<ScriptFavoriteMapper, ScriptFavorite>
|
||||
implements ScriptFavoriteService {
|
||||
|
||||
private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
@Autowired
|
||||
private EpicScriptService epicScriptService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ScriptFavoriteResponse toggleFavorite(String scriptId) {
|
||||
String userId = UserContextHolder.getCurrentUserId();
|
||||
ScriptFavoriteResponse r = new ScriptFavoriteResponse();
|
||||
r.setScriptId(scriptId);
|
||||
|
||||
if (!StringUtils.hasText(userId)) {
|
||||
r.setIsFavorited(false);
|
||||
return r;
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<ScriptFavorite> w = new LambdaQueryWrapper<>();
|
||||
w.eq(ScriptFavorite::getUserId, userId)
|
||||
.eq(ScriptFavorite::getScriptId, scriptId)
|
||||
.eq(ScriptFavorite::getIsDeleted, 0);
|
||||
|
||||
ScriptFavorite existing = this.getOne(w);
|
||||
if (existing != null) {
|
||||
this.removeById(existing.getId());
|
||||
r.setIsFavorited(false);
|
||||
} else {
|
||||
ScriptFavorite fav = new ScriptFavorite();
|
||||
fav.setUserId(userId);
|
||||
fav.setScriptId(scriptId);
|
||||
this.save(fav);
|
||||
r.setIsFavorited(true);
|
||||
if (fav.getCreateTime() != null) {
|
||||
r.setFavoriteTime(fav.getCreateTime().format(FMT));
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<EpicScriptResponse> getFavoritePage(long current, long size) {
|
||||
String userId = UserContextHolder.getCurrentUserId();
|
||||
if (!StringUtils.hasText(userId)) {
|
||||
return new PageResult<>();
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<ScriptFavorite> w = new LambdaQueryWrapper<>();
|
||||
w.eq(ScriptFavorite::getUserId, userId)
|
||||
.eq(ScriptFavorite::getIsDeleted, 0)
|
||||
.orderByDesc(ScriptFavorite::getCreateTime);
|
||||
|
||||
Page<ScriptFavorite> page = new Page<>(current, size);
|
||||
Page<ScriptFavorite> result = this.page(page, w);
|
||||
|
||||
List<EpicScriptResponse> records = result.getRecords().stream()
|
||||
.map(ScriptFavorite::getScriptId)
|
||||
.map(epicScriptService::getScriptById)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
PageResult<EpicScriptResponse> pr = new PageResult<>();
|
||||
pr.setCurrent(result.getCurrent());
|
||||
pr.setSize(result.getSize());
|
||||
pr.setTotal(result.getTotal());
|
||||
pr.setPages(result.getPages());
|
||||
pr.setRecords(records);
|
||||
return pr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getFavoritedScriptIds(Set<String> scriptIds) {
|
||||
String userId = UserContextHolder.getCurrentUserId();
|
||||
if (!StringUtils.hasText(userId) || scriptIds == null || scriptIds.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<ScriptFavorite> w = new LambdaQueryWrapper<>();
|
||||
w.eq(ScriptFavorite::getUserId, userId)
|
||||
.in(ScriptFavorite::getScriptId, scriptIds)
|
||||
.eq(ScriptFavorite::getIsDeleted, 0);
|
||||
|
||||
return this.list(w).stream()
|
||||
.map(ScriptFavorite::getScriptId)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFavorited(String scriptId) {
|
||||
String userId = UserContextHolder.getCurrentUserId();
|
||||
if (!StringUtils.hasText(userId) || !StringUtils.hasText(scriptId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<ScriptFavorite> w = new LambdaQueryWrapper<>();
|
||||
w.eq(ScriptFavorite::getUserId, userId)
|
||||
.eq(ScriptFavorite::getScriptId, scriptId)
|
||||
.eq(ScriptFavorite::getIsDeleted, 0);
|
||||
|
||||
return this.count(w) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFavoriteCount() {
|
||||
String userId = UserContextHolder.getCurrentUserId();
|
||||
if (!StringUtils.hasText(userId)) {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<ScriptFavorite> w = new LambdaQueryWrapper<>();
|
||||
w.eq(ScriptFavorite::getUserId, userId)
|
||||
.eq(ScriptFavorite::getIsDeleted, 0);
|
||||
|
||||
return this.count(w);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
-- 剧本收藏关联表
|
||||
-- 存储用户对剧本的收藏关系,多对多关联
|
||||
CREATE TABLE t_script_favorite (
|
||||
id VARCHAR(64) NOT NULL PRIMARY KEY COMMENT '主键ID',
|
||||
user_id VARCHAR(64) NOT NULL COMMENT '用户ID,关联t_user.id',
|
||||
script_id VARCHAR(64) NOT NULL COMMENT '剧本ID,关联t_epic_script.id',
|
||||
create_by VARCHAR(64) DEFAULT NULL COMMENT '创建人',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '收藏时间',
|
||||
update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人',
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
is_deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除: 0-未删除, 1-已删除',
|
||||
remarks VARCHAR(500) DEFAULT '' COMMENT '备注(预留,可存收藏夹名或备注)',
|
||||
|
||||
UNIQUE KEY uk_user_script (user_id, script_id),
|
||||
KEY idx_user_id (user_id),
|
||||
KEY idx_script_id (script_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='剧本收藏关联表';
|
||||
Reference in New Issue
Block a user