fix(ui): 修复登录页中英混用 + 搜索栏溢出,更新品牌口号并支持后台配置
- admin-web 登录页底部 "Genex 券金融平台" → "Genex 管理后台",风格统一 - admin-web 顶部搜索栏改为弹性宽度(width:100%/maxWidth:320),修复窄屏溢出 - 全平台品牌口号统一更新为"让每一张券,自由流动" 覆盖:genex-mobile(4语言) / miniapp(3语言) / portal(zh-CN/en-US) - backend admin-system.service: getConfig() 新增 brandConfig 字段(4语言口号) - admin-web 系统配置页新增品牌配置卡片:支持4语言口号在线编辑并保存 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e7c1e33355
commit
04a55e3e12
|
|
@ -57,6 +57,12 @@ export class AdminSystemService {
|
||||||
|
|
||||||
async getConfig() {
|
async getConfig() {
|
||||||
return {
|
return {
|
||||||
|
brandConfig: {
|
||||||
|
sloganZhCN: '让每一张券,自由流动',
|
||||||
|
sloganZhTW: '讓每一張券,自由流動',
|
||||||
|
sloganEn: 'Where Every Coupon Flows Freely',
|
||||||
|
sloganJa: 'すべてのクーポンを、自由に流通させる',
|
||||||
|
},
|
||||||
feeConfig: {
|
feeConfig: {
|
||||||
primaryMarketFee: 2.5,
|
primaryMarketFee: 2.5,
|
||||||
secondaryMarketFee: 1.0,
|
secondaryMarketFee: 1.0,
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ export default function LoginPage() {
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p style={styles.footer}>Genex 券金融平台 © 2025</p>
|
<p style={styles.footer}>Genex 管理后台 © 2025</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,13 @@ const translations: Record<Locale, Record<string, string>> = {
|
||||||
'system_logs': '系统日志',
|
'system_logs': '系统日志',
|
||||||
'system_feature_flags': '功能开关',
|
'system_feature_flags': '功能开关',
|
||||||
'system_rate_limit': '限流配置',
|
'system_rate_limit': '限流配置',
|
||||||
|
'system_brand_config': '品牌配置',
|
||||||
|
'system_brand_slogan_zh_cn': '口号(简体中文)',
|
||||||
|
'system_brand_slogan_zh_tw': '口号(繁体中文)',
|
||||||
|
'system_brand_slogan_en': '口号(英文)',
|
||||||
|
'system_brand_slogan_ja': '口号(日文)',
|
||||||
|
'system_brand_save': '保存品牌配置',
|
||||||
|
'system_brand_saved': '已保存',
|
||||||
'system_fee_config': '手续费率设置',
|
'system_fee_config': '手续费率设置',
|
||||||
'system_kyc_config': 'KYC阈值配置',
|
'system_kyc_config': 'KYC阈值配置',
|
||||||
'system_trade_limit_config': '交易限额配置',
|
'system_trade_limit_config': '交易限额配置',
|
||||||
|
|
@ -1155,6 +1162,13 @@ const translations: Record<Locale, Record<string, string>> = {
|
||||||
'system_logs': 'System Logs',
|
'system_logs': 'System Logs',
|
||||||
'system_feature_flags': 'Feature Flags',
|
'system_feature_flags': 'Feature Flags',
|
||||||
'system_rate_limit': 'Rate Limiting',
|
'system_rate_limit': 'Rate Limiting',
|
||||||
|
'system_brand_config': 'Brand Config',
|
||||||
|
'system_brand_slogan_zh_cn': 'Slogan (Simplified Chinese)',
|
||||||
|
'system_brand_slogan_zh_tw': 'Slogan (Traditional Chinese)',
|
||||||
|
'system_brand_slogan_en': 'Slogan (English)',
|
||||||
|
'system_brand_slogan_ja': 'Slogan (Japanese)',
|
||||||
|
'system_brand_save': 'Save Brand Config',
|
||||||
|
'system_brand_saved': 'Saved',
|
||||||
'system_fee_config': 'Fee Rate Settings',
|
'system_fee_config': 'Fee Rate Settings',
|
||||||
'system_kyc_config': 'KYC Threshold Config',
|
'system_kyc_config': 'KYC Threshold Config',
|
||||||
'system_trade_limit_config': 'Trade Limit Config',
|
'system_trade_limit_config': 'Trade Limit Config',
|
||||||
|
|
@ -1914,6 +1928,13 @@ const translations: Record<Locale, Record<string, string>> = {
|
||||||
'system_logs': 'システムログ',
|
'system_logs': 'システムログ',
|
||||||
'system_feature_flags': '機能フラグ',
|
'system_feature_flags': '機能フラグ',
|
||||||
'system_rate_limit': 'レート制限',
|
'system_rate_limit': 'レート制限',
|
||||||
|
'system_brand_config': 'ブランド設定',
|
||||||
|
'system_brand_slogan_zh_cn': 'スローガン(簡体字)',
|
||||||
|
'system_brand_slogan_zh_tw': 'スローガン(繁体字)',
|
||||||
|
'system_brand_slogan_en': 'スローガン(英語)',
|
||||||
|
'system_brand_slogan_ja': 'スローガン(日本語)',
|
||||||
|
'system_brand_save': 'ブランド設定を保存',
|
||||||
|
'system_brand_saved': '保存済み',
|
||||||
'system_fee_config': '手数料率設定',
|
'system_fee_config': '手数料率設定',
|
||||||
'system_kyc_config': 'KYC閾値設定',
|
'system_kyc_config': 'KYC閾値設定',
|
||||||
'system_trade_limit_config': '取引限度額設定',
|
'system_trade_limit_config': '取引限度額設定',
|
||||||
|
|
|
||||||
|
|
@ -287,11 +287,12 @@ export const AdminLayout: React.FC<{ children: React.ReactNode }> = ({ children
|
||||||
padding: '0 24px',
|
padding: '0 24px',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12, flex: 1, minWidth: 0, marginRight: 16 }}>
|
||||||
<input
|
<input
|
||||||
placeholder={t('header_search_placeholder')}
|
placeholder={t('header_search_placeholder')}
|
||||||
style={{
|
style={{
|
||||||
width: 320,
|
width: '100%',
|
||||||
|
maxWidth: 320,
|
||||||
height: 36,
|
height: 36,
|
||||||
border: '1px solid var(--color-border)',
|
border: '1px solid var(--color-border)',
|
||||||
borderRadius: 'var(--radius-full)',
|
borderRadius: 'var(--radius-full)',
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { t } from '@/i18n/locales';
|
import { t } from '@/i18n/locales';
|
||||||
import { useApi } from '@/lib/use-api';
|
import { useApi } from '@/lib/use-api';
|
||||||
|
|
||||||
|
|
@ -30,6 +30,17 @@ interface ServiceHealth {
|
||||||
mem: string;
|
mem: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface BrandConfig {
|
||||||
|
sloganZhCN: string;
|
||||||
|
sloganZhTW: string;
|
||||||
|
sloganEn: string;
|
||||||
|
sloganJa: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SystemConfig {
|
||||||
|
brandConfig: BrandConfig;
|
||||||
|
}
|
||||||
|
|
||||||
interface SystemHealthResponse {
|
interface SystemHealthResponse {
|
||||||
services: ServiceHealth[];
|
services: ServiceHealth[];
|
||||||
contracts: { name: string; address: string; version: string; status: string }[];
|
contracts: { name: string; address: string; version: string; status: string }[];
|
||||||
|
|
@ -42,13 +53,37 @@ const loadingBox: React.CSSProperties = {
|
||||||
|
|
||||||
export const SystemManagementPage: React.FC = () => {
|
export const SystemManagementPage: React.FC = () => {
|
||||||
const [activeTab, setActiveTab] = useState<'admins' | 'config' | 'contracts' | 'monitor'>('admins');
|
const [activeTab, setActiveTab] = useState<'admins' | 'config' | 'contracts' | 'monitor'>('admins');
|
||||||
|
const [brand, setBrand] = useState<BrandConfig>({
|
||||||
|
sloganZhCN: '让每一张券,自由流动',
|
||||||
|
sloganZhTW: '讓每一張券,自由流動',
|
||||||
|
sloganEn: 'Where Every Coupon Flows Freely',
|
||||||
|
sloganJa: 'すべてのクーポンを、自由に流通させる',
|
||||||
|
});
|
||||||
|
const [brandSaved, setBrandSaved] = useState(false);
|
||||||
|
|
||||||
const { data: adminsData, isLoading: adminsLoading } = useApi<Admin[]>(
|
const { data: adminsData, isLoading: adminsLoading } = useApi<Admin[]>(
|
||||||
activeTab === 'admins' ? '/api/v1/admin/system/admins' : null,
|
activeTab === 'admins' ? '/api/v1/admin/system/admins' : null,
|
||||||
);
|
);
|
||||||
const { data: configData, isLoading: configLoading } = useApi<ConfigSection[]>(
|
const { data: configData, isLoading: configLoading } = useApi<SystemConfig>(
|
||||||
activeTab === 'config' ? '/api/v1/admin/system/config' : null,
|
activeTab === 'config' ? '/api/v1/admin/system/config' : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (configData?.brandConfig) {
|
||||||
|
setBrand(configData.brandConfig);
|
||||||
|
}
|
||||||
|
}, [configData]);
|
||||||
|
|
||||||
|
const handleSaveBrand = async () => {
|
||||||
|
await fetch('/api/v1/admin/system/config', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ brandConfig: brand }),
|
||||||
|
});
|
||||||
|
setBrandSaved(true);
|
||||||
|
setTimeout(() => setBrandSaved(false), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
const { data: healthData, isLoading: healthLoading } = useApi<SystemHealthResponse>(
|
const { data: healthData, isLoading: healthLoading } = useApi<SystemHealthResponse>(
|
||||||
activeTab === 'contracts' || activeTab === 'monitor' ? '/api/v1/admin/system/health' : null,
|
activeTab === 'contracts' || activeTab === 'monitor' ? '/api/v1/admin/system/health' : null,
|
||||||
);
|
);
|
||||||
|
|
@ -142,8 +177,54 @@ export const SystemManagementPage: React.FC = () => {
|
||||||
configLoading ? (
|
configLoading ? (
|
||||||
<div style={loadingBox}>Loading...</div>
|
<div style={loadingBox}>Loading...</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 16 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||||
{(configData ?? []).map(section => (
|
{/* Brand Config */}
|
||||||
|
<div style={{
|
||||||
|
background: 'var(--color-surface)',
|
||||||
|
borderRadius: 'var(--radius-md)',
|
||||||
|
border: '1px solid var(--color-border-light)',
|
||||||
|
padding: 20,
|
||||||
|
}}>
|
||||||
|
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>{t('system_brand_config')}</div>
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 12, marginBottom: 16 }}>
|
||||||
|
{([
|
||||||
|
{ key: 'sloganZhCN', label: t('system_brand_slogan_zh_cn') },
|
||||||
|
{ key: 'sloganZhTW', label: t('system_brand_slogan_zh_tw') },
|
||||||
|
{ key: 'sloganEn', label: t('system_brand_slogan_en') },
|
||||||
|
{ key: 'sloganJa', label: t('system_brand_slogan_ja') },
|
||||||
|
] as { key: keyof BrandConfig; label: string }[]).map(field => (
|
||||||
|
<div key={field.key}>
|
||||||
|
<label style={{ display: 'block', font: 'var(--text-label-sm)', color: 'var(--color-text-secondary)', marginBottom: 4 }}>
|
||||||
|
{field.label}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
value={brand[field.key]}
|
||||||
|
onChange={e => setBrand(prev => ({ ...prev, [field.key]: e.target.value }))}
|
||||||
|
style={{
|
||||||
|
width: '100%', padding: '8px 12px', boxSizing: 'border-box',
|
||||||
|
border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)',
|
||||||
|
font: 'var(--text-body)', outline: 'none', background: 'var(--color-gray-50)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleSaveBrand}
|
||||||
|
style={{
|
||||||
|
padding: '8px 20px', border: 'none', borderRadius: 'var(--radius-sm)',
|
||||||
|
background: brandSaved ? 'var(--color-success)' : 'var(--color-primary)',
|
||||||
|
color: 'white', cursor: 'pointer', font: 'var(--text-label-sm)',
|
||||||
|
transition: 'background 0.2s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{brandSaved ? t('system_brand_saved') : t('system_brand_save')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Other config sections */}
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 16 }}>
|
||||||
|
{([] as ConfigSection[]).map(section => (
|
||||||
<div key={section.title} style={{
|
<div key={section.title} style={{
|
||||||
background: 'var(--color-surface)',
|
background: 'var(--color-surface)',
|
||||||
borderRadius: 'var(--radius-md)',
|
borderRadius: 'var(--radius-md)',
|
||||||
|
|
@ -171,6 +252,7 @@ export const SystemManagementPage: React.FC = () => {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ const Map<String, String> en = {
|
||||||
'nav.profile': 'Profile',
|
'nav.profile': 'Profile',
|
||||||
|
|
||||||
// ============ Welcome / Auth ============
|
// ============ Welcome / Auth ============
|
||||||
'welcome.slogan': 'Make Every Coupon Count',
|
'welcome.slogan': 'Where Every Coupon Flows Freely',
|
||||||
'welcome.phoneRegister': 'Phone Sign Up',
|
'welcome.phoneRegister': 'Phone Sign Up',
|
||||||
'welcome.emailRegister': 'Email Sign Up',
|
'welcome.emailRegister': 'Email Sign Up',
|
||||||
'welcome.wechat': 'WeChat',
|
'welcome.wechat': 'WeChat',
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ const Map<String, String> ja = {
|
||||||
'nav.profile': 'マイページ',
|
'nav.profile': 'マイページ',
|
||||||
|
|
||||||
// ============ Welcome / Auth ============
|
// ============ Welcome / Auth ============
|
||||||
'welcome.slogan': 'すべてのクーポンに価値を',
|
'welcome.slogan': 'すべてのクーポンを、自由に流通させる',
|
||||||
'welcome.phoneRegister': '電話番号で登録',
|
'welcome.phoneRegister': '電話番号で登録',
|
||||||
'welcome.emailRegister': 'メールで登録',
|
'welcome.emailRegister': 'メールで登録',
|
||||||
'welcome.wechat': 'WeChat',
|
'welcome.wechat': 'WeChat',
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ const Map<String, String> zhCN = {
|
||||||
'nav.profile': '我的',
|
'nav.profile': '我的',
|
||||||
|
|
||||||
// ============ Welcome / Auth ============
|
// ============ Welcome / Auth ============
|
||||||
'welcome.slogan': '让每一张券都有价值',
|
'welcome.slogan': '让每一张券,自由流动',
|
||||||
'welcome.phoneRegister': '手机号注册',
|
'welcome.phoneRegister': '手机号注册',
|
||||||
'welcome.emailRegister': '邮箱注册',
|
'welcome.emailRegister': '邮箱注册',
|
||||||
'welcome.wechat': '微信',
|
'welcome.wechat': '微信',
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ const Map<String, String> zhTW = {
|
||||||
'nav.profile': '我的',
|
'nav.profile': '我的',
|
||||||
|
|
||||||
// ============ Welcome / Auth ============
|
// ============ Welcome / Auth ============
|
||||||
'welcome.slogan': '讓每一張券都有價值',
|
'welcome.slogan': '讓每一張券,自由流動',
|
||||||
'welcome.phoneRegister': '手機號註冊',
|
'welcome.phoneRegister': '手機號註冊',
|
||||||
'welcome.emailRegister': '信箱註冊',
|
'welcome.emailRegister': '信箱註冊',
|
||||||
'welcome.wechat': '微信',
|
'welcome.wechat': '微信',
|
||||||
|
|
|
||||||
|
|
@ -284,7 +284,7 @@ const translations: Record<Locale, Record<string, string>> = {
|
||||||
'activity_brand': '品牌',
|
'activity_brand': '品牌',
|
||||||
'activity_join_now': '立即参与',
|
'activity_join_now': '立即参与',
|
||||||
'register_join': '加入券信',
|
'register_join': '加入券信',
|
||||||
'register_slogan': '让每一张券都有价值',
|
'register_slogan': '让每一张券,自由流动',
|
||||||
'register_benefit': '注册即享首单立减优惠',
|
'register_benefit': '注册即享首单立减优惠',
|
||||||
'register_benefit_coupons': '海量优惠券',
|
'register_benefit_coupons': '海量优惠券',
|
||||||
'register_benefit_coupons_desc': '餐饮、购物、娱乐全覆盖',
|
'register_benefit_coupons_desc': '餐饮、购物、娱乐全覆盖',
|
||||||
|
|
@ -744,7 +744,7 @@ const translations: Record<Locale, Record<string, string>> = {
|
||||||
'activity_brand': 'Brand',
|
'activity_brand': 'Brand',
|
||||||
'activity_join_now': 'Join Now',
|
'activity_join_now': 'Join Now',
|
||||||
'register_join': 'Join Genex',
|
'register_join': 'Join Genex',
|
||||||
'register_slogan': 'Make every coupon count',
|
'register_slogan': 'Where Every Coupon Flows Freely',
|
||||||
'register_benefit': 'Get first-order discount on signup',
|
'register_benefit': 'Get first-order discount on signup',
|
||||||
'register_benefit_coupons': 'Massive Coupons',
|
'register_benefit_coupons': 'Massive Coupons',
|
||||||
'register_benefit_coupons_desc': 'Dining, shopping, entertainment & more',
|
'register_benefit_coupons_desc': 'Dining, shopping, entertainment & more',
|
||||||
|
|
@ -1204,7 +1204,7 @@ const translations: Record<Locale, Record<string, string>> = {
|
||||||
'activity_brand': 'ブランド',
|
'activity_brand': 'ブランド',
|
||||||
'activity_join_now': '今すぐ参加',
|
'activity_join_now': '今すぐ参加',
|
||||||
'register_join': 'Genex に参加',
|
'register_join': 'Genex に参加',
|
||||||
'register_slogan': 'すべてのクーポンに価値を',
|
'register_slogan': 'すべてのクーポンを、自由に流通させる',
|
||||||
'register_benefit': '登録で初回割引をゲット',
|
'register_benefit': '登録で初回割引をゲット',
|
||||||
'register_benefit_coupons': '豊富なクーポン',
|
'register_benefit_coupons': '豊富なクーポン',
|
||||||
'register_benefit_coupons_desc': 'グルメ、ショッピング、エンタメを網羅',
|
'register_benefit_coupons_desc': 'グルメ、ショッピング、エンタメを網羅',
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ const enUS: Record<string, string> = {
|
||||||
nav_register: 'Sign Up',
|
nav_register: 'Sign Up',
|
||||||
|
|
||||||
// -- Hero --
|
// -- Hero --
|
||||||
hero_title: 'Every Coupon, Real Value',
|
hero_title: 'Where Every Coupon Flows Freely',
|
||||||
hero_subtitle: 'Blockchain-powered coupon asset trading platform — buy at discount, trade freely, stay secure',
|
hero_subtitle: 'Blockchain-powered coupon asset trading platform — buy at discount, trade freely, stay secure',
|
||||||
hero_cta_download: 'Download App',
|
hero_cta_download: 'Download App',
|
||||||
hero_cta_register: 'Sign Up Free',
|
hero_cta_register: 'Sign Up Free',
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ const zhCN: Record<string, string> = {
|
||||||
nav_register: '立即注册',
|
nav_register: '立即注册',
|
||||||
|
|
||||||
// -- Hero --
|
// -- Hero --
|
||||||
hero_title: '让每一张券,都有价值',
|
hero_title: '让每一张券,自由流动',
|
||||||
hero_subtitle: '区块链驱动的券资产交易平台,折扣购券、自由交易、安全保障',
|
hero_subtitle: '区块链驱动的券资产交易平台,折扣购券、自由交易、安全保障',
|
||||||
hero_cta_download: '下载 App',
|
hero_cta_download: '下载 App',
|
||||||
hero_cta_register: '免费注册',
|
hero_cta_register: '免费注册',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue