Merge pull request #6 from perfect-panel/develop

Merge develop into main
This commit is contained in:
web@ppanel 2026-01-28 02:29:40 +08:00 committed by GitHub
commit 2c345d1729
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 147 additions and 11 deletions

View File

@ -19,6 +19,20 @@ This document records all notable changes to ShadCN Admin.
--- ---
## [1.3.7-dev.1](https://github.com/perfect-panel/frontend/compare/v1.3.6...v1.3.7-dev.1) (2026-01-27)
### 🐛 Bug Fixes / 问题修复
* **admin:** refresh user list after balance update ([7e1d574](https://github.com/perfect-panel/frontend/commit/7e1d5746d46dd9bf33b1971f71f809afcdba4bbe))
* **build:** ensure version.lock directory exists + fix merge typo ([052a0a4](https://github.com/perfect-panel/frontend/commit/052a0a420921cffc09c81843d37081d05b4c4717))
* **ci:** avoid void|Promise union for refetch ([6dd743b](https://github.com/perfect-panel/frontend/commit/6dd743bbf4f383249e97549ed0ed0417b6f5609d))
* **i18n:** remove duplicate locale keys ([ed3f6cb](https://github.com/perfect-panel/frontend/commit/ed3f6cb737e0e5b3b57e4149fce3be6e46b0c5eb))
### 🔧 Chores / 其他变更
* **openapi:** regenerate services & patch axios requestType typing ([0ec4f84](https://github.com/perfect-panel/frontend/commit/0ec4f84fa9d4d94048b8219896c3d8fb1d8d2175))
* **release:** Release 1.2.4-dev.2 / 发布版本 1.2.4-dev.2 [skip ci] ([98676fa](https://github.com/perfect-panel/frontend/commit/98676fa27c74eefc9c0fe2d92a549d9e5d7d7fd6))
## [1.3.6](https://github.com/perfect-panel/frontend/compare/v1.3.5...v1.3.6) (2025-12-30) ## [1.3.6](https://github.com/perfect-panel/frontend/compare/v1.3.5...v1.3.6) (2025-12-30)
### 🐛 Bug Fixes / 问题修复 ### 🐛 Bug Fixes / 问题修复
@ -92,13 +106,20 @@ This document records all notable changes to ShadCN Admin.
* Remove the system log dialog component from the system version card ([71cb827](https://github.com/perfect-panel/frontend/commit/71cb827918ee3250f0c9d06d46d876ce6799b8ac)) * Remove the system log dialog component from the system version card ([71cb827](https://github.com/perfect-panel/frontend/commit/71cb827918ee3250f0c9d06d46d876ce6799b8ac))
* Update invite link format in auth forms and sidebar to include hash fragment for routing. ([7a8c010](https://github.com/perfect-panel/frontend/commit/7a8c0102958a859c9e7476810d5c9b822f882692)) * Update invite link format in auth forms and sidebar to include hash fragment for routing. ([7a8c010](https://github.com/perfect-panel/frontend/commit/7a8c0102958a859c9e7476810d5c9b822f882692))
## [1.2.4-dev.2](https://github.com/perfect-panel/frontend/compare/v1.2.4-dev.1...v1.2.4-dev.2) (2026-01-27)
### 🐛 Bug Fixes / 问题修复
* **admin:** refresh user list after balance update ([7e1d574](https://github.com/perfect-panel/frontend/commit/7e1d5746d46dd9bf33b1971f71f809afcdba4bbe))
* **ci:** avoid void|Promise union for refetch ([6dd743b](https://github.com/perfect-panel/frontend/commit/6dd743bbf4f383249e97549ed0ed0417b6f5609d))
### 📚 Documentation / 文档更新 ### 📚 Documentation / 文档更新
* Add one-click installation script for PPanel with Docker support ([912c5c4](https://github.com/perfect-panel/frontend/commit/912c5c4cb63eeb0ecbc33bef6b31bd50d83d6491)) * Add one-click installation script for PPanel with Docker support ([912c5c4](https://github.com/perfect-panel/frontend/commit/912c5c4cb63eeb0ecbc33bef6b31bd50d83d6491))
### 🔧 Chores / 其他变更 ### 🔧 Chores / 其他变更
* **release:** Release 1.2.4-dev.1 / 发布版本 1.2.4-dev.1 [skip ci] ([62d45bb](https://github.com/perfect-panel/frontend/commit/62d45bbac17fab9656c1cce029a379ce8bb757d6)) * **openapi:** regenerate services & patch axios requestType typing ([0ec4f84](https://github.com/perfect-panel/frontend/commit/0ec4f84fa9d4d94048b8219896c3d8fb1d8d2175))
## [1.2.4-dev.1](https://github.com/perfect-panel/frontend/compare/v1.2.3...v1.2.4-dev.1) (2025-12-22) ## [1.2.4-dev.1](https://github.com/perfect-panel/frontend/compare/v1.2.3...v1.2.4-dev.1) (2025-12-22)

View File

@ -73,6 +73,14 @@
"referrerUserId": "Referrer User ID", "referrerUserId": "Referrer User ID",
"remove": "Remove", "remove": "Remove",
"resetLogs": "Reset Logs", "resetLogs": "Reset Logs",
"resetTraffic": "Reset Traffic",
"toggleStatus": "Toggle Status",
"resetSubscriptionToken": "Reset Token",
"resetSubscriptionTokenDescription": "This will reset the subscription token. Old links will become invalid.",
"resetSubscriptionTraffic": "Reset Traffic",
"resetSubscriptionTrafficDescription": "This will reset the subscription traffic counters.",
"toggleSubscriptionStatus": "Toggle Status",
"toggleSubscriptionStatusDescription": "This will toggle the subscription status.",
"resetTime": "Reset Time", "resetTime": "Reset Time",
"resetToken": "Reset Subscription Address", "resetToken": "Reset Subscription Address",
"resetTokenDescription": "This will reset the subscription address and regenerate a new token.", "resetTokenDescription": "This will reset the subscription address and regenerate a new token.",

View File

@ -73,6 +73,14 @@
"referrerUserId": "推荐人用户 ID", "referrerUserId": "推荐人用户 ID",
"remove": "移除", "remove": "移除",
"resetLogs": "重置日志", "resetLogs": "重置日志",
"resetTraffic": "重置流量",
"toggleStatus": "切换状态",
"resetSubscriptionToken": "重置令牌",
"resetSubscriptionTokenDescription": "将重置订阅令牌,旧订阅链接会失效。",
"resetSubscriptionTraffic": "重置流量",
"resetSubscriptionTrafficDescription": "将重置该订阅的流量统计。",
"toggleSubscriptionStatus": "切换状态",
"toggleSubscriptionStatusDescription": "将切换该订阅的启用/停用状态。",
"resetTime": "重置时间", "resetTime": "重置时间",
"resetToken": "重置订阅地址", "resetToken": "重置订阅地址",
"resetTokenDescription": "这将重置订阅地址并重新生成新的令牌。", "resetTokenDescription": "这将重置订阅地址并重新生成新的令牌。",

View File

@ -77,6 +77,7 @@ const defaultValues = {
purchase_with_discount: false, purchase_with_discount: false,
reset_cycle: 0, reset_cycle: 0,
renewal_reset: false, renewal_reset: false,
show_original_price: false,
deduction_mode: "auto", deduction_mode: "auto",
}; };
@ -837,6 +838,41 @@ export default function SubscribeForm<T extends Record<string, any>>({
</FormItem> </FormItem>
)} )}
/> />
<FormField
control={form.control}
name="show_original_price"
render={({ field }) => (
<FormItem>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<FormLabel>
{t(
"form.showOriginalPrice",
"Show Original Price"
)}
</FormLabel>
<FormDescription>
{t(
"form.showOriginalPriceDescription",
"Display original price in the storefront"
)}
</FormDescription>
</div>
<FormControl>
<Switch
checked={!!field.value}
onCheckedChange={(value) =>
form.setValue(field.name, value)
}
/>
</FormControl>
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField <FormField
control={form.control} control={form.control}
name="allow_deduction" name="allow_deduction"

View File

@ -68,7 +68,11 @@ export default function User() {
action={ref} action={ref}
actions={{ actions={{
render: (row) => [ render: (row) => [
<ProfileSheet key="profile" userId={row.id} />, <ProfileSheet
key="profile"
onUpdated={() => ref.current?.refresh()}
userId={row.id}
/>,
<SubscriptionSheet key="subscription" userId={row.id} />, <SubscriptionSheet key="subscription" userId={row.id} />,
<ConfirmButton <ConfirmButton
cancelText={t("cancel", "Cancel")} cancelText={t("cancel", "Cancel")}
@ -303,7 +307,13 @@ export default function User() {
); );
} }
function ProfileSheet({ userId }: { userId: number }) { function ProfileSheet({
userId,
onUpdated,
}: {
userId: number;
onUpdated?: () => void;
}) {
const { t } = useTranslation("user"); const { t } = useTranslation("user");
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const { data: user, refetch } = useQuery({ const { data: user, refetch } = useQuery({
@ -314,6 +324,12 @@ function ProfileSheet({ userId }: { userId: number }) {
return data.data as API.User; return data.data as API.User;
}, },
}); });
const refetchAll = async () => {
await refetch();
onUpdated?.();
return Promise.resolve();
};
return ( return (
<Sheet onOpenChange={setOpen} open={open}> <Sheet onOpenChange={setOpen} open={open}>
<SheetTrigger asChild> <SheetTrigger asChild>
@ -343,13 +359,13 @@ function ProfileSheet({ userId }: { userId: number }) {
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<TabsContent className="mt-0" value="basic"> <TabsContent className="mt-0" value="basic">
<BasicInfoForm refetch={refetch as any} user={user} /> <BasicInfoForm refetch={refetchAll} user={user} />
</TabsContent> </TabsContent>
<TabsContent className="mt-0" value="notify"> <TabsContent className="mt-0" value="notify">
<NotifySettingsForm refetch={refetch as any} user={user} /> <NotifySettingsForm refetch={refetchAll} user={user} />
</TabsContent> </TabsContent>
<TabsContent className="mt-0" value="auth"> <TabsContent className="mt-0" value="auth">
<AuthMethodsForm refetch={refetch as any} user={user} /> <AuthMethodsForm refetch={refetchAll} user={user} />
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</ScrollArea> </ScrollArea>

View File

@ -21,7 +21,7 @@ export function AuthMethodsForm({
refetch, refetch,
}: { }: {
user: API.User; user: API.User;
refetch: () => void; refetch: () => Promise<unknown>;
}) { }) {
const { t } = useTranslation("user"); const { t } = useTranslation("user");

View File

@ -47,7 +47,7 @@ export function BasicInfoForm({
refetch, refetch,
}: { }: {
user: API.User; user: API.User;
refetch: () => void; refetch: () => Promise<unknown>;
}) { }) {
const { t } = useTranslation("user"); const { t } = useTranslation("user");

View File

@ -34,7 +34,7 @@ export function NotifySettingsForm({
refetch, refetch,
}: { }: {
user: API.User; user: API.User;
refetch: () => void; refetch: () => Promise<unknown>;
}) { }) {
const { t } = useTranslation("user"); const { t } = useTranslation("user");

View File

@ -1,4 +1,4 @@
import { readFileSync, writeFileSync } from "node:fs"; import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { fileURLToPath, URL } from "node:url"; import { fileURLToPath, URL } from "node:url";
import tailwindcss from "@tailwindcss/vite"; import tailwindcss from "@tailwindcss/vite";
import { devtools } from "@tanstack/devtools-vite"; import { devtools } from "@tanstack/devtools-vite";
@ -18,6 +18,8 @@ function versionLockPlugin(): Plugin {
); );
const rootPkg = JSON.parse(readFileSync(rootPkgPath, "utf-8")); const rootPkg = JSON.parse(readFileSync(rootPkgPath, "utf-8"));
const version = rootPkg.version || "0.0.0"; const version = rootPkg.version || "0.0.0";
mkdirSync(distDir, { recursive: true });
writeFileSync(`${distDir}/version.lock`, version); writeFileSync(`${distDir}/version.lock`, version);
}, },
}; };

View File

@ -1,6 +1,6 @@
{ {
"name": "frontend", "name": "frontend",
"version": "1.3.6", "version": "1.3.7-dev.1",
"private": true, "private": true,
"homepage": "https://github.com/perfect-panel/frontend", "homepage": "https://github.com/perfect-panel/frontend",
"bugs": { "bugs": {

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
// API 更新时间: // API 更新时间:
// API 唯一标识: // API 唯一标识:

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
// API 更新时间: // API 更新时间:
// API 唯一标识: // API 唯一标识:

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";
@ -38,6 +39,7 @@ export async function appleLoginCallback(
{ {
method: "POST", method: "POST",
data: formData, data: formData,
requestType: "form",
...(options || {}), ...(options || {}),
} }
); );

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
// API 更新时间: // API 更新时间:
// API 唯一标识: // API 唯一标识:

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
// API 更新时间: // API 更新时间:
// API 唯一标识: // API 唯一标识:

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import request from "@workspace/ui/lib/request"; import request from "@workspace/ui/lib/request";

View File

@ -8,3 +8,11 @@ declare global {
i18n: typeof i18n; i18n: typeof i18n;
} }
} }
// openapi2ts 生成的 request 参数里可能会包含 requestTypeumi-request 风格)。
// 我们的 request 基于 axios这里做一个类型补丁避免 TS 报错。
declare module "axios" {
export interface AxiosRequestConfig<D = any> {
requestType?: string;
}
}