From 479de04f7cc19a02bc9b95bfb482de94b6430f7b Mon Sep 17 00:00:00 2001 From: jacy <18049821889@163.com> Date: Thu, 23 Jul 2026 12:15:15 +0800 Subject: [PATCH] feat(mini-user): add configurable home banner and footer HQ system settings WeChat mini config with OSS swiper/footer uploads; client-config exposes miniHome. Co-authored-by: Cursor --- .../src/components/ConfigMediaFields.tsx | 114 ++++++++++++++++++ .../src/pages/SystemSettingsPage.tsx | 86 ++++--------- apps/mini-user/src/pages/home/index.tsx | 60 ++++++++- apps/mini-user/src/styles/home.css | 36 ++++++ packages/shared-types/src/system-config.ts | 31 ++++- packages/shared-types/src/wechat.ts | 5 + .../system-config/system-config.registry.ts | 19 +++ .../common/client-config.controller.ts | 7 ++ 8 files changed, 292 insertions(+), 66 deletions(-) create mode 100644 apps/admin-web/src/components/ConfigMediaFields.tsx diff --git a/apps/admin-web/src/components/ConfigMediaFields.tsx b/apps/admin-web/src/components/ConfigMediaFields.tsx new file mode 100644 index 0000000..33c67af --- /dev/null +++ b/apps/admin-web/src/components/ConfigMediaFields.tsx @@ -0,0 +1,114 @@ +import { Button, Space } from 'antd'; +import { ArrowDownOutlined, ArrowUpOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons'; +import { + parseMiniHomeBanners, + serializeMiniHomeBanners, +} from '@dukang/shared-types'; +import OssUpload from './OssUpload'; + +const MAX_BANNERS = 8; + +/** Ant Form 控件:单图 URL 字符串(默认 OSS 路径 footer) */ +export function ConfigImageField({ + value, + onChange, + bizType = 'footer', +}: { + value?: string; + onChange?: (url: string) => void; + bizType?: string; +}) { + return ( + + ); +} + +/** Ant Form 控件:多图 JSON 字符串(默认 OSS 路径 swiper) */ +export function ConfigImageListField({ + value, + onChange, + bizType = 'swiper', +}: { + value?: string; + onChange?: (json: string) => void; + bizType?: string; +}) { + const urls = parseMiniHomeBanners(value); + + function commit(next: string[]) { + onChange?.(serializeMiniHomeBanners(next)); + } + + function updateAt(index: number, url: string) { + const next = [...urls]; + next[index] = url; + commit(next); + } + + function removeAt(index: number) { + commit(urls.filter((_, i) => i !== index)); + } + + function move(index: number, delta: number) { + const target = index + delta; + if (target < 0 || target >= urls.length) return; + const next = [...urls]; + const tmp = next[index]; + next[index] = next[target]; + next[target] = tmp; + commit(next); + } + + function add() { + if (urls.length >= MAX_BANNERS) return; + commit([...urls, '']); + } + + return ( + + {urls.map((url, index) => ( + +
+ updateAt(index, u)} + placeholder="上传或粘贴图片 URL" + /> +
+ + + + ); +} diff --git a/apps/admin-web/src/pages/SystemSettingsPage.tsx b/apps/admin-web/src/pages/SystemSettingsPage.tsx index 4b27e1d..b9e1fab 100644 --- a/apps/admin-web/src/pages/SystemSettingsPage.tsx +++ b/apps/admin-web/src/pages/SystemSettingsPage.tsx @@ -31,10 +31,8 @@ import { } from 'antd'; import type { MockSmsCodeItem, SystemConfigFieldMeta, SystemConfigFormResponse } from '@dukang/shared-types'; - import { request } from '../lib/api'; - - +import { ConfigImageField, ConfigImageListField } from '../components/ConfigMediaFields'; const { TextArea } = Input; @@ -131,123 +129,89 @@ function renderField( const isConfiguredSecret = field.secret && configuredSecrets.includes(field.key); if (field.type === 'boolean') { - return ( -
- - {field.label} - - {field.key} - - {field.requiresRestart ? 需重启 : 即时} - - } - tooltip={field.description} - valuePropName="checked" - getValueFromEvent={(checked: boolean) => (checked ? 'true' : 'false')} - getValueProps={(v: string) => ({ checked: v === 'true' || v === '1' })} - > - - - {extra} -
- ); - } - + if (field.type === 'image' || field.type === 'imageList') { + return ( + + {field.label} + + {field.key} + + {field.requiresRestart ? 需重启 : 即时} +
+ } + tooltip={field.description} + > + {field.type === 'image' ? ( + + ) : ( + + )} + + ); + } const input = - field.type === 'textarea' ? ( -