ai_images/portal-ui/src/views/AssetGroupManage.vue

466 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="asset-group-page">
<section class="ag-panel">
<div class="ag-head">
<h3 class="ag-title">资源组管理</h3>
<a-button type="primary" @click="openCreateDialog">新增资源组</a-button>
</div>
<div class="ag-filter">
<div class="ag-field">
<label>名称</label>
<a-input v-model="filters.name" placeholder="按名称过滤" />
</div>
<div class="ag-field">
<label>资源组编号</label>
<a-input v-model="filters.groupIdsText" placeholder="多个编号用英文逗号分隔" />
</div>
<div class="ag-field">
<label>资源组类型</label>
<a-select v-model="filters.groupType">
<a-option value="AIGC">AIGC生成类</a-option>
</a-select>
</div>
<div class="ag-field">
<label>排序字段</label>
<a-select v-model="filters.sortBy">
<a-option value="CreateTime">创建时间</a-option>
<a-option value="UpdateTime">更新时间</a-option>
</a-select>
</div>
<div class="ag-field">
<label>排序方向</label>
<a-select v-model="filters.sortOrder">
<a-option value="Desc">从新到旧</a-option>
<a-option value="Asc">从旧到新</a-option>
</a-select>
</div>
<div class="ag-actions">
<a-button type="primary" :loading="listLoading" @click="search(1)">查询</a-button>
<a-button @click="resetFilters">重置</a-button>
</div>
</div>
<a-spin :loading="listLoading">
<div class="ag-total">总数{{ totalCount }}</div>
<div class="ag-table-wrap">
<table class="ag-table">
<colgroup>
<col class="ag-col-id" />
<col class="ag-col-name" />
<col class="ag-col-desc" />
<col class="ag-col-type" />
<col class="ag-col-project" />
<col class="ag-col-time" />
<col class="ag-col-time" />
<col class="ag-col-action" />
</colgroup>
<thead>
<tr>
<th>编号</th>
<th>名称</th>
<th>描述</th>
<th>类型</th>
<th>项目名称</th>
<th>创建时间</th>
<th>更新时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items" :key="item.Id || item.id">
<td>{{ item.Id || item.id }}</td>
<td>{{ item.Name || item.name }}</td>
<td>{{ item.Description || item.description || '-' }}</td>
<td>{{ formatGroupTypeLabel(item) }}</td>
<td>{{ item.ProjectName || item.projectName || '-' }}</td>
<td>{{ item.CreateTime || item.createTime || '-' }}</td>
<td>{{ item.UpdateTime || item.updateTime || '-' }}</td>
<td>
<a-button
size="mini"
type="outline"
:loading="detailLoadingId === (item.Id || item.id)"
@click="getDetail(item)">
详情
</a-button>
</td>
</tr>
<tr v-if="!items.length">
<td colspan="8" class="ag-empty">暂无数据</td>
</tr>
</tbody>
</table>
</div>
<div class="ag-pagination">
<a-pagination
:total="totalCount"
:current="filters.pageNumber"
:page-size="filters.pageSize"
show-total
show-jumper
show-page-size
:page-size-options="listPageSizeOptions"
@change="onGroupPageChange"
@page-size-change="onPageSizeChange" />
</div>
</a-spin>
</section>
<a-modal v-model:visible="detailVisible" title="资源组详情" :footer="false" width="680px">
<pre class="ag-detail">{{ prettyDetail }}</pre>
</a-modal>
<a-modal
v-model:visible="createVisible"
title="新增资源组"
width="520px"
:confirm-loading="createLoading"
@ok="createGroup">
<!-- 弹层 teleported 到 body使用 FormItem 保证标签与 dark 样式生效 -->
<a-form :model="createForm" layout="horizontal" auto-label-width class="ag-create-modal-form">
<a-form-item label="名称" required>
<a-input
v-model="createForm.name"
placeholder="请输入资源组名称≤64 字符)"
:max-length="64"
allow-clear />
</a-form-item>
<a-form-item label="描述">
<a-textarea
v-model="createForm.description"
:max-length="300"
show-word-limit
:auto-size="{ minRows: 3, maxRows: 8 }"
placeholder="请输入描述≤300 字符)" />
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script>
import { byteApiItems, byteApiTotalCount } from '@/utils/byteAssetApi'
const LIST_PAGE_NUMBER_MAX = 100
const LIST_PAGE_SIZE_OPTIONS = [10, 20, 50, 100]
export default {
name: 'AssetGroupManage',
data() {
return {
createLoading: false,
createVisible: false,
listLoading: false,
detailLoadingId: '',
detailVisible: false,
detailData: null,
createForm: {
name: '',
description: '',
groupType: 'AIGC'
},
filters: {
name: '',
groupIdsText: '',
groupType: 'AIGC',
pageNumber: 1,
pageSize: 10,
sortBy: 'CreateTime',
sortOrder: 'Desc'
},
totalCount: 0,
items: [],
listPageSizeOptions: LIST_PAGE_SIZE_OPTIONS
}
},
computed: {
prettyDetail() {
return this.detailData ? JSON.stringify(this.detailData, null, 2) : '{}'
}
},
mounted() {
this.search(1)
},
methods: {
formatGroupTypeLabel(item) {
const t = String(item?.GroupType || item?.groupType || '').trim()
if (!t) return '-'
if (t === 'AIGC') return 'AIGC生成类'
return t
},
clampGroupPage(n) {
const page = Number(n) || 1
const size = Number(this.filters.pageSize) || 10
let maxPage = LIST_PAGE_NUMBER_MAX
if (this.totalCount > 0) {
const totalPages = Math.ceil(this.totalCount / size)
maxPage = Math.min(LIST_PAGE_NUMBER_MAX, Math.max(1, totalPages))
}
return Math.min(Math.max(1, page), maxPage)
},
onGroupPageChange(page) {
this.search(this.clampGroupPage(page))
},
openCreateDialog() {
this.createVisible = true
},
buildGroupIds() {
return String(this.filters.groupIdsText || '')
.split(',')
.map((x) => x.trim())
.filter((x) => !!x)
},
async createGroup() {
const name = String(this.createForm.name || '').trim()
if (!name) {
this.$message.error('请填写名称')
return
}
this.createLoading = true
try {
const res = await this.$axios({
url: 'api/byteAssetGroup/createAssetGroup',
method: 'POST',
data: {
name,
description: String(this.createForm.description || '').trim()
}
})
if (res.code === 200) {
this.$message.success('新增成功')
this.createForm.name = ''
this.createForm.description = ''
this.createVisible = false
this.search(1)
} else {
this.$message.error(res.msg || '新增失败')
}
} catch (e) {
this.$message.error(e?.message || '新增失败')
} finally {
this.createLoading = false
}
},
async search(page = this.filters.pageNumber) {
this.filters.pageNumber = this.clampGroupPage(page)
this.listLoading = true
try {
const payload = {
filter: {
groupType: this.filters.groupType || 'AIGC'
},
pageNumber: this.filters.pageNumber,
pageSize: this.filters.pageSize,
sortBy: this.filters.sortBy,
sortOrder: this.filters.sortOrder
}
const name = String(this.filters.name || '').trim()
if (name) payload.filter.name = name
const ids = this.buildGroupIds()
if (ids.length) payload.filter.groupIds = ids
const res = await this.$axios({
url: 'api/byteAssetGroup/listAssetGroups',
method: 'POST',
data: payload
})
const data = res.data || {}
this.totalCount = byteApiTotalCount(data)
this.items = byteApiItems(data)
if (res.code !== 200) {
this.$message.error(res.msg || '查询失败')
}
} catch (e) {
this.$message.error(e?.message || '查询失败')
} finally {
this.listLoading = false
}
},
onPageSizeChange(size) {
const s = Number(size) || 10
this.filters.pageSize = LIST_PAGE_SIZE_OPTIONS.includes(s) ? s : 10
this.search(1)
},
resetFilters() {
this.filters = {
name: '',
groupIdsText: '',
groupType: 'AIGC',
pageNumber: 1,
pageSize: 10,
sortBy: 'CreateTime',
sortOrder: 'Desc'
}
this.search(1)
},
async getDetail(item) {
const id = item?.Id || item?.id
if (!id) return
this.detailLoadingId = id
try {
const res = await this.$axios({
url: 'api/byteAssetGroup/getAssetGroup',
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 || '查询详情失败')
} finally {
this.detailLoadingId = ''
}
}
}
}
</script>
<style scoped lang="less">
.asset-group-page {
display: flex;
flex-direction: column;
gap: 16px;
padding: 18px;
background: #0a0b0d;
color: rgba(255, 255, 255, 0.88);
min-height: 100%;
}
.ag-panel {
background: rgba(22, 24, 30, 0.92);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 14px;
padding: 14px;
}
.ag-title {
margin: 0;
font-size: 15px;
font-weight: 600;
}
.ag-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 14px;
}
.ag-form,
.ag-filter {
display: grid;
grid-template-columns: repeat(3, minmax(180px, 1fr));
gap: 12px;
}
.ag-field {
display: flex;
flex-direction: column;
gap: 6px;
}
.ag-field label {
font-size: 12px;
color: rgba(255, 255, 255, 0.65);
}
.ag-actions {
display: flex;
align-items: flex-end;
gap: 8px;
}
.ag-total {
margin: 6px 0 10px;
font-size: 12px;
color: rgba(255, 255, 255, 0.65);
}
.ag-table-wrap {
width: 100%;
max-width: 100%;
box-sizing: border-box;
overflow-x: auto;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 10px;
}
.ag-table {
width: 100%;
max-width: 100%;
table-layout: fixed;
border-collapse: collapse;
font-size: 12px;
}
/* 列宽按百分比分配随容器100% 随分辨率伸缩 */
.ag-col-id {
width: 10%;
}
.ag-col-name {
width: 12%;
}
.ag-col-desc {
width: 24%;
}
.ag-col-type {
width: 8%;
}
.ag-col-project {
width: 12%;
}
.ag-col-time {
width: 11%;
}
.ag-col-action {
width: 12%;
}
.ag-table th,
.ag-table td {
padding: 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
text-align: left;
vertical-align: top;
word-break: break-word;
overflow-wrap: anywhere;
}
.ag-table th {
color: rgba(255, 255, 255, 0.7);
font-weight: 600;
background: rgba(255, 255, 255, 0.02);
}
.ag-empty {
text-align: center;
color: rgba(255, 255, 255, 0.5);
}
.ag-pagination {
margin-top: 12px;
display: flex;
justify-content: flex-end;
}
.ag-detail {
margin: 0;
padding: 12px;
border-radius: 8px;
background: rgba(0, 0, 0, 0.35);
color: #d8f4f7;
max-height: 420px;
overflow: auto;
white-space: pre-wrap;
word-break: break-all;
}
@media (max-width: 960px) {
.ag-form,
.ag-filter {
grid-template-columns: 1fr;
}
}
</style>