v2.5.1: 邮件详情抽屉去掉 邮箱/Message-ID;添加账号窗口去切换控件,OAuth 按钮 + API Token 表单平铺同窗
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
/** 账号页:添加账号(Cloudflare OAuth / API Token 同窗切换),授权后自动接入并同步域名;搜索、详情、凭据管理 */
|
/** 账号页:添加账号(Cloudflare OAuth / API Token 同窗平铺),授权后自动接入并同步域名;搜索、详情、凭据管理 */
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
Form,
|
Form,
|
||||||
Input,
|
Input,
|
||||||
Modal,
|
Modal,
|
||||||
Radio,
|
|
||||||
Space,
|
Space,
|
||||||
Table,
|
Table,
|
||||||
Tag,
|
Tag,
|
||||||
@@ -123,8 +122,6 @@ export function AccountsPage() {
|
|||||||
|
|
||||||
/** 添加账号:OAuth 与 API Token 两种方式都收在同一个模态窗口里 */
|
/** 添加账号:OAuth 与 API Token 两种方式都收在同一个模态窗口里 */
|
||||||
const [addOpen, setAddOpen] = useState(false);
|
const [addOpen, setAddOpen] = useState(false);
|
||||||
/** 当前选中的授权方式 **/
|
|
||||||
const [addMode, setAddMode] = useState<"oauth" | "token">("oauth");
|
|
||||||
/** API Token 提交的 loading(要防重复点击,一次接入要走好几次 Cloudflare 调用) **/
|
/** API Token 提交的 loading(要防重复点击,一次接入要走好几次 Cloudflare 调用) **/
|
||||||
const [adding, setAdding] = useState(false);
|
const [adding, setAdding] = useState(false);
|
||||||
const [tokenForm] = Form.useForm();
|
const [tokenForm] = Form.useForm();
|
||||||
@@ -270,10 +267,8 @@ export function AccountsPage() {
|
|||||||
|
|
||||||
<AddAccountModal
|
<AddAccountModal
|
||||||
open={addOpen}
|
open={addOpen}
|
||||||
mode={addMode}
|
|
||||||
loading={adding}
|
loading={adding}
|
||||||
form={tokenForm}
|
form={tokenForm}
|
||||||
onModeChange={setAddMode}
|
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
if (adding) return;
|
if (adding) return;
|
||||||
setAddOpen(false);
|
setAddOpen(false);
|
||||||
@@ -299,22 +294,20 @@ export function AccountsPage() {
|
|||||||
/** # 成员变量: 添加账号模态窗口的属性 **/
|
/** # 成员变量: 添加账号模态窗口的属性 **/
|
||||||
interface AddAccountModalProps {
|
interface AddAccountModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
mode: "oauth" | "token";
|
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
form: ReturnType<typeof Form.useForm>[0];
|
form: ReturnType<typeof Form.useForm>[0];
|
||||||
onModeChange: (mode: "oauth" | "token") => void;
|
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onOAuth: () => void;
|
onOAuth: () => void;
|
||||||
onTokenSubmit: () => void;
|
onTokenSubmit: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** # 函数
|
/** # 函数
|
||||||
* 「添加 Cloudflare 账号」模态窗口:Cloudflare OAuth 授权与 API Token 授权在同一个窗口,
|
* 「添加 Cloudflare 账号」模态窗口:两种授权方式平铺展示、无需切换 ——
|
||||||
* 顶部按钮组切换方式,字段随方式变化 —— OAuth 只要一个「前往授权」按钮,
|
* 顶部「Cloudflare OAuth 授权」一键跳转授权,分隔线下方是 API Token 表单
|
||||||
* API Token 填账户名称 + Token 即可(账户 ID 由 Token 在 Cloudflare 侧实际所属账户自动确定)。
|
* (账户名称 + Token,账户 ID 由 Token 在 Cloudflare 侧实际所属账户自动确定)。
|
||||||
*/
|
*/
|
||||||
function AddAccountModal(props: AddAccountModalProps) {
|
function AddAccountModal(props: AddAccountModalProps) {
|
||||||
const { open, mode, loading, form, onModeChange, onCancel, onOAuth, onTokenSubmit } = props;
|
const { open, loading, form, onCancel, onOAuth, onTokenSubmit } = props;
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="添加 Cloudflare 账号"
|
title="添加 Cloudflare 账号"
|
||||||
@@ -326,25 +319,10 @@ function AddAccountModal(props: AddAccountModalProps) {
|
|||||||
footer={null}
|
footer={null}
|
||||||
destroyOnHidden
|
destroyOnHidden
|
||||||
>
|
>
|
||||||
<Radio.Group
|
|
||||||
block
|
|
||||||
value={mode}
|
|
||||||
onChange={(e) => onModeChange(e.target.value as "oauth" | "token")}
|
|
||||||
style={{ marginBottom: 4 }}
|
|
||||||
>
|
|
||||||
<Radio.Button value="oauth" style={{ width: "50%" }}>
|
|
||||||
<SafetyOutlined /> Cloudflare OAuth 授权
|
|
||||||
</Radio.Button>
|
|
||||||
<Radio.Button value="token" style={{ width: "50%" }}>
|
|
||||||
<ApiOutlined /> API Token 授权
|
|
||||||
</Radio.Button>
|
|
||||||
</Radio.Group>
|
|
||||||
<Divider style={{ margin: "16px 0" }} />
|
|
||||||
{mode === "oauth" ? (
|
|
||||||
<Button type="primary" icon={<SafetyOutlined />} onClick={onOAuth} block>
|
<Button type="primary" icon={<SafetyOutlined />} onClick={onOAuth} block>
|
||||||
前往 Cloudflare 授权
|
Cloudflare OAuth 授权
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
<Divider style={{ margin: "16px 0" }} />
|
||||||
<Form form={form} layout="vertical" requiredMark="optional">
|
<Form form={form} layout="vertical" requiredMark="optional">
|
||||||
<Form.Item name="name" label="账户名称" rules={[{ required: true, message: "请输入账户名称" }]}>
|
<Form.Item name="name" label="账户名称" rules={[{ required: true, message: "请输入账户名称" }]}>
|
||||||
<Input placeholder="我的主账号" />
|
<Input placeholder="我的主账号" />
|
||||||
@@ -353,10 +331,9 @@ function AddAccountModal(props: AddAccountModalProps) {
|
|||||||
<Input.Password placeholder="粘贴 Cloudflare API Token" autoComplete="new-password" />
|
<Input.Password placeholder="粘贴 Cloudflare API Token" autoComplete="new-password" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Button type="primary" icon={<ApiOutlined />} onClick={onTokenSubmit} loading={loading} block>
|
<Button type="primary" icon={<ApiOutlined />} onClick={onTokenSubmit} loading={loading} block>
|
||||||
{loading ? "正在校验并接入…" : "用 API Token 接入"}
|
{loading ? "正在校验并接入…" : "API Token 授权"}
|
||||||
</Button>
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -340,10 +340,6 @@ export function InboxPage() {
|
|||||||
<Descriptions.Item label="收件人">
|
<Descriptions.Item label="收件人">
|
||||||
{safeArr(detail.email.to_addresses).join(", ") || "-"}
|
{safeArr(detail.email.to_addresses).join(", ") || "-"}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="邮箱">{detail.email.mailbox_address ?? "(未匹配)"}</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="Message-ID">
|
|
||||||
<span className="mono">{detail.email.message_id ?? "-"}</span>
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="接收 / 过期">
|
<Descriptions.Item label="接收 / 过期">
|
||||||
{new Date(detail.email.received_at).toLocaleString()} →{" "}
|
{new Date(detail.email.received_at).toLocaleString()} →{" "}
|
||||||
{detail.email.retained ? (
|
{detail.email.retained ? (
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "wmailhub",
|
"name": "wmailhub",
|
||||||
"version": "2.5.0",
|
"version": "2.5.1",
|
||||||
"description": "WmailHub - 基于 Cloudflare Email Workers 的轻量邮件接收与转发平台(Console 直连 Email Worker)",
|
"description": "WmailHub - 基于 Cloudflare Email Workers 的轻量邮件接收与转发平台(Console 直连 Email Worker)",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
+17
-18
@@ -84,6 +84,12 @@ await page.locator(".ant-table-row").first().click();
|
|||||||
await page.waitForSelector(".ant-drawer-open", { timeout: 8000 });
|
await page.waitForSelector(".ant-drawer-open", { timeout: 8000 });
|
||||||
const drawerText = (await page.locator(".ant-drawer-title").textContent()) ?? "";
|
const drawerText = (await page.locator(".ant-drawer-title").textContent()) ?? "";
|
||||||
check("邮件详情抽屉打开(标题=邮件主题)", drawerText.trim().length > 0, drawerText.trim());
|
check("邮件详情抽屉打开(标题=邮件主题)", drawerText.trim().length > 0, drawerText.trim());
|
||||||
|
check(
|
||||||
|
"详情抽屉不再展示「邮箱」/「Message-ID」",
|
||||||
|
(await page.locator(".ant-drawer .ant-descriptions-item-label:has-text('邮箱')").count()) === 0 &&
|
||||||
|
(await page.locator(".ant-drawer .ant-descriptions-item-label:has-text('Message-ID')").count()) === 0,
|
||||||
|
);
|
||||||
|
await page.waitForTimeout(500); // 等抽屉滑入动画结束再截图
|
||||||
await page.screenshot({ path: `${SHOT_DIR}/08-email-detail.png` });
|
await page.screenshot({ path: `${SHOT_DIR}/08-email-detail.png` });
|
||||||
await page.keyboard.press("Escape");
|
await page.keyboard.press("Escape");
|
||||||
|
|
||||||
@@ -144,7 +150,7 @@ for (const path of ["/accounts", "/domains", "/mailboxes", "/inboxs"]) {
|
|||||||
if (path === "/mailboxes") await page.screenshot({ path: `${SHOT_DIR}/11-batch-selected.png` });
|
if (path === "/mailboxes") await page.screenshot({ path: `${SHOT_DIR}/11-batch-selected.png` });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6b. 添加账号模态窗口:OAuth / API Token 同窗(Radio 切换),只留必要字段
|
// 6b. 添加账号模态窗口:OAuth 按钮 + API Token 表单同窗平铺,无需切换控件
|
||||||
await page.goto(`${BASE}/accounts`, { waitUntil: "domcontentloaded" });
|
await page.goto(`${BASE}/accounts`, { waitUntil: "domcontentloaded" });
|
||||||
await page.waitForSelector(".ant-table", { timeout: 8000 });
|
await page.waitForSelector(".ant-table", { timeout: 8000 });
|
||||||
check(
|
check(
|
||||||
@@ -153,32 +159,25 @@ check(
|
|||||||
);
|
);
|
||||||
await page.click("button:has-text('添加账号')");
|
await page.click("button:has-text('添加账号')");
|
||||||
await page.waitForSelector(".ant-modal:has-text('添加 Cloudflare 账号')", { timeout: 8000 });
|
await page.waitForSelector(".ant-modal:has-text('添加 Cloudflare 账号')", { timeout: 8000 });
|
||||||
const modeLabels = await page.locator(".ant-modal .ant-radio-button-wrapper").allInnerTexts();
|
|
||||||
check(
|
check(
|
||||||
"添加账号模态窗口含 OAuth / API Token 两种授权方式(同窗按钮组)",
|
"两种授权方式同窗平铺(两个按钮 + 两个字段同时可见),无切换控件",
|
||||||
modeLabels.some((t) => t.includes("OAuth")) && modeLabels.some((t) => t.includes("API Token")),
|
(await page.locator(".ant-modal button:has-text('Cloudflare OAuth 授权')").count()) === 1 &&
|
||||||
modeLabels.join(" | "),
|
(await page.locator(".ant-modal button:has-text('API Token 授权')").count()) === 1 &&
|
||||||
|
(await page.locator(".ant-modal .ant-radio-button-wrapper").count()) === 0 &&
|
||||||
|
(await page.locator(".ant-modal .ant-tabs-tab").count()) === 0 &&
|
||||||
|
(await page.locator(".ant-modal:visible").count()) === 1,
|
||||||
);
|
);
|
||||||
check(
|
check(
|
||||||
"窗口内不再有说明文字(Alert)与账户 ID 字段",
|
"窗口内不再有说明文字(Alert)与账户 ID 字段",
|
||||||
(await page.locator(".ant-modal .ant-alert").count()) === 0 &&
|
(await page.locator(".ant-modal .ant-alert").count()) === 0 &&
|
||||||
(await page.locator(".ant-modal :text('账户 ID')").count()) === 0,
|
(await page.locator(".ant-modal :text('账户 ID')").count()) === 0,
|
||||||
);
|
);
|
||||||
// OAuth 模式:只有「前往 Cloudflare 授权」按钮
|
|
||||||
check(
|
check(
|
||||||
"OAuth 模式默认展示「前往 Cloudflare 授权」按钮",
|
"API Token 表单只含 账户名称 / API Token 两个字段(1 个密码框)",
|
||||||
(await page.locator(".ant-modal button:has-text('前往 Cloudflare 授权')").count()) === 1,
|
(await page.locator(".ant-modal input[type=password]").count()) === 1,
|
||||||
);
|
);
|
||||||
await page.screenshot({ path: `${SHOT_DIR}/12a-add-account-oauth.png` });
|
await page.waitForTimeout(500); // 等开窗动画结束再截图
|
||||||
// 切到 API Token:账户名称 + API Token 两个字段
|
await page.screenshot({ path: `${SHOT_DIR}/12a-add-account.png` });
|
||||||
await page.click(".ant-modal .ant-radio-button-wrapper:has-text('API Token')");
|
|
||||||
await page.waitForSelector(".ant-modal :text('账户名称')", { timeout: 8000 });
|
|
||||||
check(
|
|
||||||
"API Token 方式只含 账户名称 / API Token 两个字段",
|
|
||||||
(await page.locator(".ant-modal input[type=password]").count()) === 1 &&
|
|
||||||
(await page.locator(".ant-modal :text('账户 ID')").count()) === 0,
|
|
||||||
);
|
|
||||||
await page.screenshot({ path: `${SHOT_DIR}/12b-add-account-token.png` });
|
|
||||||
await page.keyboard.press("Escape");
|
await page.keyboard.press("Escape");
|
||||||
await page.waitForTimeout(400);
|
await page.waitForTimeout(400);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user