mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 03:15:04 +08:00
update: copyright-claims
This commit is contained in:
@@ -1,12 +1,66 @@
|
||||
package converter
|
||||
|
||||
import (
|
||||
"time"
|
||||
"github.com/ctwj/urldb/db/dto"
|
||||
"github.com/ctwj/urldb/db/entity"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CopyrightClaimToResponse 将版权申述实体转换为响应对象
|
||||
// CopyrightClaimToResponseWithResources 将版权申述实体和关联资源转换为响应对象
|
||||
func CopyrightClaimToResponseWithResources(claim *entity.CopyrightClaim, resources []*entity.Resource) *dto.CopyrightClaimResponse {
|
||||
if claim == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 转换关联的资源信息
|
||||
var resourceInfos []dto.ResourceInfo
|
||||
for _, resource := range resources {
|
||||
categoryName := ""
|
||||
if resource.Category.ID != 0 {
|
||||
categoryName = resource.Category.Name
|
||||
}
|
||||
|
||||
panName := ""
|
||||
if resource.Pan.ID != 0 {
|
||||
panName = resource.Pan.Name
|
||||
}
|
||||
|
||||
resourceInfo := dto.ResourceInfo{
|
||||
ID: resource.ID,
|
||||
Title: resource.Title,
|
||||
Description: resource.Description,
|
||||
URL: resource.URL,
|
||||
SaveURL: resource.SaveURL,
|
||||
FileSize: resource.FileSize,
|
||||
Category: categoryName,
|
||||
PanName: panName,
|
||||
ViewCount: resource.ViewCount,
|
||||
IsValid: resource.IsValid,
|
||||
CreatedAt: resource.CreatedAt.Format(time.RFC3339),
|
||||
}
|
||||
resourceInfos = append(resourceInfos, resourceInfo)
|
||||
}
|
||||
|
||||
return &dto.CopyrightClaimResponse{
|
||||
ID: claim.ID,
|
||||
ResourceKey: claim.ResourceKey,
|
||||
Identity: claim.Identity,
|
||||
ProofType: claim.ProofType,
|
||||
Reason: claim.Reason,
|
||||
ContactInfo: claim.ContactInfo,
|
||||
ClaimantName: claim.ClaimantName,
|
||||
ProofFiles: claim.ProofFiles,
|
||||
UserAgent: claim.UserAgent,
|
||||
IPAddress: claim.IPAddress,
|
||||
Status: claim.Status,
|
||||
Note: claim.Note,
|
||||
CreatedAt: claim.CreatedAt.Format(time.RFC3339),
|
||||
UpdatedAt: claim.UpdatedAt.Format(time.RFC3339),
|
||||
Resources: resourceInfos,
|
||||
}
|
||||
}
|
||||
|
||||
// CopyrightClaimToResponse 将版权申述实体转换为响应对象(不包含资源详情)
|
||||
func CopyrightClaimToResponse(claim *entity.CopyrightClaim) *dto.CopyrightClaimResponse {
|
||||
if claim == nil {
|
||||
return nil
|
||||
@@ -27,6 +81,7 @@ func CopyrightClaimToResponse(claim *entity.CopyrightClaim) *dto.CopyrightClaimR
|
||||
Note: claim.Note,
|
||||
CreatedAt: claim.CreatedAt.Format(time.RFC3339),
|
||||
UpdatedAt: claim.UpdatedAt.Format(time.RFC3339),
|
||||
Resources: []dto.ResourceInfo{}, // 空的资源列表
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,20 +21,21 @@ type CopyrightClaimUpdateRequest struct {
|
||||
|
||||
// CopyrightClaimResponse 版权申述响应
|
||||
type CopyrightClaimResponse struct {
|
||||
ID uint `json:"id"`
|
||||
ResourceKey string `json:"resource_key"`
|
||||
Identity string `json:"identity"`
|
||||
ProofType string `json:"proof_type"`
|
||||
Reason string `json:"reason"`
|
||||
ContactInfo string `json:"contact_info"`
|
||||
ClaimantName string `json:"claimant_name"`
|
||||
ProofFiles string `json:"proof_files"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
IPAddress string `json:"ip_address"`
|
||||
Status string `json:"status"`
|
||||
Note string `json:"note"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
ID uint `json:"id"`
|
||||
ResourceKey string `json:"resource_key"`
|
||||
Identity string `json:"identity"`
|
||||
ProofType string `json:"proof_type"`
|
||||
Reason string `json:"reason"`
|
||||
ContactInfo string `json:"contact_info"`
|
||||
ClaimantName string `json:"claimant_name"`
|
||||
ProofFiles string `json:"proof_files"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
IPAddress string `json:"ip_address"`
|
||||
Status string `json:"status"`
|
||||
Note string `json:"note"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Resources []ResourceInfo `json:"resources"`
|
||||
}
|
||||
|
||||
// CopyrightClaimListRequest 版权申述列表请求
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/ctwj/urldb/db/dto"
|
||||
"github.com/ctwj/urldb/db/entity"
|
||||
"github.com/ctwj/urldb/db/repo"
|
||||
"github.com/ctwj/urldb/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/validator/v10"
|
||||
@@ -15,12 +16,14 @@ import (
|
||||
|
||||
type CopyrightClaimHandler struct {
|
||||
copyrightClaimRepo repo.CopyrightClaimRepository
|
||||
resourceRepo repo.ResourceRepository
|
||||
validate *validator.Validate
|
||||
}
|
||||
|
||||
func NewCopyrightClaimHandler(copyrightClaimRepo repo.CopyrightClaimRepository) *CopyrightClaimHandler {
|
||||
func NewCopyrightClaimHandler(copyrightClaimRepo repo.CopyrightClaimRepository, resourceRepo repo.ResourceRepository) *CopyrightClaimHandler {
|
||||
return &CopyrightClaimHandler{
|
||||
copyrightClaimRepo: copyrightClaimRepo,
|
||||
resourceRepo: resourceRepo,
|
||||
validate: validator.New(),
|
||||
}
|
||||
}
|
||||
@@ -144,7 +147,38 @@ func (h *CopyrightClaimHandler) ListCopyrightClaims(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
PageResponse(c, converter.CopyrightClaimsToResponse(claims), total, req.Page, req.PageSize)
|
||||
// 转换为包含资源信息的响应
|
||||
var responses []*dto.CopyrightClaimResponse
|
||||
for _, claim := range claims {
|
||||
// 查询关联的资源信息
|
||||
resources, err := h.getResourcesByResourceKey(claim.ResourceKey)
|
||||
if err != nil {
|
||||
// 如果查询资源失败,使用空资源列表
|
||||
responses = append(responses, converter.CopyrightClaimToResponse(claim))
|
||||
} else {
|
||||
// 使用包含资源详情的转换函数
|
||||
responses = append(responses, converter.CopyrightClaimToResponseWithResources(claim, resources))
|
||||
}
|
||||
}
|
||||
|
||||
PageResponse(c, responses, total, req.Page, req.PageSize)
|
||||
}
|
||||
|
||||
// getResourcesByResourceKey 根据资源key获取关联的资源列表
|
||||
func (h *CopyrightClaimHandler) getResourcesByResourceKey(resourceKey string) ([]*entity.Resource, error) {
|
||||
// 从资源仓库获取与key关联的所有资源
|
||||
resources, err := h.resourceRepo.FindByResourceKey(resourceKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 将 []entity.Resource 转换为 []*entity.Resource
|
||||
var resourcePointers []*entity.Resource
|
||||
for i := range resources {
|
||||
resourcePointers = append(resourcePointers, &resources[i])
|
||||
}
|
||||
|
||||
return resourcePointers, nil
|
||||
}
|
||||
|
||||
// UpdateCopyrightClaim 更新版权申述状态
|
||||
@@ -263,16 +297,16 @@ func (h *CopyrightClaimHandler) GetCopyrightClaimByResource(c *gin.Context) {
|
||||
}
|
||||
|
||||
// RegisterCopyrightClaimRoutes 注册版权申述相关路由
|
||||
func RegisterCopyrightClaimRoutes(router *gin.RouterGroup, copyrightClaimRepo repo.CopyrightClaimRepository) {
|
||||
handler := NewCopyrightClaimHandler(copyrightClaimRepo)
|
||||
func RegisterCopyrightClaimRoutes(router *gin.RouterGroup, copyrightClaimRepo repo.CopyrightClaimRepository, resourceRepo repo.ResourceRepository) {
|
||||
handler := NewCopyrightClaimHandler(copyrightClaimRepo, resourceRepo)
|
||||
|
||||
claims := router.Group("/copyright-claims")
|
||||
{
|
||||
claims.POST("", handler.CreateCopyrightClaim) // 创建版权申述
|
||||
claims.GET("/:id", handler.GetCopyrightClaim) // 获取版权申述详情
|
||||
claims.GET("", handler.ListCopyrightClaims) // 获取版权申述列表
|
||||
claims.PUT("/:id", handler.UpdateCopyrightClaim) // 更新版权申述状态
|
||||
claims.DELETE("/:id", handler.DeleteCopyrightClaim) // 删除版权申述
|
||||
claims.GET("", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handler.ListCopyrightClaims) // 获取版权申述列表
|
||||
claims.PUT("/:id", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handler.UpdateCopyrightClaim) // 更新版权申述状态
|
||||
claims.DELETE("/:id", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handler.DeleteCopyrightClaim) // 删除版权申述
|
||||
claims.GET("/resource/:resource_key", handler.GetCopyrightClaimByResource) // 获取资源版权申述列表
|
||||
}
|
||||
}
|
||||
2
main.go
2
main.go
@@ -213,7 +213,7 @@ func main() {
|
||||
|
||||
// 创建举报和版权申述处理器
|
||||
reportHandler := handlers.NewReportHandler(repoManager.ReportRepository, repoManager.ResourceRepository)
|
||||
copyrightClaimHandler := handlers.NewCopyrightClaimHandler(repoManager.CopyrightClaimRepository)
|
||||
copyrightClaimHandler := handlers.NewCopyrightClaimHandler(repoManager.CopyrightClaimRepository, repoManager.ResourceRepository)
|
||||
|
||||
// API路由
|
||||
api := r.Group("/api")
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
:bordered="false"
|
||||
:single-line="false"
|
||||
:loading="loading"
|
||||
:scroll-x="1200"
|
||||
:scroll-x="1020"
|
||||
class="h-full"
|
||||
/>
|
||||
</div>
|
||||
@@ -143,7 +143,20 @@
|
||||
</div>
|
||||
<div v-if="selectedClaim.proof_files">
|
||||
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400">证明文件</h3>
|
||||
<p class="mt-1 text-sm text-gray-900 dark:text-gray-100 break-all">{{ selectedClaim.proof_files }}</p>
|
||||
<div class="mt-1 space-y-2">
|
||||
<div
|
||||
v-for="(file, index) in getProofFiles(selectedClaim.proof_files)"
|
||||
:key="index"
|
||||
class="flex items-center justify-between p-2 bg-gray-50 dark:bg-gray-800 rounded hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors cursor-pointer"
|
||||
@click="downloadFile(file)"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<i class="fas fa-file-download text-blue-500"></i>
|
||||
<span class="text-sm text-gray-900 dark:text-gray-100">{{ getFileName(file) }}</span>
|
||||
</div>
|
||||
<i class="fas fa-download text-gray-400 hover:text-blue-500 transition-colors"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400">提交时间</h3>
|
||||
@@ -204,102 +217,180 @@ const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
width: 80,
|
||||
width: 60,
|
||||
render: (row: any) => {
|
||||
return h('span', { class: 'font-medium' }, row.id)
|
||||
return h('div', { class: 'space-y-1' }, [
|
||||
h('div', { class: 'font-medium text-sm' }, row.id),
|
||||
h('div', {
|
||||
class: 'text-xs text-gray-400',
|
||||
title: `IP: ${row.ip_address || '未知'}`
|
||||
}, row.ip_address ? `IP: ${row.ip_address.slice(0, 8)}...` : 'IP:未知')
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '资源Key',
|
||||
title: '资源',
|
||||
key: 'resource_key',
|
||||
width: 200,
|
||||
render: (row: any) => {
|
||||
const resourceInfo = getResourceInfo(row);
|
||||
return h('div', { class: 'space-y-1' }, [
|
||||
// 第一行:标题(单行,省略号)
|
||||
h('div', {
|
||||
class: 'font-medium text-sm truncate max-w-[200px]',
|
||||
style: { maxWidth: '200px' },
|
||||
title: resourceInfo.title // 鼠标hover显示完整标题
|
||||
}, resourceInfo.title),
|
||||
// 第二行:详情(单行,省略号)
|
||||
h('div', {
|
||||
class: 'text-xs text-gray-500 dark:text-gray-400 truncate max-w-[200px]',
|
||||
style: { maxWidth: '200px' },
|
||||
title: resourceInfo.description // 鼠标hover显示完整描述
|
||||
}, resourceInfo.description),
|
||||
// 第三行:分类图片和链接数
|
||||
h('div', { class: 'flex items-center gap-1' }, [
|
||||
h('i', {
|
||||
class: `fas fa-${getCategoryIcon(resourceInfo.category)} text-blue-500 text-xs`,
|
||||
// 鼠标hover显示第一个资源的链接地址
|
||||
title: resourceInfo.resources.length > 0 ? `链接地址: ${resourceInfo.resources[0].save_url || resourceInfo.resources[0].url}` : `资源链接地址: ${row.resource_key}`
|
||||
}),
|
||||
h('span', { class: 'text-xs text-gray-400' }, `链接数: ${resourceInfo.resources.length}`)
|
||||
])
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '申述人信息',
|
||||
key: 'claimant_info',
|
||||
width: 180,
|
||||
render: (row: any) => {
|
||||
return h('n-tag', {
|
||||
type: 'info',
|
||||
size: 'small',
|
||||
class: 'truncate max-w-xs'
|
||||
}, { default: () => row.resource_key })
|
||||
return h('div', { class: 'space-y-1' }, [
|
||||
// 第一行:姓名和身份
|
||||
h('div', { class: 'font-medium text-sm' }, [
|
||||
h('i', { class: 'fas fa-user text-green-500 mr-1 text-xs' }),
|
||||
row.claimant_name || '未知'
|
||||
]),
|
||||
h('div', {
|
||||
class: 'text-xs text-blue-600 dark:text-blue-400 truncate max-w-[180px]',
|
||||
title: getIdentityLabel(row.identity)
|
||||
}, getIdentityLabel(row.identity)),
|
||||
// 第二行:联系方式
|
||||
h('div', {
|
||||
class: 'text-xs text-gray-500 dark:text-gray-400 truncate max-w-[180px]',
|
||||
title: row.contact_info
|
||||
}, [
|
||||
h('i', { class: 'fas fa-phone text-purple-500 mr-1' }),
|
||||
row.contact_info || '未提供'
|
||||
]),
|
||||
// 第三行:证明类型
|
||||
h('div', {
|
||||
class: 'text-xs text-orange-600 dark:text-orange-400 truncate max-w-[180px]',
|
||||
title: getProofTypeLabel(row.proof_type)
|
||||
}, [
|
||||
h('i', { class: 'fas fa-certificate text-orange-500 mr-1' }),
|
||||
getProofTypeLabel(row.proof_type)
|
||||
])
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '申述人身份',
|
||||
key: 'identity',
|
||||
width: 120,
|
||||
title: '申述详情',
|
||||
key: 'claim_details',
|
||||
width: 280,
|
||||
render: (row: any) => {
|
||||
return h('span', null, getIdentityLabel(row.identity))
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '证明类型',
|
||||
key: 'proof_type',
|
||||
width: 140,
|
||||
render: (row: any) => {
|
||||
return h('span', null, getProofTypeLabel(row.proof_type))
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '申述人姓名',
|
||||
key: 'claimant_name',
|
||||
width: 120,
|
||||
render: (row: any) => {
|
||||
return h('span', null, row.claimant_name)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '联系方式',
|
||||
key: 'contact_info',
|
||||
width: 150,
|
||||
render: (row: any) => {
|
||||
return h('span', null, row.contact_info)
|
||||
return h('div', { class: 'space-y-1' }, [
|
||||
// 第一行:申述理由和提交时间
|
||||
h('div', { class: 'space-y-1' }, [
|
||||
h('div', { class: 'text-xs text-gray-500 dark:text-gray-400' }, '申述理由:'),
|
||||
h('div', {
|
||||
class: 'text-sm text-gray-700 dark:text-gray-300 line-clamp-2 max-h-10',
|
||||
title: row.reason
|
||||
}, row.reason || '无'),
|
||||
h('div', { class: 'text-xs text-gray-400' }, [
|
||||
h('i', { class: 'fas fa-clock mr-1' }),
|
||||
`提交时间: ${formatDateTime(row.created_at)}`
|
||||
])
|
||||
]),
|
||||
// 第二行:证明文件
|
||||
row.proof_files ?
|
||||
h('div', { class: 'space-y-1' }, [
|
||||
h('div', { class: 'text-xs text-gray-500 dark:text-gray-400' }, '证明文件:'),
|
||||
...getProofFiles(row.proof_files).slice(0, 2).map((file, index) =>
|
||||
h('div', {
|
||||
class: 'text-xs text-blue-600 dark:text-blue-400 truncate max-w-[280px] cursor-pointer hover:text-blue-500 hover:underline',
|
||||
title: `点击下载: ${file}`,
|
||||
onClick: () => downloadFile(file)
|
||||
}, [
|
||||
h('i', { class: 'fas fa-download text-blue-500 mr-1' }),
|
||||
getFileName(file)
|
||||
])
|
||||
),
|
||||
getProofFiles(row.proof_files).length > 2 ?
|
||||
h('div', { class: 'text-xs text-gray-400' }, `还有 ${getProofFiles(row.proof_files).length - 2} 个文件...`) : null
|
||||
]) :
|
||||
h('div', { class: 'text-xs text-gray-400' }, '无证明文件'),
|
||||
// 第三行:处理备注(如果有)
|
||||
row.note ?
|
||||
h('div', { class: 'space-y-1' }, [
|
||||
h('div', { class: 'text-xs text-gray-500 dark:text-gray-400' }, '处理备注:'),
|
||||
h('div', {
|
||||
class: 'text-xs text-yellow-600 dark:text-yellow-400 truncate max-w-[280px]',
|
||||
title: row.note
|
||||
}, [
|
||||
h('i', { class: 'fas fa-sticky-note text-yellow-500 mr-1' }),
|
||||
row.note.length > 30 ? `${row.note.slice(0, 30)}...` : row.note
|
||||
])
|
||||
]) : null
|
||||
].filter(Boolean))
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 120,
|
||||
width: 100,
|
||||
render: (row: any) => {
|
||||
const type = getStatusType(row.status)
|
||||
return h('n-tag', {
|
||||
type: type,
|
||||
size: 'small',
|
||||
bordered: false
|
||||
}, { default: () => getStatusLabel(row.status) })
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '提交时间',
|
||||
key: 'created_at',
|
||||
width: 180,
|
||||
render: (row: any) => {
|
||||
return h('span', null, formatDateTime(row.created_at))
|
||||
return h('div', { class: 'space-y-1' }, [
|
||||
h('n-tag', {
|
||||
type: type,
|
||||
size: 'small',
|
||||
bordered: false
|
||||
}, { default: () => getStatusLabel(row.status) }),
|
||||
// 显示处理时间(如果已处理)
|
||||
(row.status !== 'pending' && row.updated_at) ?
|
||||
h('div', {
|
||||
class: 'text-xs text-gray-400',
|
||||
title: `处理时间: ${formatDateTime(row.updated_at)}`
|
||||
}, `更新: ${new Date(row.updated_at).toLocaleDateString()}`) : null
|
||||
].filter(Boolean))
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 180,
|
||||
width: 160,
|
||||
render: (row: any) => {
|
||||
const buttons = [
|
||||
h('button', {
|
||||
class: 'px-2 py-1 text-xs bg-blue-100 hover:bg-blue-200 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400 rounded transition-colors mr-1',
|
||||
class: 'px-2 py-1 text-xs bg-blue-100 hover:bg-blue-200 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400 rounded transition-colors mb-1 w-full',
|
||||
onClick: () => viewClaim(row)
|
||||
}, [
|
||||
h('i', { class: 'fas fa-eye mr-1 text-xs' }),
|
||||
'查看'
|
||||
'查看详情'
|
||||
])
|
||||
]
|
||||
|
||||
if (row.status === 'pending') {
|
||||
buttons.push(
|
||||
h('button', {
|
||||
class: 'px-2 py-1 text-xs bg-green-100 hover:bg-green-200 text-green-700 dark:bg-green-900/20 dark:text-green-400 rounded transition-colors mr-1',
|
||||
class: 'px-2 py-1 text-xs bg-green-100 hover:bg-green-200 text-green-700 dark:bg-green-900/20 dark:text-green-400 rounded transition-colors mb-1 w-full',
|
||||
onClick: () => updateClaimStatus(row, 'approved')
|
||||
}, [
|
||||
h('i', { class: 'fas fa-check mr-1 text-xs' }),
|
||||
'批准'
|
||||
]),
|
||||
h('button', {
|
||||
class: 'px-2 py-1 text-xs bg-red-100 hover:bg-red-200 text-red-700 dark:bg-red-900/20 dark:text-red-400 rounded transition-colors',
|
||||
class: 'px-2 py-1 text-xs bg-red-100 hover:bg-red-200 text-red-700 dark:bg-red-900/20 dark:text-red-400 rounded transition-colors w-full',
|
||||
onClick: () => updateClaimStatus(row, 'rejected')
|
||||
}, [
|
||||
h('i', { class: 'fas fa-times mr-1 text-xs' }),
|
||||
@@ -308,7 +399,7 @@ const columns = [
|
||||
)
|
||||
}
|
||||
|
||||
return h('div', { class: 'flex items-center gap-1' }, buttons)
|
||||
return h('div', { class: 'flex flex-col gap-1' }, buttons)
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -338,8 +429,18 @@ const fetchClaims = async () => {
|
||||
if (filters.value.resourceKey) params.resource_key = filters.value.resourceKey
|
||||
|
||||
const response = await resourceApi.getCopyrightClaims(params)
|
||||
claims.value = response.items || []
|
||||
pagination.value.total = response.total || 0
|
||||
console.log(response)
|
||||
|
||||
// 检查响应格式并处理
|
||||
if (response && response.data && response.data.list !== undefined) {
|
||||
// 如果后端返回了分页格式,使用正确的字段
|
||||
claims.value = response.data.list || []
|
||||
pagination.value.total = response.data.total || 0
|
||||
} else {
|
||||
// 如果是其他格式,尝试直接使用响应
|
||||
claims.value = response || []
|
||||
pagination.value.total = response.length || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取版权申述列表失败:', error)
|
||||
// 显示错误提示
|
||||
@@ -503,6 +604,220 @@ const formatDateTime = (dateString: string) => {
|
||||
return date.toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
// 获取分类图标
|
||||
const getCategoryIcon = (category: string) => {
|
||||
if (!category) return 'folder';
|
||||
|
||||
// 根据分类名称返回对应的图标
|
||||
const categoryMap: Record<string, string> = {
|
||||
'文档': 'file-alt',
|
||||
'文档资料': 'file-alt',
|
||||
'压缩包': 'file-archive',
|
||||
'图片': 'images',
|
||||
'视频': 'film',
|
||||
'音乐': 'music',
|
||||
'电子书': 'book',
|
||||
'软件': 'cogs',
|
||||
'应用': 'mobile-alt',
|
||||
'游戏': 'gamepad',
|
||||
'资料': 'folder',
|
||||
'其他': 'file',
|
||||
'folder': 'folder',
|
||||
'file': 'file'
|
||||
};
|
||||
|
||||
return categoryMap[category] || 'folder';
|
||||
}
|
||||
|
||||
// 获取资源信息显示
|
||||
const getResourceInfo = (row: any) => {
|
||||
// 从后端返回的资源列表中获取信息
|
||||
const resources = row.resources || [];
|
||||
|
||||
if (resources.length > 0) {
|
||||
// 如果有多个资源,可以选择第一个或合并信息
|
||||
const resource = resources[0];
|
||||
return {
|
||||
title: resource.title || `资源: ${row.resource_key}`,
|
||||
description: resource.description || `资源详情: ${row.resource_key}`,
|
||||
category: resource.category || 'folder',
|
||||
resources: resources // 返回所有资源用于显示链接数量等
|
||||
}
|
||||
} else {
|
||||
// 如果没有关联资源,使用默认值
|
||||
return {
|
||||
title: `资源: ${row.resource_key}`,
|
||||
description: `资源详情: ${row.resource_key}`,
|
||||
category: 'folder',
|
||||
resources: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 解析证明文件字符串
|
||||
const getProofFiles = (proofFiles: string) => {
|
||||
if (!proofFiles) return []
|
||||
|
||||
console.log('原始证明文件数据:', proofFiles)
|
||||
|
||||
try {
|
||||
// 尝试解析为JSON格式
|
||||
const parsed = JSON.parse(proofFiles)
|
||||
console.log('JSON解析结果:', parsed)
|
||||
|
||||
if (Array.isArray(parsed)) {
|
||||
// 处理对象数组格式:[{id: "xxx", name: "文件名.pdf", status: "pending"}]
|
||||
const fileObjects = parsed.filter(item => item && typeof item === 'object')
|
||||
if (fileObjects.length > 0) {
|
||||
// 返回原始对象,包含完整信息
|
||||
console.log('解析出文件对象数组:', fileObjects)
|
||||
return fileObjects
|
||||
}
|
||||
|
||||
// 如果不是对象数组,尝试作为字符串数组处理
|
||||
const files = parsed.filter(file => file && typeof file === 'string' && file.trim()).map(file => file.trim())
|
||||
if (files.length > 0) {
|
||||
console.log('解析出的文件字符串数组:', files)
|
||||
return files
|
||||
}
|
||||
} else if (typeof parsed === 'object' && parsed.url) {
|
||||
console.log('解析出的单个文件:', parsed.url)
|
||||
return [parsed.url]
|
||||
} else if (typeof parsed === 'object' && parsed.files) {
|
||||
// 处理 {files: ["url1", "url2"]} 格式
|
||||
if (Array.isArray(parsed.files)) {
|
||||
const files = parsed.files.filter(file => file && file.trim()).map(file => file.trim())
|
||||
console.log('解析出的files数组:', files)
|
||||
return files
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('JSON解析失败,尝试分隔符解析:', e.message)
|
||||
// 如果不是JSON格式,按分隔符解析
|
||||
// 假设文件URL以逗号、分号或换行符分隔
|
||||
const files = proofFiles.split(/[,;\n\r]+/).filter(file => file.trim()).map(file => file.trim())
|
||||
console.log('分隔符解析结果:', files)
|
||||
return files
|
||||
}
|
||||
|
||||
console.log('未解析出任何文件')
|
||||
return []
|
||||
}
|
||||
|
||||
// 获取文件名
|
||||
const getFileName = (fileInfo: any) => {
|
||||
if (!fileInfo) return '未知文件'
|
||||
|
||||
// 如果是对象,优先使用name字段
|
||||
if (typeof fileInfo === 'object') {
|
||||
return fileInfo.name || fileInfo.id || '未知文件'
|
||||
}
|
||||
|
||||
// 如果是字符串,从URL中提取文件名
|
||||
const fileName = fileInfo.split('/').pop() || fileInfo.split('\\').pop() || fileInfo
|
||||
|
||||
// 如果URL太长,截断显示
|
||||
return fileName.length > 50 ? fileName.substring(0, 47) + '...' : fileName
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
const downloadFile = async (fileInfo: any) => {
|
||||
console.log('尝试下载文件:', fileInfo)
|
||||
|
||||
if (!fileInfo) {
|
||||
console.error('文件信息为空')
|
||||
if (process.client) {
|
||||
notification.warning({
|
||||
content: '文件信息无效',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
let downloadUrl = ''
|
||||
let fileName = ''
|
||||
|
||||
// 处理文件对象格式:{id: "xxx", name: "文件名.pdf", status: "pending"}
|
||||
if (typeof fileInfo === 'object' && fileInfo.id) {
|
||||
fileName = fileInfo.name || fileInfo.id
|
||||
// 构建下载API URL,假设有 /api/files/{id} 端点
|
||||
downloadUrl = `/api/files/${fileInfo.id}`
|
||||
console.log('文件对象下载:', { id: fileInfo.id, name: fileName, url: downloadUrl })
|
||||
}
|
||||
// 处理字符串格式(直接是URL)
|
||||
else if (typeof fileInfo === 'string') {
|
||||
downloadUrl = fileInfo
|
||||
fileName = getFileName(fileInfo)
|
||||
|
||||
// 检查是否是文件名(不包含http://或https://或/开头)
|
||||
if (!fileInfo.match(/^https?:\/\//) && !fileInfo.startsWith('/')) {
|
||||
console.log('检测到纯文件名,需要通过API下载:', fileName)
|
||||
|
||||
if (process.client) {
|
||||
notification.info({
|
||||
content: `文件 "${fileName}" 需要通过API下载,功能开发中...`,
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 处理相对路径URL
|
||||
if (fileInfo.startsWith('/uploads/')) {
|
||||
downloadUrl = `${window.location.origin}${fileInfo}`
|
||||
console.log('处理本地文件URL:', downloadUrl)
|
||||
}
|
||||
}
|
||||
|
||||
if (!downloadUrl) {
|
||||
console.error('无法确定下载URL')
|
||||
if (process.client) {
|
||||
notification.warning({
|
||||
content: '无法确定下载地址',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 创建下载链接
|
||||
const link = document.createElement('a')
|
||||
link.href = downloadUrl
|
||||
link.target = '_blank' // 在新标签页打开,避免跨域问题
|
||||
|
||||
// 设置下载文件名
|
||||
link.download = fileName.includes('.') ? fileName : fileName + '.file'
|
||||
|
||||
console.log('下载参数:', {
|
||||
originalInfo: fileInfo,
|
||||
downloadUrl: downloadUrl,
|
||||
fileName: fileName
|
||||
})
|
||||
|
||||
// 添加到页面并触发点击
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
|
||||
if (process.client) {
|
||||
notification.success({
|
||||
content: `开始下载: ${fileName}`,
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('下载文件失败:', error)
|
||||
if (process.client) {
|
||||
notification.error({
|
||||
content: `下载失败: ${error.message}`,
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
onMounted(() => {
|
||||
fetchClaims()
|
||||
|
||||
Reference in New Issue
Block a user