Loading...
Loading...
Compare original and translation side by side
references/data-loading.mdreferences/forms.mdreferences/display-patterns.mdreferences/table-selection.mdreferences/navigation.mdreferences/typography.mdreferences/data-loading.mdreferences/forms.mdreferences/display-patterns.mdreferences/table-selection.mdreferences/navigation.mdreferences/typography.md// src/admin/lib/client.ts
import Medusa from "@medusajs/js-sdk"
export const sdk = new Medusa({
baseUrl: import.meta.env.VITE_BACKEND_URL || "/",
debug: import.meta.env.DEV,
auth: {
type: "session",
},
})// src/admin/lib/client.ts
import Medusa from "@medusajs/js-sdk"
export const sdk = new Medusa({
baseUrl: import.meta.env.VITE_BACKEND_URL || "/",
debug: import.meta.env.DEV,
auth: {
type: "session",
},
})undefinedundefined
**npm/yarn users:** DO NOT install these packages - already available.
**npm/yarn用户:** 无需安装这些包 - 已默认提供。| Priority | Category | Impact | Prefix |
|---|---|---|---|
| 1 | Data Loading | CRITICAL | |
| 2 | Design System | CRITICAL | |
| 3 | Data Display | HIGH (includes CRITICAL price rule) | |
| 4 | Typography | HIGH | |
| 5 | Forms & Modals | MEDIUM | |
| 6 | Selection Patterns | MEDIUM | |
| 优先级 | 类别 | 影响 | 前缀 |
|---|---|---|---|
| 1 | 数据加载 | 关键 | |
| 2 | 设计系统 | 关键 | |
| 3 | 数据展示 | 高(包含关键价格规则) | |
| 4 | 排版 | 高 | |
| 5 | 表单与模态框 | 中 | |
| 6 | 选择模式 | 中 | |
data-display-on-mountdata-separate-queriesdata-invalidate-displaydata-loading-statesdata-pnpm-install-firstdata-display-on-mountdata-separate-queriesdata-invalidate-displaydata-loading-statesdata-pnpm-install-firstdesign-semantic-colorsdesign-spacingdesign-button-sizedesign-medusa-componentsdesign-semantic-colorsdesign-spacingdesign-button-sizedesign-medusa-componentsdisplay-price-formatdisplay-price-formattypo-text-componenttypo-labels<Text size="small" leading="compact" weight="plus">typo-descriptions<Text size="small" leading="compact" className="text-ui-fg-subtle">typo-no-heading-widgetstypo-text-componenttypo-labels<Text size="small" leading="compact" weight="plus">typo-descriptions<Text size="small" leading="compact" className="text-ui-fg-subtle">typo-no-heading-widgetsform-focusmodal-createform-drawer-editform-disable-pendingform-show-loadingform-focusmodal-createform-drawer-editform-disable-pendingform-show-loadingselect-small-datasetsselect-large-datasetsselect-search-configselect-small-datasetsselect-large-datasetsselect-search-config// ✅ CORRECT - Separate queries with proper responsibilities
const RelatedProductsWidget = ({ data: product }) => {
const [modalOpen, setModalOpen] = useState(false)
// Display query - loads on mount
const { data: displayProducts } = useQuery({
queryFn: () => fetchSelectedProducts(selectedIds),
queryKey: ["related-products-display", product.id],
// No 'enabled' condition - loads immediately
})
// Modal query - loads when needed
const { data: modalProducts } = useQuery({
queryFn: () => sdk.admin.product.list({ limit: 10, offset: 0 }),
queryKey: ["products-selection"],
enabled: modalOpen, // OK for modal-only data
})
// Mutation with proper invalidation
const updateProduct = useMutation({
mutationFn: updateFunction,
onSuccess: () => {
// Invalidate display data query to refresh UI
queryClient.invalidateQueries({ queryKey: ["related-products-display", product.id] })
// Also invalidate the entity query
queryClient.invalidateQueries({ queryKey: ["product", product.id] })
// Note: No need to invalidate modal selection query
},
})
return (
<Container>
{/* Display uses displayProducts */}
{displayProducts?.map(p => <div key={p.id}>{p.title}</div>)}
<FocusModal open={modalOpen} onOpenChange={setModalOpen}>
{/* Modal uses modalProducts */}
</FocusModal>
</Container>
)
}
// ❌ WRONG - Single query with conditional loading
const BrokenWidget = ({ data: product }) => {
const [modalOpen, setModalOpen] = useState(false)
const { data } = useQuery({
queryFn: () => sdk.admin.product.list(),
enabled: modalOpen, // ❌ Display breaks on page refresh!
})
// Trying to display from modal query
const displayItems = data?.filter(item => ids.includes(item.id)) // No data until modal opens
return <div>{displayItems?.map(...)}</div> // Empty on mount!
}// ✅ 正确 - 分离查询,明确职责
const RelatedProductsWidget = ({ data: product }) => {
const [modalOpen, setModalOpen] = useState(false)
// 展示数据查询 - 组件挂载时加载
const { data: displayProducts } = useQuery({
queryFn: () => fetchSelectedProducts(selectedIds),
queryKey: ["related-products-display", product.id],
// 无'enabled'条件 - 立即加载
})
// 模态框数据查询 - 按需加载
const { data: modalProducts } = useQuery({
queryFn: () => sdk.admin.product.list({ limit: 10, offset: 0 }),
queryKey: ["products-selection"],
enabled: modalOpen, // 仅模态框数据可使用条件加载
})
// 带正确失效策略的变更操作
const updateProduct = useMutation({
mutationFn: updateFunction,
onSuccess: () => {
// 使展示数据查询失效以刷新UI
queryClient.invalidateQueries({ queryKey: ["related-products-display", product.id] })
// 同时使实体查询失效
queryClient.invalidateQueries({ queryKey: ["product", product.id] })
// 注意:无需使模态框选择数据查询失效
},
})
return (
<Container>
{/* 展示区域使用displayProducts */}
{displayProducts?.map(p => <div key={p.id}>{p.title}</div>)}
<FocusModal open={modalOpen} onOpenChange={setModalOpen}>
{/* 模态框使用modalProducts */}
</FocusModal>
</Container>
)
}
// ❌ 错误 - 使用单个查询同时处理展示和模态框数据
const BrokenWidget = ({ data: product }) => {
const [modalOpen, setModalOpen] = useState(false)
const { data } = useQuery({
queryFn: () => sdk.admin.product.list(),
enabled: modalOpen, // ❌ 页面刷新时展示功能失效!
})
// 尝试从模态框查询中获取展示数据
const displayItems = data?.filter(item => ids.includes(item.id)) // 模态框未打开时无数据
return <div>{displayItems?.map(...)}</div> // 挂载时为空!
}references/data-loading.md - useQuery/useMutation patterns, cache invalidation
references/forms.md - FocusModal/Drawer patterns, validation
references/table-selection.md - Complete DataTable selection pattern
references/display-patterns.md - Lists, tables, cards for entities
references/typography.md - Text component patterns
references/navigation.md - Link, useNavigate, useParams patternsreferences/data-loading.md - useQuery/useMutation模式、缓存失效
references/forms.md - FocusModal/Drawer模式、验证
references/table-selection.md - 完整的DataTable选择模式
references/display-patterns.md - 实体的列表、表格、卡片展示模式
references/typography.md - Text组件使用模式
references/navigation.md - Link, useNavigate, useParams使用模式// Fetch from custom backend route
const { data } = useQuery({
queryKey: ["reviews", product.id],
queryFn: () => sdk.client.fetch(`/admin/products/${product.id}/reviews`),
})
// Mutation to custom backend route
const createReview = useMutation({
mutationFn: (data) => sdk.client.fetch("/admin/reviews", {
method: "POST",
body: data
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["reviews", product.id] })
toast.success("Review created")
},
})building-with-medusa// 从自定义后端路由获取数据
const { data } = useQuery({
queryKey: ["reviews", product.id],
queryFn: () => sdk.client.fetch(`/admin/products/${product.id}/reviews`),
})
// 调用自定义后端路由执行变更
const createReview = useMutation({
mutationFn: (data) => sdk.client.fetch("/admin/reviews", {
method: "POST",
body: data
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["reviews", product.id] })
toast.success("Review created")
},
})building-with-medusa// src/admin/widgets/custom-widget.tsx
import { defineWidgetConfig } from "@medusajs/admin-sdk"
import { DetailWidgetProps } from "@medusajs/framework/types"
const MyWidget = ({ data }: DetailWidgetProps<HttpTypes.AdminProduct>) => {
return <Container>Widget content</Container>
}
export const config = defineWidgetConfig({
zone: "product.details.after",
})
export default MyWidget// src/admin/routes/custom-page/page.tsx
import { defineRouteConfig } from "@medusajs/admin-sdk"
const CustomPage = () => {
return <div>Page content</div>
}
export const config = defineRouteConfig({
label: "Custom Page",
})
export default CustomPage// src/admin/widgets/custom-widget.tsx
import { defineWidgetConfig } from "@medusajs/admin-sdk"
import { DetailWidgetProps } from "@medusajs/framework/types"
const MyWidget = ({ data }: DetailWidgetProps<HttpTypes.AdminProduct>) => {
return <Container>Widget content</Container>
}
export const config = defineWidgetConfig({
zone: "product.details.after",
})
export default MyWidget// src/admin/routes/custom-page/page.tsx
import { defineRouteConfig } from "@medusajs/admin-sdk"
const CustomPage = () => {
return <div>Page content</div>
}
export const config = defineRouteConfig({
label: "Custom Page",
})
export default CustomPageenabledenablednpm run dev # or pnpm dev / yarn devnpm run dev # 或 pnpm dev / yarn devproduct.details.afterlabelhttp://localhost:9000/app/[your-route-path]product.details.afterlabelhttp://localhost:9000/app/[your-route-path]undefinedundefinedundefinedundefined