新增首页 AW炫彩回收入口,支持后台配置展示图

首页第三入口点击查看回收说明图;配置项 mobile.aw_recycle 可配标题/图片;aw-recycle 走公开文件链路,首页鉴权预览兼容旧私有地址。
This commit is contained in:
yml2213
2026-07-19 22:50:16 +08:00
parent dabcec45cb
commit 9b1ee445b7
14 changed files with 533 additions and 18 deletions
@@ -0,0 +1,231 @@
<script setup lang="ts">
import { readError } from '@/shared/utils/error'
import { ElMessage } from 'element-plus'
import { ref, watch } from 'vue'
import { uploadAdminFile } from '@/shared/api/files'
import {
defaultAWRecycleConfig,
type AWRecycleConfig,
} from '@/features/listings/api/homeConfig'
import { updateSystemConfig, type SystemConfig } from '@/features/admin/api/systemConfigs'
import { safeParseJSON } from '@/shared/utils/json'
import AuthImage from '@/shared/components/business/AuthImage.vue'
const props = defineProps<{
modelValue: boolean
config: SystemConfig
}>()
const emit = defineEmits<{
(e: 'update:modelValue', val: boolean): void
(e: 'saved'): void
}>()
const submitting = ref(false)
const uploading = ref(false)
const description = ref('')
const draft = ref<AWRecycleConfig>({ ...defaultAWRecycleConfig })
watch(
() => props.modelValue,
val => {
if (!val) return
draft.value = parseAWRecycle(props.config.value)
description.value = props.config.description || ''
},
{ immediate: true }
)
function parseAWRecycle(raw: string): AWRecycleConfig {
const parsed = safeParseJSON(raw, defaultAWRecycleConfig)
return {
title:
typeof parsed.title === 'string' && parsed.title.trim()
? parsed.title.trim()
: defaultAWRecycleConfig.title,
subtitle:
typeof parsed.subtitle === 'string' && parsed.subtitle.trim()
? parsed.subtitle.trim()
: defaultAWRecycleConfig.subtitle,
image_url: typeof parsed.image_url === 'string' ? parsed.image_url.trim() : '',
enabled: typeof parsed.enabled === 'boolean' ? parsed.enabled : true,
}
}
async function handleUpload(event: Event) {
const input = event.target as HTMLInputElement
const file = input.files?.[0]
input.value = ''
if (!file) return
uploading.value = true
try {
const uploaded = await uploadAdminFile(file, 'aw-recycle')
draft.value.image_url = uploaded.url
ElMessage.success('图片已上传')
} catch (error) {
ElMessage.error(readError(error, '图片上传失败'))
} finally {
uploading.value = false
}
}
async function handleSave() {
submitting.value = true
try {
const value = JSON.stringify(
{
title: draft.value.title.trim() || defaultAWRecycleConfig.title,
subtitle: draft.value.subtitle.trim() || defaultAWRecycleConfig.subtitle,
image_url: draft.value.image_url.trim(),
enabled: draft.value.enabled,
},
null,
2
)
await updateSystemConfig(props.config.key, {
value,
description: description.value,
})
ElMessage.success('配置已更新')
emit('saved')
emit('update:modelValue', false)
} catch (error) {
ElMessage.error(readError(error, '保存失败'))
} finally {
submitting.value = false
}
}
</script>
<template>
<el-dialog
:model-value="modelValue"
title="编辑 AW炫彩回收"
width="640px"
@update:model-value="emit('update:modelValue', $event)"
>
<div class="dialog-body">
<p class="config-key-label">
<strong>{{ config.key }}</strong>
</p>
<el-form label-width="88px">
<el-form-item label="启用入口">
<el-switch v-model="draft.enabled" active-text="显示" inactive-text="隐藏" />
</el-form-item>
<el-form-item label="标题">
<el-input v-model="draft.title" maxlength="32" placeholder="AW炫彩回收" />
</el-form-item>
<el-form-item label="副标题">
<el-input v-model="draft.subtitle" maxlength="64" placeholder="点击查看回收说明" />
</el-form-item>
<el-form-item label="展示图片">
<div class="image-editor">
<el-input v-model="draft.image_url" placeholder="图片 URL" />
<div class="image-tools">
<AuthImage
v-if="draft.image_url"
:source="draft.image_url"
admin
alt="预览"
fit="cover"
image-class="preview-img"
/>
<div v-else class="empty-image">暂无图片</div>
<label class="upload-trigger">
<input
type="file"
accept="image/jpeg,image/png,image/webp"
@change="handleUpload"
/>
{{ uploading ? '上传中…' : '上传图片' }}
</label>
</div>
<p class="hint">
走后台通用上传(scene=aw-recycle,公开可访问)。首页点击入口后展示此图,建议竖图或说明海报。
若旧图路径含 misc/ 且无法预览,请重新上传。
</p>
</div>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="description" type="textarea" :rows="2" placeholder="配置说明" />
</el-form-item>
</el-form>
</div>
<template #footer>
<el-button @click="emit('update:modelValue', false)">取消</el-button>
<el-button type="primary" :loading="submitting" @click="handleSave">保存</el-button>
</template>
</el-dialog>
</template>
<style scoped>
.dialog-body {
display: grid;
gap: 12px;
}
.config-key-label {
margin: 0;
color: #64748b;
font-size: 12px;
}
.image-editor {
display: grid;
gap: 10px;
width: 100%;
}
.image-tools {
display: grid;
grid-template-columns: 180px auto;
gap: 12px;
align-items: end;
}
.image-tools :deep(.preview-img),
.empty-image {
width: 180px;
height: 120px;
border-radius: 10px;
border: 1px solid #e5e7eb;
object-fit: cover;
background: #f8fafc;
}
.empty-image {
display: grid;
place-items: center;
color: #94a3b8;
font-size: 13px;
}
.upload-trigger {
display: inline-flex;
align-items: center;
justify-content: center;
height: 32px;
padding: 0 12px;
border-radius: 8px;
border: 1px solid #d1d5db;
background: #fff;
color: #374151;
font-size: 13px;
font-weight: 600;
cursor: pointer;
}
.upload-trigger input {
display: none;
}
.hint {
margin: 0;
color: #94a3b8;
font-size: 12px;
line-height: 1.4;
}
</style>