feat(app): 支持多平台包名配置和应用信息编辑功能
- 后端增加至少填写一个平台包名的验证逻辑 - 前端调整应用数据模型,将包名字段改为可选类型 - 添加应用详情页的编辑功能和表单验证 - 优化应用列表页包名显示逻辑,支持多平台包名展示 - 重构应用配置指引页面,按平台分类展示商店配置指南 - 在版本管理页面增加包名配置检查和相应提示 - 新增应用信息编辑弹窗组件和相关业务逻辑
这个提交包含在:
父节点
0a8011d9be
当前提交
8abcf59147
@ -3,7 +3,7 @@ import client from './client'
|
||||
export interface App {
|
||||
id: string
|
||||
tenantId: string
|
||||
packageName: string
|
||||
packageName?: string
|
||||
iosBundleId?: string
|
||||
harmonyBundleName?: string
|
||||
name: string
|
||||
@ -15,7 +15,7 @@ export interface App {
|
||||
}
|
||||
|
||||
export interface CreateAppRequest {
|
||||
packageName: string
|
||||
packageName?: string
|
||||
iosBundleId?: string
|
||||
harmonyBundleName?: string
|
||||
name: string
|
||||
|
||||
@ -3,11 +3,17 @@
|
||||
<el-page-header @back="$router.back()" :content="app.name" style="margin-bottom:24px" />
|
||||
|
||||
<el-card class="info-card" style="margin-bottom:16px">
|
||||
<template #header>
|
||||
<div style="display:flex;justify-content:space-between;align-items:center">
|
||||
<span>应用信息</span>
|
||||
<el-button size="small" @click="openEditDialog">编辑</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="isMobile ? 1 : 2" border>
|
||||
<el-descriptions-item label="应用名称">{{ app.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="Android 包名">{{ app.packageName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="iOS Bundle ID">{{ app.iosBundleId ?? '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="鸿蒙 Bundle Name">{{ app.harmonyBundleName ?? '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="iOS Bundle ID">{{ app.iosBundleId || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="鸿蒙 Bundle Name">{{ app.harmonyBundleName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="AppKey">
|
||||
<el-text class="mono">{{ app.appKey }}</el-text>
|
||||
<el-button link @click="copy(app.appKey)"><el-icon><CopyDocument /></el-icon></el-button>
|
||||
@ -22,7 +28,7 @@
|
||||
</el-button>
|
||||
<el-button link type="warning" @click="openVerifyDialog('RESET_SECRET')">重置</el-button>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="简述" :span="2">{{ app.description ?? '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="简述" :span="2">{{ app.description || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="Config 文件">
|
||||
<el-button size="small" type="primary" plain @click="downloadConfigFile">下载</el-button>
|
||||
<el-button size="small" plain @click="regenerateConfigFile" :loading="regenerating">重新生成</el-button>
|
||||
@ -193,6 +199,35 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- Edit App Info Dialog -->
|
||||
<el-dialog v-model="showEditDialog" title="编辑应用信息" :width="dialogWidth">
|
||||
<el-form label-width="120px" :model="editForm">
|
||||
<el-form-item label="应用名称">
|
||||
<el-input v-model="editForm.name" placeholder="应用名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Android 包名">
|
||||
<el-input v-model="editForm.packageName" placeholder="com.example.app(可选)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="iOS Bundle ID">
|
||||
<el-input v-model="editForm.iosBundleId" placeholder="com.example.app(可选)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="鸿蒙 Bundle Name">
|
||||
<el-input v-model="editForm.harmonyBundleName" placeholder="com.example.app(可选)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="简述">
|
||||
<el-input v-model="editForm.description" type="textarea" :rows="3" placeholder="应用简述(可选)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="图标 URL">
|
||||
<el-input v-model="editForm.iconUrl" placeholder="https://cdn.example.com/icon.png(可选)" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="form-tip" style="margin-top:-8px">至少填写一个平台的包名</div>
|
||||
<template #footer>
|
||||
<el-button @click="showEditDialog = false">取消</el-button>
|
||||
<el-button type="primary" :loading="savingEdit" @click="submitEdit">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- Activation Request Dialog -->
|
||||
<el-dialog v-model="showActivationDialog" title="申请开通服务" :width="dialogWidth">
|
||||
<el-form label-width="80px">
|
||||
@ -243,6 +278,59 @@ const submittingActivation = ref(false)
|
||||
const activationForm = ref({ platform: '', serviceType: '', reason: '' })
|
||||
const regenerating = ref(false)
|
||||
|
||||
const showEditDialog = ref(false)
|
||||
const savingEdit = ref(false)
|
||||
const editForm = ref({
|
||||
name: '',
|
||||
packageName: '',
|
||||
iosBundleId: '',
|
||||
harmonyBundleName: '',
|
||||
description: '',
|
||||
iconUrl: '',
|
||||
})
|
||||
|
||||
function openEditDialog() {
|
||||
if (!app.value) return
|
||||
editForm.value = {
|
||||
name: app.value.name || '',
|
||||
packageName: app.value.packageName || '',
|
||||
iosBundleId: app.value.iosBundleId || '',
|
||||
harmonyBundleName: app.value.harmonyBundleName || '',
|
||||
description: app.value.description || '',
|
||||
iconUrl: app.value.iconUrl || '',
|
||||
}
|
||||
showEditDialog.value = true
|
||||
}
|
||||
|
||||
async function submitEdit() {
|
||||
if (!editForm.value.name.trim()) {
|
||||
ElMessage.warning('应用名称不能为空')
|
||||
return
|
||||
}
|
||||
if (!editForm.value.packageName.trim() && !editForm.value.iosBundleId.trim() && !editForm.value.harmonyBundleName.trim()) {
|
||||
ElMessage.warning('至少填写一个平台的包名')
|
||||
return
|
||||
}
|
||||
savingEdit.value = true
|
||||
try {
|
||||
await appApi.update(app.value!.appKey, {
|
||||
packageName: editForm.value.packageName.trim() || undefined,
|
||||
name: editForm.value.name.trim(),
|
||||
iosBundleId: editForm.value.iosBundleId.trim() || undefined,
|
||||
harmonyBundleName: editForm.value.harmonyBundleName.trim() || undefined,
|
||||
description: editForm.value.description.trim() || undefined,
|
||||
iconUrl: editForm.value.iconUrl.trim() || undefined,
|
||||
})
|
||||
ElMessage.success('应用信息已更新')
|
||||
showEditDialog.value = false
|
||||
await loadData()
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.response?.data?.message || '保存失败')
|
||||
} finally {
|
||||
savingEdit.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function isServiceEnabled(svcType: string) {
|
||||
return services.value.some(s => s.serviceType === svcType && s.enabled)
|
||||
}
|
||||
@ -435,6 +523,7 @@ onBeforeUnmount(() => {
|
||||
|
||||
<style scoped>
|
||||
.mono { font-family: monospace; font-size: 12px; }
|
||||
.form-tip { font-size: 12px; color: var(--el-text-color-secondary); margin-top: 4px; }
|
||||
.info-card :deep(.el-descriptions__body) {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
@ -10,7 +10,11 @@
|
||||
<div class="table-wrap">
|
||||
<el-table :data="apps" v-loading="loading" style="width:100%">
|
||||
<el-table-column prop="name" label="应用名称" min-width="120" />
|
||||
<el-table-column prop="packageName" label="包名" min-width="160" />
|
||||
<el-table-column label="包名" min-width="160">
|
||||
<template #default="{ row }">
|
||||
{{ row.packageName || row.iosBundleId || row.harmonyBundleName || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="appKey" label="AppKey" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="createdAt" label="创建时间" width="180">
|
||||
<template #default="{ row }">{{ formatDate(row.createdAt) }}</template>
|
||||
@ -26,22 +30,23 @@
|
||||
|
||||
<el-dialog v-model="showCreate" title="创建应用" :width="dialogWidth">
|
||||
<el-form ref="createFormRef" :model="createForm" :rules="createRules" label-position="top">
|
||||
<el-form-item label="Android 包名" prop="packageName">
|
||||
<el-input v-model="createForm.packageName" placeholder="com.example.app" />
|
||||
<el-form-item label="应用名称" prop="name">
|
||||
<el-input v-model="createForm.name" placeholder="我的应用" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Android 包名">
|
||||
<el-input v-model="createForm.packageName" placeholder="com.example.app(可选)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="iOS Bundle ID">
|
||||
<el-input v-model="createForm.iosBundleId" placeholder="com.example.app" />
|
||||
<el-input v-model="createForm.iosBundleId" placeholder="com.example.app(可选)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="鸿蒙 Bundle Name">
|
||||
<el-input v-model="createForm.harmonyBundleName" placeholder="com.example.app" />
|
||||
</el-form-item>
|
||||
<el-form-item label="应用名称" prop="name">
|
||||
<el-input v-model="createForm.name" />
|
||||
<el-input v-model="createForm.harmonyBundleName" placeholder="com.example.app(可选)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="简述">
|
||||
<el-input v-model="createForm.description" type="textarea" :rows="3" />
|
||||
<el-input v-model="createForm.description" type="textarea" :rows="3" placeholder="应用简述(可选)" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="create-tip">至少填写一个平台的包名</div>
|
||||
<template #footer>
|
||||
<el-button @click="showCreate = false">取消</el-button>
|
||||
<el-button type="primary" :loading="creating" @click="handleCreate">确定</el-button>
|
||||
@ -67,7 +72,6 @@ const dialogWidth = computed(() => (isMobile.value ? 'calc(100vw - 24px)' : '480
|
||||
|
||||
const createForm = reactive({ packageName: '', iosBundleId: '', harmonyBundleName: '', name: '', description: '' })
|
||||
const createRules: FormRules = {
|
||||
packageName: [{ required: true, message: '请输入包名' }],
|
||||
name: [{ required: true, message: '请输入应用名' }],
|
||||
}
|
||||
|
||||
@ -83,9 +87,19 @@ async function loadApps() {
|
||||
|
||||
async function handleCreate() {
|
||||
await createFormRef.value?.validate()
|
||||
if (!createForm.packageName.trim() && !createForm.iosBundleId.trim() && !createForm.harmonyBundleName.trim()) {
|
||||
ElMessage.warning('至少填写一个平台的包名')
|
||||
return
|
||||
}
|
||||
creating.value = true
|
||||
try {
|
||||
await appApi.create(createForm)
|
||||
await appApi.create({
|
||||
name: createForm.name.trim(),
|
||||
packageName: createForm.packageName.trim() || undefined,
|
||||
iosBundleId: createForm.iosBundleId.trim() || undefined,
|
||||
harmonyBundleName: createForm.harmonyBundleName.trim() || undefined,
|
||||
description: createForm.description.trim() || undefined,
|
||||
})
|
||||
showCreate.value = false
|
||||
ElMessage.success('应用创建成功')
|
||||
loadApps()
|
||||
@ -132,6 +146,12 @@ onBeforeUnmount(() => {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.create-tip {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-top: -8px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.page-header {
|
||||
|
||||
@ -1,65 +1,57 @@
|
||||
<template>
|
||||
<div class="store-guide-page">
|
||||
<el-page-header @back="$router.back()" :content="`应用配置指引 — ${appKey}`" />
|
||||
<el-page-header @back="$router.back()" content="应用配置指引" />
|
||||
|
||||
<el-alert
|
||||
title="各应用商店的凭据配置说明。配置完成后回到版本管理页提交审核。"
|
||||
type="info"
|
||||
show-icon
|
||||
:closable="false"
|
||||
style="margin: 16px 0;"
|
||||
/>
|
||||
|
||||
<div class="guide-layout">
|
||||
<!-- 左侧厂商列表 -->
|
||||
<div class="guide-sidebar">
|
||||
<div class="guide-sidebar-group" v-for="group in GUIDE_GROUPS" :key="group.label">
|
||||
<div class="guide-sidebar-group-label">{{ group.label }}</div>
|
||||
<!-- 平台选择 -->
|
||||
<div class="platform-selector">
|
||||
<div
|
||||
v-for="store in group.stores"
|
||||
:key="store.type"
|
||||
class="guide-sidebar-item"
|
||||
:class="{ active: activeStoreType === store.type }"
|
||||
@click="activeStoreType = store.type"
|
||||
v-for="p in PLATFORMS"
|
||||
:key="p.key"
|
||||
class="platform-card"
|
||||
:class="{ active: activePlatform === p.key }"
|
||||
@click="activePlatform = p.key"
|
||||
>
|
||||
<span class="guide-sidebar-label">{{ store.label }}</span>
|
||||
<div class="platform-icon">{{ p.icon }}</div>
|
||||
<div class="platform-label">{{ p.label }}</div>
|
||||
<div class="platform-desc">{{ p.desc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 商店指引列表 -->
|
||||
<div class="guide-list">
|
||||
<div v-for="store in currentStores" :key="store.type" class="guide-card">
|
||||
<div class="guide-card-left">
|
||||
<div class="guide-card-header">
|
||||
<h3 class="guide-card-title">{{ store.label }}</h3>
|
||||
<p class="guide-card-subtitle">{{ store.subtitle }}</p>
|
||||
</div>
|
||||
|
||||
<div class="guide-steps">
|
||||
<div v-for="(step, i) in store.steps" :key="i" class="guide-step">
|
||||
<div class="guide-step-num">{{ i + 1 }}</div>
|
||||
<div class="guide-step-content">
|
||||
<div class="guide-step-title">{{ step.title }}</div>
|
||||
<div class="guide-step-desc">{{ step.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧内容区 -->
|
||||
<div class="guide-content" v-if="activeStore">
|
||||
<div class="guide-content-header">
|
||||
<div class="guide-content-title">
|
||||
<span>{{ activeStore.label }}</span>
|
||||
</div>
|
||||
<p class="guide-subtitle">{{ activeStore.subtitle }}</p>
|
||||
</div>
|
||||
|
||||
<div class="guide-content-body">
|
||||
<div class="guide-steps-wrap">
|
||||
<el-steps direction="vertical" :active="activeStore.steps.length" finish-status="success">
|
||||
<el-step
|
||||
v-for="step in activeStore.steps"
|
||||
:key="step.title"
|
||||
:title="step.title"
|
||||
:description="step.description"
|
||||
/>
|
||||
</el-steps>
|
||||
<p class="guide-hint">{{ activeStore.hint }}</p>
|
||||
</div>
|
||||
|
||||
<div class="guide-image-wrap" v-if="activeStore.image">
|
||||
<img :src="activeStore.image" :alt="activeStore.label + ' 配置截图'" class="guide-image" />
|
||||
</div>
|
||||
<div class="guide-hint" v-if="store.hint">
|
||||
<span class="guide-hint-icon">💡</span>
|
||||
{{ store.hint }}
|
||||
</div>
|
||||
|
||||
<div class="guide-footer">
|
||||
<el-link :href="activeStore.url" target="_blank" type="primary">
|
||||
{{ activeStore.urlLabel }}
|
||||
<el-link :href="store.url" target="_blank" type="primary">
|
||||
{{ store.urlLabel }} →
|
||||
</el-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="guide-card-right" v-if="store.image">
|
||||
<img :src="store.image" :alt="store.label + ' 配置截图'" class="guide-screenshot" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -80,6 +72,7 @@ interface StoreGuide {
|
||||
type: string
|
||||
label: string
|
||||
subtitle: string
|
||||
platform: string
|
||||
url: string
|
||||
urlLabel: string
|
||||
steps: { title: string; description: string }[]
|
||||
@ -87,286 +80,308 @@ interface StoreGuide {
|
||||
image?: string
|
||||
}
|
||||
|
||||
const PLATFORMS = [
|
||||
{ key: 'android', label: 'Android', icon: '🤖', desc: '华为 / 小米 / OPPO / vivo / 荣耀 / Google Play' },
|
||||
{ key: 'ios', label: 'iOS', icon: '🍎', desc: 'Apple App Store' },
|
||||
{ key: 'harmony', label: '鸿蒙', icon: '🔷', desc: 'HarmonyOS 应用市场' },
|
||||
{ key: 'other', label: '通知配置', icon: '🔔', desc: '审核状态回调通知' },
|
||||
]
|
||||
|
||||
const STORE_GUIDES: StoreGuide[] = [
|
||||
{
|
||||
type: 'HUAWEI',
|
||||
label: '华为应用市场',
|
||||
type: 'HUAWEI', label: '华为应用市场', platform: 'android',
|
||||
subtitle: 'AppGallery Connect API 凭据',
|
||||
url: 'https://developer.huawei.com/consumer/cn/doc/AppGallery-connect-Guides/agcapi-getstarted-0000001111845114',
|
||||
urlLabel: '查看华为官方文档',
|
||||
steps: [
|
||||
{ title: '进入 AppGallery Connect', description: '在华为开发者平台找到目标应用。' },
|
||||
{ title: '创建 Connect API 凭据', description: '进入开发工具 → Connect API,创建服务端凭据并选择 APP 管理员角色。' },
|
||||
{ title: '填写 Client ID / Client Secret', description: '复制凭据信息保存到租户平台的凭据配置中。' },
|
||||
{ title: '进入 AppGallery Connect', description: '在华为开发者平台(developer.huawei.com)找到目标应用。' },
|
||||
{ title: '创建 Connect API 凭据', description: '进入"开发" → "API 管理" → 开启 Connect API,创建服务端凭据并选择 APP 管理员角色。' },
|
||||
{ title: '复制 Client ID 和 Client Secret', description: '创建成功后复制凭据,保存到租户平台的"凭据配置"中。' },
|
||||
],
|
||||
hint: '配置后可自动上传 APK 并提交审核,支持回调跟踪审核状态。',
|
||||
hint: '配置后可自动上传 APK 并提交审核,支持轮询跟踪审核状态。',
|
||||
image: huaweiGuideImage,
|
||||
},
|
||||
{
|
||||
type: 'MI',
|
||||
label: '小米应用商店',
|
||||
type: 'MI', label: '小米应用商店', platform: 'android',
|
||||
subtitle: '自动发布接口的账号与私钥',
|
||||
url: 'https://dev.mi.com/distribute/doc/details?pId=1134',
|
||||
urlLabel: '查看小米官方文档',
|
||||
steps: [
|
||||
{ title: '进入应用游戏管理', description: '在控制台选择目标应用。' },
|
||||
{ title: '打开自动发布接口', description: '下载公钥文件并准备 RSA 私钥。' },
|
||||
{ title: '填写用户名 / 公钥 / 私钥', description: '保存的是服务端上传所需凭据。' },
|
||||
{ title: '进入应用游戏管理', description: '在小米开放平台控制台选择目标应用。' },
|
||||
{ title: '打开自动发布接口', description: '进入"版本管理" → "自动发布接口",下载公钥文件并准备 RSA 私钥。' },
|
||||
{ title: '录入用户名和私钥', description: '将用户名、公钥证书内容、RSA 私钥分别保存到凭据配置中。' },
|
||||
],
|
||||
hint: '字段为 username / publicKey / privateKey,与后端服务一致。',
|
||||
hint: '字段为 username / publicKey / privateKey,与后端上传服务一致。',
|
||||
image: miGuideImage,
|
||||
},
|
||||
{
|
||||
type: 'OPPO',
|
||||
label: 'OPPO 软件商店',
|
||||
type: 'OPPO', label: 'OPPO 软件商店', platform: 'android',
|
||||
subtitle: '我的 API 服务端应用凭据',
|
||||
url: 'https://open.oppomobile.com/new/developmentDoc/info?id=11119',
|
||||
urlLabel: '查看 OPPO 官方文档',
|
||||
steps: [
|
||||
{ title: '进入"我的 API"', description: '确认当前应用拥有服务端应用能力。' },
|
||||
{ title: '新建服务端应用', description: '按平台要求创建接口凭据。' },
|
||||
{ title: '填写 Client ID / Client Secret', description: '对应凭据配置中的两个字段。' },
|
||||
{ title: '进入"我的 API"', description: '在 OPPO 开放平台确认当前应用拥有服务端应用能力。' },
|
||||
{ title: '新建服务端应用', description: '点击"创建服务端应用",按平台要求完成凭据申请。' },
|
||||
{ title: '复制 Client ID 和 Client Secret', description: '创建成功后复制两个值,保存到凭据配置中。' },
|
||||
],
|
||||
hint: '字段与后端 submitToOppo 读取逻辑一致。',
|
||||
image: oppoGuideImage,
|
||||
},
|
||||
{
|
||||
type: 'VIVO',
|
||||
label: 'vivo 应用商店',
|
||||
type: 'VIVO', label: 'vivo 应用商店', platform: 'android',
|
||||
subtitle: 'API 管理中的 access_key / access_secret',
|
||||
url: 'https://dev.vivo.com.cn/documentCenter/doc/326',
|
||||
urlLabel: '查看 vivo 官方文档',
|
||||
steps: [
|
||||
{ title: '进入 API 管理', description: '找到当前应用对应的接口管理入口。' },
|
||||
{ title: '激活后再读取密钥', description: '首次启用后可能需要刷新页面。' },
|
||||
{ title: '填写 Access Key / Access Secret', description: '与后端提交服务字段保持一致。' },
|
||||
{ title: '进入 API 管理', description: '在 vivo 开放平台找到当前应用对应的接口管理入口。' },
|
||||
{ title: '激活并读取密钥', description: '首次启用 API 后需要刷新页面,然后复制 access_key 和 access_secret。' },
|
||||
{ title: '保存到凭据配置', description: '将 Access Key 和 Access Secret 填入凭据配置中。' },
|
||||
],
|
||||
hint: '注意请求频率限制,状态拉取需做节流。',
|
||||
hint: '注意请求频率限制,审核状态轮询已内置节流。',
|
||||
image: vivoGuideImage,
|
||||
},
|
||||
{
|
||||
type: 'HONOR',
|
||||
label: '荣耀应用市场',
|
||||
type: 'HONOR', label: '荣耀应用市场', platform: 'android',
|
||||
subtitle: '管理中心 API 凭据',
|
||||
url: 'https://developer.honor.com/cn',
|
||||
urlLabel: '查看荣耀官方文档',
|
||||
steps: [
|
||||
{ title: '进入管理中心', description: '打开荣耀开发者后台并进入凭证页。' },
|
||||
{ title: '申请凭证', description: '创建用于服务端上传的 API 凭据。' },
|
||||
{ title: '填写 Client ID / Client Secret', description: '与华为同类流程接入。' },
|
||||
{ title: '进入管理中心', description: '打开荣耀开发者后台(developer.honor.com)并进入凭证页。' },
|
||||
{ title: '申请服务端凭据', description: '创建用于服务端上传的 API 凭据。' },
|
||||
{ title: '复制 Client ID 和 Client Secret', description: '与华为同类流程,复制凭据保存到配置中。' },
|
||||
],
|
||||
hint: '与后端 Honor 提交流程完全一致。',
|
||||
hint: '与后端 Honor 提交流程完全一致;跳转页可选填写。',
|
||||
image: honorGuideImage,
|
||||
},
|
||||
{
|
||||
type: 'APP_STORE',
|
||||
label: 'Apple App Store',
|
||||
subtitle: 'App Store Connect API Key',
|
||||
url: 'https://developer.apple.com/documentation/appstoreconnectapi',
|
||||
urlLabel: '查看 Apple 官方文档',
|
||||
steps: [
|
||||
{ title: '创建 API Key', description: '进入 App Store Connect → 用户和访问 → 整合 → 点击 "+" 创建密钥。' },
|
||||
{ title: '下载 .p8 私钥', description: '创建后立即下载私钥文件(只能下载一次),复制完整内容。' },
|
||||
{ title: '填写 Issuer ID / Key ID / 私钥', description: 'Issuer ID 在整合页面顶部,Key ID 在密钥列表中。' },
|
||||
],
|
||||
hint: '配置 API Key 后可自动查询审核状态。私钥为 .p8 文件的完整内容,包含 BEGIN/END 标记。',
|
||||
},
|
||||
{
|
||||
type: 'HARMONY_APP',
|
||||
label: '鸿蒙应用',
|
||||
subtitle: 'AppGallery Connect API 凭据',
|
||||
url: 'https://developer.huawei.com/consumer/cn/doc/AppGallery-connect-Guides/agcapi-getstarted-0000001111845114',
|
||||
urlLabel: '查看鸿蒙官方文档',
|
||||
steps: [
|
||||
{ title: '进入 AppGallery Connect', description: '在华为开发者平台找到目标鸿蒙应用。' },
|
||||
{ title: '创建 Connect API 凭据', description: '进入开发工具 → Connect API,创建服务端凭据。' },
|
||||
{ title: '填写 Client ID / Client Secret', description: '复制凭据信息保存到凭据配置中。' },
|
||||
],
|
||||
hint: '配置 API 凭证后可自动查询审核状态。鸿蒙应用审核流程与华为应用市场共用 AppGallery Connect 平台。',
|
||||
},
|
||||
{
|
||||
type: 'GOOGLE_PLAY',
|
||||
label: 'Google Play',
|
||||
type: 'GOOGLE_PLAY', label: 'Google Play', platform: 'android',
|
||||
subtitle: '服务账号 JSON 凭据',
|
||||
url: 'https://developer.android.com/google/play/developer-api',
|
||||
urlLabel: '查看 Google Play 官方文档',
|
||||
steps: [
|
||||
{ title: '创建服务账号', description: '从 Google Cloud 控制台获取服务账号 JSON。' },
|
||||
{ title: '授予 Play 管理权限', description: '把该服务账号绑定到目标应用的发布权限。' },
|
||||
{ title: '粘贴 JSON 内容', description: '将完整的 JSON 内容保存到凭据配置中。' },
|
||||
{ title: '创建服务账号', description: '从 Google Cloud 控制台创建服务账号并下载 JSON 密钥文件。' },
|
||||
{ title: '授予 Play 管理权限', description: '在 Google Play Console 中将该服务账号绑定到目标应用,授予发布权限。' },
|
||||
{ title: '粘贴 JSON 内容', description: '将完整的 JSON 文件内容保存到凭据配置中。' },
|
||||
],
|
||||
hint: '适合由服务端或脚本自动提交审核。',
|
||||
},
|
||||
{
|
||||
type: 'APP_STORE', label: 'Apple App Store', platform: 'ios',
|
||||
subtitle: 'App Store Connect API Key',
|
||||
url: 'https://developer.apple.com/documentation/appstoreconnectapi',
|
||||
urlLabel: '查看 Apple 官方文档',
|
||||
steps: [
|
||||
{ title: '创建 API Key', description: '进入 App Store Connect → 用户和访问 → 整合 → 点击"+"创建密钥,选择"管理"权限。' },
|
||||
{ title: '下载 .p8 私钥', description: '创建后立即下载私钥文件(只能下载一次),用文本编辑器打开并复制完整内容。' },
|
||||
{ title: '填写 Issuer ID / Key ID / 私钥', description: 'Issuer ID 在整合页面顶部,Key ID 在密钥列表中,私钥包含 BEGIN/END 标记。' },
|
||||
],
|
||||
hint: '配置后可自动查询 App Store 审核状态。私钥为 .p8 文件的完整 PEM 内容。',
|
||||
},
|
||||
{
|
||||
type: 'HARMONY_APP', label: '鸿蒙应用市场', platform: 'harmony',
|
||||
subtitle: 'AppGallery Connect API 凭据',
|
||||
url: 'https://developer.huawei.com/consumer/cn/doc/AppGallery-connect-Guides/agcapi-getstarted-0000001111845114',
|
||||
urlLabel: '查看鸿蒙官方文档',
|
||||
steps: [
|
||||
{ title: '进入 AppGallery Connect', description: '在华为开发者平台找到目标鸿蒙应用(与华为应用市场共用平台)。' },
|
||||
{ title: '创建 Connect API 凭据', description: '进入"开发" → "API 管理" → 开启 Connect API,创建服务端凭据。' },
|
||||
{ title: '复制 Client ID 和 Client Secret', description: '复制凭据信息保存到凭据配置中。' },
|
||||
],
|
||||
hint: '鸿蒙应用审核流程与华为应用市场共用 AppGallery Connect 平台。配置后可自动查询审核状态。',
|
||||
},
|
||||
]
|
||||
|
||||
const GUIDE_GROUPS = [
|
||||
{ label: 'Android', stores: STORE_GUIDES.filter(s => ['HUAWEI', 'MI', 'OPPO', 'VIVO', 'HONOR', 'GOOGLE_PLAY'].includes(s.type)) },
|
||||
{ label: 'iOS / 鸿蒙', stores: STORE_GUIDES.filter(s => ['APP_STORE', 'HARMONY_APP'].includes(s.type)) },
|
||||
]
|
||||
|
||||
const activeStoreType = ref(STORE_GUIDES[0].type)
|
||||
const activeStore = computed(() => STORE_GUIDES.find(s => s.type === activeStoreType.value) ?? STORE_GUIDES[0])
|
||||
const activePlatform = ref('android')
|
||||
const currentStores = computed(() => STORE_GUIDES.filter(s => s.platform === activePlatform.value))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.store-guide-page {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
padding: 20px 24px;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.guide-layout {
|
||||
/* ── 平台选择器 ── */
|
||||
.platform-selector {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 12px;
|
||||
margin: 20px 0 28px;
|
||||
}
|
||||
|
||||
.platform-card {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.guide-sidebar {
|
||||
width: 200px;
|
||||
flex-shrink: 0;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: var(--el-bg-color);
|
||||
}
|
||||
|
||||
.guide-sidebar-group-label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 10px 16px 4px;
|
||||
background: var(--el-fill-color-lighter);
|
||||
}
|
||||
|
||||
.guide-sidebar-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 10px 16px;
|
||||
padding: 20px 12px;
|
||||
border: 2px solid var(--el-border-color-light);
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.guide-sidebar-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.guide-sidebar-item:hover {
|
||||
background: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
.guide-sidebar-item.active {
|
||||
background: var(--el-color-primary-light-9);
|
||||
color: var(--el-color-primary);
|
||||
border-left: 3px solid var(--el-color-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.guide-sidebar-label {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.guide-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
transition: all 0.2s;
|
||||
background: var(--el-bg-color);
|
||||
}
|
||||
|
||||
.guide-content-header {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
.platform-card:hover {
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
background: var(--el-color-primary-light-9);
|
||||
}
|
||||
|
||||
.guide-content-title {
|
||||
.platform-card.active {
|
||||
border-color: var(--el-color-primary);
|
||||
background: var(--el-color-primary-light-9);
|
||||
box-shadow: 0 2px 8px rgba(var(--el-color-primary-rgb), 0.15);
|
||||
}
|
||||
|
||||
.platform-icon {
|
||||
font-size: 32px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.platform-label {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.guide-subtitle {
|
||||
margin: 0;
|
||||
.platform-desc {
|
||||
font-size: 11px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.guide-content-body {
|
||||
/* ── 指引卡片列表 ── */
|
||||
.guide-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.guide-card {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
padding: 24px;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 12px;
|
||||
background: var(--el-bg-color);
|
||||
}
|
||||
|
||||
.guide-steps-wrap {
|
||||
.guide-card-left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.guide-card-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.guide-card-title {
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
|
||||
.guide-card-subtitle {
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ── 步骤 ── */
|
||||
.guide-steps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.guide-step {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.guide-step-num {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: var(--el-color-primary);
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.guide-step-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.guide-hint {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 13px;
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 6px;
|
||||
line-height: 1.6;
|
||||
.guide-step-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.guide-image-wrap {
|
||||
width: 400px;
|
||||
.guide-step-desc {
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-regular);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ── 提示 ── */
|
||||
.guide-hint {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-secondary);
|
||||
line-height: 1.6;
|
||||
padding: 12px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.guide-hint-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.guide-image {
|
||||
/* ── 底部链接 ── */
|
||||
.guide-footer {
|
||||
margin-top: auto;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
/* ── 截图 ── */
|
||||
.guide-card-right {
|
||||
width: 340px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.guide-screenshot {
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
}
|
||||
|
||||
.guide-footer {
|
||||
margin-top: 20px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.guide-layout {
|
||||
/* ── 响应式 ── */
|
||||
@media (max-width: 768px) {
|
||||
.platform-selector {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
.guide-card {
|
||||
flex-direction: column;
|
||||
}
|
||||
.guide-sidebar {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.guide-sidebar-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
padding: 8px 12px;
|
||||
align-items: center;
|
||||
}
|
||||
.guide-sidebar-group-label {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.guide-sidebar-item {
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 6px;
|
||||
padding: 6px 12px;
|
||||
border-bottom: none;
|
||||
}
|
||||
.guide-sidebar-item.active {
|
||||
border-left: 1px solid var(--el-color-primary);
|
||||
}
|
||||
.guide-content-body {
|
||||
flex-direction: column;
|
||||
}
|
||||
.guide-image-wrap {
|
||||
.guide-card-right {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,9 +45,17 @@
|
||||
<el-radio-button value="IOS">iOS</el-radio-button>
|
||||
<el-radio-button value="HARMONY">Harmony</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-button type="primary" @click="openUploadAppDialog">上传新版本</el-button>
|
||||
<el-button type="primary" @click="openUploadAppDialog" :disabled="!platformPackageName">上传新版本</el-button>
|
||||
<el-button @click="loadAppVersions" :loading="loadingApp">刷新</el-button>
|
||||
</div>
|
||||
<el-alert
|
||||
v-if="!platformPackageName"
|
||||
:title="`${appPlatform === 'ANDROID' ? 'Android' : appPlatform === 'IOS' ? 'iOS' : '鸿蒙'}平台未配置包名,请先在应用信息中配置`"
|
||||
type="warning"
|
||||
show-icon
|
||||
:closable="false"
|
||||
style="margin-bottom:12px"
|
||||
/>
|
||||
|
||||
<div class="table-wrap">
|
||||
<el-table :data="appVersions" v-loading="loadingApp" border stripe>
|
||||
@ -264,37 +272,58 @@
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="应用配置指引" name="guide">
|
||||
<div v-for="group in GUIDE_GROUPS" :key="group.label" class="guide-group">
|
||||
<div class="guide-group-title">{{ group.label }}</div>
|
||||
<div class="guide-grid">
|
||||
<el-card v-for="store in group.stores" :key="store.type" class="guide-card" shadow="never">
|
||||
<div class="guide-card-title-row">
|
||||
<div>
|
||||
<div class="guide-card-title">{{ store.label }}</div>
|
||||
<div class="guide-card-subtitle">{{ store.guideSubtitle }}</div>
|
||||
<!-- 平台选择 -->
|
||||
<div class="guide-platform-tabs">
|
||||
<el-radio-group v-model="guidePlatform" size="default">
|
||||
<el-radio-button value="android">🤖 Android</el-radio-button>
|
||||
<el-radio-button value="ios">🍎 iOS</el-radio-button>
|
||||
<el-radio-button value="harmony">🔷 鸿蒙</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<!-- 商店列表 -->
|
||||
<div class="guide-store-list">
|
||||
<el-collapse v-model="guideExpandedStores">
|
||||
<el-collapse-item
|
||||
v-for="store in guideFilteredStores"
|
||||
:key="store.type"
|
||||
:name="store.type"
|
||||
class="guide-store-item"
|
||||
>
|
||||
<template #title>
|
||||
<div class="guide-store-header">
|
||||
<span class="guide-store-name">{{ store.label }}</span>
|
||||
<el-tag size="small" :type="isStoreEnabled(store.type) ? 'success' : 'info'">
|
||||
{{ isStoreEnabled(store.type) ? '已配置' : '未配置' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<el-steps direction="vertical" :active="store.guideSteps.length" finish-status="success" style="margin:12px 0 8px">
|
||||
<el-step v-for="step in store.guideSteps" :key="step.title" :title="step.title" :description="step.description" />
|
||||
</el-steps>
|
||||
</template>
|
||||
<div class="guide-store-body">
|
||||
<div class="guide-store-steps">
|
||||
<div v-for="(step, i) in store.guideSteps" :key="i" class="guide-step-row">
|
||||
<div class="guide-step-badge">{{ i + 1 }}</div>
|
||||
<div>
|
||||
<div class="guide-step-title">{{ step.title }}</div>
|
||||
<div class="guide-step-desc">{{ step.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-image
|
||||
v-if="store.guideImage"
|
||||
:src="store.guideImage"
|
||||
fit="cover"
|
||||
class="guide-image"
|
||||
fit="contain"
|
||||
class="guide-store-image"
|
||||
preview-teleported
|
||||
/>
|
||||
<div class="guide-hint">{{ store.guideHint }}</div>
|
||||
<el-divider />
|
||||
<div class="guide-link-title">应用跳转链接</div>
|
||||
<div class="guide-link-hint">{{ store.jumpLinkHint }}</div>
|
||||
<el-link :href="store.guideUrl" target="_blank" type="primary">查看官方文档</el-link>
|
||||
</el-card>
|
||||
<div class="guide-store-hint">{{ store.guideHint }}</div>
|
||||
<div class="guide-store-link">
|
||||
<div class="guide-store-link-label">应用跳转链接</div>
|
||||
<div class="guide-store-link-hint">{{ store.jumpLinkHint }}</div>
|
||||
<el-link :href="store.guideUrl" target="_blank" type="primary" size="small">查看官方文档 →</el-link>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-tab-pane>
|
||||
@ -988,6 +1017,16 @@ async function submitActivation() {
|
||||
}
|
||||
const app = ref<App | null>(null)
|
||||
const pageTitle = computed(() => app.value?.name ?? appKey.value)
|
||||
|
||||
const platformPackageName = computed(() => {
|
||||
if (!app.value) return ''
|
||||
const map: Record<string, string | undefined> = {
|
||||
ANDROID: app.value.packageName,
|
||||
IOS: app.value.iosBundleId,
|
||||
HARMONY: app.value.harmonyBundleName,
|
||||
}
|
||||
return map[appPlatform.value] ?? ''
|
||||
})
|
||||
const isMobile = ref(false)
|
||||
const dialogWidth = computed(() => (isMobile.value ? 'calc(100vw - 24px)' : '920px'))
|
||||
|
||||
@ -1311,6 +1350,18 @@ const GUIDE_GROUPS = computed(() => [
|
||||
},
|
||||
])
|
||||
|
||||
const guidePlatform = ref('android')
|
||||
const guideExpandedStores = ref<string[]>([])
|
||||
const guideFilteredStores = computed(() => {
|
||||
const map: Record<string, string[]> = {
|
||||
android: ['HUAWEI', 'MI', 'OPPO', 'VIVO', 'HONOR', 'GOOGLE_PLAY'],
|
||||
ios: ['APP_STORE'],
|
||||
harmony: ['HARMONY_APP'],
|
||||
}
|
||||
const types = map[guidePlatform.value] ?? []
|
||||
return STORE_DEFS.filter(s => types.includes(s.type))
|
||||
})
|
||||
|
||||
function getStoreConfig(type: StoreType): StoreConfig | undefined {
|
||||
return storeConfigs.value.find(c => c.storeType === type)
|
||||
}
|
||||
@ -2616,23 +2667,102 @@ onBeforeUnmount(() => {
|
||||
max-width: 920px;
|
||||
}
|
||||
|
||||
.guide-group {
|
||||
margin-bottom: 24px;
|
||||
.guide-platform-tabs {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.guide-group-title {
|
||||
font-size: 15px;
|
||||
.guide-store-list {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.guide-store-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.guide-store-name {
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
margin-bottom: 12px;
|
||||
padding-left: 10px;
|
||||
border-left: 3px solid var(--el-color-primary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.guide-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
.guide-store-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.guide-store-steps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.guide-step-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.guide-step-badge {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background: var(--el-color-primary);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.guide-step-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
.guide-step-desc {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-regular);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.guide-store-image {
|
||||
width: 100%;
|
||||
max-height: 200px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.guide-store-hint {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
line-height: 1.6;
|
||||
padding: 10px 12px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.guide-store-link {
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.guide-store-link-label {
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.guide-store-link-hint {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
line-height: 1.5;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.guide-card {
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户