feat(media-supply): add media resource supply marketplace
Desktop Client Build / Resolve Build Metadata (push) Successful in 43s
Frontend CI / Frontend (push) Successful in 3m49s
Backend CI / Backend (push) Failing after 7m10s
Desktop Client Build / Build Desktop Client (push) Successful in 23m4s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 28s

Introduce an end-to-end media-supply feature: tenant-side resource sync
service/worker backed by a Meijiequan supplier client, ops-side management
APIs, and admin/ops web views for resources, orders, favorites and
submission. Adds a shared digitocr helper, MediaSupply config blocks for
tenant and ops, shared types, and migrations for supplier media resources,
price overrides, customer visibility and order refunds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-29 23:17:01 +08:00
parent 607a3fffe7
commit 9d6181260a
121 changed files with 14909 additions and 36 deletions
+73 -4
View File
@@ -12,6 +12,8 @@ import type {
BrandLibrarySummary,
BrandRequest,
ChangePasswordRequest,
ClassifiedQuestion,
ClassifyQuestionsRequest,
Competitor,
CompetitorRequest,
ComplianceAckRecord,
@@ -24,6 +26,8 @@ import type {
CreateArticleRequest,
CreateKolPackageRequest,
CreateKolPromptRequest,
CreateMediaSupplyOrderRequest,
CreateMediaSupplyOrderResponse,
CreatePluginSessionRequest,
CreatePluginSessionResponse,
CreatePublishBatchRequest,
@@ -70,18 +74,28 @@ import type {
KolSubscriptionPromptCard,
KolSubscriptionPromptSchema,
KolWorkspaceCard,
ListCustomerSupplierMediaResourcesResponse,
ListDesktopPublishTasksParams,
ListMediaSupplyOrdersParams,
ListMediaSupplyOrdersResponse,
ListMediaSupplyWalletLedgersParams,
ListMediaSupplyWalletLedgersResponse,
ListSupplierMediaResourcesParams,
LoginRequest,
LoginResponse,
MaterializeQuestionsRequest,
MaterializeQuestionsResult,
MediaPlatform,
MediaSupplyOrderDetail,
MediaSupplySearchOptionsResponse,
MediaSupplySessionStatus,
MediaSupplyWalletStatus,
MonitoringCitationSummaryResponse,
MonitoringCollectNowResponse,
MonitoringDashboardCompositeResponse,
MonitoringQuestionDetailResponse,
PlatformAccount,
PasswordPublicKeyResponse,
PlatformAccount,
PromptRule,
PromptRuleGroup,
PromptRuleGroupRequest,
@@ -110,6 +124,7 @@ import type {
ScheduleTaskListResponse,
ScheduleTaskRequest,
ScheduleTaskStatusRequest,
SyncMediaSupplyBacklinksResponse,
TemplateAnalyzeTaskRequest,
TemplateAnalyzeTaskResultResponse,
TemplateAssistTaskCreateResponse,
@@ -131,10 +146,9 @@ import type {
UpdateQuestionRequest,
UserInfo,
WorkspaceOverview,
ClassifyQuestionsRequest,
ClassifiedQuestion,
} from '@geo/shared-types'
import { readStoredCurrentBrandId } from './current-brand'
import {
convertImageFileToWebp,
markImageUploadRequestFailed,
@@ -149,7 +163,6 @@ import {
readStoredSession,
setStoredTokens,
} from './session'
import { readStoredCurrentBrandId } from './current-brand'
const rawBaseURL = import.meta.env.VITE_API_BASE_URL ?? ''
const baseURL = normalizeApiBaseURL(rawBaseURL)
@@ -323,6 +336,7 @@ const currentBrandScopedPathPatterns = [
/^\/api\/tenant\/instant-tasks(?:\/|$)/,
/^\/api\/tenant\/publish-jobs(?:\/|$)/,
/^\/api\/tenant\/publish-tasks\/[^/]+\/retry(?:\/|$)/,
/^\/api\/tenant\/media-supply\/orders(?:\/|$)/,
/^\/api\/tenant\/prompt-rules(?:\/|$)/,
]
@@ -1204,6 +1218,61 @@ export const mediaApi = {
},
}
export const mediaSupplyApi = {
listResources(params: ListSupplierMediaResourcesParams = {}) {
return apiClient.get<ListCustomerSupplierMediaResourcesResponse>(
'/api/tenant/media-supply/resources',
{
params,
},
)
},
listResourcesByIds(ids: number[], modelId?: number) {
return apiClient.get<ListCustomerSupplierMediaResourcesResponse>(
'/api/tenant/media-supply/resources/by-ids',
{ params: { ids: ids.join(','), model_id: modelId } },
)
},
searchOptions(modelId?: number) {
return apiClient.get<MediaSupplySearchOptionsResponse>(
'/api/tenant/media-supply/resources/search-options',
{ params: { model_id: modelId } },
)
},
createOrder(payload: CreateMediaSupplyOrderRequest) {
return apiClient.post<CreateMediaSupplyOrderResponse, CreateMediaSupplyOrderRequest>(
'/api/tenant/media-supply/orders',
payload,
)
},
listOrders(params: ListMediaSupplyOrdersParams = {}) {
return apiClient.get<ListMediaSupplyOrdersResponse>('/api/tenant/media-supply/orders', {
params,
})
},
getOrder(id: number) {
return apiClient.get<MediaSupplyOrderDetail>(`/api/tenant/media-supply/orders/${id}`)
},
syncBacklinks() {
return apiClient.post<SyncMediaSupplyBacklinksResponse, Record<string, never>>(
'/api/tenant/media-supply/orders/sync-backlinks',
{},
)
},
wallet() {
return apiClient.get<MediaSupplyWalletStatus>('/api/tenant/media-supply/wallet')
},
listWalletLedgers(params: ListMediaSupplyWalletLedgersParams = {}) {
return apiClient.get<ListMediaSupplyWalletLedgersResponse>(
'/api/tenant/media-supply/wallet/ledgers',
{ params },
)
},
sessionStatus() {
return apiClient.get<MediaSupplySessionStatus>('/api/tenant/media-supply/supplier-session')
},
}
export const tenantAccountsApi = {
list() {
return apiClient.get<DesktopAccountInfo[]>('/api/tenant/accounts')