719ed5b65c
- 大小屏融合: 去掉「四期·」前缀 - 角色工作台: 增加三方协作全流程说明 - 全流程演示: 增加一键闭环演示说明 - 文案均不含阶段标号,面向业务演示
225 lines
9.3 KiB
React
225 lines
9.3 KiB
React
import React, { useState } from 'react'
|
||
import {
|
||
Card, Input, Button, Space, Tag, message, Descriptions, Row, Col,
|
||
Segmented, Typography, Result, Divider,
|
||
} from 'antd'
|
||
import {
|
||
ScanOutlined, GlobalOutlined, MobileOutlined, DesktopOutlined,
|
||
ShoppingOutlined, SafetyCertificateOutlined,
|
||
} from '@ant-design/icons'
|
||
import { api } from './api.js'
|
||
|
||
const { Text, Paragraph } = Typography
|
||
|
||
const screenMeta = {
|
||
iptv: { label: 'IPTV 大屏', color: 'blue', icon: <DesktopOutlined /> },
|
||
ott: { label: 'OTT/智能电视', color: 'geekblue', icon: <DesktopOutlined /> },
|
||
app: { label: '手机 APP', color: 'green', icon: <MobileOutlined /> },
|
||
}
|
||
|
||
function ScreenTags({ screens }) {
|
||
if (!screens || screens.length === 0) return <Tag>暂不可用</Tag>
|
||
return (
|
||
<Space>
|
||
{screens.map((s) => (
|
||
<Tag key={s} color={screenMeta[s]?.color} icon={screenMeta[s]?.icon}>
|
||
{screenMeta[s]?.label || s}
|
||
</Tag>
|
||
))}
|
||
</Space>
|
||
)
|
||
}
|
||
|
||
// ============ 跨域解析网关(C.1/C.2)============
|
||
function ResolvePanel() {
|
||
const [maCode, setMaCode] = useState('')
|
||
const [res, setRes] = useState(null)
|
||
const [loading, setLoading] = useState(false)
|
||
|
||
async function doResolve() {
|
||
if (!maCode) return message.warning('请输入 MA 码(支持集级子标识 #E03)')
|
||
setLoading(true)
|
||
const r = await api.resolve(maCode.trim())
|
||
setLoading(false)
|
||
if (r.ok) setRes(r.data.data)
|
||
else { setRes(null); message.error(r.data.message || '解析失败') }
|
||
}
|
||
|
||
const p = res?.parsed
|
||
return (
|
||
<Card size="small" title={<Space><GlobalOutlined />MA 跨域解析网关 · 同一码三屏统一解析</Space>}>
|
||
<Space.Compact style={{ width: '100%', maxWidth: 640 }}>
|
||
<Input placeholder="如 MA.156.8531.6101/WD/20260000021 或 ...#E03"
|
||
value={maCode} onChange={(e) => setMaCode(e.target.value)} onPressEnter={doResolve} />
|
||
<Button type="primary" loading={loading} onClick={doResolve}>解析</Button>
|
||
</Space.Compact>
|
||
|
||
{res && (
|
||
<div style={{ marginTop: 16 }}>
|
||
<Descriptions bordered size="small" column={2}>
|
||
<Descriptions.Item label="解析结果">
|
||
{res.resolved ? <Tag color="green">解析成功</Tag> : <Tag color="red">未解析/未登记</Tag>}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="流通状态">
|
||
{res.in_circulation ? <Tag color="blue">流通中</Tag> : <Tag color="orange">{res.status || '不可用'}</Tag>}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="作品">{res.title || '-'}</Descriptions.Item>
|
||
<Descriptions.Item label="发证主体">{res.issuer || '-'}</Descriptions.Item>
|
||
<Descriptions.Item label="跨屏可用" span={2}><ScreenTags screens={res.screens} /></Descriptions.Item>
|
||
<Descriptions.Item label="结构解析" span={2}>
|
||
{p?.valid ? (
|
||
<Space wrap>
|
||
<Tag>国家码 {p.country_code}</Tag>
|
||
<Tag>行业 {p.industry_node}</Tag>
|
||
<Tag>机构 {p.org_node}</Tag>
|
||
<Tag color="purple">类目 {p.category}</Tag>
|
||
<Tag>{p.year} 年</Tag>
|
||
<Tag>序列 {p.sequence}</Tag>
|
||
{p.episode > 0 && <Tag color="magenta">第 {p.episode} 集</Tag>}
|
||
</Space>
|
||
) : <Tag color="red">结构非法</Tag>}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="说明" span={2}><Text type="secondary">{res.message}</Text></Descriptions.Item>
|
||
</Descriptions>
|
||
</div>
|
||
)}
|
||
</Card>
|
||
)
|
||
}
|
||
|
||
// ============ 扫码验真(B.2)============
|
||
function ScanVerifyPanel() {
|
||
const [maCode, setMaCode] = useState('')
|
||
const [res, setRes] = useState(null)
|
||
const [loading, setLoading] = useState(false)
|
||
|
||
async function doScan() {
|
||
if (!maCode) return message.warning('请输入/扫描 MA 码')
|
||
setLoading(true)
|
||
const r = await api.scanVerify(maCode.trim())
|
||
setLoading(false)
|
||
if (r.ok) setRes(r.data.data)
|
||
else { setRes(null); message.error(r.data.message || '验真失败') }
|
||
}
|
||
|
||
let status = 'info', title = '请扫码验真'
|
||
if (res) {
|
||
if (res.authentic && res.compliant) { status = 'success'; title = '正版内容 · 合规流通' }
|
||
else if (res.authentic && !res.compliant) { status = 'warning'; title = '真码 · 但不合规(已下架/未流通)' }
|
||
else { status = 'error'; title = '验真失败 · 疑似盗版/伪造' }
|
||
}
|
||
|
||
return (
|
||
<Card size="small" title={<Space><ScanOutlined />用户扫码验真 · 防盗版</Space>}>
|
||
<Space.Compact style={{ width: '100%', maxWidth: 640 }}>
|
||
<Input placeholder="模拟扫码:粘贴 MA 码" value={maCode}
|
||
onChange={(e) => setMaCode(e.target.value)} onPressEnter={doScan} />
|
||
<Button type="primary" icon={<ScanOutlined />} loading={loading} onClick={doScan}>扫码验真</Button>
|
||
</Space.Compact>
|
||
|
||
{res && (
|
||
<Result style={{ paddingTop: 16, paddingBottom: 8 }}
|
||
status={status} title={title}
|
||
subTitle={
|
||
<Space direction="vertical">
|
||
<Space>
|
||
<Tag color={res.authentic ? 'green' : 'red'} icon={<SafetyCertificateOutlined />}>
|
||
{res.authentic ? '真码' : '假码/未登记'}
|
||
</Tag>
|
||
<Tag color={res.compliant ? 'blue' : 'orange'}>{res.compliant ? '合规流通' : '不合规'}</Tag>
|
||
{res.title && <Text>《{res.title}》</Text>}
|
||
</Space>
|
||
<ScreenTags screens={res.screens} />
|
||
<Text type="secondary">{res.message}</Text>
|
||
</Space>
|
||
} />
|
||
)}
|
||
</Card>
|
||
)
|
||
}
|
||
|
||
// ============ 跨屏权益通兑(D.1)============
|
||
function RightsPanel() {
|
||
const [maCode, setMaCode] = useState('')
|
||
const [userHash, setUserHash] = useState('user-demo-001')
|
||
const [buyScreen, setBuyScreen] = useState('iptv')
|
||
const [verifyScreen, setVerifyScreen] = useState('app')
|
||
const [buyRes, setBuyRes] = useState(null)
|
||
const [verifyRes, setVerifyRes] = useState(null)
|
||
|
||
async function doBuy() {
|
||
if (!maCode) return message.warning('请输入 MA 码')
|
||
const r = await api.purchase(maCode.trim(), userHash, buyScreen)
|
||
if (r.ok) { setBuyRes(r.data.data); message.success(`已在「${screenMeta[buyScreen].label}」购买`) }
|
||
else message.error(r.data.message || '购买失败')
|
||
}
|
||
async function doVerify() {
|
||
if (!maCode) return message.warning('请输入 MA 码')
|
||
const r = await api.verifyRights(maCode.trim(), userHash, verifyScreen)
|
||
if (r.ok) setVerifyRes(r.data.data)
|
||
else message.error(r.data.message || '核验失败')
|
||
}
|
||
|
||
const opts = Object.entries(screenMeta).map(([v, m]) => ({ label: m.label, value: v }))
|
||
|
||
return (
|
||
<Card size="small" title={<Space><ShoppingOutlined />跨屏权益通兑 · 一次购买全屏通看</Space>}>
|
||
<Space direction="vertical" style={{ width: '100%' }}>
|
||
<Space wrap>
|
||
<Input addonBefore="MA 码" style={{ width: 380 }} value={maCode}
|
||
onChange={(e) => setMaCode(e.target.value)} placeholder="已发布的 MA 码" />
|
||
<Input addonBefore="用户" style={{ width: 220 }} value={userHash}
|
||
onChange={(e) => setUserHash(e.target.value)} />
|
||
</Space>
|
||
|
||
<Row gutter={16}>
|
||
<Col span={12}>
|
||
<Card size="small" title="① 购买(任一屏)" type="inner">
|
||
<Space direction="vertical" style={{ width: '100%' }}>
|
||
<Segmented value={buyScreen} onChange={setBuyScreen} options={opts} />
|
||
<Button type="primary" icon={<ShoppingOutlined />} onClick={doBuy}>购买</Button>
|
||
{buyRes && (
|
||
<Text type="success">
|
||
已购买:{screenMeta[buyRes.screen]?.label}({new Date(buyRes.purchased_at).toLocaleString()})
|
||
</Text>
|
||
)}
|
||
</Space>
|
||
</Card>
|
||
</Col>
|
||
<Col span={12}>
|
||
<Card size="small" title="② 换一屏核验权益" type="inner">
|
||
<Space direction="vertical" style={{ width: '100%' }}>
|
||
<Segmented value={verifyScreen} onChange={setVerifyScreen} options={opts} />
|
||
<Button icon={<SafetyCertificateOutlined />} onClick={doVerify}>核验权益</Button>
|
||
{verifyRes && (
|
||
verifyRes.entitled
|
||
? <Tag color="green" style={{ whiteSpace: 'normal' }}>✓ 有权益(通兑):{verifyRes.message}</Tag>
|
||
: <Tag color="red" style={{ whiteSpace: 'normal' }}>✗ 无权益:{verifyRes.message}</Tag>
|
||
)}
|
||
</Space>
|
||
</Card>
|
||
</Col>
|
||
</Row>
|
||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||
演示:在「IPTV 大屏」购买后,切到「手机 APP」核验,应通兑通看且不重复付费(权益归一到整剧 MA 码)。
|
||
</Text>
|
||
</Space>
|
||
</Card>
|
||
)
|
||
}
|
||
|
||
export default function ScreenFusion() {
|
||
return (
|
||
<div>
|
||
<Paragraph type="secondary" style={{ marginBottom: 16 }}>
|
||
大小屏融合:同一 MA 码贯通 IPTV / OTT / 手机 APP,统一解析、扫码验真、一次购买全屏通看。
|
||
</Paragraph>
|
||
<Space direction="vertical" style={{ width: '100%' }} size={16}>
|
||
<ResolvePanel />
|
||
<ScanVerifyPanel />
|
||
<RightsPanel />
|
||
</Space>
|
||
</div>
|
||
)
|
||
}
|