fix: keydown优化
This commit is contained in:
parent
a347873a4e
commit
d2c1fff13f
|
|
@ -1030,6 +1030,10 @@ const onEditorKeyup = (e) => {
|
||||||
mentionActiveIndex.value = -1
|
mentionActiveIndex.value = -1
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 上下键由 keydown 中控制高亮索引,keyup 不应重置为首项
|
||||||
|
if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter') {
|
||||||
|
return
|
||||||
|
}
|
||||||
mentionVisible.value = isReference.value && hasActiveMentionTrigger()
|
mentionVisible.value = isReference.value && hasActiveMentionTrigger()
|
||||||
if (mentionVisible.value && e.key === '@' && mentionCandidates.value.length === 0) {
|
if (mentionVisible.value && e.key === '@' && mentionCandidates.value.length === 0) {
|
||||||
Message.warning('请等待上传完成后再引用')
|
Message.warning('请等待上传完成后再引用')
|
||||||
|
|
@ -1037,7 +1041,15 @@ const onEditorKeyup = (e) => {
|
||||||
mentionActiveIndex.value = -1
|
mentionActiveIndex.value = -1
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mentionActiveIndex.value = mentionVisible.value && mentionCandidates.value.length ? 0 : -1
|
if (mentionVisible.value && mentionCandidates.value.length) {
|
||||||
|
const max = mentionCandidates.value.length - 1
|
||||||
|
mentionActiveIndex.value =
|
||||||
|
mentionActiveIndex.value >= 0
|
||||||
|
? Math.min(mentionActiveIndex.value, max)
|
||||||
|
: 0
|
||||||
|
} else {
|
||||||
|
mentionActiveIndex.value = -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectMentionItem = (item) => {
|
const selectMentionItem = (item) => {
|
||||||
|
|
|
||||||
|
|
@ -22,15 +22,11 @@
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-menu-item :key="item.key">
|
<a-menu-item :key="item.key">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<a-image
|
<span
|
||||||
:width="28"
|
class="menu-svg-icon"
|
||||||
:preview="false"
|
:class="{ active: selectedKeys.indexOf(item.key) > -1 }"
|
||||||
:height="28"
|
v-html="getMenuSvg(item.key)"
|
||||||
:src="`/images/nav/${
|
></span>
|
||||||
selectedKeys.indexOf(item.key) > -1
|
|
||||||
? item.icon + '-a'
|
|
||||||
: item.icon
|
|
||||||
}.png`" />
|
|
||||||
</template>
|
</template>
|
||||||
<div class="menu-item">
|
<div class="menu-item">
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
|
|
@ -148,6 +144,19 @@ const count = (system) => {
|
||||||
? servicePolicyCount.value
|
? servicePolicyCount.value
|
||||||
: agencyPolicyCount.value
|
: agencyPolicyCount.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getMenuSvg = (key) => {
|
||||||
|
if (key === 'video-gen') {
|
||||||
|
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3a9 9 0 1 0 0 18a9 9 0 0 0 0-18Zm-1.5 5.2a.9.9 0 0 1 1.35-.78l4.7 2.7a.9.9 0 0 1 0 1.56l-4.7 2.7a.9.9 0 0 1-1.35-.78V8.2Z"/></svg>'
|
||||||
|
}
|
||||||
|
if (key === 'asset-group-manage') {
|
||||||
|
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 6.5A2.5 2.5 0 0 1 5.5 4H9l1.7 2H18.5A2.5 2.5 0 0 1 21 8.5v8A2.5 2.5 0 0 1 18.5 19h-13A2.5 2.5 0 0 1 3 16.5v-10Z"/></svg>'
|
||||||
|
}
|
||||||
|
if (key === 'asset-manage') {
|
||||||
|
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 4h14a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Zm1.8 11.5h10.4L14 11.4l-2.6 3.1-1.7-2.1-2.9 3.1Zm2.7-6.1a1.6 1.6 0 1 0 0-3.2a1.6 1.6 0 0 0 0 3.2Z"/></svg>'
|
||||||
|
}
|
||||||
|
return '<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="8"/></svg>'
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.arco-tooltip-content {
|
.arco-tooltip-content {
|
||||||
|
|
@ -174,4 +183,25 @@ const count = (system) => {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu-svg-icon {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: rgba(255, 255, 255, 0.74);
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-svg-icon:deep(svg) {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
display: block;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-svg-icon.active {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="asset-group-page">
|
<div class="asset-group-page">
|
||||||
<section class="ag-panel">
|
<section class="ag-panel">
|
||||||
<h3 class="ag-title">新增资源组</h3>
|
<div class="ag-head">
|
||||||
<div class="ag-form">
|
<h3 class="ag-title">资源组管理</h3>
|
||||||
<div class="ag-field">
|
<a-button type="primary" @click="openCreateDialog">新增资源组</a-button>
|
||||||
<label>名称</label>
|
|
||||||
<a-input v-model="createForm.name" placeholder="请输入资源组名称(<=64字符)" />
|
|
||||||
</div>
|
|
||||||
<div class="ag-field">
|
|
||||||
<label>描述</label>
|
|
||||||
<a-textarea
|
|
||||||
v-model="createForm.description"
|
|
||||||
:max-length="300"
|
|
||||||
show-word-limit
|
|
||||||
placeholder="请输入描述(<=300字符)" />
|
|
||||||
</div>
|
|
||||||
<div class="ag-field">
|
|
||||||
<label>GroupType</label>
|
|
||||||
<a-select v-model="createForm.groupType" :disabled="true">
|
|
||||||
<a-option value="AIGC">AIGC</a-option>
|
|
||||||
</a-select>
|
|
||||||
</div>
|
|
||||||
<div class="ag-actions">
|
|
||||||
<a-button type="primary" :loading="createLoading" @click="createGroup">新增</a-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="ag-panel">
|
|
||||||
<h3 class="ag-title">查询资源组</h3>
|
|
||||||
<div class="ag-filter">
|
<div class="ag-filter">
|
||||||
<div class="ag-field">
|
<div class="ag-field">
|
||||||
<label>名称</label>
|
<label>名称</label>
|
||||||
|
|
@ -121,6 +97,26 @@
|
||||||
<a-modal v-model:visible="detailVisible" title="资源组详情" :footer="false" width="680px">
|
<a-modal v-model:visible="detailVisible" title="资源组详情" :footer="false" width="680px">
|
||||||
<pre class="ag-detail">{{ prettyDetail }}</pre>
|
<pre class="ag-detail">{{ prettyDetail }}</pre>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="createVisible"
|
||||||
|
title="新增资源组"
|
||||||
|
:confirm-loading="createLoading"
|
||||||
|
@ok="createGroup">
|
||||||
|
<div class="ag-create-form">
|
||||||
|
<div class="ag-field">
|
||||||
|
<label>名称</label>
|
||||||
|
<a-input v-model="createForm.name" placeholder="请输入资源组名称(<=64字符)" />
|
||||||
|
</div>
|
||||||
|
<div class="ag-field">
|
||||||
|
<label>描述</label>
|
||||||
|
<a-textarea
|
||||||
|
v-model="createForm.description"
|
||||||
|
:max-length="300"
|
||||||
|
show-word-limit
|
||||||
|
placeholder="请输入描述(<=300字符)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -130,6 +126,7 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
createLoading: false,
|
createLoading: false,
|
||||||
|
createVisible: false,
|
||||||
listLoading: false,
|
listLoading: false,
|
||||||
detailLoadingId: '',
|
detailLoadingId: '',
|
||||||
detailVisible: false,
|
detailVisible: false,
|
||||||
|
|
@ -161,6 +158,9 @@ export default {
|
||||||
this.search(1)
|
this.search(1)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
openCreateDialog() {
|
||||||
|
this.createVisible = true
|
||||||
|
},
|
||||||
buildGroupIds() {
|
buildGroupIds() {
|
||||||
return String(this.filters.groupIdsText || '')
|
return String(this.filters.groupIdsText || '')
|
||||||
.split(',')
|
.split(',')
|
||||||
|
|
@ -189,6 +189,7 @@ export default {
|
||||||
this.$message.success('新增成功')
|
this.$message.success('新增成功')
|
||||||
this.createForm.name = ''
|
this.createForm.name = ''
|
||||||
this.createForm.description = ''
|
this.createForm.description = ''
|
||||||
|
this.createVisible = false
|
||||||
this.search(1)
|
this.search(1)
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.msg || '新增失败')
|
this.$message.error(res.msg || '新增失败')
|
||||||
|
|
@ -295,10 +296,16 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.ag-title {
|
.ag-title {
|
||||||
margin: 0 0 14px;
|
margin: 0;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
.ag-head {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
.ag-form,
|
.ag-form,
|
||||||
.ag-filter {
|
.ag-filter {
|
||||||
|
|
@ -306,6 +313,11 @@ export default {
|
||||||
grid-template-columns: repeat(3, minmax(180px, 1fr));
|
grid-template-columns: repeat(3, minmax(180px, 1fr));
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
.ag-create-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.ag-field {
|
.ag-field {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="asset-manage-page">
|
<div class="asset-manage-page">
|
||||||
<section class="asset-left">
|
<section class="asset-left">
|
||||||
<div class="panel-title">素材组树</div>
|
<div class="asset-left-head">
|
||||||
<a-button size="mini" type="outline" :loading="groupLoading" @click="loadGroups">刷新分组</a-button>
|
<div class="panel-title">素材组树</div>
|
||||||
<div class="form-grid mtop">
|
|
||||||
<div class="field">
|
|
||||||
<label>素材组名称</label>
|
|
||||||
<a-input v-model="groupForm.name" placeholder="输入分组名称" />
|
|
||||||
</div>
|
|
||||||
<div class="field actions">
|
<div class="field actions">
|
||||||
<a-button type="primary" size="mini" @click="createGroup">新建分组</a-button>
|
<a-button size="mini" type="outline" :loading="groupLoading" @click="loadGroups">刷新分组</a-button>
|
||||||
<a-button size="mini" @click="updateGroup" :disabled="!groupForm.id">更新分组</a-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="group-tree">
|
<div class="group-tree">
|
||||||
|
|
@ -30,31 +24,10 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="asset-right">
|
<section class="asset-right">
|
||||||
<div class="panel-title">新增素材</div>
|
<div class="asset-left-head">
|
||||||
<div class="form-grid">
|
<div class="panel-title">素材管理</div>
|
||||||
<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">
|
<div class="field actions">
|
||||||
<a-button type="primary" :loading="createLoading" @click="createAsset">新增素材</a-button>
|
<a-button type="primary" size="mini" @click="openCreateAssetDialog">新增素材</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -150,14 +123,44 @@
|
||||||
<a-modal v-model:visible="detailVisible" title="素材详情" :footer="false" width="700px">
|
<a-modal v-model:visible="detailVisible" title="素材详情" :footer="false" width="700px">
|
||||||
<pre class="detail-box">{{ detailText }}</pre>
|
<pre class="detail-box">{{ detailText }}</pre>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="createAssetVisible"
|
||||||
|
title="新增素材"
|
||||||
|
:confirm-loading="createLoading"
|
||||||
|
@ok="createAsset">
|
||||||
|
<div class="create-form-vertical">
|
||||||
|
<div class="field">
|
||||||
|
<label>素材组</label>
|
||||||
|
<a-select v-model="createForm.groupId" placeholder="请选择素材组">
|
||||||
|
<a-option v-for="g in groups" :key="g.Id || g.id" :value="String(g.Id || g.id)">
|
||||||
|
{{ g.Name || g.name || (g.Id || g.id) }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>上传素材</label>
|
||||||
|
<input type="file" @change="onFileChange" />
|
||||||
|
<div class="group-id">{{ createForm.fileName || '未选择文件' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>素材名称</label>
|
||||||
|
<a-input v-model="createForm.name" placeholder="素材名称(可选)" />
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>类型</label>
|
||||||
|
<a-select v-model="createForm.assetType">
|
||||||
|
<a-option value="Image">图片</a-option>
|
||||||
|
<a-option value="Video">视频</a-option>
|
||||||
|
<a-option value="Audio">音频</a-option>
|
||||||
|
</a-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const GROUP_LIST_API = 'api/byteAssetGroup/listAssetGroups'
|
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_CREATE_API = 'api/byteAsset/createAsset'
|
||||||
const ASSET_LIST_API = 'api/byteAsset/listAssets'
|
const ASSET_LIST_API = 'api/byteAsset/listAssets'
|
||||||
const ASSET_GET_API = 'api/byteAsset/getAsset'
|
const ASSET_GET_API = 'api/byteAsset/getAsset'
|
||||||
|
|
@ -170,6 +173,7 @@ export default {
|
||||||
groupLoading: false,
|
groupLoading: false,
|
||||||
createLoading: false,
|
createLoading: false,
|
||||||
listLoading: false,
|
listLoading: false,
|
||||||
|
createAssetVisible: false,
|
||||||
groups: [],
|
groups: [],
|
||||||
selectedGroupId: '',
|
selectedGroupId: '',
|
||||||
createForm: {
|
createForm: {
|
||||||
|
|
@ -179,10 +183,6 @@ export default {
|
||||||
name: '',
|
name: '',
|
||||||
assetType: 'Image'
|
assetType: 'Image'
|
||||||
},
|
},
|
||||||
groupForm: {
|
|
||||||
name: '',
|
|
||||||
id: ''
|
|
||||||
},
|
|
||||||
filters: {
|
filters: {
|
||||||
groupId: '',
|
groupId: '',
|
||||||
name: '',
|
name: '',
|
||||||
|
|
@ -207,6 +207,15 @@ export default {
|
||||||
this.loadGroups()
|
this.loadGroups()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async openCreateAssetDialog() {
|
||||||
|
await this.loadGroups()
|
||||||
|
this.createForm.groupId = this.selectedGroupId || this.createForm.groupId || ''
|
||||||
|
this.createForm.file = null
|
||||||
|
this.createForm.fileName = ''
|
||||||
|
this.createForm.name = ''
|
||||||
|
this.createForm.assetType = 'Image'
|
||||||
|
this.createAssetVisible = true
|
||||||
|
},
|
||||||
async loadGroups() {
|
async loadGroups() {
|
||||||
this.groupLoading = true
|
this.groupLoading = true
|
||||||
try {
|
try {
|
||||||
|
|
@ -225,8 +234,6 @@ export default {
|
||||||
if (!this.selectedGroupId && this.groups.length) {
|
if (!this.selectedGroupId && this.groups.length) {
|
||||||
const gid = this.groups[0].Id || this.groups[0].id
|
const gid = this.groups[0].Id || this.groups[0].id
|
||||||
this.selectedGroupId = gid
|
this.selectedGroupId = gid
|
||||||
this.groupForm.id = gid
|
|
||||||
this.groupForm.name = this.groups[0].Name || this.groups[0].name || ''
|
|
||||||
this.createForm.groupId = gid
|
this.createForm.groupId = gid
|
||||||
this.filters.groupId = gid
|
this.filters.groupId = gid
|
||||||
this.searchAssets(1)
|
this.searchAssets(1)
|
||||||
|
|
@ -240,64 +247,10 @@ export default {
|
||||||
selectGroup(g) {
|
selectGroup(g) {
|
||||||
const gid = g?.Id || g?.id
|
const gid = g?.Id || g?.id
|
||||||
this.selectedGroupId = gid
|
this.selectedGroupId = gid
|
||||||
this.groupForm.id = gid
|
|
||||||
this.groupForm.name = g?.Name || g?.name || ''
|
|
||||||
this.createForm.groupId = gid
|
this.createForm.groupId = gid
|
||||||
this.filters.groupId = gid
|
this.filters.groupId = gid
|
||||||
this.searchAssets(1)
|
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) {
|
onFileChange(e) {
|
||||||
const file = e?.target?.files?.[0]
|
const file = e?.target?.files?.[0]
|
||||||
this.createForm.file = file || null
|
this.createForm.file = file || null
|
||||||
|
|
@ -324,6 +277,7 @@ export default {
|
||||||
this.createForm.file = null
|
this.createForm.file = null
|
||||||
this.createForm.fileName = ''
|
this.createForm.fileName = ''
|
||||||
this.createForm.name = ''
|
this.createForm.name = ''
|
||||||
|
this.createAssetVisible = false
|
||||||
this.searchAssets(1)
|
this.searchAssets(1)
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.msg || '新增素材失败')
|
this.$message.error(res.msg || '新增素材失败')
|
||||||
|
|
@ -457,6 +411,12 @@ export default {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
.asset-left-head {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
.group-tree {
|
.group-tree {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -512,6 +472,11 @@ export default {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
.create-form-vertical {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
.mtop {
|
.mtop {
|
||||||
margin-top: 14px;
|
margin-top: 14px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,35 +6,21 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.MapperFeature;
|
import com.fasterxml.jackson.databind.MapperFeature;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||||
import com.ruoyi.ai.mapper.AiUserMapper;
|
import com.ruoyi.ai.service.IByteDeptApiKeyService;
|
||||||
import com.ruoyi.common.core.domain.entity.AiUser;
|
|
||||||
import com.ruoyi.common.core.domain.entity.SysDept;
|
|
||||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
|
||||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
|
||||||
import com.ruoyi.common.utils.SecurityUtils;
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.http.OkHttpUtils;
|
import com.ruoyi.common.utils.http.OkHttpUtils;
|
||||||
import com.ruoyi.system.mapper.SysDeptMapper;
|
|
||||||
import com.ruoyi.system.mapper.SysUserMapper;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import okhttp3.MediaType;
|
import okhttp3.MediaType;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.RequestBody;
|
import okhttp3.RequestBody;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
import org.apache.catalina.User;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Service
|
@Service
|
||||||
public class BaseByteApiService {
|
public class BaseByteApiService {
|
||||||
@Resource
|
|
||||||
private SysDeptMapper deptMapper;
|
|
||||||
@Resource
|
|
||||||
private AiUserMapper userMapper;
|
|
||||||
|
|
||||||
protected final ObjectMapper objectMapper = new ObjectMapper()
|
protected final ObjectMapper objectMapper = new ObjectMapper()
|
||||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||||
.setPropertyNamingStrategy(PropertyNamingStrategies.UPPER_CAMEL_CASE)
|
.setPropertyNamingStrategy(PropertyNamingStrategies.UPPER_CAMEL_CASE)
|
||||||
|
|
@ -46,12 +32,13 @@ public class BaseByteApiService {
|
||||||
@Value("${byteapi.url}")
|
@Value("${byteapi.url}")
|
||||||
protected String API_URL;
|
protected String API_URL;
|
||||||
|
|
||||||
protected String DEPT_ANCESTORS_SPLIT = ",";
|
@Autowired
|
||||||
|
protected IByteDeptApiKeyService byteDeptApiKeyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行 POST,成功则返回响应体字符串(可能为空字符串)。
|
* POST JSON 调用方舟 OpenAPI;若响应含 {@code Result} 节点则解析为业务对象(与 api.docx 返回示例一致)。
|
||||||
*/
|
*/
|
||||||
private String doPost(String path, Object request) throws IOException {
|
protected <T> T httpExecute(String path, Object request, Class<T> clz) throws IOException {
|
||||||
String jsonBody = objectMapper.writeValueAsString(request);
|
String jsonBody = objectMapper.writeValueAsString(request);
|
||||||
RequestBody body = RequestBody.create(
|
RequestBody body = RequestBody.create(
|
||||||
MediaType.parse("application/json; charset=utf-8"),
|
MediaType.parse("application/json; charset=utf-8"),
|
||||||
|
|
@ -59,7 +46,7 @@ public class BaseByteApiService {
|
||||||
Request httpRequest = new Request.Builder()
|
Request httpRequest = new Request.Builder()
|
||||||
.url(API_URL + path)
|
.url(API_URL + path)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.header("Authorization", "Bearer " + getUserDeptApiKey())
|
.header("Authorization", "Bearer " + resolveCurrentAiUserApiKey())
|
||||||
.post(body)
|
.post(body)
|
||||||
.build();
|
.build();
|
||||||
try (Response response = OkHttpUtils.newCall(httpRequest).execute()) {
|
try (Response response = OkHttpUtils.newCall(httpRequest).execute()) {
|
||||||
|
|
@ -68,96 +55,50 @@ public class BaseByteApiService {
|
||||||
throw new RuntimeException("execute error:" + errorMsg);
|
throw new RuntimeException("execute error:" + errorMsg);
|
||||||
}
|
}
|
||||||
if (response.body() == null) {
|
if (response.body() == null) {
|
||||||
return "";
|
throw new RuntimeException("response body null");
|
||||||
}
|
}
|
||||||
return response.body().string();
|
String responseBody = response.body().string();
|
||||||
|
JsonNode root = objectMapper.readTree(responseBody);
|
||||||
|
JsonNode result = root.get("Result");
|
||||||
|
if (result != null && !result.isNull()) {
|
||||||
|
return objectMapper.treeToValue(result, clz);
|
||||||
|
}
|
||||||
|
return objectMapper.readValue(responseBody, clz);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* POST JSON 调用方舟 OpenAPI;若响应含 {@code Result} 节点则解析为业务对象(与 api.docx 返回示例一致)。
|
|
||||||
*/
|
|
||||||
protected <T> T httpExecute(String path, Object request, Class<T> clz) throws IOException {
|
|
||||||
String responseBody = doPost(path, request);
|
|
||||||
if (responseBody == null || responseBody.isEmpty()) {
|
|
||||||
throw new RuntimeException("response body null");
|
|
||||||
}
|
|
||||||
JsonNode root = objectMapper.readTree(responseBody);
|
|
||||||
JsonNode result = root.get("Result");
|
|
||||||
if (result != null && !result.isNull()) {
|
|
||||||
return objectMapper.treeToValue(result, clz);
|
|
||||||
}
|
|
||||||
return objectMapper.readValue(responseBody, clz);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 无业务体返回的 OpenAPI 调用(如 DeleteAsset)。
|
* 无业务体返回的 OpenAPI 调用(如 DeleteAsset)。
|
||||||
*/
|
*/
|
||||||
protected void httpExecuteNoContent(String path, Object request) throws IOException {
|
protected void httpExecuteNoContent(String path, Object request) throws IOException {
|
||||||
doPost(path, request);
|
String jsonBody = objectMapper.writeValueAsString(request);
|
||||||
|
RequestBody body = RequestBody.create(
|
||||||
|
MediaType.parse("application/json; charset=utf-8"),
|
||||||
|
jsonBody);
|
||||||
|
Request httpRequest = new Request.Builder()
|
||||||
|
.url(API_URL + path)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Authorization", "Bearer " + resolveCurrentAiUserApiKey())
|
||||||
|
.post(body)
|
||||||
|
.build();
|
||||||
|
try (Response response = OkHttpUtils.newCall(httpRequest).execute()) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
String errorMsg = response.body() != null ? response.body().string() : "execute error";
|
||||||
|
throw new RuntimeException("execute error:" + errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据用户找到对应的project
|
* 根据用户找到对应的project
|
||||||
*/
|
*/
|
||||||
protected String getUserProject() {
|
protected String getUserProject() {
|
||||||
Long userId = SecurityUtils.getAiUserId();
|
// TODO
|
||||||
if (userId == null) {
|
return "default";
|
||||||
return null;
|
|
||||||
}
|
|
||||||
AiUser user = userMapper.selectAiUserById(userId);
|
|
||||||
if (user == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// 第二层部门ID,API_KEY放在这里
|
|
||||||
Long secondLvDeptId = getSecondLevelDept(user.getDeptId());
|
|
||||||
if (secondLvDeptId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
SysDept secondDept = deptMapper.selectDeptById(secondLvDeptId);
|
|
||||||
return secondDept.getProject();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected String resolveCurrentAiUserApiKey() {
|
||||||
* 根据用户所在部门找到对应的火山方舟ApiKey
|
return byteDeptApiKeyService.resolveVolcApiKey(SecurityUtils.getAiUserId());
|
||||||
*/
|
|
||||||
protected String getUserDeptApiKey() {
|
|
||||||
Long userId = SecurityUtils.getAiUserId();
|
|
||||||
if (userId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
AiUser user = userMapper.selectAiUserById(userId);
|
|
||||||
if (user == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// 第二层部门ID,API_KEY放在这里
|
|
||||||
Long secondLvDeptId = getSecondLevelDept(user.getDeptId());
|
|
||||||
if (secondLvDeptId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
SysDept secondDept = deptMapper.selectDeptById(secondLvDeptId);
|
|
||||||
return secondDept.getByteApiKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 找到当前部门所属第二部门ID
|
|
||||||
* @param deptId 当前部门
|
|
||||||
*/
|
|
||||||
protected Long getSecondLevelDept(long deptId) {
|
|
||||||
SysDept dept = deptMapper.selectDeptById(deptId);
|
|
||||||
String ancestors = dept.getAncestors();
|
|
||||||
// 判断是第几层
|
|
||||||
if (ancestors == null || ancestors.isEmpty() || "0".equals(ancestors)) {
|
|
||||||
// 第一层
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String[] parentDeptArray = ancestors.split(DEPT_ANCESTORS_SPLIT);
|
|
||||||
int length = parentDeptArray.length;
|
|
||||||
if (length == 1) {
|
|
||||||
// 只有一个上级,所以当前节点是第二层,直接返回
|
|
||||||
return deptId;
|
|
||||||
}
|
|
||||||
// 大于二级
|
|
||||||
return Long.parseLong(parentDeptArray[1]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue