运营后台前端:权限管理(RBAC)+ 系统配置 + 比价记录店/商品搜索 (#36)
一、权限管理(原「管理员」页,重命名为「权限管理」) - 重建为 4 视图:账号列表 / 新增·编辑人员 / 角色设置(只读权限矩阵,无权限置灰)/ 角色权限勾选矩阵 - 左侧导航按当前角色的可见页过滤(拉 /me 刷新);角色显示中文名 管理员/运营/财务/技术 - 新增人员自动生成一次性密码;编辑复看已确定登录密码(只读),无留存明文的账号不显示密码 二、系统配置 - 首页数据「保存」改为保存即生效 - 首页轮播种子页加 真实/种子/混播 数据源切换 三、比价记录 - 店/商品拆两列 + 各自搜索框;整格换行不截断;搜索命中黄底高亮 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: zzhyyyyy <2685922758@qq.com> Reviewed-on: #36 Co-authored-by: zhuzihao <zhuzihao@wonderable.ai> Co-committed-by: zhuzihao <zhuzihao@wonderable.ai>
This commit was merged in pull request #36.
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
InputNumber,
|
||||
Modal,
|
||||
Popconfirm,
|
||||
Segmented,
|
||||
Space,
|
||||
Spin,
|
||||
Switch,
|
||||
@@ -55,6 +56,8 @@ export default function HomeMarqueeSeeds() {
|
||||
const [seeds, setSeeds] = useState<Seed[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<number[]>([]); // 批量操作选中行
|
||||
const [mode, setMode] = useState<string>('mixed'); // 轮播数据源 mixed/real/seed
|
||||
const [modeSaving, setModeSaving] = useState(false);
|
||||
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [editing, setEditing] = useState<Seed | null>(null);
|
||||
@@ -80,8 +83,29 @@ export default function HomeMarqueeSeeds() {
|
||||
};
|
||||
useEffect(() => {
|
||||
load();
|
||||
// 轮播数据源模式(mixed/real/seed):单独拉,与种子列表解耦
|
||||
api
|
||||
.get<{ mode: string }>('/admin/api/marquee-seeds/mode')
|
||||
.then(({ data }) => setMode(data.mode))
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
// 切换数据源:乐观更新 + 失败回滚
|
||||
const changeMode = async (m: string) => {
|
||||
const prev = mode;
|
||||
setMode(m);
|
||||
setModeSaving(true);
|
||||
try {
|
||||
await api.patch('/admin/api/marquee-seeds/mode', { mode: m });
|
||||
message.success('已切换,客户端下次进首页生效');
|
||||
} catch (e) {
|
||||
setMode(prev);
|
||||
message.error(errMsg(e));
|
||||
} finally {
|
||||
setModeSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
// ===== 单条 新增 / 编辑 =====
|
||||
const openAdd = () => {
|
||||
setEditing(null);
|
||||
@@ -260,6 +284,22 @@ export default function HomeMarqueeSeeds() {
|
||||
首页「用户xxx 比价后节省xx元」轮播的兜底假数据。真实比价记录不足时随机抽取这些补齐;停用的不参与混播。
|
||||
用户名留空则每次展示随机合成;金额为区间则每次随机取值。
|
||||
</p>
|
||||
<Space style={{ marginBottom: 12 }} align="center" wrap>
|
||||
<span style={{ fontSize: 13 }}>数据源</span>
|
||||
<Segmented
|
||||
value={mode}
|
||||
disabled={modeSaving}
|
||||
onChange={(v) => changeMode(v as string)}
|
||||
options={[
|
||||
{ label: '混播(默认)', value: 'mixed' },
|
||||
{ label: '只真实', value: 'real' },
|
||||
{ label: '只种子', value: 'seed' },
|
||||
]}
|
||||
/>
|
||||
<span style={{ color: '#999', fontSize: 12 }}>
|
||||
混播=真实优先+种子补位;只真实=不掺假数据(真实不足则少显示);只种子=只用下方种子/合成(演示用)
|
||||
</span>
|
||||
</Space>
|
||||
<Space style={{ marginBottom: 12 }} wrap>
|
||||
<Button type="primary" size="small" onClick={openAdd}>
|
||||
新增种子
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
Checkbox,
|
||||
Col,
|
||||
InputNumber,
|
||||
Popconfirm,
|
||||
Radio,
|
||||
Row,
|
||||
Select,
|
||||
@@ -630,16 +629,14 @@ export default function HomeStatsConfig() {
|
||||
{!loading && (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<Space>
|
||||
<Button type="primary" loading={saving != null} onClick={() => saveAll(false)}>
|
||||
保存
|
||||
{/* 保存即生效:PATCH 带 apply_now,不再等各自「更新时间」的 tick(原「改了 app 不变」的主因)。
|
||||
自增长模式下保存会顺带推进一档。改小数字若没生效,记得勾对应卡片的「允许数字下降」。 */}
|
||||
<Button type="primary" loading={saving != null} onClick={() => saveAll(true)}>
|
||||
保存(立即生效)
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title="立即更新会马上把三项都推进一次(自增长各走一档),不等更新时间。确定?"
|
||||
onConfirm={() => saveAll(true)}
|
||||
>
|
||||
<Button loading={saving != null}>立即更新</Button>
|
||||
</Popconfirm>
|
||||
<span style={{ color: '#999', fontSize: 12 }}>三项配置共用,一次保存全部生效</span>
|
||||
<span style={{ color: '#999', fontSize: 12 }}>
|
||||
三项共用,一次保存全部立即生效,客户端下次进首页即可见
|
||||
</span>
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user