topfans/docs/superpowers/plans/2026-06-17-plan-c-uniapp-client.md
2026-06-22 17:19:48 +08:00

245 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Plan C: 客户端 uni-app 集成
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task.
**父计划:** `docs/superpowers/plans/2026-06-17-moderation-report-feedback-system.md`
**Goal:** 在 uni-app 客户端集成举报与反馈入口,使用户可对藏品/用户/描述词举报,提交反馈,查看自己的工单
**Architecture:**
- 8 个 API 方法追加到 `frontend/utils/api.js`(不新建文件)
- 通用 `ReportModal.vue` 弹窗uView 2.x
- 我的页面菜单追加"我的举报"/"我的反馈"
- 复用 evidence 上传(`type=report` / `type=feedback`spec §9.1
**Tech Stack:** uni-app + uView 2.x + Vue3
**仓库:** `/Users/liulujian/Documents/code/TopFansByGithub/frontend`
**前置:** Plan A 阶段 A.9 Gateway 路由已注册
---
## 阶段 C.1API 方法追加
### Task C.1.1: 在 api.js 末尾追加 8 个方法
**Files:**
- Modify: `frontend/utils/api.js`
```javascript
// 在文件末尾追加(不覆盖现有内容):
export function getReportCategoriesApi() {
return request.get('/api/v1/moderation/report-categories')
}
export function submitReportApi(payload) {
return request.post('/api/v1/moderation/reports', payload)
}
export function getMyReportsApi(params) {
return request.get('/api/v1/moderation/reports', { params })
}
export function getMyReportDetailApi(id) {
return request.get(`/api/v1/moderation/reports/${id}`)
}
export function getFeedbackCategoriesApi() {
return request.get('/api/v1/moderation/feedback-categories')
}
export function submitFeedbackApi(payload) {
return request.post('/api/v1/moderation/feedbacks', payload)
}
export function getMyFeedbacksApi(params) {
return request.get('/api/v1/moderation/feedbacks', { params })
}
export function getMyFeedbackDetailApi(id) {
return request.get(`/api/v1/moderation/feedbacks/${id}`)
}
```
---
## 阶段 C.2ReportModal 通用举报弹窗
### Task C.2.1: 创建组件
**Files:**
- Create: `frontend/components/ReportModal.vue`
**关键实现**uView 2.x
```vue
<template>
<u-popup v-model="show" mode="bottom" border-radius="20" :mask-close-able="false">
<view class="report-modal">
<view class="header">
<text class="title">举报{{ targetTypeLabel }}</text>
<u-icon name="close" @click="show = false" />
</view>
<u-form :model="form" ref="formRef" label-width="80">
<u-form-item label="举报分类" prop="category_code">
<u-radio-group v-model="form.category_code" placement="column">
<u-radio v-for="cat in categories" :key="cat.code" :name="cat.code">
{{ cat.name }}
</u-radio>
</u-radio-group>
</u-form-item>
<u-form-item label="详细描述" prop="description">
<u-textarea v-model="form.description" :maxlength="500" :count="true" placeholder="请描述违规情况" />
</u-form-item>
<u-form-item label="证据图">
<u-upload :file-list="form.evidence_urls" @afterRead="afterRead" @delete="deletePic" :max-count="5" />
</u-form-item>
<u-form-item label="匿名举报">
<u-switch v-model="form.is_anonymous" />
</u-form-item>
<u-button type="primary" @click="onSubmit" :loading="submitting">提交举报</u-button>
</u-form>
</view>
</u-popup>
</template>
<script setup>
import { ref, watch, onMounted } from 'vue'
import { getReportCategoriesApi, submitReportApi } from '@/utils/api'
const props = defineProps({
modelValue: Boolean,
targetType: { type: String, required: true, validator: v => ['asset', 'user_profile'].includes(v) },
targetId: { type: Number, required: true },
targetName: { type: String, default: '' },
})
const emit = defineEmits(['update:modelValue', 'success'])
const show = ref(props.modelValue)
watch(() => props.modelValue, v => { show.value = v })
watch(show, v => { if (v !== props.modelValue) emit('update:modelValue', v) })
const categories = ref([])
const formRef = ref()
const submitting = ref(false)
const form = ref({
category_code: '',
description: '',
evidence_keys: [],
evidence_urls: [],
is_anonymous: false,
})
const targetTypeLabel = computed(() => props.targetType === 'asset' ? '藏品' : '用户')
onMounted(async () => {
const resp = await getReportCategoriesApi()
categories.value = resp.data || []
})
const afterRead = async (file) => {
// 1. 调 OSS 签名type=report
const sig = await getOSSUploadSignatureApi('report')
// 2. 直传 OSS
await uploadToOSS(sig, file)
// 3. 收集 key
form.value.evidence_keys.push(sig.dir + file.name)
form.value.evidence_urls.push({ url: sig.host + '/' + sig.dir + file.name })
}
const deletePic = (index) => {
form.value.evidence_urls.splice(index, 1)
form.value.evidence_keys.splice(index, 1)
}
const onSubmit = async () => {
if (!form.value.category_code) {
uni.showToast({ title: '请选择举报分类', icon: 'none' })
return
}
submitting.value = true
try {
const resp = await submitReportApi({
target_type: props.targetType,
target_id: props.targetId,
category_code: form.value.category_code,
description: form.value.description,
is_anonymous: form.value.is_anonymous,
evidence_keys: form.value.evidence_keys,
})
// 双分支 toastspec §阶段 5.2
if (resp.data.target_hidden) {
uni.showToast({ title: '已自动隐藏,等待审核', icon: 'none' })
} else {
uni.showToast({ title: '举报已提交', icon: 'success' })
}
emit('success', resp.data)
show.value = false
} catch (e) {
// 错误码 50004/50011/50012 等
uni.showToast({ title: e.message || '提交失败', icon: 'none' })
} finally {
submitting.value = false
}
}
</script>
```
---
## 阶段 C.3:接入到各入口
### Task C.3.1: 藏品详情页
**Files:**
- Modify: `frontend/components/NftDetailModal.vue`(或具体藏品详情组件)
在 "..." 菜单中加"举报"项,点击弹出 ReportModal。
### Task C.3.2: 用户主页
**Files:**
- Modify: `frontend/pages/user/userProfile.vue`
粉丝身份下的 "..." 菜单加"举报用户"。
### Task C.3.3: 描述词展示组件
(如有)在 "..." 菜单加"举报"项。
---
## 阶段 C.4:意见反馈页
### Task C.4.1: 创建意见反馈页
**Files:**
- Create: `frontend/pages/profile/feedback.vue`
表单:分类下拉 + 标题 + 内容 + 联系方式 + 截图(最多 5 张)+ 匿名开关 + 提交。
### Task C.4.2: 我的举报列表
**Files:**
- Create: `frontend/pages/profile/myReports.vue`
调用 `getMyReportsApi` 渲染列表,点击详情跳转 `myReportDetail`
### Task C.4.3: 我的反馈列表
**Files:**
- Create: `frontend/pages/profile/myFeedbacks.vue`
同模式。
### Task C.4.4: 我的页面菜单追加
**Files:**
- Modify: `frontend/pages/profile/profile.vue`
加"我的举报"和"我的反馈"菜单项。
---
## 自审检查清单Plan C
- [ ] 8 个 API 方法追加完整
- [ ] ReportModal 双分支 toast 正确target_hidden true/false
- [ ] 错误码处理50004/50011/50012 等)
- [ ] evidence 上传用 `type=report` / `type=feedback`
- [ ] uView 2.x UI 组件一致性
- [ ] 我的页面菜单项接入