748 lines
20 KiB
Vue
748 lines
20 KiB
Vue
<template>
|
||
<div class="material-list">
|
||
<div class="material-list__header">
|
||
<h1 class="material-list__title">素材列表</h1>
|
||
<div class="material-list__actions">
|
||
<a-button type="primary" @click="handleOpenUploadModal">
|
||
<template #icon>
|
||
<UploadOutlined />
|
||
</template>
|
||
上传素材
|
||
</a-button>
|
||
<a-button
|
||
type="primary"
|
||
ghost
|
||
:disabled="selectedFileIds.length === 0"
|
||
@click="handleOpenMixModal"
|
||
>
|
||
素材混剪
|
||
</a-button>
|
||
<a-button
|
||
v-if="selectedFileIds.length > 0"
|
||
type="primary"
|
||
status="danger"
|
||
@click="handleBatchDelete"
|
||
>
|
||
批量删除 ({{ selectedFileIds.length }})
|
||
</a-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 筛选条件 -->
|
||
<div class="material-list__filters">
|
||
<a-space>
|
||
<a-select
|
||
v-model:value="filters.fileCategory"
|
||
style="width: 120px"
|
||
placeholder="文件分类"
|
||
@change="handleFilterChange"
|
||
>
|
||
<a-select-option value="">全部分类</a-select-option>
|
||
<a-select-option value="video">视频</a-select-option>
|
||
<a-select-option value="generate">生成</a-select-option>
|
||
<a-select-option value="audio">音频</a-select-option>
|
||
<a-select-option value="mix">混剪</a-select-option>
|
||
<a-select-option value="voice">配音</a-select-option>
|
||
</a-select>
|
||
|
||
<a-input
|
||
v-model="filters.fileName"
|
||
placeholder="搜索文件名"
|
||
style="width: 200px"
|
||
allow-clear
|
||
@press-enter="handleFilterChange"
|
||
>
|
||
<template #prefix>
|
||
<SearchOutlined />
|
||
</template>
|
||
</a-input>
|
||
<a-range-picker
|
||
v-model:value="filters.createTime"
|
||
style="width: 300px"
|
||
format="YYYY-MM-DD"
|
||
value-format="YYYY-MM-DD"
|
||
placeholder="['开始日期', '结束日期']"
|
||
@change="handleFilterChange"
|
||
/>
|
||
<a-button type="primary" @click="handleFilterChange">查询</a-button>
|
||
<a-button @click="handleResetFilters">重置</a-button>
|
||
</a-space>
|
||
</div>
|
||
|
||
<!-- 文件列表 -->
|
||
<div class="material-list__content">
|
||
<a-spin :spinning="loading" tip="加载中..." style="width: 100%; min-height: 400px;">
|
||
<template v-if="fileList.length > 0">
|
||
<div class="material-grid">
|
||
<div
|
||
v-for="file in fileList"
|
||
:key="file.id"
|
||
class="material-item"
|
||
:class="{ 'material-item--selected': selectedFileIds.includes(file.id) }"
|
||
@click="handleFileClick(file)"
|
||
>
|
||
<div class="material-item__content">
|
||
<!-- 预览图 -->
|
||
<div class="material-item__preview">
|
||
<!-- 视频文件:统一使用 coverBase64,如果没有则使用 coverUrl,最后使用视频标签显示第一帧 -->
|
||
<img
|
||
v-if="file.isVideo && (file.coverBase64 || file.coverUrl)"
|
||
:src="file.coverBase64"
|
||
:alt="file.fileName"
|
||
@error="handleImageError"
|
||
/>
|
||
<!-- 视频文件:如果没有封面,使用视频标签显示第一帧 -->
|
||
<video
|
||
v-else-if="file.isVideo && file.previewUrl"
|
||
:src="file.previewUrl"
|
||
muted
|
||
preload="metadata"
|
||
class="preview-video"
|
||
/>
|
||
<!-- 图片或其他文件:使用图片标签 -->
|
||
<!-- 优先级:coverUrl > coverBase64 > previewUrl -->
|
||
<img
|
||
v-else-if="file.coverUrl || file.coverBase64 || file.previewUrl"
|
||
:src="file.coverUrl || file.coverBase64 || file.previewUrl"
|
||
:alt="file.fileName"
|
||
@error="handleImageError"
|
||
/>
|
||
<div v-else class="material-item__placeholder">
|
||
<FileOutlined />
|
||
</div>
|
||
<!-- 文件类型标识 -->
|
||
<div class="material-item__badge">
|
||
<a-tag v-if="file.isVideo" color="red">视频</a-tag>
|
||
<a-tag v-else-if="file.isImage" color="blue">图片</a-tag>
|
||
<a-tag v-else color="gray">文件</a-tag>
|
||
</div>
|
||
<!-- 删除图标 -->
|
||
<div class="material-item__delete" @click.stop="handleDeleteFile(file)">
|
||
<DeleteOutlined />
|
||
</div>
|
||
</div>
|
||
<!-- 文件信息 -->
|
||
<div class="material-item__info">
|
||
<div class="material-item__name" :title="file.fileName">
|
||
{{ file.fileName }}
|
||
</div>
|
||
<div class="material-item__meta">
|
||
<span>{{ formatFileSize(file.fileSize) }}</span>
|
||
<span>{{ formatDate(file.createTime) }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<template v-else>
|
||
<a-empty description="暂无素材" />
|
||
</template>
|
||
</a-spin>
|
||
</div>
|
||
|
||
<!-- 分页 -->
|
||
<div class="material-list__pagination">
|
||
<a-pagination
|
||
v-model:current="pagination.pageNo"
|
||
v-model:page-size="pagination.pageSize"
|
||
:total="pagination.total"
|
||
:show-total="(total) => `共 ${total} 条`"
|
||
:show-size-changer="true"
|
||
@change="handlePageChange"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 上传对话框 -->
|
||
<MaterialUploadModal
|
||
v-model:visible="uploadModalVisible"
|
||
:uploading="uploading"
|
||
@confirm="handleConfirmUpload"
|
||
@cancel="handleUploadCancel"
|
||
/>
|
||
<a-modal
|
||
v-model:open="mixModalVisible"
|
||
title="素材混剪"
|
||
centered
|
||
:confirm-loading="mixing"
|
||
ok-text="提交混剪"
|
||
cancel-text="取消"
|
||
@ok="handleMixConfirm"
|
||
@cancel="handleMixCancel"
|
||
>
|
||
<div class="mix-modal__summary">
|
||
<p>选中素材:{{ selectedFiles.length }} 个</p>
|
||
<p>视频素材:{{ selectedVideoUrls.length }} 个</p>
|
||
<p>背景音乐:{{ selectedAudioUrls.length }} 个</p>
|
||
</div>
|
||
<a-form layout="vertical">
|
||
<a-form-item label="视频标题" required>
|
||
<a-input v-model:value="mixForm.title" placeholder="请输入生成视频标题" />
|
||
</a-form-item>
|
||
<a-form-item label="文案内容" required>
|
||
<a-textarea
|
||
v-model:value="mixForm.text"
|
||
placeholder="请输入文案(每句话换行以便自动拆分)"
|
||
:rows="4"
|
||
/>
|
||
</a-form-item>
|
||
<a-form-item label="生成成片数量" required>
|
||
<a-input-number
|
||
v-model:value="mixForm.produceCount"
|
||
:min="1"
|
||
:max="10"
|
||
style="width: 100%"
|
||
/>
|
||
</a-form-item>
|
||
</a-form>
|
||
</a-modal>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, computed, onMounted } from 'vue'
|
||
import { message, Modal } from 'ant-design-vue'
|
||
import {
|
||
UploadOutlined,
|
||
SearchOutlined,
|
||
FileOutlined,
|
||
DeleteOutlined
|
||
} from '@ant-design/icons-vue'
|
||
import { MaterialService } from '@/api/material'
|
||
import { MixService } from '@/api/mix'
|
||
import MaterialUploadModal from '@/components/material/MaterialUploadModal.vue'
|
||
import { formatFileSize, formatDate } from '@/utils/file'
|
||
|
||
// 数据
|
||
const loading = ref(false)
|
||
const fileList = ref([])
|
||
const selectedFileIds = ref([])
|
||
const uploadModalVisible = ref(false)
|
||
const uploading = ref(false)
|
||
const mixModalVisible = ref(false)
|
||
const mixing = ref(false)
|
||
|
||
// 筛选条件
|
||
const filters = reactive({
|
||
fileCategory: 'video', // 默认分类为视频
|
||
fileName: '',
|
||
createTime: undefined
|
||
})
|
||
|
||
// 分页
|
||
const pagination = reactive({
|
||
pageNo: 1,
|
||
pageSize: 20,
|
||
total: 0
|
||
})
|
||
|
||
// 构建查询参数
|
||
const buildQueryParams = () => {
|
||
const params = {
|
||
pageNo: pagination.pageNo,
|
||
pageSize: pagination.pageSize,
|
||
fileCategory: filters.fileCategory || undefined,
|
||
fileName: filters.fileName || undefined
|
||
}
|
||
|
||
// 处理日期范围
|
||
if (filters.createTime && Array.isArray(filters.createTime) && filters.createTime.length === 2) {
|
||
params.createTime = [
|
||
`${filters.createTime[0]} 00:00:00`,
|
||
`${filters.createTime[1]} 23:59:59`
|
||
]
|
||
}
|
||
|
||
return params
|
||
}
|
||
|
||
// 加载文件列表
|
||
const loadFileList = async () => {
|
||
loading.value = true
|
||
try {
|
||
const res = await MaterialService.getFilePage(buildQueryParams())
|
||
if (res.code === 0) {
|
||
fileList.value = res.data.list || []
|
||
pagination.total = res.data.total || 0
|
||
} else {
|
||
message.error(res.msg || '加载失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('加载文件列表失败:', error)
|
||
message.error('加载失败,请重试')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 打开上传对话框
|
||
const handleOpenUploadModal = () => {
|
||
uploadModalVisible.value = true
|
||
}
|
||
|
||
// 确认上传
|
||
const handleConfirmUpload = async (filesWithCover, fileCategory) => {
|
||
if (!filesWithCover?.length) {
|
||
message.warning('请选择文件')
|
||
return
|
||
}
|
||
|
||
uploading.value = true
|
||
let successCount = 0
|
||
let failCount = 0
|
||
|
||
try {
|
||
for (const { file, coverBase64 } of filesWithCover) {
|
||
try {
|
||
await MaterialService.uploadFile(file, fileCategory, coverBase64)
|
||
successCount++
|
||
} catch (error) {
|
||
console.error('文件上传失败:', file.name, error)
|
||
failCount++
|
||
}
|
||
}
|
||
|
||
if (successCount > 0) {
|
||
message.success(`成功上传 ${successCount} 个文件${failCount > 0 ? `,${failCount} 个失败` : ''}`)
|
||
uploadModalVisible.value = false
|
||
loadFileList()
|
||
} else {
|
||
message.error('所有文件上传失败,请重试')
|
||
}
|
||
} catch (error) {
|
||
console.error('上传失败:', error)
|
||
message.error(error.message || '上传失败,请重试')
|
||
} finally {
|
||
uploading.value = false
|
||
}
|
||
}
|
||
|
||
// 取消上传
|
||
const handleUploadCancel = () => {
|
||
uploadModalVisible.value = false
|
||
}
|
||
|
||
// 删除文件
|
||
const handleBatchDelete = () => {
|
||
if (selectedFileIds.value.length === 0) return
|
||
|
||
// 二次确认弹窗
|
||
Modal.confirm({
|
||
title: '确认删除',
|
||
content: `确定要删除选中的 ${selectedFileIds.value.length} 个文件吗?删除后无法恢复。`,
|
||
okText: '确定删除',
|
||
cancelText: '取消',
|
||
okType: 'danger',
|
||
onOk: async () => {
|
||
try {
|
||
await MaterialService.deleteFiles(selectedFileIds.value)
|
||
message.success('删除成功')
|
||
selectedFileIds.value = []
|
||
loadFileList()
|
||
} catch (error) {
|
||
console.error('删除失败:', error)
|
||
message.error(error.message || '删除失败,请重试')
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 删除单个文件
|
||
const handleDeleteFile = (file) => {
|
||
if (!file?.id) return
|
||
|
||
// 二次确认弹窗
|
||
Modal.confirm({
|
||
title: '确认删除',
|
||
content: `确定要删除文件 "${file.fileName}" 吗?删除后无法恢复。`,
|
||
okText: '确定删除',
|
||
cancelText: '取消',
|
||
okType: 'danger',
|
||
onOk: async () => {
|
||
try {
|
||
await MaterialService.deleteFiles([file.id])
|
||
message.success('删除成功')
|
||
// 如果在选中列表中,也移除
|
||
const index = selectedFileIds.value.indexOf(file.id)
|
||
if (index > -1) {
|
||
selectedFileIds.value.splice(index, 1)
|
||
}
|
||
loadFileList()
|
||
} catch (error) {
|
||
console.error('删除失败:', error)
|
||
message.error(error.message || '删除失败,请重试')
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 文件点击
|
||
const handleFileClick = (file) => {
|
||
const isSelected = selectedFileIds.value.includes(file.id)
|
||
// 切换选中状态
|
||
if (isSelected) {
|
||
selectedFileIds.value = selectedFileIds.value.filter(id => id !== file.id)
|
||
} else {
|
||
selectedFileIds.value.push(file.id)
|
||
}
|
||
}
|
||
|
||
// 筛选
|
||
const handleFilterChange = () => {
|
||
pagination.pageNo = 1
|
||
loadFileList()
|
||
}
|
||
|
||
const handleResetFilters = () => {
|
||
filters.fileCategory = 'video'
|
||
filters.fileName = ''
|
||
filters.createTime = undefined
|
||
pagination.pageNo = 1
|
||
loadFileList()
|
||
}
|
||
|
||
// 分页
|
||
const handlePageChange = (page, pageSize) => {
|
||
pagination.pageNo = page
|
||
if (pageSize && pageSize !== pagination.pageSize) {
|
||
pagination.pageSize = pageSize
|
||
pagination.pageNo = 1
|
||
}
|
||
loadFileList()
|
||
}
|
||
|
||
// 图片加载错误处理(支持回退到其他图片源)
|
||
const handleImageError = (e) => {
|
||
const img = e.target
|
||
const currentSrc = img.src
|
||
|
||
// 找到对应的文件对象
|
||
const file = fileList.value.find(f =>
|
||
f.coverBase64 === currentSrc ||
|
||
f.coverUrl === currentSrc ||
|
||
f.previewUrl === currentSrc
|
||
)
|
||
|
||
if (file) {
|
||
// 视频文件:优先使用 coverBase64,如果失败则尝试 coverUrl,最后尝试 previewUrl
|
||
if (file.isVideo) {
|
||
if (currentSrc === file.coverBase64 && file.coverUrl && file.coverUrl !== currentSrc) {
|
||
// coverBase64 失败,尝试 coverUrl
|
||
img.src = file.coverUrl
|
||
return
|
||
}
|
||
if ((currentSrc === file.coverBase64 || currentSrc === file.coverUrl) && file.previewUrl && file.previewUrl !== currentSrc) {
|
||
// coverBase64 和 coverUrl 都失败,尝试 previewUrl(视频第一帧)
|
||
img.src = file.previewUrl
|
||
return
|
||
}
|
||
} else {
|
||
// 非视频文件:尝试其他图片源
|
||
if (file.coverUrl && file.coverUrl !== currentSrc) {
|
||
img.src = file.coverUrl
|
||
return
|
||
}
|
||
if (file.coverBase64 && file.coverBase64 !== currentSrc && file.coverBase64.startsWith('data:image')) {
|
||
img.src = file.coverBase64
|
||
return
|
||
}
|
||
if (file.previewUrl && file.previewUrl !== currentSrc) {
|
||
img.src = file.previewUrl
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果所有图片源都失败,隐藏图片
|
||
img.style.display = 'none'
|
||
}
|
||
|
||
const selectedFiles = computed(() =>
|
||
fileList.value.filter((file) => selectedFileIds.value.includes(file.id))
|
||
)
|
||
|
||
const isVideoFile = (file) => {
|
||
if (!file) return false
|
||
if (file.isVideo) return true
|
||
if (file.fileCategory === 'video') return true
|
||
if (typeof file.fileType === 'string' && file.fileType.startsWith('video')) return true
|
||
return false
|
||
}
|
||
|
||
const isAudioFile = (file) => {
|
||
if (!file) return false
|
||
if (file.fileCategory === 'audio') return true
|
||
if (file.fileType === 'audio') return true
|
||
if (typeof file.fileType === 'string' && file.fileType.startsWith('audio')) return true
|
||
return false
|
||
}
|
||
|
||
const selectedVideoUrls = computed(() =>
|
||
selectedFiles.value.map((file) => (isVideoFile(file) ? file?.fileUrl || file?.previewUrl : null)).filter(Boolean)
|
||
)
|
||
|
||
const selectedAudioUrls = computed(() =>
|
||
selectedFiles.value.map((file) => (isAudioFile(file) ? file?.fileUrl || file?.previewUrl : null)).filter(Boolean)
|
||
)
|
||
|
||
const mixForm = reactive({
|
||
title: '',
|
||
text: '',
|
||
produceCount: 1
|
||
})
|
||
|
||
const resetMixForm = () => {
|
||
mixForm.title = ''
|
||
mixForm.text = ''
|
||
mixForm.produceCount = 1
|
||
}
|
||
|
||
const handleOpenMixModal = () => {
|
||
if (selectedFileIds.value.length === 0) {
|
||
message.warning('请先选择至少一个素材')
|
||
return
|
||
}
|
||
if (selectedVideoUrls.value.length === 0) {
|
||
message.warning('请至少选择一个视频素材')
|
||
return
|
||
}
|
||
if (selectedAudioUrls.value.length === 0) {
|
||
message.warning('请至少选择一个背景音乐素材')
|
||
return
|
||
}
|
||
mixModalVisible.value = true
|
||
}
|
||
|
||
const handleMixCancel = () => {
|
||
mixModalVisible.value = false
|
||
}
|
||
|
||
const handleMixConfirm = async () => {
|
||
const title = mixForm.title.trim()
|
||
const text = mixForm.text.trim()
|
||
if (!title) {
|
||
message.warning('请输入视频标题')
|
||
return
|
||
}
|
||
if (!text) {
|
||
message.warning('请输入文案内容')
|
||
return
|
||
}
|
||
const produceCount = Math.max(1, Math.min(10, Number(mixForm.produceCount) || 1))
|
||
if (selectedVideoUrls.value.length === 0) {
|
||
message.warning('请至少选择一个视频素材')
|
||
return
|
||
}
|
||
if (selectedAudioUrls.value.length === 0) {
|
||
message.warning('请至少选择一个背景音乐素材')
|
||
return
|
||
}
|
||
mixing.value = true
|
||
try {
|
||
const { data } = await MixService.batchProduceAlignment({
|
||
title,
|
||
text,
|
||
videoUrls: selectedVideoUrls.value,
|
||
bgMusicUrls: selectedAudioUrls.value,
|
||
produceCount
|
||
})
|
||
const jobIds = Array.isArray(data) ? data : []
|
||
message.success(
|
||
jobIds.length > 0
|
||
? `混剪任务提交成功,JobId:${jobIds.join(', ')}`
|
||
: '混剪任务提交成功'
|
||
)
|
||
mixModalVisible.value = false
|
||
resetMixForm()
|
||
} catch (error) {
|
||
console.error('混剪失败:', error)
|
||
message.error(error?.message || '混剪任务提交失败,请重试')
|
||
} finally {
|
||
mixing.value = false
|
||
}
|
||
}
|
||
|
||
// 初始化
|
||
onMounted(() => {
|
||
loadFileList()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.material-list {
|
||
padding: 24px;
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.material-list__header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.material-list__title {
|
||
font-size: 24px;
|
||
font-weight: 600;
|
||
margin: 0;
|
||
}
|
||
|
||
.material-list__actions {
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
|
||
.material-list__filters {
|
||
margin-bottom: 24px;
|
||
padding: 16px;
|
||
background: var(--color-surface);
|
||
border-radius: var(--radius-card);
|
||
border: 1px solid var(--color-border);
|
||
}
|
||
|
||
.material-list__content {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
margin-bottom: 24px;
|
||
position: relative;
|
||
}
|
||
|
||
.material-list__content :deep(.arco-spin) {
|
||
min-height: 400px;
|
||
}
|
||
|
||
.material-list__content :deep(.arco-spin-content) {
|
||
min-height: 400px;
|
||
}
|
||
|
||
.material-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||
gap: 16px;
|
||
}
|
||
|
||
.material-list__pagination {
|
||
display: flex;
|
||
justify-content: center;
|
||
}
|
||
|
||
.material-item {
|
||
cursor: pointer;
|
||
}
|
||
|
||
.material-item__content {
|
||
background: var(--color-surface);
|
||
border: 2px solid transparent;
|
||
border-radius: var(--radius-card);
|
||
overflow: hidden;
|
||
transition: border-color 0.2s;
|
||
}
|
||
|
||
.material-item--selected .material-item__content {
|
||
border-color: var(--color-primary);
|
||
}
|
||
|
||
.material-item__preview {
|
||
position: relative;
|
||
width: 100%;
|
||
padding-top: 56.25%; /* 16:9 */
|
||
background: var(--color-bg-2);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.material-item__preview img,
|
||
.material-item__preview video {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.material-item__preview video {
|
||
pointer-events: none; /* 禁用视频交互,避免点击播放 */
|
||
}
|
||
|
||
.material-item__placeholder {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
font-size: 48px;
|
||
color: var(--color-text-3);
|
||
}
|
||
|
||
.material-item__badge {
|
||
position: absolute;
|
||
top: 8px;
|
||
left: 8px;
|
||
}
|
||
|
||
.material-item__delete {
|
||
position: absolute;
|
||
bottom: 8px;
|
||
right: 8px;
|
||
width: 28px;
|
||
height: 28px;
|
||
background: rgba(255, 77, 79, 0.9);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: white;
|
||
cursor: pointer;
|
||
opacity: 0;
|
||
transition: all 0.3s;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.material-item:hover .material-item__delete {
|
||
opacity: 1;
|
||
}
|
||
|
||
.material-item__delete:hover {
|
||
background: rgb(255, 77, 79);
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.material-item__info {
|
||
padding: 12px;
|
||
}
|
||
|
||
.material-item__name {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
margin-bottom: 8px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.material-item__meta {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
font-size: 12px;
|
||
color: var(--color-text-3);
|
||
}
|
||
|
||
.mix-modal__summary {
|
||
margin-bottom: 16px;
|
||
padding: 12px;
|
||
background: var(--color-bg-2);
|
||
border: 1px dashed var(--color-border);
|
||
border-radius: var(--radius-card);
|
||
font-size: 13px;
|
||
color: var(--color-text-2);
|
||
}
|
||
|
||
.mix-modal__summary p {
|
||
margin: 0;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
</style>
|
||
|