feat(web): 积分商品图片选择器 — 媒体库 + 上传替代手动 URL
- PointsProductList: 图片字段改为 Input + 媒体库按钮 + 上传按钮 + 图片预览 - DrawerForm: 新增可选 form prop,允许外部控制表单实例
This commit is contained in:
@@ -19,6 +19,7 @@ interface DrawerFormProps {
|
||||
sections?: FormSection[];
|
||||
children?: React.ReactNode;
|
||||
columns?: 1 | 2;
|
||||
form?: ReturnType<typeof Form.useForm>[0];
|
||||
}
|
||||
|
||||
export function DrawerForm({
|
||||
@@ -32,8 +33,10 @@ export function DrawerForm({
|
||||
sections,
|
||||
children,
|
||||
columns = 2,
|
||||
form: externalForm,
|
||||
}: DrawerFormProps) {
|
||||
const [form] = Form.useForm();
|
||||
const [internalForm] = Form.useForm();
|
||||
const form = externalForm ?? internalForm;
|
||||
const isDark = useThemeMode();
|
||||
|
||||
React.useEffect(() => {
|
||||
|
||||
@@ -11,12 +11,15 @@ import {
|
||||
Tag,
|
||||
Badge,
|
||||
Switch,
|
||||
Upload,
|
||||
message,
|
||||
} from 'antd';
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
PictureOutlined,
|
||||
UploadOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
pointsApi,
|
||||
@@ -26,8 +29,11 @@ import {
|
||||
import { AuthButton } from '../../components/AuthButton';
|
||||
import { DrawerForm } from '../../components/DrawerForm';
|
||||
import type { FormSection } from '../../components/DrawerForm';
|
||||
import MediaPicker from '../../components/MediaPicker';
|
||||
import { PageContainer } from '../../components/PageContainer';
|
||||
import { uploadFile } from '../../api/upload';
|
||||
import { usePaginatedData } from '../../hooks/usePaginatedData';
|
||||
import { useThemeMode } from '../../hooks/useThemeMode';
|
||||
import { formatDateTime } from '../../utils/format';
|
||||
|
||||
/** 商品类型映射 */
|
||||
@@ -59,6 +65,9 @@ interface ProductFilters {
|
||||
export default function PointsProductList() {
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [editing, setEditing] = useState<PointsProduct | null>(null);
|
||||
const [mediaPickerOpen, setMediaPickerOpen] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const isDark = useThemeMode();
|
||||
|
||||
const fetchProducts = useCallback(
|
||||
async (page: number, pageSize: number, filters: ProductFilters) => {
|
||||
@@ -309,8 +318,53 @@ export default function PointsProductList() {
|
||||
title: '展示设置',
|
||||
fields: (
|
||||
<>
|
||||
<Form.Item name="image_url" label="图片链接">
|
||||
<Input placeholder="商品图片 URL" />
|
||||
<Form.Item name="image_url" label="商品图片">
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
<Input placeholder="输入 URL 或从媒体库选择" style={{ flex: 1 }} />
|
||||
<Button icon={<PictureOutlined />} onClick={() => setMediaPickerOpen(true)}>
|
||||
媒体库
|
||||
</Button>
|
||||
<Upload
|
||||
accept="image/*"
|
||||
showUploadList={false}
|
||||
beforeUpload={async (file) => {
|
||||
try {
|
||||
const result = await uploadFile(file);
|
||||
form.setFieldValue('image_url', result.url);
|
||||
message.success('图片上传成功');
|
||||
} catch {
|
||||
message.error('图片上传失败');
|
||||
}
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<Button icon={<UploadOutlined />}>上传</Button>
|
||||
</Upload>
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
<Form.Item noStyle shouldUpdate={(prev, cur) => prev.image_url !== cur.image_url}>
|
||||
{({ getFieldValue }) => {
|
||||
const url: string | undefined = getFieldValue('image_url');
|
||||
if (!url) return null;
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
marginTop: -20,
|
||||
marginBottom: 16,
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
border: `1px solid ${isDark ? '#1e293b' : '#e2e8f0'}`,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={url}
|
||||
alt="商品图片预览"
|
||||
style={{ width: '100%', height: 120, objectFit: 'cover' }}
|
||||
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item name="sort_order" label="排序">
|
||||
<InputNumber min={0} max={9999} style={{ width: '100%' }} placeholder="0" />
|
||||
@@ -402,6 +456,16 @@ export default function PointsProductList() {
|
||||
width={600}
|
||||
columns={2}
|
||||
sections={formSections}
|
||||
form={form}
|
||||
/>
|
||||
|
||||
<MediaPicker
|
||||
open={mediaPickerOpen}
|
||||
onClose={() => setMediaPickerOpen(false)}
|
||||
onSelect={(url) => {
|
||||
form.setFieldValue('image_url', url);
|
||||
message.success('已选择图片');
|
||||
}}
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user