修复WorkDetailActivity和WorkItem - 删除重复方法并添加作者字段

This commit is contained in:
xiao12feng8 2026-01-08 15:31:28 +08:00
parent a61c50e455
commit e08318d92f
11 changed files with 81 additions and 371 deletions

View File

@ -0,0 +1,46 @@
我们正在开发直播App的三个新功能请继续之前的工作
【功能1】阅后即焚图片消息
- 好友私聊中发送阅后即焚图片
- 查看几秒后自动销毁
- 需要在Android端和后端实现
【功能2】作品上热门
- 用户付费让作品上热门
- 热门作品在首页优先展示
- 需要付费档位和热门队列
【功能3】发布动态 + 附近动态
- 用户发布动态(文字+图片+位置)
- 查看附近的人的动态
- 基于地理位置发现
当前项目结构:
- Android端android-app/app/src/main/java/com/example/livestreaming/
- 后端JavaZhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/
- 后端ControllerZhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/
请根据我指定的功能继续开发。
我们正在开发直播App的三个新功能请继续之前的工作
【功能1】阅后即焚图片消息
- 好友私聊中发送阅后即焚图片
- 查看几秒后自动销毁
- 需要在Android端和后端实现
【功能2】作品上热门
- 用户付费让作品上热门
- 热门作品在首页优先展示
- 需要付费档位和热门队列
【功能3】发布动态 + 附近动态
- 用户发布动态(文字+图片+位置)
- 查看附近的人的动态
- 基于地理位置发现
当前项目结构:
- Android端android-app/app/src/main/java/com/example/livestreaming/
- 后端JavaZhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/
- 后端ControllerZhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/
请根据我指定的功能继续开发。

View File

@ -194,7 +194,6 @@ public class PublishWorkActivity extends AppCompatActivity {
getSupportActionBar().setTitle("编辑作品");
}
}
}
private void setupToolbar() {
setSupportActionBar(binding.toolbar);

View File

@ -804,171 +804,6 @@ public class WorkDetailActivity extends AppCompatActivity {
}
}
private void setupActionButton() {
// ============================================
// TODO: 判断是否是当前用户的作品
// ============================================
// 判断方式推荐方式3:
// 1. 从WorkItem中获取userId字段与当前登录用户ID比较需要从AuthStore获取当前用户ID
// 2. 或调用接口: GET /api/works/{workId}/isOwner
// 3. 或从作品详情接口返回的WorkItem中包含isOwner字段推荐已在获取详情时返回
//
// 如果使用方式3推荐:
// - 在获取作品详情时后端已经返回isOwner字段
// - 直接使用 workItem.getIsOwner() 或从详情接口返回的isOwner字段
// - 如果isOwner为true显示编辑/删除按钮
// - 如果isOwner为false或用户未登录隐藏编辑/删除按钮
//
// 如果使用方式1:
// - 需要从AuthStore获取当前登录用户的userId
// - 比较 workItem.getUserId() 与当前用户ID
// - 如果相同显示编辑/删除按钮否则隐藏
//
// 如果使用方式2:
// 接口路径: GET /api/works/{workId}/isOwner
// 请求方法: GET
// 请求头:
// - Authorization: Bearer {token} (必填需要登录)
// 路径参数:
// - workId: String (必填) - 作品ID
// 返回数据格式: ApiResponse<IsOwnerResponse>
// 后端返回: { "code": 200, "data": { "isOwner": true/false } }
//
// 目前简化处理所有作品都显示操作按钮
// TODO: 根据isOwner字段控制按钮显示/隐藏
// 新布局中actionButton是ImageView不是FloatingActionButton添加防抖
binding.actionButton.setOnClickListener(new DebounceClickListener() {
@Override
public void onDebouncedClick(View v) {
showActionMenu();
}
});
}
private void showActionMenu() {
// TODO: 判断是否是当前用户的作品
boolean isOwner = true; // 简化处理
if (!isOwner) {
return;
}
String[] options = {"编辑", "删除"};
new AlertDialog.Builder(this)
.setItems(options, (dialog, which) -> {
if (which == 0) {
// 编辑
editWork();
} else if (which == 1) {
// 删除
deleteWork();
}
})
.show();
}
private void editWork() {
// ============================================
// TODO: 实现编辑作品功能
// ============================================
// 方案1: 跳转到编辑页面PublishWorkActivity传入workItem数据
// 方案2: 在当前页面弹出编辑对话框
//
// 如果使用方案1推荐:
// 1. 创建EditWorkActivity或复用PublishWorkActivity
// 2. 传入workItem数据通过Intent传递
// 3. 在编辑页面预填充作品信息标题描述图片/视频
// 4. 用户修改后调用更新接口保存
//
// 更新作品接口:
// 接口路径: PUT /api/works/{workId}
// 请求方法: PUT
// 请求头:
// - Authorization: Bearer {token} (必填需要登录)
// 路径参数:
// - workId: String (必填) - 作品ID
// 请求体 (multipart/form-data JSON):
// - title: String (可选) - 作品标题
// - description: String (可选) - 作品描述
// - cover: File (可选) - 新的封面图片如果要更换封面
// - video: File (可选) - 新的视频文件如果要更换视频仅视频作品
// - images: File[] (可选) - 新的图片文件数组如果要更换图片仅图片作品
// - deletedImageIds: String[] (可选) - 要删除的图片ID列表图片作品
// 返回数据格式: ApiResponse<WorkItem>
//
// 前端需要传入的参数:
// - workId: String (从workItem.getId()获取)
// - title: String (用户修改后的标题)
// - description: String (用户修改后的描述可选)
// - cover: File (如果要更换封面)
// - video/images: File/File[] (如果要更换媒体文件)
// - token: String (必填从AuthStore获取)
//
// 实现步骤:
// 1. 跳转到编辑页面传入workItem
// 2. 在编辑页面加载作品数据并预填充表单
// 3. 用户修改内容后点击保存
// 4. 验证输入标题不能为空等
// 5. 如果有新上传的文件先上传文件获取URL
// 6. 调用 PUT /api/works/{workId} 更新作品信息
// 7. 更新成功后刷新当前页面或返回并刷新列表
// 8. 处理错误情况未登录无权限文件上传失败等
//
// 注意:
// - 只有作品作者才能编辑
// - 如果更换媒体文件需要先上传新文件
// - 图片作品可以删除部分图片需要传递deletedImageIds
Toast.makeText(this, "编辑功能待实现", Toast.LENGTH_SHORT).show();
}
private void deleteWork() {
new AlertDialog.Builder(this)
.setTitle("删除作品")
.setMessage("确定要删除这个作品吗?")
.setPositiveButton("删除", (dialog, which) -> {
// 检查登录状态
if (!AuthHelper.requireLogin(this, "删除作品需要登录")) {
return;
}
try {
long worksId = Long.parseLong(workItem.getId());
ApiService apiService = ApiClient.getService(this);
Call<ApiResponse<Boolean>> call = apiService.deleteWork(worksId);
call.enqueue(new retrofit2.Callback<ApiResponse<Boolean>>() {
@Override
public void onResponse(Call<ApiResponse<Boolean>> call, retrofit2.Response<ApiResponse<Boolean>> response) {
if (response.isSuccessful() && response.body() != null) {
ApiResponse<Boolean> apiResponse = response.body();
if (apiResponse.getCode() == 200 && Boolean.TRUE.equals(apiResponse.getData())) {
Toast.makeText(WorkDetailActivity.this, "删除成功", Toast.LENGTH_SHORT).show();
finish();
} else {
Toast.makeText(WorkDetailActivity.this,
apiResponse.getMessage() != null ? apiResponse.getMessage() : "删除失败",
Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(WorkDetailActivity.this, "删除失败", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<ApiResponse<Boolean>> call, Throwable t) {
Toast.makeText(WorkDetailActivity.this, "网络错误: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
} catch (NumberFormatException e) {
Toast.makeText(this, "作品ID格式错误", Toast.LENGTH_SHORT).show();
}
})
.setNegativeButton("取消", null)
.show();
}
// 图片ViewPager适配器
private static class ImagePagerAdapter extends RecyclerView.Adapter<ImagePagerAdapter.VH> {
private final List<Uri> imageUris;
@ -1100,64 +935,6 @@ public class WorkDetailActivity extends AppCompatActivity {
}
}
/**
* 将WorksResponse转换为WorkItem
*/
private WorkItem convertToWorkItem(WorksResponse response) {
WorkItem item = new WorkItem();
item.setId(String.valueOf(response.getId()));
item.setTitle(response.getTitle());
item.setDescription(response.getDescription());
item.setLikeCount(response.getLikeCount());
item.setCollectCount(response.getCollectCount());
item.setViewCount(response.getViewCount());
// 防止 publishTime null 导致 NullPointerException
item.setPublishTime(response.getPublishTime() != null ? response.getPublishTime() : 0L);
// 设置作品类型
if ("VIDEO".equals(response.getType())) {
item.setType(WorkItem.WorkType.VIDEO);
item.setVideoUrl(response.getVideoUrl());
if (!TextUtils.isEmpty(response.getVideoUrl())) {
item.setVideoUri(Uri.parse(response.getVideoUrl()));
}
} else {
item.setType(WorkItem.WorkType.IMAGE);
item.setImageUrls(response.getImageUrls());
if (response.getImageUrls() != null && !response.getImageUrls().isEmpty()) {
List<Uri> imageUris = new ArrayList<>();
for (String url : response.getImageUrls()) {
if (!TextUtils.isEmpty(url)) {
imageUris.add(Uri.parse(url));
}
}
item.setImageUris(imageUris);
}
}
// 设置封面
item.setCoverUrl(response.getCoverUrl());
if (!TextUtils.isEmpty(response.getCoverUrl())) {
item.setCoverUri(Uri.parse(response.getCoverUrl()));
}
return item;
}
}
}
});
} catch (NumberFormatException e) {
android.util.Log.e("WorkDetail", "workId格式错误: " + workId, e);
Toast.makeText(this, "作品ID格式错误", Toast.LENGTH_SHORT).show();
finish();
} catch (Exception e) {
android.util.Log.e("WorkDetail", "加载作品详情异常", e);
Toast.makeText(this, "加载失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
finish();
}
}
/**
* 将WorksResponse转换为WorkItem
*/
@ -1354,153 +1131,6 @@ public class WorkDetailActivity extends AppCompatActivity {
.show();
}
private void setupActionButton() {
// 检查是否应该显示菜单
boolean showMenu = getIntent().getBooleanExtra(EXTRA_SHOW_MENU, false);
if (!showMenu) {
binding.actionButton.setVisibility(View.GONE);
return;
}
// 检查是否是当前用户的作品
String currentUserIdStr = AuthStore.getUserId(this);
if (currentUserIdStr == null || workItem == null) {
binding.actionButton.setVisibility(View.GONE);
return;
}
try {
int currentUserId = Integer.parseInt(currentUserIdStr);
int authorId = workItem.getAuthorId();
if (currentUserId != authorId) {
// 不是自己的作品隐藏菜单
binding.actionButton.setVisibility(View.GONE);
return;
}
} catch (NumberFormatException e) {
binding.actionButton.setVisibility(View.GONE);
return;
}
// 是自己的作品且需要显示菜单设置点击事件
binding.actionButton.setVisibility(View.VISIBLE);
binding.actionButton.setOnClickListener(new DebounceClickListener() {
@Override
public void onDebouncedClick(View v) {
showActionMenu();
}
});
}
private void showActionMenu() {
// 检查是否是当前用户的作品
String currentUserIdStr = AuthStore.getUserId(this);
if (currentUserIdStr == null || workItem == null) {
return;
}
try {
int currentUserId = Integer.parseInt(currentUserIdStr);
int authorId = workItem.getAuthorId();
if (currentUserId != authorId) {
Toast.makeText(this, "只能编辑自己的作品", Toast.LENGTH_SHORT).show();
return;
}
} catch (NumberFormatException e) {
return;
}
String[] options = {"编辑", "删除"};
new AlertDialog.Builder(this)
.setItems(options, (dialog, which) -> {
if (which == 0) {
// 编辑
editWork();
} else if (which == 1) {
// 删除
deleteWork();
}
})
.show();
}
private void editWork() {
// 检查登录状态
if (!AuthHelper.requireLogin(this, "编辑作品需要登录")) {
return;
}
if (workItem == null) {
Toast.makeText(this, "作品信息不完整", Toast.LENGTH_SHORT).show();
return;
}
// 跳转到编辑页面复用PublishWorkActivity
Intent intent = new Intent(this, PublishWorkActivity.class);
intent.putExtra("edit_mode", true);
intent.putExtra("work_id", workItem.getId());
intent.putExtra("work_title", workItem.getTitle());
intent.putExtra("work_description", workItem.getDescription());
intent.putExtra("work_type", workItem.getType().name());
intent.putExtra("work_cover_url", workItem.getCoverUrl());
if (workItem.getType() == WorkItem.WorkType.VIDEO) {
intent.putExtra("work_video_url", workItem.getVideoUrl());
} else if (workItem.getImageUrls() != null) {
intent.putStringArrayListExtra("work_image_urls", new ArrayList<>(workItem.getImageUrls()));
}
startActivityForResult(intent, 1001);
}
private void deleteWork() {
new AlertDialog.Builder(this)
.setTitle("删除作品")
.setMessage("确定要删除这个作品吗?")
.setPositiveButton("删除", (dialog, which) -> {
// 检查登录状态
if (!AuthHelper.requireLogin(this, "删除作品需要登录")) {
return;
}
try {
long worksId = Long.parseLong(workItem.getId());
ApiService apiService = ApiClient.getService(this);
Call<ApiResponse<Boolean>> call = apiService.deleteWork(worksId);
call.enqueue(new retrofit2.Callback<ApiResponse<Boolean>>() {
@Override
public void onResponse(Call<ApiResponse<Boolean>> call, retrofit2.Response<ApiResponse<Boolean>> response) {
if (response.isSuccessful() && response.body() != null) {
ApiResponse<Boolean> apiResponse = response.body();
if (apiResponse.getCode() == 200 && Boolean.TRUE.equals(apiResponse.getData())) {
Toast.makeText(WorkDetailActivity.this, "删除成功", Toast.LENGTH_SHORT).show();
finish();
} else {
Toast.makeText(WorkDetailActivity.this,
apiResponse.getMessage() != null ? apiResponse.getMessage() : "删除失败",
Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(WorkDetailActivity.this, "删除失败", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<ApiResponse<Boolean>> call, Throwable t) {
Toast.makeText(WorkDetailActivity.this, "网络错误: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
} catch (NumberFormatException e) {
Toast.makeText(this, "作品ID格式错误", Toast.LENGTH_SHORT).show();
}
})
.setNegativeButton("取消", null)
.show();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);

View File

@ -23,6 +23,11 @@ public class WorkItem implements Parcelable {
private long publishTime;
private WorkType type; // 作品类型图片或视频
// 作者信息
private int authorId;
private String authorName;
private String authorAvatar;
// 本地使用的URI发布时使用
private transient Uri coverUri;
private transient Uri videoUri;
@ -171,6 +176,30 @@ public class WorkItem implements Parcelable {
this.imageUris = imageUris;
}
public int getAuthorId() {
return authorId;
}
public void setAuthorId(int authorId) {
this.authorId = authorId;
}
public String getAuthorName() {
return authorName;
}
public void setAuthorName(String authorName) {
this.authorName = authorName;
}
public String getAuthorAvatar() {
return authorAvatar;
}
public void setAuthorAvatar(String authorAvatar) {
this.authorAvatar = authorAvatar;
}
// Parcelable implementation
protected WorkItem(Parcel in) {
id = in.readString();
@ -186,6 +215,9 @@ public class WorkItem implements Parcelable {
int typeOrdinal = in.readInt();
type = typeOrdinal >= 0 && typeOrdinal < WorkType.values().length
? WorkType.values()[typeOrdinal] : WorkType.IMAGE;
authorId = in.readInt();
authorName = in.readString();
authorAvatar = in.readString();
}
@Override
@ -201,6 +233,9 @@ public class WorkItem implements Parcelable {
dest.writeInt(viewCount);
dest.writeLong(publishTime);
dest.writeInt(type != null ? type.ordinal() : -1);
dest.writeInt(authorId);
dest.writeString(authorName);
dest.writeString(authorAvatar);
}
@Override