DownloadAssets.vue 3.72 KB
<template>
  <el-card shadow="never">
    <template #header>
      <div class="hd">
        <span class="ttl">下载素材配置</span>
        <span class="sub">可替换用户端“下载证书/下载攻略”的图片素材</span>
      </div>
    </template>

    <el-row :gutter="16">
      <el-col :span="12">
        <el-card class="inner" shadow="never">
          <template #header><span>证书底图</span></template>
          <el-image :src="certPreview" fit="contain" class="preview" />
          <p class="tip">
            建议预留姓名、日期空白区域(系统下载证书时会叠加“用户名 + 解锁日期”)。
          </p>
          <div class="ops">
            <el-upload
              :show-file-list="false"
              accept="image/*"
              :http-request="uploadCert"
            >
              <el-button type="primary" :loading="certUploading">上传并保存</el-button>
            </el-upload>
          </div>
        </el-card>
      </el-col>

      <el-col :span="12">
        <el-card class="inner" shadow="never">
          <template #header><span>攻略图片</span></template>
          <el-image :src="guidePreview" fit="contain" class="preview" />
          <p class="tip">建议上传竖版长图(用户端弹窗更适配)。</p>
          <div class="ops">
            <el-upload
              :show-file-list="false"
              accept="image/*"
              :http-request="uploadGuide"
            >
              <el-button type="primary" :loading="guideUploading">上传并保存</el-button>
            </el-upload>
          </div>
        </el-card>
      </el-col>
    </el-row>
  </el-card>
</template>

<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import { ElMessage } from "element-plus";
import {
  fetchDownloadAssets,
  saveDownloadAsset,
  uploadAdminImage,
} from "@/api/admin";
import { resolveAdminMediaUrl } from "@/utils/mediaUrl";

const certificate = ref("/static/certificate-dialog-bg.png");
const guide = ref("/static/daocheng-yading-map.png");
const certUploading = ref(false);
const guideUploading = ref(false);

const certPreview = computed(() => resolveAdminMediaUrl(certificate.value));
const guidePreview = computed(() => resolveAdminMediaUrl(guide.value));

async function loadConfig() {
  const res = await fetchDownloadAssets();
  certificate.value = res.data.certificate || "/static/certificate-dialog-bg.png";
  guide.value = res.data.guide || "/static/daocheng-yading-map.png";
}

async function uploadAndSave(
  file: File,
  type: "certificate" | "guide",
  loadingRef: { value: boolean }
) {
  try {
    loadingRef.value = true;
    const up = await uploadAdminImage(file);
    const url = up.data.url;
    await saveDownloadAsset({ type, image_url: url });
    if (type === "certificate") {
      certificate.value = url;
    } else {
      guide.value = url;
    }
    ElMessage.success("已保存");
  } catch {
    ElMessage.error("上传或保存失败");
  } finally {
    loadingRef.value = false;
  }
}

async function uploadCert(opt: { file: File }) {
  await uploadAndSave(opt.file, "certificate", certUploading);
}

async function uploadGuide(opt: { file: File }) {
  await uploadAndSave(opt.file, "guide", guideUploading);
}

onMounted(() => {
  void loadConfig();
});
</script>

<style scoped>
.hd {
  display: flex;
  align-items: baseline;
  gap: 12px;
}

.ttl {
  font-size: 16px;
  font-weight: 600;
  color: #0f172a;
}

.sub {
  font-size: 13px;
  color: #64748b;
}

.inner {
  height: 100%;
}

.preview {
  width: 100%;
  height: 360px;
  border-radius: 8px;
  background: #f8fafc;
}

.tip {
  margin: 12px 0 16px;
  font-size: 13px;
  line-height: 1.6;
  color: #64748b;
}

.ops {
  display: flex;
  justify-content: flex-end;
}
</style>