Explorar o código

feat: add product list page and detail page scaffold

BaiLuoYan hai 3 días
pai
achega
e3038275d7
Modificáronse 2 ficheiros con 163 adicións e 2 borrados
  1. 69 1
      src/pages/Admin/Product/Detail/index.tsx
  2. 94 1
      src/pages/Admin/Product/index.tsx

+ 69 - 1
src/pages/Admin/Product/Detail/index.tsx

@@ -1,3 +1,71 @@
+import { fetchProductDetail } from '@/services/product';
+import { PageContainer } from '@ant-design/pro-components';
+import { useNavigate, useParams } from '@umijs/max';
+import { Spin, Tabs, Tag } from 'antd';
+import { useEffect, useState } from 'react';
+import { ProductForm } from '../components/Form';
+
+// Tab 内容由计划六实现,此处先用占位
+const RoleTab = ({ productCode }: { productCode: string }) => <div>角色 Tab - {productCode}</div>;
+const PermTab = ({ productCode }: { productCode: string }) => <div>权限 Tab - {productCode}</div>;
+const MemberTab = ({ productCode }: { productCode: string }) => <div>成员 Tab - {productCode}</div>;
+
 export default function ProductDetailPage() {
-  return null;
+  const { id } = useParams<{ id: string }>();
+  const navigate = useNavigate();
+  const [product, setProduct] = useState<API.ProductItem | null>(null);
+  const [loading, setLoading] = useState(false);
+
+  const loadProduct = async () => {
+    if (!id) return;
+    setLoading(true);
+    try {
+      const res = await fetchProductDetail({ id: Number(id) });
+      setProduct(res.data ?? null);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  useEffect(() => {
+    loadProduct();
+  }, [id]);
+
+  if (!product && !loading) return null;
+
+  return (
+    <Spin spinning={loading}>
+      <PageContainer
+        title={
+          <span>
+            {product?.name}
+            <Tag color={product?.status === 1 ? 'success' : 'default'} className="ml-2">
+              {product?.status === 1 ? '启用' : '禁用'}
+            </Tag>
+          </span>
+        }
+        onBack={() => navigate('/admin/products')}
+        extra={
+          product && (
+            <ProductForm
+              mode="edit"
+              initialValues={product}
+              onSuccess={loadProduct}
+              trigger={<a>编辑产品</a>}
+            />
+          )
+        }
+      >
+        {product && (
+          <Tabs
+            items={[
+              { key: 'roles', label: '角色', children: <RoleTab productCode={product.code} /> },
+              { key: 'perms', label: '权限', children: <PermTab productCode={product.code} /> },
+              { key: 'members', label: '成员', children: <MemberTab productCode={product.code} /> },
+            ]}
+          />
+        )}
+      </PageContainer>
+    </Spin>
+  );
 }

+ 94 - 1
src/pages/Admin/Product/index.tsx

@@ -1,3 +1,96 @@
+import { fetchInitialCredentials, fetchProductList, fetchUpdateProduct } from '@/services/product';
+import { ActionType, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { useIntl, useNavigate } from '@umijs/max';
+import { Button, Popconfirm, Tag, message } from 'antd';
+import { useRef, useState } from 'react';
+import { ProductForm } from './components/Form';
+import { CredentialModal } from './Detail/components/CredentialModal';
+
 export default function ProductPage() {
-  return null;
+  const intl = useIntl();
+  const navigate = useNavigate();
+  const actionRef = useRef<ActionType>();
+  const [credData, setCredData] = useState<API.FetchInitialCredentialsResp | null>(null);
+  const [credOpen, setCredOpen] = useState(false);
+
+  const handleCreateSuccess = async (ticket?: string) => {
+    actionRef.current?.reload();
+    if (!ticket) return;
+    const res = await fetchInitialCredentials({ ticket });
+    setCredData(res.data ?? null);
+    setCredOpen(true);
+  };
+
+  const handleToggleStatus = async (record: API.ProductItem) => {
+    await fetchUpdateProduct({
+      id: record.id,
+      name: record.name,
+      status: record.status === 1 ? 0 : 1,
+    });
+    message.success('操作成功');
+    actionRef.current?.reload();
+  };
+
+  const columns: ProColumns<API.ProductItem>[] = [
+    {
+      title: '名称',
+      dataIndex: 'name',
+      render: (_, r) => <a onClick={() => navigate(`/admin/products/${r.id}`)}>{r.name}</a>,
+    },
+    { title: 'Code', dataIndex: 'code', copyable: true },
+    { title: 'App Key', dataIndex: 'appKey', copyable: true },
+    {
+      title: '状态',
+      dataIndex: 'status',
+      render: (_, r) => (
+        <Tag color={r.status === 1 ? 'success' : 'default'}>{r.status === 1 ? '启用' : '禁用'}</Tag>
+      ),
+    },
+    { title: '创建时间', dataIndex: 'createTime', valueType: 'dateTime' },
+    {
+      title: '操作',
+      valueType: 'option',
+      render: (_, r) => [
+        <ProductForm
+          key="edit"
+          mode="edit"
+          initialValues={r}
+          onSuccess={() => actionRef.current?.reload()}
+          trigger={<a>编辑</a>}
+        />,
+        <Popconfirm
+          key="status"
+          title={r.status === 1 ? '确认禁用?' : '确认启用?'}
+          onConfirm={() => handleToggleStatus(r)}
+        >
+          <a className={r.status === 1 ? 'text-red-500' : ''}>{r.status === 1 ? '禁用' : '启用'}</a>
+        </Popconfirm>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer title={intl.formatMessage({ id: 'admin.product.title' })}>
+      <ProTable<API.ProductItem>
+        actionRef={actionRef}
+        columns={columns}
+        rowKey="id"
+        request={async ({ current, pageSize }) => {
+          const res = await fetchProductList({ page: current, pageSize });
+          return { data: res.data?.list ?? [], total: res.data?.total ?? 0, success: true };
+        }}
+        toolBarRender={() => [
+          <ProductForm
+            key="create"
+            mode="add"
+            onSuccess={handleCreateSuccess}
+            trigger={
+              <Button type="primary">{intl.formatMessage({ id: 'admin.product.create' })}</Button>
+            }
+          />,
+        ]}
+      />
+      <CredentialModal open={credOpen} data={credData} onClose={() => setCredOpen(false)} />
+    </PageContainer>
+  );
 }