fix: 修复问题
This commit is contained in:
@@ -9,6 +9,7 @@ import GmIcon from '@/components/icons/Icon.vue'
|
||||
import { UserPromptApi } from '@/api/userPrompt'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import GradientButton from '@/components/GradientButton.vue'
|
||||
import PromptSelector from '@/components/PromptSelector.vue'
|
||||
import { setJSON, getJSON } from '@/utils/storage'
|
||||
import BasicLayout from '@/layouts/components/BasicLayout.vue'
|
||||
|
||||
@@ -38,28 +39,10 @@ const { getVoiceText } = useVoiceText()
|
||||
// 提示词相关状态
|
||||
const allPrompts = ref([])
|
||||
const loadingPrompts = ref(false)
|
||||
const showAllPromptsModal = ref(false)
|
||||
const selectedPromptId = ref(null)
|
||||
const promptSearchKeyword = ref('')
|
||||
const DISPLAY_COUNT = 6 // 展示的提示词数量
|
||||
|
||||
// 计算属性:展示的部分提示词
|
||||
const displayPrompts = computed(() => {
|
||||
return allPrompts.value.slice(0, DISPLAY_COUNT)
|
||||
})
|
||||
|
||||
// 计算属性:过滤后的全部提示词(用于"更多"弹窗)
|
||||
const filteredPrompts = computed(() => {
|
||||
if (!promptSearchKeyword.value.trim()) {
|
||||
return allPrompts.value
|
||||
}
|
||||
const keyword = promptSearchKeyword.value.trim().toLowerCase()
|
||||
return allPrompts.value.filter(p =>
|
||||
p.name.toLowerCase().includes(keyword) ||
|
||||
(p.content && p.content.toLowerCase().includes(keyword))
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* 加载用户提示词列表
|
||||
* 从服务器获取当前用户的提示词,并按创建时间倒序排列
|
||||
@@ -88,9 +71,6 @@ async function loadUserPrompts() {
|
||||
const timeB = b.createTime ? new Date(b.createTime).getTime() : 0
|
||||
return timeB - timeA
|
||||
})
|
||||
|
||||
// 如果用户没有选择提示词,尝试恢复本地存储的选中项或默认选中第一个
|
||||
await restoreOrSelectPrompt()
|
||||
} else {
|
||||
throw new Error(response?.msg || response?.message || '加载失败')
|
||||
}
|
||||
@@ -102,97 +82,17 @@ async function loadUserPrompts() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存选中的提示词ID到本地存储
|
||||
*/
|
||||
async function saveSelectedPromptId(promptId) {
|
||||
if (promptId) {
|
||||
await setJSON('copywriting_selected_prompt_id', promptId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从本地存储恢复选中的提示词ID
|
||||
*/
|
||||
async function loadSelectedPromptId() {
|
||||
try {
|
||||
const savedId = await getJSON('copywriting_selected_prompt_id', null)
|
||||
return savedId
|
||||
} catch (error) {
|
||||
console.error('加载保存的提示词ID失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID选中提示词
|
||||
*/
|
||||
async function selectPromptById(promptId) {
|
||||
if (!promptId) return false
|
||||
|
||||
const prompt = allPrompts.value.find(p => p.id === promptId)
|
||||
if (prompt && prompt.content) {
|
||||
selectedPromptId.value = prompt.id
|
||||
form.value.prompt = prompt.content
|
||||
promptStore.setPrompt(prompt.content, prompt)
|
||||
await saveSelectedPromptId(promptId)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复或选中提示词
|
||||
* 优先级:本地存储的选中项 > 第一个提示词
|
||||
*/
|
||||
async function restoreOrSelectPrompt() {
|
||||
if (allPrompts.value.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 如果已经有选中项且内容存在,不需要重新选择
|
||||
if (selectedPromptId.value && form.value.prompt) {
|
||||
// 验证选中的提示词是否还在列表中
|
||||
const currentPrompt = allPrompts.value.find(p => p.id === selectedPromptId.value)
|
||||
if (currentPrompt && currentPrompt.content === form.value.prompt) {
|
||||
return true // 已经正确选中,无需操作
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试恢复本地存储的选中项
|
||||
const savedPromptId = await loadSelectedPromptId()
|
||||
if (savedPromptId) {
|
||||
const restored = await selectPromptById(savedPromptId)
|
||||
if (restored) {
|
||||
return true // 成功恢复保存的选中项
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有保存的选中项或恢复失败,则选中第一个
|
||||
const firstPrompt = allPrompts.value[0]
|
||||
if (firstPrompt?.content) {
|
||||
selectedPromptId.value = firstPrompt.id
|
||||
form.value.prompt = firstPrompt.content
|
||||
promptStore.setPrompt(firstPrompt.content, firstPrompt)
|
||||
await saveSelectedPromptId(firstPrompt.id)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 选择提示词
|
||||
async function selectPrompt(prompt) {
|
||||
// 处理提示词选择
|
||||
function handlePromptChange(prompt) {
|
||||
if (!prompt || !prompt.content) {
|
||||
message.warning('提示词内容为空')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
selectedPromptId.value = prompt.id
|
||||
form.value.prompt = prompt.content
|
||||
promptStore.setPrompt(prompt.content, prompt)
|
||||
await saveSelectedPromptId(prompt.id)
|
||||
showAllPromptsModal.value = false
|
||||
}
|
||||
|
||||
|
||||
@@ -217,7 +117,7 @@ async function waitForUserInfo() {
|
||||
async function ensureUserInfoLoaded() {
|
||||
const isLoggedIn = userStore.isLoggedIn
|
||||
const hasNoUserId = !userStore.userId
|
||||
|
||||
|
||||
if (isLoggedIn && hasNoUserId) {
|
||||
try {
|
||||
await userStore.fetchUserInfo()
|
||||
@@ -239,13 +139,13 @@ async function initializePage() {
|
||||
if (promptStore.currentPrompt) {
|
||||
form.value.prompt = promptStore.currentPrompt
|
||||
}
|
||||
|
||||
|
||||
// 2. 等待用户信息初始化完成
|
||||
await waitForUserInfo()
|
||||
|
||||
|
||||
// 3. 确保用户信息已加载
|
||||
await ensureUserInfoLoaded()
|
||||
|
||||
|
||||
// 4. 加载提示词列表
|
||||
await loadUserPrompts()
|
||||
}
|
||||
@@ -255,34 +155,11 @@ onMounted(() => {
|
||||
initializePage()
|
||||
})
|
||||
|
||||
/**
|
||||
* keep-alive 激活时的处理
|
||||
* 1. 如果有提示词列表,尝试恢复或选中提示词
|
||||
* 2. 如果没有提示词列表但用户已登录,加载提示词列表
|
||||
*/
|
||||
onActivated(async () => {
|
||||
// 如果已经有提示词列表,尝试恢复或选中提示词
|
||||
if (allPrompts.value.length > 0) {
|
||||
await restoreOrSelectPrompt()
|
||||
}
|
||||
// 如果提示词列表为空,但用户已登录,则尝试加载
|
||||
else if (userStore.userId) {
|
||||
await loadUserPrompts()
|
||||
}
|
||||
// 如果用户未登录,等待用户信息加载
|
||||
else if (!userStore.userId && userStore.isLoggedIn) {
|
||||
await ensureUserInfoLoaded()
|
||||
if (userStore.userId) {
|
||||
await loadUserPrompts()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 监听 userId 变化:如果之前没有 userId,现在有了,则自动加载提示词
|
||||
watch(() => userStore.userId, async (newUserId, oldUserId) => {
|
||||
const userIdChanged = newUserId && !oldUserId
|
||||
const hasNoPrompts = allPrompts.value.length === 0
|
||||
|
||||
|
||||
if (userIdChanged && hasNoPrompts) {
|
||||
await loadUserPrompts()
|
||||
}
|
||||
@@ -510,47 +387,25 @@ defineOptions({ name: 'ContentStyleCopywriting' })
|
||||
<a-form :model="form" layout="vertical" class="form-container">
|
||||
<a-form-item class="form-item">
|
||||
<template #label>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>
|
||||
选择提示词风格
|
||||
<span class="form-tip-inline">从已保存的提示词中选择</span>
|
||||
</span>
|
||||
<a-space>
|
||||
<a-button
|
||||
size="small"
|
||||
type="link"
|
||||
@click="showAllPromptsModal = true"
|
||||
:disabled="allPrompts.length === 0">
|
||||
更多 ({{ allPrompts.length }})
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
<span>
|
||||
选择提示词风格
|
||||
<span class="form-tip-inline">从已保存的提示词中选择</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<!-- 提示词标签展示区域 -->
|
||||
<div v-if="displayPrompts.length > 0" class="prompt-tags-container">
|
||||
<div class="prompt-tags-grid">
|
||||
<div
|
||||
v-for="prompt in displayPrompts"
|
||||
:key="prompt.id"
|
||||
class="prompt-tag"
|
||||
:class="{ 'prompt-tag-selected': selectedPromptId === prompt.id }"
|
||||
@click="selectPrompt(prompt)">
|
||||
<span class="prompt-tag-name">{{ prompt.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 使用 PromptSelector 组件 -->
|
||||
<PromptSelector
|
||||
v-model="selectedPromptId"
|
||||
:prompts="allPrompts"
|
||||
:loading="loadingPrompts"
|
||||
:search-keyword="promptSearchKeyword"
|
||||
@change="handlePromptChange"
|
||||
@update:searchKeyword="promptSearchKeyword = $event"
|
||||
/>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="!loadingPrompts" class="prompt-empty">
|
||||
<div style="color: var(--color-text-secondary); font-size: 14px; text-align: center; padding: 20px;">
|
||||
您可以在视频分析页面保存风格
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-else class="prompt-loading">
|
||||
<a-spin size="small" />
|
||||
<!-- 空状态提示 -->
|
||||
<div v-if="!loadingPrompts && allPrompts.length === 0" class="prompt-empty" style="color: var(--color-text-secondary); font-size: 14px; text-align: center; padding: 20px;">
|
||||
您可以在视频分析页面保存风格
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
@@ -664,58 +519,6 @@ defineOptions({ name: 'ContentStyleCopywriting' })
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 更多提示词弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="showAllPromptsModal"
|
||||
title="选择提示词"
|
||||
:width="800"
|
||||
:maskClosable="false"
|
||||
>
|
||||
<div class="all-prompts-modal">
|
||||
<!-- 搜索框 -->
|
||||
<a-input
|
||||
v-model:value="promptSearchKeyword"
|
||||
placeholder="搜索提示词名称或内容..."
|
||||
allow-clear
|
||||
style="margin-bottom: 16px;"
|
||||
>
|
||||
<template #prefix>
|
||||
<GmIcon name="icon-search" :size="16" />
|
||||
</template>
|
||||
</a-input>
|
||||
|
||||
<!-- 提示词列表(标签形式) -->
|
||||
<div v-if="filteredPrompts.length > 0" class="all-prompts-tags">
|
||||
<div
|
||||
v-for="prompt in filteredPrompts"
|
||||
:key="prompt.id"
|
||||
class="all-prompt-tag"
|
||||
:class="{ 'all-prompt-tag-selected': selectedPromptId === prompt.id }"
|
||||
@click="selectPrompt(prompt)"
|
||||
>
|
||||
<span class="all-prompt-tag-name">{{ prompt.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else style="text-align: center; padding: 40px; color: var(--color-text-secondary);">
|
||||
没有找到匹配的提示词
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<a-space>
|
||||
<a-button @click="showAllPromptsModal = false">取消</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="loadUserPrompts"
|
||||
:loading="loadingPrompts"
|
||||
>
|
||||
刷新列表
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-modal>
|
||||
</BasicLayout>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -57,6 +57,8 @@
|
||||
v-model:open="modalVisible"
|
||||
:title="isCreateMode ? '新建配音' : '编辑配音'"
|
||||
:width="600"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
:confirm-loading="submitting"
|
||||
@ok="handleSubmit"
|
||||
@cancel="handleCancel"
|
||||
|
||||
@@ -150,6 +150,7 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-input
|
||||
ref="nameInputRef"
|
||||
v-model:value="editingDisplayName"
|
||||
size="small"
|
||||
@blur="handleSaveName(file)"
|
||||
@@ -207,6 +208,7 @@
|
||||
<MaterialUploadModal
|
||||
v-model:visible="uploadModalVisible"
|
||||
:group-id="selectedGroupId"
|
||||
:uploading="uploadLoading"
|
||||
@confirm="handleFileUpload"
|
||||
/>
|
||||
|
||||
@@ -230,7 +232,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, watch } from 'vue';
|
||||
import { ref, reactive, onMounted, watch, nextTick } from 'vue';
|
||||
import {
|
||||
UploadOutlined,
|
||||
SearchOutlined,
|
||||
@@ -253,6 +255,7 @@ const totalFileCount = ref(0);
|
||||
const searchKeyword = ref('');
|
||||
|
||||
const uploadModalVisible = ref(false);
|
||||
const uploadLoading = ref(false);
|
||||
const createGroupModalVisible = ref(false);
|
||||
|
||||
// Upload Hook
|
||||
@@ -270,6 +273,7 @@ const selectedFileIds = ref([]);
|
||||
// 编辑状态
|
||||
const editingFileId = ref(null);
|
||||
const editingDisplayName = ref('');
|
||||
const nameInputRef = ref(null);
|
||||
|
||||
// 分页信息
|
||||
const pagination = reactive({
|
||||
@@ -409,6 +413,7 @@ const handleOpenUploadModal = () => {
|
||||
|
||||
const handleFileUpload = async (filesWithCover, category, groupId) => {
|
||||
try {
|
||||
uploadLoading.value = true;
|
||||
// 上传每个文件
|
||||
for (const fileWithCover of filesWithCover) {
|
||||
await upload(fileWithCover.file, {
|
||||
@@ -433,6 +438,8 @@ const handleFileUpload = async (filesWithCover, category, groupId) => {
|
||||
} catch (error) {
|
||||
console.error("文件上传失败:", error);
|
||||
message.error("文件上传失败: " + (error.message || "未知错误"));
|
||||
} finally {
|
||||
uploadLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -525,17 +532,21 @@ const getFileTypeText = (fileName) => {
|
||||
return ext ? `${ext.toLowerCase()}` : '';
|
||||
};
|
||||
|
||||
const handleEditName = (file) => {
|
||||
const handleEditName = async (file) => {
|
||||
editingFileId.value = file.id;
|
||||
editingDisplayName.value = file.displayName || file.fileName;
|
||||
// 延迟聚焦,确保输入框已渲染
|
||||
setTimeout(() => {
|
||||
const input = document.querySelector('.ant-input:focus');
|
||||
// 使用 nextTick 确保 DOM 更新后再聚焦
|
||||
await nextTick();
|
||||
|
||||
// 查找当前编辑文件的输入框
|
||||
const nameElement = document.querySelector(`[data-file-id="${file.id}"] .material-item__name`);
|
||||
if (nameElement) {
|
||||
const input = nameElement.querySelector('input');
|
||||
if (input) {
|
||||
input.focus();
|
||||
input.select();
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveName = async (file) => {
|
||||
@@ -708,11 +719,6 @@ onMounted(() => {
|
||||
&--active {
|
||||
background: #e6f7ff;
|
||||
color: #1890ff;
|
||||
|
||||
// 移除active状态下的hover效果
|
||||
&:hover {
|
||||
background: #e6f7ff;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
@@ -798,9 +804,9 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.ant-btn {
|
||||
transition: all 0.2s ease;
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 8px rgba(59, 130, 246, 0.2);
|
||||
@@ -940,15 +946,29 @@ onMounted(() => {
|
||||
.ant-input {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
height: 32px;
|
||||
line-height: 30px;
|
||||
border-radius: 6px;
|
||||
height: 20px; /* 与文字高度一致 */
|
||||
line-height: 1.4;
|
||||
border-radius: 4px;
|
||||
border-color: #3B82F6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
padding: 0 4px; /* 减少内边距 */
|
||||
margin: 0; /* 移除margin避免布局问题 */
|
||||
display: block; /* 确保输入框正常显示 */
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.15);
|
||||
}
|
||||
|
||||
/* 移除默认的内边距和高度 */
|
||||
input {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
line-height: inherit;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1014,67 +1034,7 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
// 键盘导航支持
|
||||
.material-item:focus-within {
|
||||
outline: 2px solid #3B82F6;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.material-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.material-content__search {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 12px;
|
||||
|
||||
.material-content__actions {
|
||||
margin-left: 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.material-item {
|
||||
&__preview {
|
||||
height: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.material-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.material-content {
|
||||
margin: 8px;
|
||||
border-radius: 8px;
|
||||
|
||||
&__search,
|
||||
&__list,
|
||||
&__pagination {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 无障碍支持
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
// 批量删除确认弹窗样式
|
||||
:deep(.batch-delete-modal) {
|
||||
|
||||
@@ -1,515 +0,0 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, reactive, h } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { EditOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue'
|
||||
import { UserPromptApi } from '@/api/userPrompt'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
|
||||
// 表格数据
|
||||
const dataSource = ref([])
|
||||
const loading = ref(false)
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
})
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
name: '',
|
||||
category: '',
|
||||
status: undefined,
|
||||
})
|
||||
|
||||
// 编辑弹窗
|
||||
const editModalVisible = ref(false)
|
||||
const editForm = reactive({
|
||||
id: null,
|
||||
name: '',
|
||||
content: '',
|
||||
category: '',
|
||||
status: 1,
|
||||
})
|
||||
const editFormRef = ref(null)
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '内容',
|
||||
dataIndex: 'content',
|
||||
key: 'content',
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
if (!text) return '-'
|
||||
const preview = text.length > 100 ? text.substring(0, 100) + '...' : text
|
||||
return h('span', { title: text }, preview)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '分类',
|
||||
dataIndex: 'category',
|
||||
key: 'category',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
customRender: ({ text }) => {
|
||||
return h('span', {
|
||||
style: {
|
||||
color: text === 1 ? 'var(--color-green-500)' : 'var(--color-red-500)',
|
||||
},
|
||||
}, text === 1 ? '启用' : '禁用')
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '使用次数',
|
||||
dataIndex: 'useCount',
|
||||
key: 'useCount',
|
||||
width: 120,
|
||||
sorter: (a, b) => (a.useCount || 0) - (b.useCount || 0),
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
width: 180,
|
||||
customRender: ({ text }) => {
|
||||
return text ? dayjs(text).format('YYYY-MM-DD HH:mm:ss') : '-'
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 150,
|
||||
fixed: 'right',
|
||||
customRender: ({ record }) => {
|
||||
return h('div', { class: 'action-buttons' }, [
|
||||
h('a-button', {
|
||||
type: 'link',
|
||||
size: 'small',
|
||||
class: 'action-btn action-btn-edit',
|
||||
onClick: () => handleEdit(record),
|
||||
}, [
|
||||
h(EditOutlined),
|
||||
'编辑',
|
||||
]),
|
||||
h('a-button', {
|
||||
type: 'link',
|
||||
size: 'small',
|
||||
danger: true,
|
||||
class: 'action-btn action-btn-delete',
|
||||
onClick: () => handleDelete(record),
|
||||
}, [
|
||||
h(DeleteOutlined),
|
||||
'删除',
|
||||
]),
|
||||
])
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
// 加载数据
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
pageNo: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
name: searchForm.name || undefined,
|
||||
category: searchForm.category || undefined,
|
||||
status: searchForm.status,
|
||||
}
|
||||
|
||||
const response = await UserPromptApi.getUserPromptPage(params)
|
||||
|
||||
if (response && (response.code === 0 || response.code === 200)) {
|
||||
dataSource.value = response.data?.list || []
|
||||
pagination.total = response.data?.total || 0
|
||||
} else {
|
||||
throw new Error(response?.msg || response?.message || '加载失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载提示词列表失败:', error)
|
||||
message.error(error?.message || '加载失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function handleSearch() {
|
||||
pagination.current = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
function handleReset() {
|
||||
searchForm.name = ''
|
||||
searchForm.category = ''
|
||||
searchForm.status = undefined
|
||||
pagination.current = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 新增
|
||||
function handleAdd() {
|
||||
resetEditForm()
|
||||
editModalVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑
|
||||
function handleEdit(record) {
|
||||
editForm.id = record.id
|
||||
editForm.name = record.name || ''
|
||||
editForm.content = record.content || ''
|
||||
editForm.category = record.category || ''
|
||||
editForm.status = record.status !== null && record.status !== undefined ? record.status : 1
|
||||
editModalVisible.value = true
|
||||
}
|
||||
|
||||
// 表单重置
|
||||
function resetEditForm() {
|
||||
editForm.id = null
|
||||
editForm.name = ''
|
||||
editForm.content = ''
|
||||
editForm.category = ''
|
||||
editForm.status = 1
|
||||
}
|
||||
|
||||
// 通用API调用
|
||||
async function apiCall(apiFunc, param, successMessage, isDelete = false) {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await apiFunc(param)
|
||||
if (response && (response.code === 0 || response.code === 200)) {
|
||||
message.success(successMessage)
|
||||
if (!isDelete) {
|
||||
editModalVisible.value = false
|
||||
}
|
||||
loadData()
|
||||
return true
|
||||
} else {
|
||||
throw new Error(response?.msg || response?.message || '操作失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('API调用失败:', error)
|
||||
message.error(error?.message || '操作失败,请稍后重试')
|
||||
return false
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 保存(新增/编辑)
|
||||
async function handleSave() {
|
||||
try {
|
||||
await editFormRef.value.validate()
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error)
|
||||
return
|
||||
}
|
||||
|
||||
const payload = {
|
||||
name: editForm.name.trim(),
|
||||
content: editForm.content.trim(),
|
||||
category: editForm.category.trim() || null,
|
||||
status: editForm.status,
|
||||
}
|
||||
|
||||
if (editForm.id) {
|
||||
payload.id = editForm.id
|
||||
await apiCall(
|
||||
(data) => UserPromptApi.updateUserPrompt(data),
|
||||
payload,
|
||||
'更新成功'
|
||||
)
|
||||
} else {
|
||||
await apiCall(
|
||||
(data) => UserPromptApi.createUserPrompt(data),
|
||||
payload,
|
||||
'创建成功'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
function handleDelete(record) {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: `确定要删除提示词"${record.name}"吗?`,
|
||||
onOk: async () => {
|
||||
await apiCall(
|
||||
(id) => UserPromptApi.deleteUserPrompt(id),
|
||||
record.id,
|
||||
'删除成功',
|
||||
true
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 分页变化
|
||||
function handleTableChange(pag) {
|
||||
pagination.current = pag.current
|
||||
pagination.pageSize = pag.pageSize
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="style-settings-page">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">风格设置</h1>
|
||||
<p class="page-description">管理您的提示词模板,用于内容创作和风格定制</p>
|
||||
</div>
|
||||
|
||||
<div class="page-content">
|
||||
<!-- 搜索表单 -->
|
||||
<div class="search-card card">
|
||||
<a-form :model="searchForm" layout="inline" class="search-form">
|
||||
<a-form-item label="名称">
|
||||
<a-input
|
||||
v-model:value="searchForm.name"
|
||||
placeholder="请输入提示词名称"
|
||||
allow-clear
|
||||
style="width: 200px"
|
||||
@pressEnter="handleSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="分类">
|
||||
<a-input
|
||||
v-model:value="searchForm.category"
|
||||
placeholder="请输入分类"
|
||||
allow-clear
|
||||
style="width: 200px"
|
||||
@pressEnter="handleSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-select
|
||||
v-model:value="searchForm.status"
|
||||
placeholder="请选择状态"
|
||||
allow-clear
|
||||
style="width: 120px"
|
||||
>
|
||||
<a-select-option :value="1">启用</a-select-option>
|
||||
<a-select-option :value="0">禁用</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleSearch">搜索</a-button>
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<!-- 操作栏 -->
|
||||
<div class="action-bar">
|
||||
<a-button type="primary" @click="handleAdd">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
新增提示词
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<div class="table-card card">
|
||||
<a-table
|
||||
:dataSource="dataSource"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
rowKey="id"
|
||||
@change="handleTableChange"
|
||||
:scroll="{ x: 1200 }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<a-modal
|
||||
v-model:visible="editModalVisible"
|
||||
:title="editForm.id ? '编辑提示词' : '新增提示词'"
|
||||
width="800px"
|
||||
@ok="handleSave"
|
||||
@cancel="editModalVisible = false"
|
||||
>
|
||||
<a-form
|
||||
ref="editFormRef"
|
||||
:model="editForm"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 20 }"
|
||||
>
|
||||
<a-form-item
|
||||
label="名称"
|
||||
name="name"
|
||||
:rules="[{ required: true, message: '请输入提示词名称' }]"
|
||||
>
|
||||
<a-input v-model:value="editForm.name" placeholder="请输入提示词名称" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="内容"
|
||||
name="content"
|
||||
:rules="[{ required: true, message: '请输入提示词内容' }]"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="editForm.content"
|
||||
placeholder="请输入提示词内容"
|
||||
:rows="8"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="分类" name="category">
|
||||
<a-input v-model:value="editForm.category" placeholder="请输入分类(可选)" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="状态"
|
||||
name="status"
|
||||
:rules="[{ required: true, message: '请选择状态' }]"
|
||||
>
|
||||
<a-radio-group v-model:value="editForm.status">
|
||||
<a-radio :value="1">启用</a-radio>
|
||||
<a-radio :value="0">禁用</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.style-settings-page {
|
||||
padding: 24px;
|
||||
min-height: calc(100vh - 70px);
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.page-description {
|
||||
font-size: 14px;
|
||||
color: var(--color-text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-card);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.ant-table) {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
background: var(--color-surface);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr > td) {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr:hover > td) {
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
:deep(.ant-pagination) {
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 使用 :deep() 穿透 Ant Design 的样式 */
|
||||
:deep(.action-btn) {
|
||||
transition: all 0.2s ease;
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:deep(.action-btn-edit:hover),
|
||||
:deep(.action-btn-edit:hover .anticon) {
|
||||
background: rgba(59, 130, 246, 0.1) !important;
|
||||
color: var(--color-primary) !important;
|
||||
}
|
||||
|
||||
:deep(.action-btn-delete:hover),
|
||||
:deep(.action-btn-delete:hover .anticon) {
|
||||
background: rgba(59, 130, 246, 0.1) !important;
|
||||
color: var(--color-primary) !important;
|
||||
}
|
||||
|
||||
:deep(.action-btn:hover) {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
</style>
|
||||
|
||||
600
frontend/app/web-gold/src/views/system/style-settings/index.vue
Normal file
600
frontend/app/web-gold/src/views/system/style-settings/index.vue
Normal file
@@ -0,0 +1,600 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, reactive, h } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { EditOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue'
|
||||
import { UserPromptApi } from '@/api/userPrompt'
|
||||
import { usePromptStore } from '@/stores/prompt'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
// 表格数据
|
||||
const dataSource = ref([])
|
||||
const loading = ref(false)
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
})
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
name: '',
|
||||
status: undefined,
|
||||
})
|
||||
|
||||
// 编辑弹窗
|
||||
const editModalVisible = ref(false)
|
||||
const editForm = reactive({
|
||||
id: null,
|
||||
name: '',
|
||||
content: '',
|
||||
status: 1,
|
||||
})
|
||||
const editFormRef = ref(null)
|
||||
|
||||
// Store
|
||||
const promptStore = usePromptStore()
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '内容',
|
||||
dataIndex: 'content',
|
||||
key: 'content',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 150,
|
||||
fixed: 'right',
|
||||
},
|
||||
]
|
||||
|
||||
// 加载数据
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
pageNo: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
name: searchForm.name || undefined,
|
||||
status: searchForm.status,
|
||||
}
|
||||
|
||||
const response = await UserPromptApi.getUserPromptPage(params)
|
||||
|
||||
if (response && (response.code === 0 || response.code === 200)) {
|
||||
dataSource.value = response.data?.list || []
|
||||
pagination.total = response.data?.total || 0
|
||||
} else {
|
||||
throw new Error(response?.msg || response?.message || '加载失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载提示词列表失败:', error)
|
||||
message.error(error?.message || '加载失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function handleSearch() {
|
||||
pagination.current = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
function handleReset() {
|
||||
searchForm.name = ''
|
||||
searchForm.status = undefined
|
||||
pagination.current = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 新增
|
||||
function handleAdd() {
|
||||
resetEditForm()
|
||||
editModalVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑
|
||||
function handleEdit(record) {
|
||||
editForm.id = record.id
|
||||
editForm.name = record.name || ''
|
||||
editForm.content = record.content || ''
|
||||
editForm.status = record.status !== null && record.status !== undefined ? record.status : 1
|
||||
editModalVisible.value = true
|
||||
}
|
||||
|
||||
// 表单重置
|
||||
function resetEditForm() {
|
||||
editForm.id = null
|
||||
editForm.name = ''
|
||||
editForm.content = ''
|
||||
editForm.status = 1
|
||||
}
|
||||
|
||||
// 通用API调用
|
||||
async function apiCall(apiFunc, param, successMessage, isDelete = false) {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await apiFunc(param)
|
||||
if (response && (response.code === 0 || response.code === 200)) {
|
||||
message.success(successMessage)
|
||||
if (!isDelete) {
|
||||
editModalVisible.value = false
|
||||
}
|
||||
loadData()
|
||||
return true
|
||||
} else {
|
||||
throw new Error(response?.msg || response?.message || '操作失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('API调用失败:', error)
|
||||
message.error(error?.message || '操作失败,请稍后重试')
|
||||
return false
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 保存(新增/编辑)
|
||||
async function handleSave() {
|
||||
try {
|
||||
await editFormRef.value.validate()
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error)
|
||||
return
|
||||
}
|
||||
|
||||
const payload = {
|
||||
name: editForm.name.trim(),
|
||||
content: editForm.content.trim(),
|
||||
status: editForm.status,
|
||||
}
|
||||
|
||||
if (editForm.id) {
|
||||
payload.id = editForm.id
|
||||
const result = await apiCall(
|
||||
(data) => UserPromptApi.updateUserPrompt(data),
|
||||
payload,
|
||||
'更新成功'
|
||||
)
|
||||
// 同步更新 store
|
||||
if (result && result.data) {
|
||||
promptStore.updatePromptInList(result.data)
|
||||
}
|
||||
} else {
|
||||
const result = await apiCall(
|
||||
(data) => UserPromptApi.createUserPrompt(data),
|
||||
payload,
|
||||
'创建成功'
|
||||
)
|
||||
// 同步更新 store
|
||||
if (result && result.data) {
|
||||
promptStore.addPromptToList(result.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
function handleDelete(record) {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: `确定要删除提示词"${record.name}"吗?`,
|
||||
onOk: async () => {
|
||||
const result = await apiCall(
|
||||
(id) => UserPromptApi.deleteUserPrompt(id),
|
||||
record.id,
|
||||
'删除成功',
|
||||
true
|
||||
)
|
||||
// 同步更新 store
|
||||
if (result) {
|
||||
promptStore.removePromptFromList(record.id)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 分页变化
|
||||
function handleTableChange(pag) {
|
||||
pagination.current = pag.current
|
||||
pagination.pageSize = pag.pageSize
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="style-settings-page">
|
||||
<!-- 筛选条件 -->
|
||||
<div class="style-settings-page__filters">
|
||||
<a-space :size="16">
|
||||
<a-input
|
||||
v-model:value="searchForm.name"
|
||||
class="filter-input"
|
||||
placeholder="搜索提示词名称"
|
||||
allow-clear
|
||||
@press-enter="handleSearch"
|
||||
>
|
||||
<template #prefix>
|
||||
<EditOutlined />
|
||||
</template>
|
||||
</a-input>
|
||||
|
||||
<a-select
|
||||
v-model:value="searchForm.status"
|
||||
class="filter-select"
|
||||
placeholder="状态筛选"
|
||||
allow-clear
|
||||
@change="handleSearch"
|
||||
>
|
||||
<a-select-option :value="1">启用</a-select-option>
|
||||
<a-select-option :value="0">禁用</a-select-option>
|
||||
</a-select>
|
||||
|
||||
<a-button type="primary" class="filter-button" @click="handleSearch">
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button class="filter-button" @click="handleReset">
|
||||
重置
|
||||
</a-button>
|
||||
|
||||
<a-button type="primary" class="filter-button add-btn" @click="handleAdd">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
新增提示词
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<!-- 任务列表 -->
|
||||
<div class="style-settings-page__content">
|
||||
<a-spin :spinning="loading" tip="加载中...">
|
||||
<a-table
|
||||
:data-source="dataSource"
|
||||
:columns="columns"
|
||||
:row-key="record => record.id"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
:scroll="{ x: 1200 }"
|
||||
>
|
||||
<!-- 名称列 -->
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
<div class="name-cell">
|
||||
<strong>{{ record.name }}</strong>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 内容列 -->
|
||||
<template v-else-if="column.key === 'content'">
|
||||
<div class="content-cell" :title="record.content">
|
||||
{{ record.content ? (record.content.length > 100 ? record.content.substring(0, 100) + '...' : record.content) : '-' }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 状态列 -->
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-tag :color="record.status === 1 ? 'success' : 'error'">
|
||||
{{ record.status === 1 ? '启用' : '禁用' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 创建时间列 -->
|
||||
<template v-else-if="column.key === 'createTime'">
|
||||
{{ record.createTime ? dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss') : '-' }}
|
||||
</template>
|
||||
|
||||
<!-- 操作列 -->
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleEdit(record)"
|
||||
class="action-btn-edit"
|
||||
>
|
||||
<template #icon>
|
||||
<EditOutlined />
|
||||
</template>
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleDelete(record)"
|
||||
class="action-btn-delete"
|
||||
>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-spin>
|
||||
</div>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<a-modal
|
||||
v-model:visible="editModalVisible"
|
||||
:title="editForm.id ? '编辑提示词' : '新增提示词'"
|
||||
width="800px"
|
||||
:footer="null"
|
||||
class="edit-modal"
|
||||
>
|
||||
<a-form
|
||||
ref="editFormRef"
|
||||
:model="editForm"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 20 }"
|
||||
>
|
||||
<a-form-item
|
||||
label="名称"
|
||||
name="name"
|
||||
:rules="[{ required: true, message: '请输入提示词名称' }]"
|
||||
>
|
||||
<a-input v-model:value="editForm.name" placeholder="请输入提示词名称" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="内容"
|
||||
name="content"
|
||||
:rules="[{ required: true, message: '请输入提示词内容' }]"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="editForm.content"
|
||||
placeholder="请输入提示词内容"
|
||||
:rows="8"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="状态"
|
||||
name="status"
|
||||
:rules="[{ required: true, message: '请选择状态' }]"
|
||||
>
|
||||
<a-radio-group v-model:value="editForm.status">
|
||||
<a-radio :value="1">启用</a-radio>
|
||||
<a-radio :value="0">禁用</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- 弹窗底部按钮 -->
|
||||
<div class="modal-footer">
|
||||
<a-space :size="12" class="button-container">
|
||||
<a-button @click="editModalVisible = false" class="footer-btn">
|
||||
取消
|
||||
</a-button>
|
||||
<a-button type="primary" @click="handleSave" :loading="loading" class="footer-btn">
|
||||
确定
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.style-settings-page {
|
||||
padding: var(--space-3);
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
|
||||
&__filters {
|
||||
padding: var(--space-3);
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-card);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
||||
|
||||
.filter-input {
|
||||
width: 200px;
|
||||
|
||||
@media (max-width: 1199px) {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
width: 140px;
|
||||
|
||||
@media (max-width: 1199px) {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-button {
|
||||
min-width: 80px;
|
||||
|
||||
@media (max-width: 767px) {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
&.add-btn {
|
||||
margin-left: auto;
|
||||
|
||||
@media (max-width: 767px) {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-card);
|
||||
padding: var(--space-3);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格单元格样式 */
|
||||
.name-cell {
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.content-cell {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
.action-btn-edit {
|
||||
color: var(--color-primary);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary-hover, var(--color-blue-600));
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn-delete {
|
||||
color: var(--color-error);
|
||||
|
||||
&:hover {
|
||||
color: #dc2626;
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
:deep(.ant-table-tbody > tr > td) {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
background: var(--color-bg-2);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 编辑弹窗样式 */
|
||||
.edit-modal {
|
||||
:deep(.ant-modal-body) {
|
||||
padding: var(--space-3);
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-label > label) {
|
||||
color: var(--color-text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:deep(.ant-input),
|
||||
:deep(.ant-input-number),
|
||||
:deep(.ant-select),
|
||||
:deep(.ant-radio-group) {
|
||||
border-radius: var(--radius-tag);
|
||||
}
|
||||
|
||||
:deep(.ant-input:focus),
|
||||
:deep(.ant-input-focused) {
|
||||
border-color: var(--color-border-focus);
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: var(--space-3);
|
||||
border-top: 1px solid var(--color-border);
|
||||
margin-top: var(--space-3);
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer-btn {
|
||||
min-width: 88px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式优化 */
|
||||
@media (max-width: 767px) {
|
||||
.style-settings-page {
|
||||
padding: var(--space-2);
|
||||
|
||||
&__filters {
|
||||
padding: var(--space-2);
|
||||
|
||||
.ant-space {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.ant-input,
|
||||
.ant-select,
|
||||
.ant-btn {
|
||||
width: calc(50% - var(--space-1)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: var(--space-2);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr > td) {
|
||||
padding: 8px 4px;
|
||||
}
|
||||
|
||||
.action-btn-edit,
|
||||
.action-btn-delete {
|
||||
font-size: 12px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -59,16 +59,7 @@
|
||||
>
|
||||
<template #action>
|
||||
<a-space>
|
||||
<a-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="handleBatchDownload"
|
||||
>
|
||||
<template #icon>
|
||||
<DownloadOutlined />
|
||||
</template>
|
||||
批量下载
|
||||
</a-button>
|
||||
|
||||
<a-popconfirm
|
||||
title="确定要删除选中的任务吗?删除后无法恢复。"
|
||||
@confirm="handleBatchDelete"
|
||||
@@ -116,7 +107,6 @@
|
||||
<!-- 进度列 -->
|
||||
<template v-else-if="column.key === 'progress'">
|
||||
<div style="min-width: 100px">
|
||||
<div style="font-size: 12px; margin-bottom: 4px">{{ record.progress }}%</div>
|
||||
<a-progress
|
||||
:percent="record.progress"
|
||||
:status="getProgressStatus(record.status)"
|
||||
@@ -134,27 +124,33 @@
|
||||
<!-- 操作列 -->
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<a-space>
|
||||
<!-- 预览按钮 -->
|
||||
<a-button
|
||||
v-if="isStatus(record.status, 'success')"
|
||||
type="primary"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handlePreview(record)"
|
||||
class="action-btn-preview"
|
||||
>
|
||||
<template #icon>
|
||||
<PlayCircleOutlined />
|
||||
</template>
|
||||
预览
|
||||
</a-button>
|
||||
<!-- 下载按钮 -->
|
||||
<a-button
|
||||
v-if="isStatus(record.status, 'success')"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleDownload(record)"
|
||||
class="action-btn-download"
|
||||
>
|
||||
<template #icon>
|
||||
<DownloadOutlined />
|
||||
</template>
|
||||
下载
|
||||
</a-button>
|
||||
<!-- 取消按钮 -->
|
||||
<a-button
|
||||
v-if="isStatus(record.status, 'running')"
|
||||
size="small"
|
||||
@@ -167,7 +163,7 @@
|
||||
title="确定删除这个任务吗?删除后无法恢复。"
|
||||
@confirm="() => handleDelete(record.id)"
|
||||
>
|
||||
<a-button size="small" danger>删除</a-button>
|
||||
<a-button size="small" type="link" class="action-btn-delete">删除</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
@@ -309,7 +305,7 @@ const columns = [
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 280,
|
||||
width: 240,
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
@@ -443,7 +439,7 @@ onMounted(() => {
|
||||
|
||||
<style scoped lang="less">
|
||||
.digital-human-task-page {
|
||||
padding: var(--space-3);
|
||||
padding: 0 var(--space-3);
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -458,35 +454,12 @@ onMounted(() => {
|
||||
.filter-select,
|
||||
.filter-input {
|
||||
width: 200px;
|
||||
|
||||
@media (max-width: 1199px) {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-date-picker {
|
||||
width: 280px;
|
||||
|
||||
@media (max-width: 1199px) {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-button {
|
||||
min-width: 80px;
|
||||
|
||||
@media (max-width: 767px) {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
@@ -529,6 +502,31 @@ onMounted(() => {
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
.action-btn-preview {
|
||||
color: var(--color-primary);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary-hover, var(--color-blue-600));
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn-download {
|
||||
color: var(--color-success);
|
||||
|
||||
&:hover {
|
||||
color: #059669;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn-delete {
|
||||
color: var(--color-error);
|
||||
|
||||
&:hover {
|
||||
color: #dc2626;
|
||||
}
|
||||
}
|
||||
|
||||
/* 文本截断 */
|
||||
.text-ellipsis {
|
||||
display: inline-block;
|
||||
@@ -620,34 +618,5 @@ onMounted(() => {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 响应式优化 */
|
||||
@media (max-width: 767px) {
|
||||
.digital-human-task-page {
|
||||
padding: var(--space-2);
|
||||
|
||||
&__filters {
|
||||
padding: var(--space-2);
|
||||
|
||||
.ant-space {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.ant-select,
|
||||
.ant-input,
|
||||
.ant-picker,
|
||||
.ant-btn {
|
||||
width: calc(50% - var(--space-1)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: var(--space-2);
|
||||
}
|
||||
}
|
||||
|
||||
.batch-actions {
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
}
|
||||
/* 桌面端样式优化 */
|
||||
</style>
|
||||
|
||||
@@ -71,24 +71,11 @@
|
||||
|
||||
<!-- 状态列 -->
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
<a-tag :color="getStatusColor(record.status)" class="status-tag">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 进度列 -->
|
||||
<template v-else-if="column.key === 'progress'">
|
||||
<div style="min-width: 100px">
|
||||
<div style="font-size: 12px; margin-bottom: 4px">{{ record.progress }}%</div>
|
||||
<a-progress
|
||||
:percent="record.progress"
|
||||
:status="getProgressStatus(record.status)"
|
||||
size="small"
|
||||
:show-info="false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 创建时间列 -->
|
||||
<template v-else-if="column.key === 'createTime'">
|
||||
{{ formatDate(record.createTime) }}
|
||||
@@ -107,9 +94,38 @@
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
<!-- 操作列 -->
|
||||
<!-- 操作列 (增强版:预览+下载+其他操作) -->
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<a-space>
|
||||
<!-- 预览按钮 -->
|
||||
<a-button
|
||||
v-if="isStatus(record.status, 'success') && record.outputUrls && record.outputUrls.length > 0"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="openPreviewModal(record)"
|
||||
class="action-btn-preview"
|
||||
>
|
||||
<template #icon>
|
||||
<PlayCircleOutlined />
|
||||
</template>
|
||||
预览
|
||||
</a-button>
|
||||
|
||||
<!-- 下载按钮 -->
|
||||
<a-button
|
||||
v-if="isStatus(record.status, 'success') && record.outputUrls && record.outputUrls.length > 0"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleDownload(record)"
|
||||
class="action-btn-download"
|
||||
>
|
||||
<template #icon>
|
||||
<DownloadOutlined />
|
||||
</template>
|
||||
下载
|
||||
</a-button>
|
||||
|
||||
<!-- 取消按钮 -->
|
||||
<a-button
|
||||
v-if="isStatus(record.status, 'running')"
|
||||
size="small"
|
||||
@@ -117,6 +133,8 @@
|
||||
>
|
||||
取消
|
||||
</a-button>
|
||||
|
||||
<!-- 重试按钮 -->
|
||||
<a-button
|
||||
v-if="isStatus(record.status, 'failed')"
|
||||
size="small"
|
||||
@@ -124,17 +142,19 @@
|
||||
>
|
||||
重试
|
||||
</a-button>
|
||||
|
||||
<!-- 删除按钮 -->
|
||||
<a-popconfirm
|
||||
title="确定删除这个任务吗?删除后无法恢复。"
|
||||
@confirm="() => handleDelete(record.id)"
|
||||
>
|
||||
<a-button size="small" danger>删除</a-button>
|
||||
<a-button size="small" type="link" class="action-btn-delete">删除</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- 展开行内容 -->
|
||||
<!-- 展开行内容 (优化版) -->
|
||||
<template #expandedRowRender="{ record }">
|
||||
<div class="expanded-content">
|
||||
<!-- 任务详情 -->
|
||||
@@ -143,17 +163,24 @@
|
||||
<p>{{ record.text }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 生成结果 -->
|
||||
<div v-if="record.outputUrls && record.outputUrls.length > 0" class="task-results">
|
||||
<div class="result-header">
|
||||
<strong>生成结果:</strong>
|
||||
<span class="result-count">{{ record.outputUrls.length }} 个视频</span>
|
||||
</div>
|
||||
<div class="result-list">
|
||||
<div
|
||||
v-for="(url, index) in record.outputUrls"
|
||||
:key="index"
|
||||
@click="handleDownloadSignedUrl(record.id, index)"
|
||||
class="result-item"
|
||||
>
|
||||
<a-button
|
||||
v-if="isStatus(record.status, 'success')"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handlePreviewSingle(record, index)"
|
||||
class="result-preview-btn"
|
||||
>
|
||||
<PlayCircleOutlined />
|
||||
视频 {{ index + 1 }}
|
||||
@@ -162,6 +189,8 @@
|
||||
v-if="isStatus(record.status, 'success')"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleDownloadSingle(record.id, index)"
|
||||
class="result-download-btn"
|
||||
>
|
||||
<DownloadOutlined />
|
||||
</a-button>
|
||||
@@ -185,6 +214,30 @@
|
||||
</a-table>
|
||||
</a-spin>
|
||||
</div>
|
||||
|
||||
<!-- 预览模态框 -->
|
||||
<a-modal
|
||||
v-model:open="previewVisible"
|
||||
:title="previewTitle"
|
||||
width="800px"
|
||||
:footer="null"
|
||||
:centered="true"
|
||||
class="preview-modal"
|
||||
>
|
||||
<div v-if="previewUrl" class="preview-container">
|
||||
<video
|
||||
:src="previewUrl"
|
||||
controls
|
||||
autoplay
|
||||
style="width: 100%; max-height: 600px; border-radius: 8px;"
|
||||
>
|
||||
您的浏览器不支持视频播放
|
||||
</video>
|
||||
</div>
|
||||
<div v-else class="preview-loading">
|
||||
<a-spin size="large" tip="正在加载预览..." />
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -219,17 +272,77 @@ const {
|
||||
handleDelete,
|
||||
handleCancel,
|
||||
handleRetry,
|
||||
handlePreview,
|
||||
handleBatchDownload
|
||||
} = useTaskOperations(
|
||||
{
|
||||
deleteApi: MixTaskService.deleteTask,
|
||||
cancelApi: MixTaskService.cancelTask,
|
||||
retryApi: MixTaskService.retryTask
|
||||
retryApi: MixTaskService.retryTask,
|
||||
getSignedUrlsApi: MixTaskService.getSignedUrls
|
||||
},
|
||||
fetchList
|
||||
)
|
||||
|
||||
// 预览相关状态
|
||||
const previewVisible = ref(false)
|
||||
const previewUrl = ref('')
|
||||
const previewTitle = ref('')
|
||||
|
||||
// 预览单个视频
|
||||
const handlePreviewSingle = async (record, index) => {
|
||||
try {
|
||||
previewTitle.value = `${record.title} - 视频 ${index + 1}`
|
||||
previewVisible.value = true
|
||||
previewUrl.value = ''
|
||||
|
||||
// 获取签名URL
|
||||
const res = await MixTaskService.getSignedUrls(record.id)
|
||||
if (res.code === 0 && res.data && res.data[index]) {
|
||||
previewUrl.value = res.data[index]
|
||||
} else {
|
||||
console.warn('获取预览链接失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取预览链接失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 下载单个视频
|
||||
const handleDownloadSingle = async (taskId, index) => {
|
||||
try {
|
||||
const res = await MixTaskService.getSignedUrls(taskId)
|
||||
if (res.code === 0 && res.data && res.data[index]) {
|
||||
const link = document.createElement('a')
|
||||
link.href = res.data[index]
|
||||
link.download = `video_${taskId}_${index + 1}.mp4`
|
||||
link.target = '_blank'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
} else {
|
||||
console.warn('获取下载链接失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取下载链接失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 预览任务(主列表)
|
||||
const openPreviewModal = async (record) => {
|
||||
await handlePreviewSingle(record, 0)
|
||||
}
|
||||
|
||||
// 下载任务
|
||||
const handleDownload = async (record) => {
|
||||
if (record.outputUrls && record.outputUrls.length > 0) {
|
||||
await handleBatchDownload(
|
||||
[],
|
||||
MixTaskService.getSignedUrls,
|
||||
record.id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 使用轮询 Composable
|
||||
useTaskPolling(MixTaskService.getTaskPage, {
|
||||
onTaskUpdate: () => {
|
||||
@@ -267,12 +380,6 @@ const columns = [
|
||||
key: 'status',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '进度',
|
||||
dataIndex: 'progress',
|
||||
key: 'progress',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '生成结果',
|
||||
dataIndex: 'outputUrls',
|
||||
@@ -340,34 +447,8 @@ const isStatus = (status, targetStatus) => {
|
||||
return status === targetStatus || status === targetStatus.toUpperCase()
|
||||
}
|
||||
|
||||
// 下载单个视频(使用签名URL)
|
||||
const handleDownloadSignedUrl = async (taskId, index) => {
|
||||
try {
|
||||
const res = await MixTaskService.getSignedUrls(taskId)
|
||||
if (res.code === 0 && res.data && res.data[index]) {
|
||||
const link = document.createElement('a')
|
||||
link.href = res.data[index]
|
||||
link.download = `video_${taskId}_${index + 1}`
|
||||
link.target = '_blank'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
} else {
|
||||
console.warn('获取下载链接失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取下载链接失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 批量下载所有视频
|
||||
const handleDownloadAll = async (taskId) => {
|
||||
handleBatchDownload(
|
||||
[],
|
||||
MixTaskService.getSignedUrls,
|
||||
taskId
|
||||
)
|
||||
}
|
||||
// 删除未使用的方法
|
||||
// handleDownloadSignedUrl 和 handleDownloadAll 已被移除
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
@@ -377,7 +458,7 @@ onMounted(() => {
|
||||
|
||||
<style scoped lang="less">
|
||||
.mix-task-page {
|
||||
padding: var(--space-3);
|
||||
padding: 0 var(--space-3);
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -392,35 +473,12 @@ onMounted(() => {
|
||||
.filter-select,
|
||||
.filter-input {
|
||||
width: 200px;
|
||||
|
||||
@media (max-width: 1199px) {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-date-picker {
|
||||
width: 280px;
|
||||
|
||||
@media (max-width: 1199px) {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-button {
|
||||
min-width: 80px;
|
||||
|
||||
@media (max-width: 767px) {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
@@ -440,6 +498,31 @@ onMounted(() => {
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
.action-btn-preview {
|
||||
color: var(--color-primary);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary-hover, var(--color-blue-600));
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn-download {
|
||||
color: var(--color-success);
|
||||
|
||||
&:hover {
|
||||
color: #059669;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn-delete {
|
||||
color: var(--color-error);
|
||||
|
||||
&:hover {
|
||||
color: #dc2626;
|
||||
}
|
||||
}
|
||||
|
||||
/* 展开内容 */
|
||||
.expanded-content {
|
||||
padding: var(--space-3);
|
||||
@@ -463,6 +546,18 @@ onMounted(() => {
|
||||
.task-results {
|
||||
margin-bottom: var(--space-3);
|
||||
|
||||
.result-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--space-2);
|
||||
|
||||
.result-count {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-3, #8c8c8c);
|
||||
}
|
||||
}
|
||||
|
||||
.result-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -482,6 +577,26 @@ onMounted(() => {
|
||||
&:hover {
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.result-preview-btn {
|
||||
color: var(--color-primary);
|
||||
padding: 0;
|
||||
height: auto;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary-hover, var(--color-blue-600));
|
||||
}
|
||||
}
|
||||
|
||||
.result-download-btn {
|
||||
color: var(--color-success);
|
||||
padding: 0;
|
||||
height: auto;
|
||||
|
||||
&:hover {
|
||||
color: #059669;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -510,30 +625,26 @@ onMounted(() => {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 响应式优化 */
|
||||
@media (max-width: 767px) {
|
||||
.mix-task-page {
|
||||
padding: var(--space-2);
|
||||
|
||||
&__filters {
|
||||
padding: var(--space-2);
|
||||
|
||||
.ant-space {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.ant-select,
|
||||
.ant-input,
|
||||
.ant-picker,
|
||||
.ant-btn {
|
||||
width: calc(50% - var(--space-1)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: var(--space-2);
|
||||
}
|
||||
/* 预览模态框样式 */
|
||||
.preview-modal {
|
||||
:deep(.ant-modal-body) {
|
||||
padding: var(--space-3);
|
||||
}
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.preview-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
/* 桌面端样式优化 */
|
||||
</style>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { CommonService } from '@/api/common'
|
||||
import { UserPromptApi } from '@/api/userPrompt'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import GradientButton from '@/components/GradientButton.vue'
|
||||
import PromptSelector from '@/components/PromptSelector.vue'
|
||||
import { getVoiceText } from '@gold/hooks/web/useVoiceText'
|
||||
|
||||
defineOptions({ name: 'ForecastView' })
|
||||
@@ -42,22 +43,9 @@ const topicDetails = reactive({
|
||||
// 提示词相关
|
||||
const allPrompts = ref([])
|
||||
const loadingPrompts = ref(false)
|
||||
const showAllPromptsModal = ref(false)
|
||||
const promptSearchKeyword = ref('')
|
||||
const DISPLAY_COUNT = 6
|
||||
|
||||
// 计算属性
|
||||
const displayPrompts = computed(() => allPrompts.value.slice(0, DISPLAY_COUNT))
|
||||
|
||||
const filteredPrompts = computed(() => {
|
||||
if (!promptSearchKeyword.value.trim()) return allPrompts.value
|
||||
const keyword = promptSearchKeyword.value.trim().toLowerCase()
|
||||
return allPrompts.value.filter(p =>
|
||||
p.name.toLowerCase().includes(keyword) ||
|
||||
(p.content && p.content.toLowerCase().includes(keyword))
|
||||
)
|
||||
})
|
||||
|
||||
// 工具函数
|
||||
const formatNumber = (num) => {
|
||||
if (!num) return '0'
|
||||
@@ -101,12 +89,6 @@ async function loadUserPrompts() {
|
||||
|
||||
if (response?.data?.list) {
|
||||
allPrompts.value = response.data.list
|
||||
// 自动选中第一个提示词
|
||||
if (!topicDetails.stylePromptId && allPrompts.value.length > 0) {
|
||||
const firstPrompt = allPrompts.value[0]
|
||||
topicDetails.stylePromptId = firstPrompt.id
|
||||
topicDetails.stylePrompt = firstPrompt.content || ''
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载提示词失败:', error)
|
||||
@@ -116,14 +98,14 @@ async function loadUserPrompts() {
|
||||
}
|
||||
}
|
||||
|
||||
function selectPrompt(prompt) {
|
||||
// 处理提示词选择
|
||||
function handlePromptChange(prompt) {
|
||||
if (!prompt?.content) {
|
||||
message.warning('提示词内容为空')
|
||||
return
|
||||
}
|
||||
topicDetails.stylePromptId = prompt.id
|
||||
topicDetails.stylePrompt = prompt.content
|
||||
showAllPromptsModal.value = false
|
||||
}
|
||||
|
||||
// 语音分析
|
||||
@@ -649,44 +631,21 @@ onMounted(async () => {
|
||||
|
||||
<!-- 风格提示词 -->
|
||||
<div>
|
||||
<div class="form-label-wrapper">
|
||||
<label class="form-label">风格提示词</label>
|
||||
<a-button
|
||||
v-if="allPrompts.length > DISPLAY_COUNT"
|
||||
size="small"
|
||||
type="link"
|
||||
@click="showAllPromptsModal = true"
|
||||
style="padding: 0; height: auto; font-size: 14px;"
|
||||
>
|
||||
更多 ({{ allPrompts.length }})
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<!-- 提示词标签展示区域 -->
|
||||
<div v-if="displayPrompts.length > 0" class="prompt-tags-container">
|
||||
<div class="prompt-tags-grid">
|
||||
<div
|
||||
v-for="prompt in displayPrompts"
|
||||
:key="prompt.id"
|
||||
class="prompt-tag"
|
||||
:class="{ 'prompt-tag-selected': topicDetails.stylePromptId === prompt.id }"
|
||||
@click="selectPrompt(prompt)"
|
||||
>
|
||||
<span class="prompt-tag-name">{{ prompt.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="!loadingPrompts" class="prompt-empty">
|
||||
<div style="color: var(--color-text-secondary); font-size: 14px; text-align: center; padding: 20px;">
|
||||
您可以在视频分析页面保存风格
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-else class="prompt-loading">
|
||||
<a-spin size="small" />
|
||||
<label class="form-label" style="display: block; margin-bottom: 8px; font-size: 14px; font-weight: 500; color: var(--color-text);">风格提示词</label>
|
||||
|
||||
<!-- 使用 PromptSelector 组件 -->
|
||||
<PromptSelector
|
||||
v-model="topicDetails.stylePromptId"
|
||||
:prompts="allPrompts"
|
||||
:loading="loadingPrompts"
|
||||
:search-keyword="promptSearchKeyword"
|
||||
@change="handlePromptChange"
|
||||
@update:searchKeyword="promptSearchKeyword = $event"
|
||||
/>
|
||||
|
||||
<!-- 空状态提示 -->
|
||||
<div v-if="!loadingPrompts && allPrompts.length === 0" class="prompt-empty" style="color: var(--color-text-secondary); font-size: 14px; text-align: center; padding: 20px;">
|
||||
您可以在视频分析页面保存风格
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -719,47 +678,6 @@ onMounted(async () => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 更多提示词弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="showAllPromptsModal"
|
||||
title="选择提示词风格"
|
||||
:width="600"
|
||||
:footer="null"
|
||||
>
|
||||
<div class="prompt-modal-content">
|
||||
<!-- 搜索框 -->
|
||||
<a-input
|
||||
v-model:value="promptSearchKeyword"
|
||||
placeholder="搜索提示词..."
|
||||
style="margin-bottom: 16px;"
|
||||
allow-clear
|
||||
>
|
||||
<template #prefix>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
</template>
|
||||
</a-input>
|
||||
|
||||
<!-- 提示词列表 -->
|
||||
<div v-if="filteredPrompts.length > 0" class="all-prompts-grid">
|
||||
<div
|
||||
v-for="prompt in filteredPrompts"
|
||||
:key="prompt.id"
|
||||
class="all-prompt-tag"
|
||||
:class="{ 'all-prompt-tag-selected': topicDetails.stylePromptId === prompt.id }"
|
||||
@click="selectPrompt(prompt)"
|
||||
>
|
||||
<span class="all-prompt-tag-name">{{ prompt.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else style="text-align: center; padding: 40px; color: var(--color-text-secondary);">
|
||||
没有找到匹配的提示词
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user