Browse Source

feat: 新增站点配置系统,添加客服二维码弹窗功能

1. 定义了ContentConfig和SystemConfig类型以及ConfigResponse响应类型
2. 重构HeaderIconButton组件支持透传属性
3. 新增获取站点配置接口调用逻辑
4. 给微信客服按钮添加二维码弹窗展示功能
feature/group
huangmin 1 month ago
parent
commit
39d82bdf03
  1. 76
      src/app/api/config/route.ts
  2. 59
      src/app/page.tsx

76
src/app/api/config/route.ts

@ -1,7 +1,83 @@
import { NextRequest, NextResponse } from "next/server";
import { requireLogin } from "@/app/api/_auth";
const UPSTREAM_CONFIG_PATH = "/api_client/param/config";
type ContentConfig = {
indexMainSlogan: string;
indexSubSlogan: string;
indexBannerImage: string;
exploreHeaderImage: string;
exploreHeaderTitle: string;
subscribeHeaderImage: string;
subscribeHeaderTitle: string;
userHomepageHeaderImage: string;
};
type SystemConfig = {
serviceEmail: string;
servicePhone: string;
contactUsPageUrl: string;
useNoticePageUrl: string;
paymentTermsPageUrl: string;
copyRightRulesPageUrl: string;
customCharacterPageUrl: string;
usageTutorialPageUrl: string;
serviceQrCode: string;
wxFollowReplyMsg: string;
wxLoginReplyMsg: string;
wxRegisterReplyMsg: string;
genImageBatchSize: number;
genImageTaskRunningSize: number;
genVideoTaskRunningSize: number;
genAudioTaskRunningSize: number;
aiExpandSystemPrompt: string;
defaultBaseModelType: string;
characterLoraStrength: number;
styleLoraStrength: number;
enableMemberSubscription: boolean;
nonMemberGenImageLimitPerDay: number;
nonMemberGenVideoLimitPerDay: number;
nonMemberGenVoiceLimitPerDay: number;
nonMemberTaskQueueSize: number;
basicMemberTaskQueueSize: number;
standardMemberTaskQueueSize: number;
seniorMemberTaskQueueSize: number;
futureCreatorMemberTaskQueueSize: number;
sdImageGenerationCoinPrice: number;
hdImageGenerationCoinPrice: number;
sdImageOptimizeCoinPrice: number;
hdImageOptimizeCoinPrice: number;
sdVideoGenerationCoinPrice: number;
hdVideoGenerationCoinPrice: number;
audioGenerationCoinPrice: number;
watermarkText: string;
waterMarkFontSize: number;
videoPowerPerSecond: number;
audioPowerPerSecond: number;
trainPowerPerAvatar: number;
trainPowerPerSpeaker: number;
avatarLimitPerUser: number;
speakerLimitPerUser: number;
userJoinActivityNum: number;
activityGenImageTaskSize: number;
storyAgentPromptShow: boolean;
inviteCodeUseNumLimit: number;
inviteCodeGenerateNumUserPer: number;
genImageTimeLimit: number;
genVideoTimeLimit: number;
genAudioTimeLimit: number;
popiartStudioWindowsDownloadLink: string;
popiartStudioMacMDownloadLink: string;
popiartStudioMacInDownloadLink: string;
popiartEvidencePreservationAgreement: string;
aliyunRishDetectionConfidence: number;
};
export type ConfigResponse = {
contentConfig?: ContentConfig;
systemConfig?: SystemConfig;
status?: string;
message?: string;
};
const getUpstreamUrl = (request: NextRequest): string => {
const baseUrl =
process.env.POPIART_API_BASE_URL ||

59
src/app/page.tsx

@ -1,7 +1,14 @@
"use client";
import Link from "next/link";
import { useMemo, useEffect, useState, type ReactNode } from "react";
import { Dropdown, type MenuProps } from "antd";
import {
forwardRef,
useMemo,
useEffect,
useState,
type ButtonHTMLAttributes,
type ReactNode,
} from "react";
import { Dropdown, Popover, QRCode, type MenuProps } from "antd";
import { HomeProjectsContent } from "@/components/HomeProjectsContent";
import { authFetch } from "@/utils/authFetch";
import { useUserStore } from "@/store/userStore";
@ -13,6 +20,7 @@ import {
WeChatIcon,
SparklingIcon,
} from "@/components/icons";
import { ConfigResponse } from "./api/config/route";
type HeroCardData = {
title: string;
@ -94,29 +102,30 @@ function DiamondLogo({ label }: { label: string }) {
);
}
function HeaderIconButton({
children,
label,
}: {
const HeaderIconButton = forwardRef<HTMLButtonElement, ButtonHTMLAttributes<HTMLButtonElement> & {
children: ReactNode;
label: string;
}) {
}>(
function HeaderIconButton({ children, label, className = "", type = "button", ...buttonProps }, ref) {
return (
<button
type="button"
aria-label={label}
title={label}
className="grid h-[33px] w-[33px] place-items-center rounded-full bg-[#f9f9f9] text-[#606266] transition hover:bg-[#f0f0f0] hover:text-[#5c3bc7]"
{...buttonProps}
ref={ref}
type={type}
aria-label={buttonProps["aria-label"] ?? label}
title={buttonProps.title ?? label}
className={`grid h-[33px] w-[33px] place-items-center rounded-full bg-[#f9f9f9] text-[#606266] transition hover:bg-[#f0f0f0] hover:text-[#5c3bc7] ${className}`}
>
{children}
</button>
);
}
});
function Header() {
const [showBanner, setShowBanner] = useState(true);
const { userInfo } = useUserStore();
const { language, setLanguage, t } = useI18n();
const [systemConfig, setSystemConfig] = useState<ConfigResponse["systemConfig"]>();
const languageMenuItems: MenuProps["items"] = useMemo(
() =>
@ -130,6 +139,19 @@ function Header() {
})),
[language],
);
useEffect(() => {
const fetchConfig = async () => {
const response = await authFetch(`/api/config`, {
method: "GET",
});
if (!response.ok) return;
const result = (await response.json()) as ConfigResponse;
console.log(result);
setSystemConfig(result.systemConfig);
};
fetchConfig();
}, [])
return (
<header className="sticky top-0 z-30 bg-[#f9f9f9]">
@ -201,9 +223,16 @@ function Header() {
</Link>
</div>
<div className="hidden items-center gap-2 sm:flex">
<HeaderIconButton label={t("home.messages")}>
<WeChatIcon />
</HeaderIconButton>
<Popover arrow={false} placement="bottomRight" content={
<div className="flex flex-col items-center gap-2">
<QRCode value={systemConfig?.serviceQrCode || '-'} />
<span></span>
</div>
}>
<HeaderIconButton label={t("home.messages")}>
<WeChatIcon />
</HeaderIconButton>
</Popover>
<Link
href="https://tcnshqo5yu6i.feishu.cn/wiki/QqaHwOTLHiQn72kUA8FcVpAknXe"
target="_blank"

Loading…
Cancel
Save