提交 f29f2487 authored 作者: 王鹏飞's avatar 王鹏飞

refactor(admin): migrate personalized settings

上级 fac449c9
# Settings Personalized Migration Report
## Status
- Migrated the active `/setting/personalized-setting` route to `src/modules/settings/personalized`.
- Preserved the page as a dedicated configuration form; it is not a table/list page, so `AppList` and `query.ts` were not introduced.
- Kept the legacy page because deprecated route metadata still references it.
## Preserved behavior
- Initial load remains `POST /system/config/getSystem` with an empty form-encoded body.
- Save remains `POST /system/config/systemEdit` with the three form values.
- Successful saves show the server message and reload the current settings.
- Non-200 responses show the server message or the fallback `数据获取失败!`; transport failures remain silent.
- The dedicated form retains:
- `老师分成百分比(%)` / `teacher_divide`
- `用户等级有效期(月)` / `user_grade_validity`
- `评论是否需要审核` / `comments_need_approval`, with string values `"1"` and `"0"`
- the original required-field messages, explanatory copy, widths, reset-to-empty action, and submit action
- The loading spinner and submit loading state remain tied to settings reloads.
- The source page contains no operation-permission checks, so none were added.
## Structure
- `api.ts`: form-encoded load/save requests and server notifications.
- `routes.tsx`: private route boundary that preserves `/setting/personalized-setting`.
- `views/PersonalizedSettingsView.tsx`: initial loading and save-then-reload coordination.
- `components/PersonalizedForm.tsx`: private dedicated form layout, validation, and reset behavior.
## Verification
- `pnpm exec oxlint src/modules/settings/personalized src/modules/settings/routes.tsx --deny-warnings`: passed.
- `pnpm exec oxfmt --check src/modules/settings/personalized src/modules/settings/routes.tsx`: passed.
- `pnpm test`: passed, 11 tests.
- `pnpm build`: passed; only the existing formula-editor and large-chunk warnings were emitted.
- `pnpm exec tsc --noEmit`: no diagnostics under `src/modules/settings/personalized` or `src/modules/settings/routes.tsx`; the workspace still has unrelated existing diagnostics.
## Concerns
- Per task direction, no API/query/Hook feature tests were added; this page does not require query or list Hook files.
- `src/routes/routes.jsx` still imports `@/pages/setting/personalized-setting/index`, and `src/config/component.js` still names `setting/personalized-setting/index`. Both are outside this task's allowed scope. Therefore the legacy `src/pages/setting/personalized-setting/index.jsx` was intentionally retained instead of deleting a still-referenced page.
import { message } from "antd";
// @ts-expect-error The legacy workspace does not install the qs declaration package.
import qs from "qs";
import request from "@/utils/request.js";
export interface PersonalizedSettings {
teacher_divide?: string | number;
user_grade_validity?: string | number;
comments_need_approval?: "" | "0" | "1";
}
type ApiBody<T> = {
code?: number;
data?: T;
message?: string;
};
export async function getPersonalizedSettings(): Promise<PersonalizedSettings | false> {
try {
const response = await request.post("/system/config/getSystem", qs.stringify(undefined));
const body = response.data as ApiBody<PersonalizedSettings>;
const text = body?.message ?? "数据获取失败!";
if (body?.code === 200) return body.data ?? {};
message.error(text);
return false;
} catch {
return false;
}
}
export async function savePersonalizedSettings(values: PersonalizedSettings) {
try {
const response = await request.post("/system/config/systemEdit", qs.stringify(values));
const body = response.data as ApiBody<unknown>;
const text = body?.message ?? "数据获取失败!";
if (body?.code === 200) {
message.success(text);
return true;
}
message.error(text);
return false;
} catch {
return false;
}
}
import { Button, Form, Input, Radio, Space, Spin } from "antd";
import { useEffect } from "react";
import type { PersonalizedSettings } from "../api";
interface PersonalizedFormProps {
loading: boolean;
values: PersonalizedSettings;
onSubmit: (values: PersonalizedSettings) => void | Promise<void>;
}
const EMPTY_VALUES: PersonalizedSettings = {
teacher_divide: "",
user_grade_validity: "",
comments_need_approval: "",
};
export default function PersonalizedForm({ loading, values, onSubmit }: PersonalizedFormProps) {
const [form] = Form.useForm<PersonalizedSettings>();
useEffect(() => {
form.setFieldsValue(values);
}, [form, values]);
return (
<Spin spinning={loading}>
<Form form={form} onFinish={onSubmit}>
<Form.Item
label="老师分成百分比(%)"
name="teacher_divide"
rules={[{ required: true, message: "老师分成百分比不能为空" }]}
extra="老师佣金计算(预计总收入),编写书籍售卖总金额+分成比例"
>
<Input autoComplete="off" style={{ width: 260 }} placeholder="请输入老师分成百分比" />
</Form.Item>
<Form.Item
label="用户等级有效期(月)"
name="user_grade_validity"
rules={[{ required: true, message: "请输入用户等级有效期(月)" }]}
extra="用于计算用户等级,根据当前时间之前有效期内的消费金额总和来计算"
>
<Input autoComplete="off" style={{ width: 260 }} placeholder="请输入用户等级有效期" />
</Form.Item>
<Form.Item
name="comments_need_approval"
label="评论是否需要审核"
rules={[{ required: true, message: "请选择评论是否需要审核" }]}
>
<Radio.Group>
<Radio value="1">是</Radio>
<Radio value="0">否</Radio>
</Radio.Group>
</Form.Item>
<Form.Item wrapperCol={{ offset: 2 }}>
<Space>
<Button type="primary" ghost onClick={() => form.setFieldsValue(EMPTY_VALUES)}>
重置
</Button>
<Button type="primary" loading={loading} htmlType="submit">
确定
</Button>
</Space>
</Form.Item>
</Form>
</Spin>
);
}
import { lazy } from "react";
import { Navigate, useRoutes } from "react-router";
const PersonalizedSettingsView = lazy(() => import("./views/PersonalizedSettingsView"));
export default function PersonalizedRoutes() {
return useRoutes([
{ index: true, Component: PersonalizedSettingsView },
{ path: "*", element: <Navigate to="/setting/personalized-setting" /> },
]);
}
import { useCallback, useEffect, useState } from "react";
import {
getPersonalizedSettings,
savePersonalizedSettings,
type PersonalizedSettings,
} from "../api";
import PersonalizedForm from "../components/PersonalizedForm";
export default function PersonalizedSettingsView() {
const [loading, setLoading] = useState(false);
const [values, setValues] = useState<PersonalizedSettings>({});
const load = useCallback(async () => {
setLoading(true);
const result = await getPersonalizedSettings();
if (result !== false) setValues(result);
setLoading(false);
}, []);
useEffect(() => {
void load();
}, [load]);
const handleSubmit = async (nextValues: PersonalizedSettings) => {
const success = await savePersonalizedSettings(nextValues);
if (success) await load();
};
return (
<div style={{ padding: 20 }}>
<PersonalizedForm loading={loading} values={values} onSubmit={handleSubmit} />
</div>
);
}
...@@ -5,7 +5,7 @@ import ModuleBoundary from "@/app/ModuleBoundary"; ...@@ -5,7 +5,7 @@ import ModuleBoundary from "@/app/ModuleBoundary";
const SettingsLayout = lazy(() => import("@/pages/setting/index")); const SettingsLayout = lazy(() => import("@/pages/setting/index"));
const HelpRoutes = lazy(() => import("./help/routes")); const HelpRoutes = lazy(() => import("./help/routes"));
const AppliedRoutes = lazy(() => import("./applied/routes")); const AppliedRoutes = lazy(() => import("./applied/routes"));
const PersonalizedSetting = lazy(() => import("@/pages/setting/personalized-setting/index")); const PersonalizedRoutes = lazy(() => import("./personalized/routes"));
const PointManagement = lazy(() => import("@/pages/setting/point-management/index")); const PointManagement = lazy(() => import("@/pages/setting/point-management/index"));
const AppVersionRoutes = lazy(() => import("./app-version/routes")); const AppVersionRoutes = lazy(() => import("./app-version/routes"));
const CouponRoutes = lazy(() => import("./coupon/routes")); const CouponRoutes = lazy(() => import("./coupon/routes"));
...@@ -20,7 +20,7 @@ export default function SettingsRoutes() { ...@@ -20,7 +20,7 @@ export default function SettingsRoutes() {
{ index: true, element: <Navigate to="/setting/applied" /> }, { index: true, element: <Navigate to="/setting/applied" /> },
{ path: "help/*", Component: HelpRoutes }, { path: "help/*", Component: HelpRoutes },
{ path: "applied/*", Component: AppliedRoutes }, { path: "applied/*", Component: AppliedRoutes },
{ path: "personalized-setting", Component: PersonalizedSetting }, { path: "personalized-setting/*", Component: PersonalizedRoutes },
{ path: "point-management", Component: PointManagement }, { path: "point-management", Component: PointManagement },
{ path: "app-version/*", Component: AppVersionRoutes }, { path: "app-version/*", Component: AppVersionRoutes },
{ path: "coupon/*", Component: CouponRoutes }, { path: "coupon/*", Component: CouponRoutes },
......
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论