1.修改首页下载功能
This commit is contained in:
@@ -64,10 +64,15 @@ export const deletePage = (id) => request.delete(`/admin/pages/${id}`)
|
||||
export const getHomepage = (siteId) => request.get(`/admin/sites/${siteId}/homepage`)
|
||||
export const updateHomepage = (siteId, data) => request.put(`/admin/sites/${siteId}/homepage`, data)
|
||||
export const downloadHomepage = (siteId) => request.get(`/admin/sites/${siteId}/homepage/download`, { responseType: 'blob' })
|
||||
export const getDownloadableAssets = (siteId) => request.get(`/admin/sites/${siteId}/assets/downloadable`)
|
||||
|
||||
// 文件管理(功能模块:多级目录、可下载)
|
||||
export const getSiteAssets = (siteId, path) =>
|
||||
request.get(`/admin/sites/${siteId}/assets`, { params: path ? { path } : {} })
|
||||
export const getSiteAssets = (siteId, path, opts = {}) => {
|
||||
const params = {}
|
||||
if (path) params.path = path
|
||||
if (opts.downloadable) params.downloadable = '1'
|
||||
return request.get(`/admin/sites/${siteId}/assets`, { params })
|
||||
}
|
||||
export const uploadSiteAsset = (siteId, file, opts = {}) => {
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
|
||||
@@ -43,14 +43,16 @@
|
||||
<el-input v-model="form.download_text" placeholder="START EXPLORING" />
|
||||
</el-form-item>
|
||||
<el-form-item label="按钮链接">
|
||||
<el-input v-model="form.download_url" placeholder="#" />
|
||||
<el-input v-model="form.download_url" placeholder="#" style="flex: 1" />
|
||||
<el-button type="primary" link @click="openFilePicker('download')">选择可下载文件</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">平台(轨道)</el-divider>
|
||||
<el-form-item label="平台列表">
|
||||
<div v-for="(p, i) in form.platforms" :key="i" style="display: flex; gap: 8px; margin-bottom: 8px">
|
||||
<div v-for="(p, i) in form.platforms" :key="i" style="display: flex; gap: 8px; margin-bottom: 8px; align-items: center">
|
||||
<el-input v-model="p.name" placeholder="如 WINDOWS" style="width: 140px" />
|
||||
<el-input v-model="p.url" placeholder="链接" style="flex: 1" />
|
||||
<el-button type="primary" link @click="openFilePicker('platform', i)">选择文件</el-button>
|
||||
<el-button link type="danger" @click="form.platforms.splice(i, 1)">删除</el-button>
|
||||
</div>
|
||||
<el-button link type="primary" @click="form.platforms.push({ name: '', url: '#' })">+ 添加平台</el-button>
|
||||
@@ -84,13 +86,31 @@
|
||||
|
||||
<el-empty v-else description="请先选择站点" />
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="filePickerVisible" title="选择可下载文件" width="640px">
|
||||
<el-table
|
||||
v-loading="fileListLoading"
|
||||
:data="downloadableFiles"
|
||||
highlight-current-row
|
||||
@current-change="onSelectFile"
|
||||
>
|
||||
<el-table-column property="name" label="文件名" min-width="180" />
|
||||
<el-table-column property="file_path" label="路径" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="confirmSelectFile(row)">选择</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!fileListLoading && downloadableFiles.length === 0" description="暂无可下载文件,请在文件管理中上传并勾选“允许下载”" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getSites, getOfficialSite, getHomepage, updateHomepage } from '../../api/admin'
|
||||
import { getSites, getOfficialSite, getHomepage, updateHomepage, getDownloadableAssets } from '../../api/admin'
|
||||
import { useAuthStore } from '../../stores/auth'
|
||||
|
||||
const siteId = ref('')
|
||||
@@ -98,6 +118,10 @@ const sites = ref([])
|
||||
const saving = ref(false)
|
||||
const downloading = ref(false)
|
||||
const formRef = ref(null)
|
||||
const filePickerVisible = ref(false)
|
||||
const fileListLoading = ref(false)
|
||||
const downloadableFiles = ref([])
|
||||
const filePickerTarget = ref({ type: 'download' }) // { type: 'download' } | { type: 'platform', index: number }
|
||||
|
||||
const defaultForm = () => ({
|
||||
logo_text: 'YUHENG ONE',
|
||||
@@ -203,6 +227,45 @@ const handleDownload = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
function openFilePicker(type, index) {
|
||||
filePickerTarget.value = type === 'platform' ? { type: 'platform', index } : { type: 'download' }
|
||||
filePickerVisible.value = true
|
||||
fetchDownloadableFiles()
|
||||
}
|
||||
|
||||
async function fetchDownloadableFiles() {
|
||||
if (!siteId.value) return
|
||||
fileListLoading.value = true
|
||||
try {
|
||||
const res = await getDownloadableAssets(siteId.value)
|
||||
downloadableFiles.value = res.list || []
|
||||
} catch (e) {
|
||||
ElMessage.error(e.message)
|
||||
downloadableFiles.value = []
|
||||
} finally {
|
||||
fileListLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function buildDownloadUrl(asset) {
|
||||
return `/api/web/sites/${siteId.value}/assets/${asset.id}/download`
|
||||
}
|
||||
|
||||
function confirmSelectFile(asset) {
|
||||
const url = buildDownloadUrl(asset)
|
||||
if (filePickerTarget.value.type === 'download') {
|
||||
form.download_url = url
|
||||
} else {
|
||||
form.platforms[filePickerTarget.value.index].url = url
|
||||
}
|
||||
filePickerVisible.value = false
|
||||
ElMessage.success('已选择:' + asset.name)
|
||||
}
|
||||
|
||||
function onSelectFile(row) {
|
||||
if (row) confirmSelectFile(row)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchSites().then(() => fetchData())
|
||||
})
|
||||
|
||||
@@ -32,13 +32,18 @@ func pathPrefix(siteID string) string {
|
||||
return "sites/" + siteID + "/"
|
||||
}
|
||||
|
||||
// ListSiteAssets 站点功能模块/上传文件列表;query path 为当前目录相对路径(空为根)
|
||||
// ListSiteAssets 站点功能模块/上传文件列表;query path 为当前目录相对路径(空为根);downloadable=1 时返回该站点下所有可下载文件(供首页编辑选择)
|
||||
func ListSiteAssets(c *gin.Context) {
|
||||
siteID := c.Param("site_id")
|
||||
if siteID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请提供 site_id"})
|
||||
return
|
||||
}
|
||||
onlyDownloadable := c.Query("downloadable") == "1" || c.Query("downloadable") == "true"
|
||||
if onlyDownloadable {
|
||||
listDownloadableAssets(c, siteID)
|
||||
return
|
||||
}
|
||||
path := c.Query("path")
|
||||
prefix := pathPrefix(siteID)
|
||||
if path != "" {
|
||||
@@ -52,7 +57,6 @@ func ListSiteAssets(c *gin.Context) {
|
||||
defer cancel()
|
||||
|
||||
coll := config.GetDB(config.DBName).Collection("site_assets")
|
||||
// 仅当前目录下直接文件(file_path 为 prefix + 不含 / 的文件名)
|
||||
filter := bson.M{"site_id": siteID, "file_path": bson.M{"$regex": "^" + regexp.QuoteMeta(prefix) + "[^/]+$"}}
|
||||
opts := options.Find().SetSort(bson.D{{Key: "created_at", Value: -1}})
|
||||
cursor, err := coll.Find(ctx, filter, opts)
|
||||
@@ -68,11 +72,40 @@ func ListSiteAssets(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
total, _ := coll.CountDocuments(ctx, filter)
|
||||
// 子目录列表:从 file_path 中提取当前 path 下的一级子目录名
|
||||
subDirs := listSubDirs(c, siteID, path)
|
||||
c.JSON(http.StatusOK, gin.H{"list": list, "total": total, "sub_dirs": subDirs})
|
||||
}
|
||||
|
||||
func listDownloadableAssets(c *gin.Context, siteID string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
coll := config.GetDB(config.DBName).Collection("site_assets")
|
||||
filter := bson.M{"site_id": siteID, "downloadable": true}
|
||||
opts := options.Find().SetSort(bson.D{{Key: "created_at", Value: -1}})
|
||||
cursor, err := coll.Find(ctx, filter, opts)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
var list []models.SiteAsset
|
||||
if err = cursor.All(ctx, &list); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"list": list, "total": len(list)})
|
||||
}
|
||||
|
||||
// ListDownloadableAssets 仅返回可下载文件列表(供首页编辑选择,仅需 homepage:edit 权限)
|
||||
func ListDownloadableAssets(c *gin.Context) {
|
||||
siteID := c.Param("site_id")
|
||||
if siteID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请提供 site_id"})
|
||||
return
|
||||
}
|
||||
listDownloadableAssets(c, siteID)
|
||||
}
|
||||
|
||||
func listSubDirs(c *gin.Context, siteID, currentPath string) []string {
|
||||
prefix := pathPrefix(siteID)
|
||||
if currentPath != "" {
|
||||
@@ -250,3 +283,41 @@ func CreateSiteFolder(c *gin.Context) {
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "创建成功", "path": filepath.ToSlash(clean)})
|
||||
}
|
||||
|
||||
// DownloadSiteAsset 前台公开下载:仅当资源标记为可下载时返回文件(供首页等使用)
|
||||
func DownloadSiteAsset(c *gin.Context) {
|
||||
siteID := c.Param("site_id")
|
||||
assetIDStr := c.Param("asset_id")
|
||||
if siteID == "" || assetIDStr == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
|
||||
return
|
||||
}
|
||||
oid, err := bson.ObjectIDFromHex(assetIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的ID"})
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
coll := config.GetDB(config.DBName).Collection("site_assets")
|
||||
var asset models.SiteAsset
|
||||
err = coll.FindOne(ctx, bson.M{"_id": oid, "site_id": siteID}).Decode(&asset)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "资源不存在"})
|
||||
return
|
||||
}
|
||||
if !asset.Downloadable {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "该资源不可下载"})
|
||||
return
|
||||
}
|
||||
fullPath := filepath.Join(getUploadDir(), filepath.FromSlash(asset.FilePath))
|
||||
if _, err := os.Stat(fullPath); err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "文件不存在"})
|
||||
return
|
||||
}
|
||||
c.Header("Content-Disposition", "attachment; filename=\""+asset.Name+"\"")
|
||||
if asset.ContentType != "" {
|
||||
c.Header("Content-Type", asset.ContentType)
|
||||
}
|
||||
c.File(fullPath)
|
||||
}
|
||||
|
||||
@@ -161,6 +161,7 @@ func main() {
|
||||
admin.GET("/sites/:site_id/homepage/download", handlers.RequirePermission(models.PermHomepageEdit), handlers.DownloadHomepage)
|
||||
admin.GET("/sites/:site_id/homepage", handlers.RequirePermission(models.PermHomepageEdit), handlers.GetHomepage)
|
||||
admin.PUT("/sites/:site_id/homepage", handlers.RequirePermission(models.PermHomepageEdit), handlers.UpdateHomepage)
|
||||
admin.GET("/sites/:site_id/assets/downloadable", handlers.RequirePermission(models.PermHomepageEdit), handlers.ListDownloadableAssets)
|
||||
admin.GET("/sites/:site_id/assets", handlers.RequirePermission(models.PermSiteManage), handlers.ListSiteAssets)
|
||||
admin.POST("/sites/:site_id/assets", handlers.RequirePermission(models.PermModuleUpload), handlers.UploadSiteAsset)
|
||||
admin.POST("/sites/:site_id/folders", handlers.RequirePermission(models.PermModuleUpload), handlers.CreateSiteFolder)
|
||||
@@ -212,6 +213,8 @@ func main() {
|
||||
web.GET("/info", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "web api"})
|
||||
})
|
||||
// 可下载资源公开下载(首页等链接指向此路径)
|
||||
web.GET("/sites/:site_id/assets/:asset_id/download", handlers.DownloadSiteAsset)
|
||||
}
|
||||
|
||||
port := os.Getenv("PORT")
|
||||
|
||||
Reference in New Issue
Block a user