Files
MAcode/tcs-iptv/web-console/src/ScreenFusion.jsx
T
selfrelease 719ed5b65c feat(web): 三页统一加顶部说明,去掉「X期」字样
- 大小屏融合: 去掉「四期·」前缀
- 角色工作台: 增加三方协作全流程说明
- 全流程演示: 增加一键闭环演示说明
- 文案均不含阶段标号,面向业务演示
2026-06-14 19:54:17 +08:00

225 lines
9.3 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 } 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>
)
}