Files
MAcode/tcs-iptv/web-console/src/GovernancePanel.jsx
T

147 lines
6.9 KiB
React
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.
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({ ccCode }) {
const [sum, setSum] = useState(null)
const [st, setSt] = useState(null)
async function load() {
const s = await api.playbackSummary(ccCode)
setSum(s.data?.data)
const r = await api.settlement(ccCode, '2026-06')
if (r.ok) setSt(r.data.data)
}
useEffect(() => { load() }, [ccCode])
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({ ccCode }) {
const [rep, setRep] = useState(null)
useEffect(() => { api.accountability(ccCode).then((r) => setRep(r.data?.data)) }, [ccCode])
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({ ccCode }) {
const [ev, setEv] = useState(null)
useEffect(() => { api.evidence(ccCode).then((r) => setEv(r.data?.data)) }, [ccCode])
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({ ccCode }) {
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(ccCode, 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(ccCode, 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({ ccCode }) {
return (
<Tabs size="small" items={[
{ key: 'settle', label: '💰 分账', children: <SettlementPanel ccCode={ccCode} /> },
{ key: 'account', label: '⚖️ 追责取证', children: <AccountabilityPanel ccCode={ccCode} /> },
{ key: 'evidence', label: '📜 确权举证', children: <EvidencePanel ccCode={ccCode} /> },
{ key: 'auth', label: '🔑 授权管理', children: <AuthPanel ccCode={ccCode} /> },
]} />
)
}