响应指令管理支持媒体上传并自动回填路径

1. 新增响应指令管理专用媒体上传接口,按图片语音视频白名单校验并分目录存储。

2. 在动作配置UI中为图片语音视频增加上传按钮,上传成功后自动回填本地绝对路径。

3. 保留结构化动作表单,进一步减少手工维护路径和JSON的场景。
This commit is contained in:
liuwei
2026-04-23 13:30:17 +08:00
parent 23b9d76b06
commit 3c7becd94f
2 changed files with 119 additions and 0 deletions

View File

@@ -200,6 +200,14 @@
<template v-else-if="scope.row.type === 'image'">
<el-input v-model="scope.row.path" placeholder="图片文件路径,例如 D:/learn/abot/static/uploads/a.jpg"></el-input>
<div class="upload-row">
<el-upload
:show-file-list="false"
:http-request="(options) => uploadActionFile(options, scope.row, 'image')"
accept=".png,.jpg,.jpeg,.gif,.webp">
<el-button size="mini" type="primary" plain style="margin-top:6px;">上传图片并回填</el-button>
</el-upload>
</div>
</template>
<template v-else-if="scope.row.type === 'voice'">
@@ -209,11 +217,27 @@
<el-option label="wav" value="wav"></el-option>
<el-option label="amr" value="amr"></el-option>
</el-select>
<div class="upload-row">
<el-upload
:show-file-list="false"
:http-request="(options) => uploadActionFile(options, scope.row, 'voice')"
accept=".mp3,.wav,.amr">
<el-button size="mini" type="primary" plain style="margin-top:6px;">上传语音并回填</el-button>
</el-upload>
</div>
</template>
<template v-else-if="scope.row.type === 'video'">
<el-input v-model="scope.row.path" placeholder="视频文件路径"></el-input>
<el-input v-model="scope.row.cover_path" style="margin-top:6px;" placeholder="封面路径(可选)"></el-input>
<div class="upload-row">
<el-upload
:show-file-list="false"
:http-request="(options) => uploadActionFile(options, scope.row, 'video')"
accept=".mp4,.mov,.m4v">
<el-button size="mini" type="primary" plain style="margin-top:6px;">上传视频并回填</el-button>
</el-upload>
</div>
</template>
<template v-else-if="scope.row.type === 'link'">
@@ -429,6 +453,38 @@ new Vue({
return row
})
},
async uploadActionFile(uploadOptions, row, mediaType) {
// 统一处理媒体上传:
// 1. 使用后端专用接口保存到固定目录;
// 2. 上传成功后自动把绝对路径回填到当前动作;
// 3. 这样配置者不需要手动复制路径,维护成本更低。
const file = uploadOptions && uploadOptions.file
if (!file) {
this.$message.error('未获取到上传文件')
return
}
const formData = new FormData()
formData.append('file', file)
formData.append('media_type', mediaType)
try {
const resp = await axios.post('/fun_command_rules/api/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
if (resp.data && resp.data.success) {
const path = (((resp.data || {}).data || {}).path) || ''
if (path) {
row.path = path
}
this.$message.success(resp.data.message || '上传成功')
return
}
this.$message.error((resp.data && resp.data.message) || '上传失败')
} catch (error) {
const msg = (error.response && error.response.data && error.response.data.message) || '上传失败'
this.$message.error(msg)
}
},
async loadGroups() {
const resp = await axios.get('/contacts/api/groups')
const groups = (resp.data && resp.data.data && resp.data.data.groups) || {}
@@ -592,6 +648,7 @@ new Vue({
.trigger-text{font-size:13px;color:#334155;word-break:break-all}
.action-toolbar{display:flex;gap:8px;margin-bottom:8px}
.action-config{display:flex;flex-direction:column;gap:0}
.upload-row{display:flex;align-items:center}
.payload-tip{margin-top:8px;color:#64748b;font-size:12px;line-height:1.7}
.payload-tip code{background:#f1f5f9;border:1px solid #dbe3ee;padding:1px 6px;border-radius:6px;margin-right:6px}
</style>