feat(phase2-fe): 二期可视化(分账/追责/确权/授权/回传)

- GovernancePanel: 监管片库详情新增'权益与治理'标签(分账/追责取证/确权举证/授权管理)
- 分账面板: 播放聚合统计+CP60/平台34/服务费6分账展示
- 追责面板: 全链路存证Timeline+审播一致/篡改定位结果
- 确权面板: 证据链+谁先锁定谁有权声明
- 授权面板: 登记授权范围(地域/平台/期限)+核验
- 运营商台: 回传播放(含购买)按钮喂分账数据
- 前端build通过, HMR生效
This commit is contained in:
selfrelease
2026-06-14 17:31:49 +08:00
parent 57ea9f122d
commit 73e22f79d2
3 changed files with 173 additions and 1 deletions
@@ -0,0 +1,146 @@
import React, { useState, useEffect } from 'react'
import {
Tabs, Card, Button, Space, Tag, Table, Descriptions, Statistic, Row, Col,
Form, Input, DatePicker, message, Timeline, Typography, Alert, Result,
} from 'antd'
import { api } from './api.js'
const { Text, Paragraph } = Typography
const yuan = (cent) => '¥' + (cent / 100).toFixed(2)
const nodeLabel = {
cp_submit: 'CP送审', csps_review: 'CSPS审核', ma_issue: '发码签发',
transcode: '转码', media_ingest: '媒资入库', cdn_inject: 'CDN注入',
}
// 分账面板
function SettlementPanel({ maCode }) {
const [sum, setSum] = useState(null)
const [st, setSt] = useState(null)
async function load() {
const s = await api.playbackSummary(maCode)
setSum(s.data?.data)
const r = await api.settlement(maCode, '2026-06')
if (r.ok) setSt(r.data.data)
}
useEffect(() => { load() }, [maCode])
if (!sum) return null
return (
<div>
<Row gutter={16} style={{ marginBottom: 12 }}>
<Col span={6}><Card size="small"><Statistic title="总播放" value={sum.total_plays} /></Card></Col>
<Col span={6}><Card size="small"><Statistic title="完播" value={sum.total_complete} /></Card></Col>
<Col span={6}><Card size="small"><Statistic title="总收益" value={yuan(sum.total_revenue_cent || 0)} /></Card></Col>
</Row>
{st ? (
<Card size="small" title="分账结算(依据:链上可信播放数据)">
<Descriptions bordered size="small" column={2}>
<Descriptions.Item label="总收益">{yuan(st.total_revenue_cent)}</Descriptions.Item>
<Descriptions.Item label="结算周期">{st.period}</Descriptions.Item>
<Descriptions.Item label="内容方(CP) 60%"><Text strong>{yuan(st.cp_share_cent)}</Text></Descriptions.Item>
<Descriptions.Item label="平台/运营商 34%">{yuan(st.platform_share_cent)}</Descriptions.Item>
<Descriptions.Item label="运营服务费 6%">{yuan(st.hub_fee_cent)}</Descriptions.Item>
<Descriptions.Item label="数据来源"><Tag color="blue">{st.data_source}</Tag></Descriptions.Item>
</Descriptions>
</Card>
) : <Alert type="info" message="暂无收益数据,可在运营商工作台回传播放/购买事件" />}
</div>
)
}
// 追责取证面板
function AccountabilityPanel({ maCode }) {
const [rep, setRep] = useState(null)
useEffect(() => { api.accountability(maCode).then((r) => setRep(r.data?.data)) }, [maCode])
if (!rep) return null
return (
<div>
{rep.consistent
? <Result status="success" title="审播一致" subTitle={rep.conclusion} style={{ padding: 12 }} />
: <Result status="error" title="检出哈希不一致" subTitle={rep.conclusion} style={{ padding: 12 }} />}
<Card size="small" title={'全链路存证(基准哈希 ' + (rep.baseline_hash || '-') + ''}>
<Timeline items={(rep.trail || []).map((e) => ({
color: rep.first_change && e.node === rep.first_change.node && e.hash_value === rep.first_change.hash_value ? 'red' : 'green',
children: (
<Space direction="vertical" size={0}>
<Space><Tag>{nodeLabel[e.node] || e.node}</Tag><Text strong>{e.operator}</Text></Space>
<Text type="secondary" style={{ fontSize: 12 }}>{e.detail} {e.hash_value ? '· hash=' + e.hash_value : ''}</Text>
</Space>
),
}))} />
</Card>
</div>
)
}
// 确权举证面板
function EvidencePanel({ maCode }) {
const [ev, setEv] = useState(null)
useEffect(() => { api.evidence(maCode).then((r) => setEv(r.data?.data)) }, [maCode])
if (!ev) return null
return (
<Card size="small" title="版权确权证据链">
<Descriptions bordered size="small" column={1}>
<Descriptions.Item label="作品">{ev.title}</Descriptions.Item>
<Descriptions.Item label="MA 码">{ev.ma_code}</Descriptions.Item>
<Descriptions.Item label="内容哈希">{ev.content_hash || '-'}</Descriptions.Item>
<Descriptions.Item label="签发方">{ev.issuer}</Descriptions.Item>
<Descriptions.Item label="链上锚定">{ev.chain_anchor}</Descriptions.Item>
<Descriptions.Item label="最早登记时间">{ev.first_seen_at}</Descriptions.Item>
</Descriptions>
<Alert style={{ marginTop: 12 }} type="success" showIcon message="确权声明" description={ev.statement} />
</Card>
)
}
// 授权管理面板
function AuthPanel({ maCode }) {
const [form] = Form.useForm()
const [checkResult, setCheckResult] = useState(null)
async function grant() {
const v = await form.validateFields()
const regions = v.regions ? v.regions.split(',').map((s) => s.trim()).filter(Boolean) : []
const platforms = v.platforms ? v.platforms.split(',').map((s) => s.trim()).filter(Boolean) : []
const expiry = v.expiry ? v.expiry.toISOString() : ''
const res = await api.authorize(maCode, regions, platforms, expiry)
if (res.ok) message.success('授权已登记')
else message.error(res.data.message)
}
async function check() {
const v = form.getFieldsValue()
const res = await api.authCheck(maCode, v.checkRegion || '', v.checkPlatform || '')
setCheckResult(res.data?.data)
}
return (
<Form form={form} layout="vertical" size="small">
<Card size="small" title="登记授权范围" style={{ marginBottom: 12 }}>
<Form.Item label="授权地域(省码,逗号分隔,空=全国)" name="regions"><Input placeholder="610000,440000" /></Form.Item>
<Form.Item label="授权平台(逗号分隔,空=不限)" name="platforms"><Input placeholder="CT-SX-IPTV,CM-SX-IPTV" /></Form.Item>
<Form.Item label="到期时间(空=长期)" name="expiry"><DatePicker showTime style={{ width: '100%' }} /></Form.Item>
<Button type="primary" onClick={grant}>登记授权</Button>
</Card>
<Card size="small" title="授权核验">
<Space>
<Form.Item name="checkRegion" noStyle><Input placeholder="地域省码" style={{ width: 130 }} /></Form.Item>
<Form.Item name="checkPlatform" noStyle><Input placeholder="平台编码" style={{ width: 150 }} /></Form.Item>
<Button onClick={check}>核验</Button>
</Space>
{checkResult && (
<Alert style={{ marginTop: 12 }} type={checkResult.allowed ? 'success' : 'error'}
message={checkResult.allowed ? '在授权范围内' : '拦截:' + checkResult.reason} />
)}
</Card>
</Form>
)
}
export default function GovernancePanel({ maCode }) {
return (
<Tabs size="small" items={[
{ key: 'settle', label: '💰 分账', children: <SettlementPanel maCode={maCode} /> },
{ key: 'account', label: '⚖️ 追责取证', children: <AccountabilityPanel maCode={maCode} /> },
{ key: 'evidence', label: '📜 确权举证', children: <EvidencePanel maCode={maCode} /> },
{ key: 'auth', label: '🔑 授权管理', children: <AuthPanel maCode={maCode} /> },
]} />
)
}