bc518174
王天杨
提交两个项目文件
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
|
<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>
|