增加广播栏
This commit is contained in:
1 parent
85b0e70907
commit
d8786eb28a
21 files changed
+516
-59
No files matched your search
@@ -13,6 +13,11 @@ interface Stats {
|
||||
revenueFen: number;
|
||||
payEnabled: boolean;
|
||||
payEnv: number;
|
||||
payOfferId: string;
|
||||
/** 管理端当前查看的库 */
|
||||
dataEnv: string;
|
||||
/** 线上小程序当前读的库 */
|
||||
miniProgramEnv: string;
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
@@ -45,11 +50,41 @@ export default function DashboardPage() {
|
||||
<Metric label="已退款" value={String(stats.refunded)} />
|
||||
<Metric label="成交金额(元)" value={yuan(stats.revenueFen)} />
|
||||
<Metric
|
||||
label="支付能力"
|
||||
label="支付能力(当前查看的库)"
|
||||
value={stats.payEnabled ? (stats.payEnv === 1 ? '已配置 · 沙箱' : '已配置 · 现网') : '未配置'}
|
||||
/>
|
||||
<Metric label="支付 OfferID" value={stats.payOfferId || '—'} />
|
||||
<Metric
|
||||
label="小程序当前读的库"
|
||||
value={stats.miniProgramEnv === 'develop' ? '开发库' : '生产库'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/*
|
||||
支付参数跟着数据源走:切到开发库用的是沙箱那一套,不会误扣真钱。
|
||||
这里把当前生效的组合摊开,避免「以为在测试、其实在收钱」。
|
||||
*/}
|
||||
<Card title="当前支付配置">
|
||||
<ul className="space-y-1 text-sm text-gray-600">
|
||||
<li>
|
||||
正在查看:<span className="font-medium">{stats.dataEnv === 'develop' ? '开发库' : '生产库'}</span>
|
||||
</li>
|
||||
<li>
|
||||
支付环境:
|
||||
<span className={stats.payEnv === 1 ? 'font-medium text-amber-600' : 'font-medium text-green-600'}>
|
||||
{stats.payEnv === 1 ? '沙箱(不产生真实扣款)' : '现网(真实收款)'}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
OfferID:<span className="font-mono">{stats.payOfferId || '—'}</span>
|
||||
</li>
|
||||
<li className="pt-1 text-xs text-gray-500">
|
||||
小程序读的是「{stats.miniProgramEnv === 'develop' ? '开发库' : '生产库'}」,下单就用那个库对应的这套配置。
|
||||
开发库默认走沙箱;生产库的环境由服务端 WX_PAY_ENV 决定。
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
|
||||
{!stats.payEnabled && (
|
||||
<Card title="支付尚未启用">
|
||||
<p className="text-sm text-gray-600">
|
||||
|
||||
@@ -11,6 +11,20 @@ interface SettingRow {
|
||||
remark: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染成开关(而不是文本框)的配置项。
|
||||
*
|
||||
* 约定:值 '1' = 开,其余(含库里没有这一行)= 关。所以默认就是关,不用先往库里插数据。
|
||||
* key 要和服务端 src/common/site-flags.ts 里的一致。
|
||||
*/
|
||||
const SWITCHES = [
|
||||
{
|
||||
key: 'develop.show_owned_courses',
|
||||
label: '使用开发库时,显示已购买课程',
|
||||
hint: '关(默认):小程序读开发库时,课程不显示「已购」状态,也不返回已购课程列表。开:按实际权益正常显示。生产库不受这个开关影响。',
|
||||
},
|
||||
];
|
||||
|
||||
export default function SettingsPage() {
|
||||
const [rows, setRows] = useState<SettingRow[]>([]);
|
||||
const [draft, setDraft] = useState<Record<string, string>>({});
|
||||
@@ -49,6 +63,28 @@ export default function SettingsPage() {
|
||||
.finally(() => setSavingKey(''));
|
||||
};
|
||||
|
||||
const isOn = (key: string) => (draft[key] ?? '') === '1';
|
||||
|
||||
/** 开关一点即存,不用再点「保存」。 */
|
||||
const toggle = (key: string, label: string) => {
|
||||
const next = isOn(key) ? '0' : '1';
|
||||
setDraft({ ...draft, [key]: next });
|
||||
setSavingKey(key);
|
||||
setError('');
|
||||
setNotice('');
|
||||
api
|
||||
.upsertSetting(key, next, label)
|
||||
.then(() => {
|
||||
setNotice(`已保存:${label} → ${next === '1' ? '开' : '关'}`);
|
||||
load();
|
||||
})
|
||||
.catch((err: { message: string }) => setError(err.message))
|
||||
.finally(() => setSavingKey(''));
|
||||
};
|
||||
|
||||
// 开关项已经在上面单独渲染,别再以文本框的形式出现一次
|
||||
const textRows = rows.filter((r) => !SWITCHES.some((s) => s.key === r.key));
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-lg font-semibold text-gray-900">系统配置</h1>
|
||||
@@ -59,8 +95,45 @@ export default function SettingsPage() {
|
||||
<p className="mb-4 text-xs text-gray-500">
|
||||
小程序首屏显示的标题、我的页面菜单文案、版本号等都在这里。修改后小程序下拉刷新即可生效。
|
||||
</p>
|
||||
|
||||
<div className="mb-5 space-y-4 border-b border-gray-100 pb-5">
|
||||
{SWITCHES.map((s) => {
|
||||
const on = isOn(s.key);
|
||||
return (
|
||||
<div key={s.key} className="flex items-start gap-3">
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={on}
|
||||
disabled={savingKey === s.key}
|
||||
onClick={() => toggle(s.key, s.label)}
|
||||
className={`mt-0.5 h-6 w-11 shrink-0 rounded-full transition-colors ${
|
||||
on ? 'bg-green-600' : 'bg-gray-300'
|
||||
} ${savingKey === s.key ? 'opacity-50' : ''}`}
|
||||
>
|
||||
<span
|
||||
className={`block h-5 w-5 rounded-full bg-white shadow transition-transform ${
|
||||
on ? 'translate-x-5' : 'translate-x-0.5'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
<div className="min-w-0">
|
||||
<div className="text-sm text-gray-700">
|
||||
{s.label}
|
||||
<span className={`ml-2 text-xs ${on ? 'text-green-600' : 'text-gray-400'}`}>
|
||||
{savingKey === s.key ? '保存中…' : on ? '已开启' : '已关闭'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-0.5 text-xs text-gray-500">{s.hint}</div>
|
||||
<div className="mt-0.5 font-mono text-xs text-gray-400">{s.key}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{rows.map((row) => (
|
||||
{textRows.map((row) => (
|
||||
<div key={row.key} className="flex items-center gap-3">
|
||||
<div className="w-56 shrink-0">
|
||||
<div className="text-sm text-gray-700">{row.remark || row.key}</div>
|
||||
@@ -77,10 +150,11 @@ export default function SettingsPage() {
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
{rows.length === 0 && <div className="py-6 text-center text-sm text-gray-400">暂无配置</div>}
|
||||
{textRows.length === 0 && (
|
||||
<div className="py-6 text-center text-sm text-gray-400">暂无配置</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user