577 lines
15 KiB
Vue
577 lines
15 KiB
Vue
<template>
|
||
<div class="asset-manage-page">
|
||
<section class="asset-left">
|
||
<div class="panel-title">素材组树</div>
|
||
<a-button size="mini" type="outline" :loading="groupLoading" @click="loadGroups">刷新分组</a-button>
|
||
<div class="form-grid mtop">
|
||
<div class="field">
|
||
<label>素材组名称</label>
|
||
<a-input v-model="groupForm.name" placeholder="输入分组名称" />
|
||
</div>
|
||
<div class="field actions">
|
||
<a-button type="primary" size="mini" @click="createGroup">新建分组</a-button>
|
||
<a-button size="mini" @click="updateGroup" :disabled="!groupForm.id">更新分组</a-button>
|
||
</div>
|
||
</div>
|
||
<div class="group-tree">
|
||
<div
|
||
v-for="g in groups"
|
||
:key="g.Id || g.id"
|
||
:class="['group-node', { active: selectedGroupId === (g.Id || g.id) }]"
|
||
@click="selectGroup(g)">
|
||
<span class="folder-icon">📁</span>
|
||
<div class="group-meta">
|
||
<div class="group-name">{{ g.Name || g.name || (g.Id || g.id) }}</div>
|
||
<div class="group-id">{{ g.Id || g.id }}</div>
|
||
</div>
|
||
</div>
|
||
<div v-if="!groups.length" class="empty-tip">暂无素材组,请先在“资源组管理”创建</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="asset-right">
|
||
<div class="panel-title">新增素材</div>
|
||
<div class="form-grid">
|
||
<div class="field">
|
||
<label>GroupId</label>
|
||
<a-input v-model="createForm.groupId" placeholder="请选择左侧分组或手动输入" />
|
||
</div>
|
||
<div class="field">
|
||
<label>文件</label>
|
||
<input type="file" @change="onFileChange" />
|
||
<div class="group-id">{{ createForm.fileName || '未选择文件' }}</div>
|
||
</div>
|
||
<div class="field">
|
||
<label>Name</label>
|
||
<a-input v-model="createForm.name" placeholder="素材名称(可选)" />
|
||
</div>
|
||
<div class="field">
|
||
<label>AssetType</label>
|
||
<a-select v-model="createForm.assetType">
|
||
<a-option value="Image">Image</a-option>
|
||
<a-option value="Video">Video</a-option>
|
||
<a-option value="Audio">Audio</a-option>
|
||
</a-select>
|
||
</div>
|
||
<div class="field actions">
|
||
<a-button type="primary" :loading="createLoading" @click="createAsset">新增素材</a-button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="panel-title mtop">查询素材</div>
|
||
<div class="form-grid">
|
||
<div class="field">
|
||
<label>GroupId</label>
|
||
<a-input v-model="filters.groupId" placeholder="按组过滤(默认选中左侧)" />
|
||
</div>
|
||
<div class="field">
|
||
<label>Name</label>
|
||
<a-input v-model="filters.name" placeholder="按名称过滤" />
|
||
</div>
|
||
<div class="field">
|
||
<label>Status</label>
|
||
<a-select v-model="filters.status">
|
||
<a-option value="">全部</a-option>
|
||
<a-option value="Active">Active</a-option>
|
||
<a-option value="Processing">Processing</a-option>
|
||
<a-option value="Failed">Failed</a-option>
|
||
</a-select>
|
||
</div>
|
||
<div class="field">
|
||
<label>SortBy</label>
|
||
<a-select v-model="filters.sortBy">
|
||
<a-option value="CreateTime">CreateTime</a-option>
|
||
<a-option value="UpdateTime">UpdateTime</a-option>
|
||
<a-option value="GroupId">GroupId</a-option>
|
||
</a-select>
|
||
</div>
|
||
<div class="field">
|
||
<label>SortOrder</label>
|
||
<a-select v-model="filters.sortOrder">
|
||
<a-option value="Desc">Desc</a-option>
|
||
<a-option value="Asc">Asc</a-option>
|
||
</a-select>
|
||
</div>
|
||
<div class="field actions">
|
||
<a-button type="primary" :loading="listLoading" @click="searchAssets(1)">查询</a-button>
|
||
<a-button @click="resetFilters">重置</a-button>
|
||
</div>
|
||
</div>
|
||
|
||
<a-spin :loading="listLoading">
|
||
<div class="total-line">总数:{{ totalCount }}</div>
|
||
<div class="table-wrap">
|
||
<table class="asset-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Id</th>
|
||
<th>Name</th>
|
||
<th>URL</th>
|
||
<th>GroupId</th>
|
||
<th>AssetType</th>
|
||
<th>Status</th>
|
||
<th>CreateTime</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-for="it in items" :key="it.Id || it.id">
|
||
<td>{{ it.Id || it.id }}</td>
|
||
<td>{{ it.Name || it.name || '-' }}</td>
|
||
<td class="url-cell">{{ it.URL || it.url || '-' }}</td>
|
||
<td>{{ it.GroupId || it.groupId || '-' }}</td>
|
||
<td>{{ it.AssetType || it.assetType || '-' }}</td>
|
||
<td>{{ it.Status || it.status || '-' }}</td>
|
||
<td>{{ it.CreateTime || it.createTime || '-' }}</td>
|
||
<td>
|
||
<a-button size="mini" type="outline" @click="getAsset(it)">详情</a-button>
|
||
<a-button size="mini" status="danger" @click="deleteAsset(it)">删除</a-button>
|
||
</td>
|
||
</tr>
|
||
<tr v-if="!items.length">
|
||
<td colspan="8" class="empty-tip">暂无素材</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="pager">
|
||
<a-pagination
|
||
:total="totalCount"
|
||
:current="filters.pageNumber"
|
||
:page-size="filters.pageSize"
|
||
show-total
|
||
show-jumper
|
||
@change="searchAssets"
|
||
@page-size-change="onPageSizeChange" />
|
||
</div>
|
||
</a-spin>
|
||
</section>
|
||
|
||
<a-modal v-model:visible="detailVisible" title="素材详情" :footer="false" width="700px">
|
||
<pre class="detail-box">{{ detailText }}</pre>
|
||
</a-modal>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
const GROUP_LIST_API = 'api/byteAssetGroup/listAssetGroups'
|
||
const GROUP_CREATE_API = 'api/byteAssetGroup/createAssetGroup'
|
||
const GROUP_GET_API = 'api/byteAssetGroup/getAssetGroup'
|
||
const GROUP_UPDATE_API = 'api/byteAssetGroup/updateAssetGroup'
|
||
const ASSET_CREATE_API = 'api/byteAsset/createAsset'
|
||
const ASSET_LIST_API = 'api/byteAsset/listAssets'
|
||
const ASSET_GET_API = 'api/byteAsset/getAsset'
|
||
const ASSET_DELETE_API = 'api/byteAsset/deleteAsset'
|
||
|
||
export default {
|
||
name: 'AssetManage',
|
||
data() {
|
||
return {
|
||
groupLoading: false,
|
||
createLoading: false,
|
||
listLoading: false,
|
||
groups: [],
|
||
selectedGroupId: '',
|
||
createForm: {
|
||
groupId: '',
|
||
file: null,
|
||
fileName: '',
|
||
name: '',
|
||
assetType: 'Image'
|
||
},
|
||
groupForm: {
|
||
name: '',
|
||
id: ''
|
||
},
|
||
filters: {
|
||
groupId: '',
|
||
name: '',
|
||
status: '',
|
||
pageNumber: 1,
|
||
pageSize: 10,
|
||
sortBy: 'CreateTime',
|
||
sortOrder: 'Desc'
|
||
},
|
||
totalCount: 0,
|
||
items: [],
|
||
detailVisible: false,
|
||
detailData: null
|
||
}
|
||
},
|
||
computed: {
|
||
detailText() {
|
||
return this.detailData ? JSON.stringify(this.detailData, null, 2) : '{}'
|
||
}
|
||
},
|
||
mounted() {
|
||
this.loadGroups()
|
||
},
|
||
methods: {
|
||
async loadGroups() {
|
||
this.groupLoading = true
|
||
try {
|
||
const res = await this.$axios({
|
||
url: GROUP_LIST_API,
|
||
method: 'POST',
|
||
data: {
|
||
Filter: { GroupType: 'AIGC' },
|
||
PageNumber: 1,
|
||
PageSize: 100,
|
||
SortBy: 'CreateTime',
|
||
SortOrder: 'Desc'
|
||
}
|
||
})
|
||
this.groups = Array.isArray(res?.data?.Items) ? res.data.Items : []
|
||
if (!this.selectedGroupId && this.groups.length) {
|
||
const gid = this.groups[0].Id || this.groups[0].id
|
||
this.selectedGroupId = gid
|
||
this.groupForm.id = gid
|
||
this.groupForm.name = this.groups[0].Name || this.groups[0].name || ''
|
||
this.createForm.groupId = gid
|
||
this.filters.groupId = gid
|
||
this.searchAssets(1)
|
||
}
|
||
} catch (e) {
|
||
this.$message.error(e?.message || '加载分组失败')
|
||
} finally {
|
||
this.groupLoading = false
|
||
}
|
||
},
|
||
selectGroup(g) {
|
||
const gid = g?.Id || g?.id
|
||
this.selectedGroupId = gid
|
||
this.groupForm.id = gid
|
||
this.groupForm.name = g?.Name || g?.name || ''
|
||
this.createForm.groupId = gid
|
||
this.filters.groupId = gid
|
||
this.searchAssets(1)
|
||
},
|
||
async createGroup() {
|
||
const name = String(this.groupForm.name || '').trim()
|
||
if (!name) return this.$message.error('请输入素材组名称')
|
||
try {
|
||
const res = await this.$axios({
|
||
url: GROUP_CREATE_API,
|
||
method: 'POST',
|
||
data: { Name: name, ProjectName: 'default' }
|
||
})
|
||
if (res.code === 200) {
|
||
this.$message.success('创建素材组成功')
|
||
await this.loadGroups()
|
||
} else {
|
||
this.$message.error(res.msg || '创建素材组失败')
|
||
}
|
||
} catch (e) {
|
||
this.$message.error(e?.message || '创建素材组失败')
|
||
}
|
||
},
|
||
async updateGroup() {
|
||
const id = String(this.groupForm.id || '').trim()
|
||
const name = String(this.groupForm.name || '').trim()
|
||
if (!id) return this.$message.error('请先选择素材组')
|
||
if (!name) return this.$message.error('请输入素材组名称')
|
||
try {
|
||
const detailRes = await this.$axios({
|
||
url: GROUP_GET_API,
|
||
method: 'POST',
|
||
data: { Id: id }
|
||
})
|
||
const detail = detailRes?.data || {}
|
||
const payload = {
|
||
Id: id,
|
||
Name: name,
|
||
GroupType: detail.GroupType || detail.groupType || 'AIGC',
|
||
ProjectName: detail.ProjectName || detail.projectName || 'default'
|
||
}
|
||
const res = await this.$axios({
|
||
url: GROUP_UPDATE_API,
|
||
method: 'POST',
|
||
data: payload
|
||
})
|
||
if (res.code === 200) {
|
||
this.$message.success('更新素材组成功')
|
||
await this.loadGroups()
|
||
} else {
|
||
this.$message.error(res.msg || '更新素材组失败')
|
||
}
|
||
} catch (e) {
|
||
this.$message.error(e?.message || '更新素材组失败')
|
||
}
|
||
},
|
||
onFileChange(e) {
|
||
const file = e?.target?.files?.[0]
|
||
this.createForm.file = file || null
|
||
this.createForm.fileName = file?.name || ''
|
||
},
|
||
async createAsset() {
|
||
const groupId = String(this.createForm.groupId || '').trim()
|
||
if (!groupId) return this.$message.error('请填写 GroupId')
|
||
if (!this.createForm.file) return this.$message.error('请选择上传文件')
|
||
this.createLoading = true
|
||
try {
|
||
const fd = new FormData()
|
||
fd.append('file', this.createForm.file)
|
||
fd.append('groupId', groupId)
|
||
fd.append('assetType', this.createForm.assetType)
|
||
fd.append('name', String(this.createForm.name || '').trim())
|
||
const res = await this.$axios({
|
||
url: ASSET_CREATE_API,
|
||
method: 'POST',
|
||
data: fd
|
||
})
|
||
if (res.code === 200) {
|
||
this.$message.success('新增素材成功')
|
||
this.createForm.file = null
|
||
this.createForm.fileName = ''
|
||
this.createForm.name = ''
|
||
this.searchAssets(1)
|
||
} else {
|
||
this.$message.error(res.msg || '新增素材失败')
|
||
}
|
||
} catch (e) {
|
||
this.$message.error(e?.message || '新增素材失败')
|
||
} finally {
|
||
this.createLoading = false
|
||
}
|
||
},
|
||
buildListPayload() {
|
||
const filter = {
|
||
GroupType: 'AIGC'
|
||
}
|
||
const gid = String(this.filters.groupId || '').trim()
|
||
if (gid) filter.GroupIds = [gid]
|
||
const name = String(this.filters.name || '').trim()
|
||
if (name) filter.Name = name
|
||
if (this.filters.status) filter.Statuses = [this.filters.status]
|
||
return {
|
||
Filter: filter,
|
||
PageNumber: this.filters.pageNumber,
|
||
PageSize: this.filters.pageSize,
|
||
SortBy: this.filters.sortBy,
|
||
SortOrder: this.filters.sortOrder
|
||
}
|
||
},
|
||
async searchAssets(page = this.filters.pageNumber) {
|
||
this.filters.pageNumber = Number(page) || 1
|
||
this.listLoading = true
|
||
try {
|
||
const res = await this.$axios({
|
||
url: ASSET_LIST_API,
|
||
method: 'POST',
|
||
data: this.buildListPayload()
|
||
})
|
||
this.totalCount = Number(res?.data?.TotalCount || 0)
|
||
this.items = Array.isArray(res?.data?.Items) ? res.data.Items : []
|
||
if (res.code !== 200) {
|
||
this.$message.error(res.msg || '查询素材失败')
|
||
}
|
||
} catch (e) {
|
||
this.$message.error(e?.message || '查询素材失败')
|
||
} finally {
|
||
this.listLoading = false
|
||
}
|
||
},
|
||
onPageSizeChange(size) {
|
||
this.filters.pageSize = Number(size) || 10
|
||
this.searchAssets(1)
|
||
},
|
||
resetFilters() {
|
||
this.filters = {
|
||
groupId: this.selectedGroupId || '',
|
||
name: '',
|
||
status: '',
|
||
pageNumber: 1,
|
||
pageSize: 10,
|
||
sortBy: 'CreateTime',
|
||
sortOrder: 'Desc'
|
||
}
|
||
this.searchAssets(1)
|
||
},
|
||
async getAsset(it) {
|
||
const id = it?.Id || it?.id
|
||
if (!id) return
|
||
try {
|
||
const res = await this.$axios({
|
||
url: ASSET_GET_API,
|
||
method: 'POST',
|
||
data: { Id: id }
|
||
})
|
||
if (res.code === 200) {
|
||
this.detailData = res.data || {}
|
||
this.detailVisible = true
|
||
} else {
|
||
this.$message.error(res.msg || '查询详情失败')
|
||
}
|
||
} catch (e) {
|
||
this.$message.error(e?.message || '查询详情失败')
|
||
}
|
||
},
|
||
deleteAsset(it) {
|
||
const id = it?.Id || it?.id
|
||
if (!id) return
|
||
this.$confirm({
|
||
title: '删除素材',
|
||
content: `确认删除素材 ${id} 吗?`,
|
||
onOk: async () => {
|
||
try {
|
||
const res = await this.$axios({
|
||
url: ASSET_DELETE_API,
|
||
method: 'POST',
|
||
data: { Id: id }
|
||
})
|
||
if (res.code === 200) {
|
||
this.$message.success('删除成功')
|
||
this.searchAssets(this.filters.pageNumber)
|
||
} else {
|
||
this.$message.error(res.msg || '删除失败')
|
||
}
|
||
} catch (e) {
|
||
this.$message.error(e?.message || '删除失败')
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="less">
|
||
.asset-manage-page {
|
||
display: grid;
|
||
grid-template-columns: 300px 1fr;
|
||
gap: 14px;
|
||
padding: 16px;
|
||
min-height: 100%;
|
||
background: #0a0b0d;
|
||
color: rgba(255, 255, 255, 0.9);
|
||
}
|
||
.asset-left,
|
||
.asset-right {
|
||
background: rgba(22, 24, 30, 0.92);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
border-radius: 12px;
|
||
padding: 12px;
|
||
}
|
||
.panel-title {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
margin-bottom: 10px;
|
||
}
|
||
.group-tree {
|
||
margin-top: 10px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
max-height: 72vh;
|
||
overflow: auto;
|
||
}
|
||
.group-node {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: flex-start;
|
||
padding: 8px;
|
||
border-radius: 8px;
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
cursor: pointer;
|
||
}
|
||
.group-node:hover {
|
||
background: rgba(255, 255, 255, 0.04);
|
||
}
|
||
.group-node.active {
|
||
border-color: rgba(0, 202, 224, 0.45);
|
||
background: rgba(0, 202, 224, 0.12);
|
||
}
|
||
.folder-icon {
|
||
font-size: 14px;
|
||
line-height: 1.6;
|
||
}
|
||
.group-name {
|
||
font-size: 13px;
|
||
}
|
||
.group-id {
|
||
font-size: 12px;
|
||
color: rgba(255, 255, 255, 0.55);
|
||
word-break: break-all;
|
||
}
|
||
.form-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(180px, 1fr));
|
||
gap: 10px;
|
||
}
|
||
.field {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
.field label {
|
||
font-size: 12px;
|
||
color: rgba(255, 255, 255, 0.65);
|
||
}
|
||
.field.actions {
|
||
align-self: end;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
.mtop {
|
||
margin-top: 14px;
|
||
}
|
||
.total-line {
|
||
margin: 10px 0;
|
||
font-size: 12px;
|
||
color: rgba(255, 255, 255, 0.65);
|
||
}
|
||
.table-wrap {
|
||
overflow: auto;
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
border-radius: 10px;
|
||
}
|
||
.asset-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 12px;
|
||
}
|
||
.asset-table th,
|
||
.asset-table td {
|
||
padding: 10px;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||
text-align: left;
|
||
vertical-align: top;
|
||
}
|
||
.asset-table th {
|
||
color: rgba(255, 255, 255, 0.72);
|
||
background: rgba(255, 255, 255, 0.02);
|
||
}
|
||
.url-cell {
|
||
max-width: 280px;
|
||
word-break: break-all;
|
||
}
|
||
.pager {
|
||
margin-top: 10px;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
.empty-tip {
|
||
color: rgba(255, 255, 255, 0.5);
|
||
font-size: 12px;
|
||
text-align: center;
|
||
padding: 10px 0;
|
||
}
|
||
.detail-box {
|
||
margin: 0;
|
||
padding: 10px;
|
||
background: rgba(0, 0, 0, 0.35);
|
||
border-radius: 8px;
|
||
max-height: 420px;
|
||
overflow: auto;
|
||
color: #d8f4f7;
|
||
}
|
||
@media (max-width: 980px) {
|
||
.asset-manage-page {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
.form-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
</style>
|