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
@@ -108,14 +108,19 @@ import {
type RichImageAlign,
} from '@/lib/milkdown/richImageBlock'
const props = defineProps<{
articleId: number
const props = withDefaults(defineProps<{
articleId?: number | null
title: string
modelValue: string
disabled?: boolean
toolbar?: boolean
aiOptimizeEnabled?: boolean
complianceViolations?: ComplianceViolation[]
uploadImage?: (file: File) => Promise<string>
}>()
}>(), {
toolbar: true,
aiOptimizeEnabled: true,
})
const emit = defineEmits<{
'update:title': [value: string]
@@ -532,6 +537,9 @@ function createCrepe(root: HTMLElement): Crepe {
featureConfigs: {
[CrepeFeature.Toolbar]: {
buildToolbar: (builder) => {
if (!canUseAiOptimize.value) {
return
}
builder.addGroup('ai-optimize', 'AI Optimize').addItem('ai-optimize', {
icon: AI_OPTIMIZE_TOOLBAR_ICON,
active: () => false,
@@ -604,6 +612,13 @@ let aiOptimizeIgnoreNextWindowPointerDown = false
const EDITOR_ACTION_MENU_DROPDOWN_SELECTOR = '.editor-action-menu__dropdown-overlay'
const tableContextHeaderRow = computed(() => tableContextMenu.value.rowIndex === 0)
const showEditorToolbar = computed(() => props.toolbar !== false)
const canUseAiOptimize = computed(
() =>
props.aiOptimizeEnabled !== false &&
Number.isInteger(props.articleId) &&
Number(props.articleId) > 0,
)
const aiOptimizeStreaming = computed(() => aiOptimizeStatus.value === 'generating')
const aiOptimizeHasPreview = computed(() => aiOptimizePreview.value.trim().length > 0)
const aiOptimizeUiText = computed(() => ({
@@ -1354,7 +1369,7 @@ function resolveAiOptimizePanelFrame(anchorRect: DOMRect): {
}
function openAiOptimizePanel(ctx: Ctx): void {
if (editorDisabled.value) {
if (editorDisabled.value || !canUseAiOptimize.value) {
return
}
@@ -1434,7 +1449,7 @@ function handleAiOptimizeStreamEvent(event: ArticleSelectionOptimizeStreamEvent)
}
async function generateAiOptimizePreview(): Promise<void> {
if (!aiOptimizePanel.value.open || editorDisabled.value) {
if (!canUseAiOptimize.value || !aiOptimizePanel.value.open || editorDisabled.value) {
return
}
@@ -1454,7 +1469,7 @@ async function generateAiOptimizePreview(): Promise<void> {
try {
await streamArticleSelectionOptimize(
props.articleId,
Number(props.articleId),
{
title: props.title,
markdown_content: props.modelValue,
@@ -2259,7 +2274,7 @@ function runTableContextAction(action: TableContextMenuAction): void {
'article-editor-canvas--ai-optimize-open': aiOptimizePanel.open,
}"
>
<div class="article-editor-canvas__toolbar">
<div v-if="showEditorToolbar" class="article-editor-canvas__toolbar">
<a-button size="small" type="text" @click="runCommand(undoCommand)">
<template #icon><IconFont type="icon-Undo" /></template>
</a-button>
@@ -2385,7 +2400,7 @@ function runTableContextAction(action: TableContextMenuAction): void {
/>
<EditorAiAssistPanel
v-if="aiOptimizePanel.open"
v-if="canUseAiOptimize && aiOptimizePanel.open"
v-model:prompt="aiOptimizeInstruction"
:x="aiOptimizePanel.x"
:y="aiOptimizePanel.y"
@@ -2417,7 +2432,7 @@ function runTableContextAction(action: TableContextMenuAction): void {
<CoverPickerModal
v-model:open="imagePickerOpen"
:article-id="articleId"
:article-id="articleId ?? null"
:platform-ids="[]"
:current-url="imagePickerCurrentUrl"
:current-asset-id="imagePickerCurrentAssetId"